@docyrus/app-utils
v0.11.0
Published
Utility functions for frontend applications using Docyrus as backend
Downloads
1,563
Keywords
Readme
@docyrus/app-utils
Framework-agnostic utility functions for frontend applications using Docyrus as backend. Provides date/number formatting, template compilation (Handlebars + JSONata), and a comprehensive set of expression helpers — all configured from tenant preferences.
Features
- Tenant Preferences: Fetch tenant configuration from the Docyrus API
- Date Formatting: PHP-style date formatting with timezone support (
Y-m-d,H:i:s, etc.) - Number Formatting: Locale-aware number formatting with custom separators and precision
- Number to Words: Convert numbers to words in Turkish and English with currency labels
- Duration Formatting: Format seconds as
HH:MM, decimal hours, or human-readable words - App Config: Get, upsert, and delete a per-app JSON configuration (1:1 per app)
- User App Config: Get, upsert, and delete a per-user JSON configuration scoped to an app (1:1 per user per app)
- Data Views: CRUD for saved data source view configurations (columns, filters, sort, color rules)
- Data Forms: CRUD for dynamic form designs attached to a data source (layout JSON, title, icon, color, default flag, soft-archive)
- Data Source Metadata: Read data source metadata across apps, by app, by ID, or by slug with optional expansions like
fields - TanStack Query Integration (optional, via
@docyrus/app-utils/query): PrebuiltqueryOptionsfactories and a query-key registry for data source metadata, with reactive caching and per-resource invalidation helpers - Envelope Unwrapping:
envelopeUnwrapInterceptor(aRestApiClientinterceptor) andunwrapEnvelope(a per-call helper) for handling Docyrus'{ success, data }API envelope — both shape-strict so domain payloads with their owndatacolumn stay safe - Template Engine: Handlebars compilation with async support and inline JSONata formulas
- Formula Evaluation: JSONata expression evaluation with 70+ built-in helper functions
- JSONata Helpers: Date arithmetic (date-fns), string manipulation, number formatting, equality checks, and more
- TypeScript: Full type definitions included
- Framework-Agnostic: No Vue, React, or Angular dependency — works everywhere
Installation
npm install @docyrus/app-utilspnpm add @docyrus/app-utilsPeer Dependencies
| Package | Version | Required |
|---------|---------|----------|
| @docyrus/api-client | >= 0.1.0 | Yes (for getTenantPreferences) |
| handlebars | >= 4.7.0 | Yes (for template engine) |
| jsonata | >= 2.0.0 | Yes (for formula evaluation) |
| date-fns | >= 3.0.0 | Yes (for JSONata date helpers) |
| @tanstack/query-core | >= 5.17.0 | Optional (only for @docyrus/app-utils/query) |
Quick Start
import {
getTenantPreferences,
createDateUtils,
createNumberUtils,
createTemplateEngine,
createDataSourceClient
} from '@docyrus/app-utils';
// 1. Fetch tenant preferences
const preferences = await getTenantPreferences(apiClient);
// 2. Create utility instances
const dateUtils = createDateUtils({
preferences,
userTimezone: currentUser.timeZone?.id // e.g. 'Europe/Istanbul'
});
const numberUtils = createNumberUtils({ preferences });
const dataSources = createDataSourceClient(apiClient);
// 3. Create template engine (optional)
const engine = createTemplateEngine({
dateUtils,
numberUtils,
user: currentUser,
extraJsonataBindings: { hasRole }
});
// 4. Use utilities
dateUtils.formatDate('2024-01-15'); // '15.01.2024' (tenant format)
numberUtils.formatNumber(1234.5); // '1.234,50' (tenant locale)
await engine.compileFormula('$sum(items.price)', data);
// 5. Read app and data source metadata
const apps = await dataSources.listApps();
const sources = await dataSources.list({ expand: 'fields' });
const contacts = await dataSources.getBySlug('crm', 'contacts', {
expand: 'fields'
});
// 6. Use metadata
const appSlugs = apps.map(app => app.slug);
const fieldSlugs = contacts.fields?.map(field => field.slug) ?? [];
const requiredFields = contacts.fields?.filter(field => field.required) ?? [];Envelope handling
Docyrus REST endpoints wrap their payload in a { success, data } envelope (optionally with meta for pagination, or error / message for controller-level errors). @docyrus/app-utils provides two ways to handle that — both are tolerant of pre-stripped envelopes (i.e. idempotent), and both refuse to falsely unwrap domain payloads that carry their own data field (AppConfig, UserAppConfig, records with a data column).
envelopeUnwrapInterceptor — global, conservative
A RestApiClient response interceptor. After it runs, client.get/post/put/delete returns the inner payload directly (so generated TanStack DB collections, hand-written calls, and app-utils clients all see bare values). Required when using @docyrus/tanstack-db-generator collections — the generator emits methods that don't read .data themselves.
import { RestApiClient } from '@docyrus/api-client';
import { envelopeUnwrapInterceptor } from '@docyrus/app-utils';
const client = new RestApiClient({ baseURL: 'https://api.example.com' });
client.addInterceptor(envelopeUnwrapInterceptor);
// Now plain envelopes unwrap automatically:
const views = await client.get<DataView[]>('/v1/apps/crm/data-sources/contacts/views');
// ^ DataView[] (from { success, data: [...] })It's deliberately shape-strict and lossless — it strips only when the envelope's only top-level keys are data and optionally success. Envelopes that carry sidecar information are left intact so callers can read them:
| Response shape | Behaviour | Why |
|---|---|---|
| { data } | unwrap → data | Bare envelope. |
| { success, data } | unwrap → data | Most endpoints. |
| { data, meta } / { success, data, meta } | left alone | Paginated lists — meta.count/meta.total needed by data grids. |
| { success: false, data, error, message } | left alone | Controller-level error envelope — error info preserved. |
| { id, data, status, tenant_app_id, … } | left alone | Domain record (e.g. AppConfig) carrying its own data column. |
For paginated endpoints, read both the array and the page total off the envelope:
const { data: rows, meta } = await client.get<{
data: Record[];
meta?: { count?: number; total?: number };
}>('/v1/apps/crm/data-sources/contacts/items');unwrapEnvelope<T>(value) — per-call, aggressive
A small helper for ad-hoc unwrap at known sites. It's wider than the interceptor — it also strips envelopes carrying meta / error / message because the caller has explicitly typed the return as T and isn't surfacing sidecar fields. This is exactly what app-utils' built-in clients (createDataSourceClient, createDataViewClient, createDataFormClient, createAppConfigClient, createUserAppConfigClient) use internally, so they keep working whether or not you've registered the interceptor globally.
import { unwrapEnvelope } from '@docyrus/app-utils';
const records = unwrapEnvelope<Record[]>(
await client.get('/v1/apps/crm/data-sources/contacts/items')
);
// → Record[] (meta intentionally discarded)Domain payloads with a data column are still safe — the closed-set check ({ data, success, meta, error, message }) rejects records that carry off-set keys like id / status.
App Config & Data Views
createAppConfigClient(client, appId)
Manages the single JSON configuration object for an app (1:1 relationship — no config IDs).
import { createAppConfigClient } from '@docyrus/app-utils';
const config = createAppConfigClient(apiClient, appId);config.get()
Returns the app's configuration. Throws 404 if none exists.
const appConfig = await config.get();
// { id: '...', data: { theme: 'dark', ... }, status: 1, tenant_app_id: '...', ... }config.upsert(body)
Creates the config if it doesn't exist, or updates it if it does.
const updated = await config.upsert({
data: { theme: 'dark', sidebar: { collapsed: false } },
status: 1
});config.remove()
Hard deletes the config (irreversible).
await config.remove();createUserAppConfigClient(client, appId)
Manages the per-user JSON configuration object for an app (1:1 per user per app). Useful for storing user preferences, UI state, or personalisation that should persist per user independently of the shared app config.
import { createUserAppConfigClient } from '@docyrus/app-utils';
const userConfig = createUserAppConfigClient(apiClient, appId);userConfig.get()
Returns the current user's configuration for the app. Throws 404 if none exists.
const config = await userConfig.get();
// { id: '...', data: { theme: 'dark', ... }, status: 1, tenant_app_id: '...', user_id: '...', ... }userConfig.upsert(body)
Creates the config if it doesn't exist, or updates it if it does.
const updated = await userConfig.upsert({
data: { theme: 'dark', sidebarCollapsed: true },
status: 1
});userConfig.remove()
Hard deletes the user config (irreversible).
await userConfig.remove();createDataViewClient(client, appSlug, dataSourceSlug)
Manages saved view configurations for a specific data source (1:many). Views define columns, filters, sorting, color rules, and quick-filter shortcuts.
import { createDataViewClient } from '@docyrus/app-utils';
const dataViews = createDataViewClient(apiClient, 'my-app', 'my-data-source');dataViews.list(params?)
Returns all non-archived views. Optionally pass appId to filter views configured for a specific app (different from the data source's owning app).
const views = await dataViews.list();
const forSpecificApp = await dataViews.list({ appId: 'other-app-uuid' });dataViews.get(viewId)
Returns a single non-archived view by ID.
const view = await dataViews.get('view-uuid');dataViews.create(body)
Creates a new data view. name is required. The data source is determined by the dataSourceSlug passed to the client factory.
const view = await dataViews.create({
name: 'High Priority',
columns: { visible: ['title', 'priority', 'assignee'] },
filters: { priority: 'high' },
sort: { field: 'due_date', direction: 'asc' },
color: '#E74C3C',
icon: 'alert-triangle',
is_default: false,
sort_order: 2
});dataViews.update(viewId, body)
Partially updates a view. Use archived: true to soft-delete.
await dataViews.update('view-uuid', { name: 'Renamed View' });
await dataViews.update('view-uuid', { archived: true }); // soft-deletedataViews.remove(viewId)
Hard deletes a view (irreversible).
await dataViews.remove('view-uuid');createDataFormClient(client, appSlug, dataSourceSlug)
Manages dynamic form designs attached to a data source (1:many). Forms describe the editable layout used to view and edit records of a data source — title, icon, color, the layout JSON itself (sections / fields / tabs), default selection, and soft-archive state.
import { createDataFormClient } from '@docyrus/app-utils';
const dataForms = createDataFormClient(apiClient, 'crm', 'contacts');dataForms.list()
Returns all non-archived forms for the data source, ordered by created_on ascending.
const forms = await dataForms.list();dataForms.get(formId)
Returns a single non-archived form by ID. Throws 404 if the form does not exist or is archived.
const form = await dataForms.get('form-uuid');dataForms.create(body)
Creates a new form. name is required; everything else is optional. The data source is determined by the dataSourceSlug passed to the client factory — tenant_data_source_id, tenant scope, and the created_by audit fields are populated server-side.
const form = await dataForms.create({
name: 'Compact contact form',
title: 'Contact',
icon: 'user',
color: '#4F46E5',
layout: {
sections: [
{ id: 'main', fields: ['name', 'email', 'phone'] }
]
},
is_default: false,
status: 1
});dataForms.update(formId, body)
Partially updates a form. Only the fields present in the body are written; omitted fields are preserved. layout is replaced wholesale when provided. Use archived: true to soft-archive (the row is then excluded from list/get).
await dataForms.update('form-uuid', {
title: 'Customer Contact',
icon: 'address-book',
layout: { sections: [/* updated sections */] }
});
await dataForms.update('form-uuid', { archived: true }); // soft-deletedataForms.remove(formId)
Hard deletes a form (irreversible). Use update(formId, { archived: true }) instead if you want a recoverable soft-delete.
await dataForms.remove('form-uuid');createDataSourceClient(client)
Reads Docyrus app and data source metadata across apps or for a specific app. This is useful when you need schema-aware UI behavior such as loading available apps, reading field definitions, or resolving a data source by slug.
import { createDataSourceClient } from '@docyrus/app-utils';
const dataSources = createDataSourceClient(apiClient);Expand options
The metadata endpoints support comma-separated expansions via expand.
Available values:
childrenfieldsformsacltemplatesclient-configurationmisc
Example:
const sources = await dataSources.list({
expand: 'fields,forms'
});dataSources.listApps()
Lists apps available to the current tenant.
const apps = await dataSources.listApps();
// [{ id: 'uuid', name: 'CRM', slug: 'crm', logo_url: '...', status: 'active' }]dataSources.list(params?)
Lists all data sources the current tenant can access across all apps.
const all = await dataSources.list();
const allWithFields = await dataSources.list({ expand: 'fields' });dataSources.listByAppSlug(appSlug, params?)
Lists all data sources for a specific app slug.
const crmSources = await dataSources.listByAppSlug('crm');
const crmWithFields = await dataSources.listByAppSlug('crm', {
expand: 'fields,acl'
});dataSources.listByAppId(appId, params?)
Lists all data sources for a specific app ID.
const appSources = await dataSources.listByAppId('app-uuid');
const appSourcesWithFields = await dataSources.listByAppId('app-uuid', {
expand: 'fields'
});dataSources.getById(dataSourceId, params?)
Returns a single data source metadata object by data source ID.
const contacts = await dataSources.getById('data-source-uuid');
const contactsWithFields = await dataSources.getById('data-source-uuid', {
expand: 'fields'
});dataSources.getBySlug(appSlug, dataSourceSlug, params?)
Returns a single data source metadata object by app slug and data source slug.
const contacts = await dataSources.getBySlug('crm', 'contacts');
const contactsWithFields = await dataSources.getBySlug('crm', 'contacts', {
expand: 'fields,forms'
});Response shape
Basic responses include fields such as:
{
id: 'uuid',
name: 'Contacts',
slug: 'contacts',
appSlug: 'crm'
}When expand=fields is used, the response can include fields metadata as well.
createDataSourceQueries(client) — TanStack Query integration
Returns prebuilt queryOptions-style factories and a query-key registry, so you get caching, deduplication, and reactive auto-invalidation for free. Works with @tanstack/react-query, @tanstack/vue-query, @tanstack/solid-query, etc. — only @tanstack/query-core is required at runtime.
Imported from a separate entrypoint to keep the optional @tanstack/query-core peer dep out of the main bundle:
import { createDataSourceQueries } from '@docyrus/app-utils/query';
const queries = createDataSourceQueries(apiClient);Usage with React
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { createDataSourceQueries } from '@docyrus/app-utils/query';
const queries = createDataSourceQueries(apiClient);
function ContactsTable() {
const { data: contacts } = useQuery(
queries.getBySlugOptions('crm', 'contacts', { expand: 'fields,forms' })
);
const queryClient = useQueryClient();
const handleSchemaChange = () => queries.invalidateBySlug(queryClient, 'crm', 'contacts');
// ...
}Available *Options factories
Each factory mirrors the corresponding createDataSourceClient method and produces a { queryKey, queryFn } object you pass straight to useQuery (or useSuspenseQuery, prefetchQuery, etc.):
| Factory | Wraps |
|---------|-------|
| listAppsOptions() | dataSources.listApps() |
| listOptions(params?) | dataSources.list(params) |
| listByAppSlugOptions(appSlug, params?) | dataSources.listByAppSlug(...) |
| listByAppIdOptions(appId, params?) | dataSources.listByAppId(...) |
| getByIdOptions(dataSourceId, params?) | dataSources.getById(...) |
| getBySlugOptions(appSlug, dataSourceSlug, params?) | dataSources.getBySlug(...) |
Per-request options (staleTime, enabled, select, placeholderData, …) are passed at the call site:
useQuery({
...queries.listOptions({ expand: 'fields' }),
staleTime: 5 * 60 * 1000,
enabled: isAuthenticated
});Invalidation helpers
All take a QueryClient so consumers can call them anywhere (event handlers, mutation onSuccess, etc.).
| Helper | Invalidates |
|--------|-------------|
| invalidateAll(qc) | Every cached data-source query (apps + lists + details) |
| invalidateApps(qc) | Cached app list |
| invalidateLists(qc) | All list* variants (across apps and params) |
| invalidateDetails(qc) | All getById / getBySlug results |
| invalidateById(qc, dataSourceId) | Every cached detail for a single data source ID (any expand) |
| invalidateBySlug(qc, appSlug, dataSourceSlug) | Every cached detail for a single app+slug pair (any expand) |
| invalidateForAppSlug(qc, appSlug) | All list queries scoped to an app slug |
| invalidateForAppId(qc, appId) | All list queries scoped to an app ID |
import { useMutation, useQueryClient } from '@tanstack/react-query';
const queryClient = useQueryClient();
const updateSchema = useMutation({
mutationFn: (changes) => api.patchSchema(changes),
onSuccess: () => queries.invalidateBySlug(queryClient, 'crm', 'contacts')
});dataSourceKeys registry
The same query keys used internally are exported as dataSourceKeys (and as queries.keys). Use them when you need to read or set cache entries directly:
import { dataSourceKeys } from '@docyrus/app-utils/query';
queryClient.setQueryData(
dataSourceKeys.bySlug('crm', 'contacts', { expand: 'fields' }),
optimisticData
);The key shape is stable: every key starts with ['docyrus', 'data-sources', ...], so queryClient.invalidateQueries({ queryKey: dataSourceKeys.all() }) clears everything this module owns.
API Reference
getTenantPreferences(client)
Fetches tenant preferences from the Docyrus API.
import { getTenantPreferences } from '@docyrus/app-utils';
const preferences = await getTenantPreferences(apiClient);
// { date_format: 'd.m.Y', decimal_separator: ',', thousand_separator: '.', ... }Parameters:
client— A configuredRestApiClientfrom@docyrus/api-client
Returns: Promise<TenantPreferences>
createDateUtils(config)
Creates date formatting utilities configured from tenant preferences.
import { createDateUtils } from '@docyrus/app-utils';
const dateUtils = createDateUtils({
preferences,
userTimezone: 'Europe/Istanbul' // defaults to 'UTC'
});dateUtils.formatDate(date, options?)
Formats a date using the tenant's date_format (default Y-m-d).
dateUtils.formatDate('2024-06-15'); // uses tenant format
dateUtils.formatDate(new Date(), { format: 'd/m/Y' }); // custom format
dateUtils.formatDate('2024-06-15', { timezone: 'US/Eastern' }); // custom timezonedateUtils.formatDateTime(date, options?)
Formats a datetime using the tenant's date_time_format (default Y-m-d H:i:s). Handles timezone-aware parsing — appends Z to strings without a timezone suffix.
dateUtils.formatDateTime('2024-06-15T14:30:00'); // parses as UTC
dateUtils.formatDateTime('2024-06-15T14:30:00+03:00'); // respects offsetdateUtils.formatDateLong(date, options?)
Formats a date using the tenant's long_date_format (default Y-m-d H:i:s).
dateUtils.toUserTimezone(date)
Converts a date to the user's timezone without formatting.
const localDate = dateUtils.toUserTimezone('2024-06-15T12:00:00Z');Format strings use PHP date format syntax (via php-date-formatter):
| Token | Output | Example |
|-------|--------|---------|
| Y | 4-digit year | 2024 |
| m | Month (01-12) | 06 |
| d | Day (01-31) | 15 |
| H | Hours 24h (00-23) | 14 |
| i | Minutes (00-59) | 30 |
| s | Seconds (00-59) | 00 |
| D | Short day name | Sat |
| l | Full day name | Saturday |
| F | Full month name | June |
createNumberUtils(config)
Creates number formatting utilities configured from tenant preferences.
import { createNumberUtils } from '@docyrus/app-utils';
const numberUtils = createNumberUtils({ preferences });numberUtils.formatNumber(value, options?)
Formats a number using the tenant's locale, separators, and precision.
numberUtils.formatNumber(1234567.89);
// With tenant settings: thousand_separator='.', decimal_separator=',', decimal_precision=2
// → '1.234.567,89'
// Override per call:
numberUtils.formatNumber(1234.5, {
decimalPrecision: 3,
decimalSeparator: '.',
thousandSeparator: ','
});
// → '1,234.500'Formatting logic:
- If both
thousandSeparatoranddecimalSeparatorare set → manual formatting with regex - Otherwise →
toLocaleString()with the tenant'slocale - Pass
thousandSeparator: ''to disable grouping
formatNumberToWords(value, options?)
Converts a number to words with currency labels. Standalone function (no config needed).
import { formatNumberToWords } from '@docyrus/app-utils';
formatNumberToWords(1234.56);
// → 'BİNİKİYÜZOTUZDÖRT TÜRK LİRASI ELLİALTI KURUŞ'
formatNumberToWords(1234.56, { lang: 'EN', currency: 'USD' });
// → 'ONE THOUSAND TWO HUNDRED THIRTY FOUR DOLAR FIFTY SIX CENT'
formatNumberToWords(1234.56, { lang: 'TR', currency: 'EUR', useSpaces: true });
// → 'BİN İKİ YÜZ OTUZ DÖRT EURO ELLİ ALTI CENT'Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| lang | 'TR' \| 'EN' | 'TR' | Word language |
| currency | 'TRY' \| 'EUR' \| 'USD' \| 'GBP' \| 'JPY' | 'TRY' | Currency labels |
| useSpaces | boolean | false | Add spaces between word groups |
Duration Formatters
Standalone functions for formatting durations (in seconds).
import {
formatDurationAsTime,
formatDurationAsHours,
formatDurationAsWords
} from '@docyrus/app-utils';
formatDurationAsTime(5461); // → '01:31'
formatDurationAsHours(5461); // → '1.52'
formatDurationAsHours(5461, 1); // → '1.5'
formatDurationAsWords(5461); // → '1 hr 31 mins'
formatDurationAsWords(45); // → '0 mins'createTemplateEngine(config?)
Creates a template engine combining Handlebars (with async support) and JSONata formula evaluation.
import { createTemplateEngine } from '@docyrus/app-utils';
const engine = createTemplateEngine({
dateUtils, // from createDateUtils()
numberUtils, // from createNumberUtils()
user: currentUser, // injected as metadata.user in formulas
extraJsonataBindings: { hasRole } // additional JSONata bindings
});Each createTemplateEngine call creates an isolated Handlebars instance, so multiple engines (e.g., different tenants) won't conflict.
engine.compileTpl(templateString)
Preprocesses and compiles a Handlebars template. Returns an async function.
const tpl = engine.compileTpl('Hello {{name}}, total: {{formatNumber amount}}');
const html = await tpl({ name: 'John', amount: 1234.5 });
// → 'Hello John, total: 1.234,50'Template preprocessing:
{{formula $expr}}→ wraps expression in quotes for the formula helper{{#if $expr}}→ converts to{{#if (formula '$expr')}}for JSONata evaluation{{<content}}→ strips HTML tags from inline content
engine.compileFormula(expression, data?)
Evaluates a JSONata expression with all helpers bound. Injects metadata.user from config.
const total = await engine.compileFormula('$sum(items.price)', { items });
const greeting = await engine.compileFormula('"Hello " & name', { name: 'World' });engine.jsonataHelpers
Direct access to the full bindings object (all built-in helpers + extraJsonataBindings).
Handlebars Helpers
The following helpers are registered automatically when using compileTpl:
Formatting Helpers
| Helper | Usage | Description |
|--------|-------|-------------|
| formatDate | {{formatDate dateField format="d/m/Y"}} | Format date (uses tenant date_format by default) |
| formatDateTime | {{formatDateTime dateField}} | Format datetime (uses tenant date_time_format) |
| formatNumber | {{formatNumber amount decimalPrecision=2}} | Format number with tenant settings |
| formatNumberToWords | {{formatNumberToWords amount lang="TR" currency="TRY"}} | Number to words |
| formatDurationAsTime | {{formatDurationAsTime seconds}} | Duration as HH:MM |
| formatDurationAsWords | {{formatDurationAsWords seconds}} | Duration as X hrs Y mins |
| formatDurationAsHours | {{formatDurationAsHours seconds decimalPrecision=1}} | Duration as decimal hours |
Logic & Data Helpers
| Helper | Usage | Description |
|--------|-------|-------------|
| formula | {{formula "$sum(items.price)"}} or {{#formula}}$expr{{/formula}} | Evaluate JSONata expression |
| path | {{#path "$.items[0]"}}...{{/path}} | JSONPath query — sets context to result |
| repeat | {{#repeat 5}}...{{/repeat}} | Repeat block N times |
| sum | {{sum items "price"}} or {{sum 1 2 3}} | Sum array field or values |
| json | {{json data}} | Pretty-print JSON |
JSONata Helpers
All helpers are available inside compileFormula and {{formula}} blocks. They are also exported as jsonataHelpers for direct use.
import { jsonataHelpers } from '@docyrus/app-utils';Date Functions (date-fns, null-safe wrapped)
All date functions accept strings or Date objects. Returns null for null/empty input.
Formatting:
formatDate, formatDistance, formatDistanceStrict, formatDistanceToNow, formatDistanceToNowStrict, formatDuration, formatISO, formatRelative
Arithmetic:
addYears, addMonths, addWeeks, addDays, addHours, addMinutes, addSeconds, addMilliseconds, subYears, subMonths, subWeeks, subDays, subHours, subMinutes, subSeconds, subMilliseconds
Comparison:
isAfter, isBefore, isDatesEqual, isPast
Difference:
differenceInYears, differenceInMonths, differenceInWeeks, differenceInDays, differenceInHours, differenceInMinutes, differenceInSeconds, differenceInBusinessDays, differenceInBusinessDaysCustom
Period boundaries:
startOfDay, startOfMonth, startOfWeek, startOfToday, startOfQuarter, endOfDay, endOfMonth, endOfWeek, endOfToday, endOfQuarter
differenceInBusinessDaysCustom(startDate, endDate, options?)
Calculates business days with configurable work hours and lunch breaks.
$differenceInBusinessDaysCustom(startDate, endDate, {
"workStartHour": 9,
"workEndHour": 18,
"lunchBreakHours": 1,
"lunchStartHour": 12.5,
"lunchEndHour": 13.5,
"includePartialDays": true
})Number / Format Functions
| Function | Signature | Description |
|----------|-----------|-------------|
| formatDecimal | (number, precision?, decSep?, thousandSep?) | Format decimal with separators |
| formatMoney | (number, precision?, decSep?, thousandSep?, currency?, position?) | Format as currency |
| percentage | (value, total, decimals) | Calculate percentage |
| truncate | (text, limit) | Truncate with ... |
| join | (array, separator) | Join array to string |
| formatDurationInSeconds | (seconds) | Human-readable duration via date-fns |
String Functions
| Function | Signature | Description |
|----------|-----------|-------------|
| sha256 | (message) | SHA-256 hash (async) |
| ascii | (str) | Strip accents and non-ASCII characters |
| slug | (str, separator?) | URL-friendly slug |
| extractNameFromEmail | (email, fallback?) | Extract name from email (e.g. john.doe@... → John Doe) |
| filterEmpty | (array) | Remove null/undefined/empty from array |
| padLeft | (value, length, char?) | Left-pad string |
| padRight | (value, length, char?) | Right-pad string |
| ifNull | (value, alternative) | Null coalescing |
| objectToString | (object) | Convert object to key:value string |
| startsWith | (haystack, needle) | String starts with |
| endsWith | (haystack, needle) | String ends with |
| includes | (haystack, needle) | String includes (null-safe) |
Equality / DB Helpers
| Function | Signature | Description |
|----------|-----------|-------------|
| isEqual | (a, b) | Deep equality — compares by id, Date, JSON, or strict |
| isEqualOrContained | (needle, haystack) | Check if value is equal to or contained in array |
| isNotEqualOrContained | (needle, haystack) | Negation of isEqualOrContained |
| getDbValue | (value) | Extract id from object or first item of array |
TypeScript
All types are exported:
import type {
TenantPreferences,
DateUtils,
DateUtilsConfig,
FormatOptions,
NumberUtils,
NumberUtilsConfig,
NumberFormatOptions,
NumberToWordsOptions,
NumberWordLang,
CurrencyCode,
TemplateEngine,
TemplateEngineConfig,
AppConfig,
UpsertAppConfigBody,
AppConfigClient,
UserAppConfig,
UpsertUserAppConfigBody,
UserAppConfigClient,
DataView,
CreateDataViewBody,
UpdateDataViewBody,
ListDataViewsParams,
DataViewClient,
DataForm,
CreateDataFormBody,
UpdateDataFormBody,
DataFormClient,
DataSourceExpand,
DataSourceField,
DataSource,
DataSourceApp,
DataSourceListParams,
DataSourceClient,
AppUtilsConfig
} from '@docyrus/app-utils';License
MIT
