@stndrds/schema 0.1.0-alpha.20 → 0.1.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-3RVLIIUK.mjs +9257 -0
- package/dist/chunk-6FNFXEQY.mjs +9316 -0
- package/dist/chunk-6TJR4WDK.mjs +9262 -0
- package/dist/chunk-F27AKBTL.mjs +9452 -0
- package/dist/chunk-HSNBVKQU.mjs +9316 -0
- package/dist/chunk-JJLODIBK.mjs +9389 -0
- package/dist/chunk-MAXEN3GU.mjs +9266 -0
- package/dist/chunk-ZD3BFM25.mjs +9316 -0
- package/dist/index.d.mts +3 -197
- package/dist/index.d.ts +3 -197
- package/dist/index.js +624 -21
- package/dist/index.mjs +21 -1
- package/dist/runtime-0gaIqXql.d.mts +7074 -0
- package/dist/runtime-0gaIqXql.d.ts +7074 -0
- package/dist/runtime-3u5doYAK.d.mts +6934 -0
- package/dist/runtime-3u5doYAK.d.ts +6934 -0
- package/dist/runtime-B6mOrMV4.d.mts +6985 -0
- package/dist/runtime-B6mOrMV4.d.ts +6985 -0
- package/dist/runtime-BNML-fPx.d.mts +6969 -0
- package/dist/runtime-BNML-fPx.d.ts +6969 -0
- package/dist/runtime-BPdsKDJo.d.mts +6969 -0
- package/dist/runtime-BPdsKDJo.d.ts +6969 -0
- package/dist/runtime-D1jyS1lS.d.mts +7008 -0
- package/dist/runtime-D1jyS1lS.d.ts +7008 -0
- package/dist/runtime-DjEUAeL4.d.mts +6713 -0
- package/dist/runtime-DjEUAeL4.d.ts +6713 -0
- package/dist/runtime-rTM9ddEe.d.mts +6934 -0
- package/dist/runtime-rTM9ddEe.d.ts +6934 -0
- package/dist/runtime-xLzAkAnp.d.mts +6969 -0
- package/dist/runtime-xLzAkAnp.d.ts +6969 -0
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +612 -21
- package/dist/runtime.mjs +17 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -159,6 +159,9 @@ __export(index_exports, {
|
|
|
159
159
|
PermissionService: () => PermissionService,
|
|
160
160
|
ProtectedResourceError: () => ProtectedResourceError,
|
|
161
161
|
ProtectedRoleError: () => ProtectedRoleError,
|
|
162
|
+
QueryBuilder: () => QueryBuilder,
|
|
163
|
+
QueryMultipleResultsError: () => QueryMultipleResultsError,
|
|
164
|
+
QueryNoResultError: () => QueryNoResultError,
|
|
162
165
|
RecordNotFoundError: () => RecordNotFoundError,
|
|
163
166
|
RecordReferencedError: () => RecordReferencedError,
|
|
164
167
|
RecordService: () => RecordService,
|
|
@@ -167,6 +170,8 @@ __export(index_exports, {
|
|
|
167
170
|
RoleNotFoundError: () => RoleNotFoundError,
|
|
168
171
|
RollupScheduler: () => RollupScheduler,
|
|
169
172
|
RollupService: () => RollupService,
|
|
173
|
+
SHORTCUT_TO_FILTER_OPERATOR: () => SHORTCUT_TO_FILTER_OPERATOR,
|
|
174
|
+
SYSTEM_FIELD_NAMES: () => SYSTEM_FIELD_NAMES,
|
|
170
175
|
SYSTEM_RESOURCES: () => SYSTEM_RESOURCES,
|
|
171
176
|
SYSTEM_RESOURCE_LABELS: () => SYSTEM_RESOURCE_LABELS,
|
|
172
177
|
SchemaError: () => SchemaError,
|
|
@@ -189,6 +194,7 @@ __export(index_exports, {
|
|
|
189
194
|
createCheckboxValidator: () => createCheckboxValidator,
|
|
190
195
|
createCurrencyValidator: () => createCurrencyValidator,
|
|
191
196
|
createDateValidator: () => createDateValidator,
|
|
197
|
+
createDefaultState: () => createDefaultState,
|
|
192
198
|
createDraftValidator: () => createDraftValidator,
|
|
193
199
|
createFileValidator: () => createFileValidator,
|
|
194
200
|
createFormulaValidator: () => createFormulaValidator,
|
|
@@ -199,6 +205,7 @@ __export(index_exports, {
|
|
|
199
205
|
createNumberValidator: () => createNumberValidator,
|
|
200
206
|
createObjectValidator: () => createObjectValidator,
|
|
201
207
|
createPhoneValidator: () => createPhoneValidator,
|
|
208
|
+
createQueryBuilder: () => createQueryBuilder,
|
|
202
209
|
createRatingValidator: () => createRatingValidator,
|
|
203
210
|
createRelationValidator: () => createRelationValidator,
|
|
204
211
|
createRollupValidator: () => createRollupValidator,
|
|
@@ -228,6 +235,8 @@ __export(index_exports, {
|
|
|
228
235
|
flow: () => flow,
|
|
229
236
|
formatAttributeValue: () => formatAttributeValue,
|
|
230
237
|
formatFormulaResult: () => formatFormulaResult,
|
|
238
|
+
formatRecord: () => formatRecord,
|
|
239
|
+
formatRecords: () => formatRecords,
|
|
231
240
|
formula: () => formula,
|
|
232
241
|
formulaConfigSchema: () => formulaConfigSchema,
|
|
233
242
|
generateId: () => generateId,
|
|
@@ -482,6 +491,9 @@ var NoopGeocodingAdapter = class {
|
|
|
482
491
|
}
|
|
483
492
|
};
|
|
484
493
|
|
|
494
|
+
// src/types/inference.ts
|
|
495
|
+
var SYSTEM_FIELD_NAMES = ["id", "createdAt", "updatedAt"];
|
|
496
|
+
|
|
485
497
|
// src/types/views.ts
|
|
486
498
|
function isFormTab(tab) {
|
|
487
499
|
return tab.type === "form";
|
|
@@ -889,6 +901,7 @@ var VALID_ICONS = new Set(import_constants2.ICONS);
|
|
|
889
901
|
var ATTRIBUTE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
890
902
|
var MAX_NAME_LENGTH = 63;
|
|
891
903
|
var MAX_LABEL_LENGTH = 128;
|
|
904
|
+
var RESERVED_ATTRIBUTE_NAMES = ["id", "createdAt", "updatedAt", "metadata"];
|
|
892
905
|
function validateAttributeName(name) {
|
|
893
906
|
if (!name || name.length === 0) {
|
|
894
907
|
throw new Error("[AttributeBuilder] Attribute name cannot be empty");
|
|
@@ -903,6 +916,12 @@ function validateAttributeName(name) {
|
|
|
903
916
|
"[AttributeBuilder] Invalid attribute name format.\nName must be a valid variable identifier:\n \u2705 Valid: 'firstName', 'first_name', 'FirstName', 'FIRST_NAME'\n \u274C Invalid: 'first-name', 'first name', '123name', 'first.name'"
|
|
904
917
|
);
|
|
905
918
|
}
|
|
919
|
+
if (RESERVED_ATTRIBUTE_NAMES.includes(name)) {
|
|
920
|
+
throw new Error(
|
|
921
|
+
`[AttributeBuilder] "${name}" is a reserved name and cannot be used as an attribute name.
|
|
922
|
+
Reserved names: ${RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
923
|
+
);
|
|
924
|
+
}
|
|
906
925
|
}
|
|
907
926
|
function validateLabel(label) {
|
|
908
927
|
if (!label || label.length === 0) {
|
|
@@ -3057,7 +3076,7 @@ function createCheckboxValidator(_attr) {
|
|
|
3057
3076
|
return import_zod4.z.boolean();
|
|
3058
3077
|
}
|
|
3059
3078
|
function createDateValidator(attr) {
|
|
3060
|
-
return import_zod4.z.
|
|
3079
|
+
return import_zod4.z.coerce.date({ message: `${attr.label} must be a valid ISO date` });
|
|
3061
3080
|
}
|
|
3062
3081
|
function createPhoneValidator(_attr) {
|
|
3063
3082
|
return import_zod4.z.object({
|
|
@@ -3163,7 +3182,7 @@ function createRelationValidator(attr) {
|
|
|
3163
3182
|
return createSingleRelationValidator(attr);
|
|
3164
3183
|
}
|
|
3165
3184
|
function createRatingValidator(attr) {
|
|
3166
|
-
let schema = import_zod4.z.number().
|
|
3185
|
+
let schema = import_zod4.z.number().min(0);
|
|
3167
3186
|
if (attr.max !== void 0) {
|
|
3168
3187
|
schema = schema.max(attr.max, `${attr.label} must be at most ${attr.max}`);
|
|
3169
3188
|
}
|
|
@@ -3336,6 +3355,477 @@ function computeRecordStatus(objectDef, data) {
|
|
|
3336
3355
|
return isRecordComplete(objectDef, data) ? "complete" : "draft";
|
|
3337
3356
|
}
|
|
3338
3357
|
|
|
3358
|
+
// src/runtime/client/types.ts
|
|
3359
|
+
function formatRecord(record) {
|
|
3360
|
+
return {
|
|
3361
|
+
id: record.id,
|
|
3362
|
+
createdAt: record.createdAt,
|
|
3363
|
+
updatedAt: record.updatedAt,
|
|
3364
|
+
metadata: record.metadata ?? {},
|
|
3365
|
+
...record.values
|
|
3366
|
+
};
|
|
3367
|
+
}
|
|
3368
|
+
function formatRecords(records) {
|
|
3369
|
+
return records.map((r) => formatRecord(r));
|
|
3370
|
+
}
|
|
3371
|
+
function createDefaultState(objectName) {
|
|
3372
|
+
return {
|
|
3373
|
+
objectName,
|
|
3374
|
+
filters: [],
|
|
3375
|
+
combinator: "and",
|
|
3376
|
+
sorts: [],
|
|
3377
|
+
raw: false,
|
|
3378
|
+
includeDeleted: false
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
var SHORTCUT_TO_FILTER_OPERATOR = {
|
|
3382
|
+
eq: "is",
|
|
3383
|
+
neq: "is_not",
|
|
3384
|
+
gt: "gt",
|
|
3385
|
+
gte: "gte",
|
|
3386
|
+
lt: "lt",
|
|
3387
|
+
lte: "lte",
|
|
3388
|
+
contains: "contains",
|
|
3389
|
+
notContains: "not_contains",
|
|
3390
|
+
startsWith: "starts_with",
|
|
3391
|
+
endsWith: "ends_with",
|
|
3392
|
+
isEmpty: "is_empty",
|
|
3393
|
+
isNotEmpty: "is_not_empty"
|
|
3394
|
+
};
|
|
3395
|
+
var QueryNoResultError = class extends Error {
|
|
3396
|
+
constructor(objectName, filters) {
|
|
3397
|
+
const filterInfo = filters?.length ? ` with filters: ${JSON.stringify(filters)}` : "";
|
|
3398
|
+
super(`No record found in "${objectName}"${filterInfo}`);
|
|
3399
|
+
this.name = "QueryNoResultError";
|
|
3400
|
+
this.objectName = objectName;
|
|
3401
|
+
this.filters = filters;
|
|
3402
|
+
}
|
|
3403
|
+
};
|
|
3404
|
+
var QueryMultipleResultsError = class extends Error {
|
|
3405
|
+
constructor(objectName, count) {
|
|
3406
|
+
super(
|
|
3407
|
+
`Expected single record in "${objectName}", but found ${count}. Use first() or add more filters.`
|
|
3408
|
+
);
|
|
3409
|
+
this.name = "QueryMultipleResultsError";
|
|
3410
|
+
this.objectName = objectName;
|
|
3411
|
+
this.count = count;
|
|
3412
|
+
}
|
|
3413
|
+
};
|
|
3414
|
+
|
|
3415
|
+
// src/runtime/client/query-builder.ts
|
|
3416
|
+
var QueryBuilder = class _QueryBuilder {
|
|
3417
|
+
constructor(recordService, adapter, objectName, state) {
|
|
3418
|
+
this.objectId = null;
|
|
3419
|
+
this.recordService = recordService;
|
|
3420
|
+
this.adapter = adapter;
|
|
3421
|
+
this.state = {
|
|
3422
|
+
...createDefaultState(objectName),
|
|
3423
|
+
...state
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
/**
|
|
3427
|
+
* Clone the builder with new state (immutability pattern)
|
|
3428
|
+
*/
|
|
3429
|
+
clone(updates) {
|
|
3430
|
+
return new _QueryBuilder(this.recordService, this.adapter, this.state.objectName, {
|
|
3431
|
+
...this.state,
|
|
3432
|
+
...updates
|
|
3433
|
+
});
|
|
3434
|
+
}
|
|
3435
|
+
/**
|
|
3436
|
+
* Resolve object name to object ID (cached)
|
|
3437
|
+
*/
|
|
3438
|
+
async resolveObjectId() {
|
|
3439
|
+
if (this.objectId) return this.objectId;
|
|
3440
|
+
const tenantId = this.recordService.tenantId;
|
|
3441
|
+
const dbObject = await this.adapter.objects.findByName(tenantId, this.state.objectName);
|
|
3442
|
+
if (!dbObject) {
|
|
3443
|
+
throw new Error(`Object "${this.state.objectName}" not found`);
|
|
3444
|
+
}
|
|
3445
|
+
this.objectId = dbObject.id;
|
|
3446
|
+
return dbObject.id;
|
|
3447
|
+
}
|
|
3448
|
+
// ============================================================================
|
|
3449
|
+
// FILTER METHODS
|
|
3450
|
+
// ============================================================================
|
|
3451
|
+
/**
|
|
3452
|
+
* Add a filter rule with explicit operator
|
|
3453
|
+
*
|
|
3454
|
+
* @example
|
|
3455
|
+
* ```typescript
|
|
3456
|
+
* qb.where('status', 'is', 'active')
|
|
3457
|
+
* .where('price', 'gte', 100)
|
|
3458
|
+
* ```
|
|
3459
|
+
*/
|
|
3460
|
+
where(attribute, operator, value) {
|
|
3461
|
+
const rule = { attribute, operator, value };
|
|
3462
|
+
return this.clone({
|
|
3463
|
+
filters: [...this.state.filters, rule]
|
|
3464
|
+
});
|
|
3465
|
+
}
|
|
3466
|
+
/**
|
|
3467
|
+
* Set filter combinator (default: 'and')
|
|
3468
|
+
*/
|
|
3469
|
+
or() {
|
|
3470
|
+
return this.clone({ combinator: "or" });
|
|
3471
|
+
}
|
|
3472
|
+
// ============================================================================
|
|
3473
|
+
// SHORTCUT OPERATORS
|
|
3474
|
+
// ============================================================================
|
|
3475
|
+
/**
|
|
3476
|
+
* Equal (is)
|
|
3477
|
+
*/
|
|
3478
|
+
eq(attribute, value) {
|
|
3479
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.eq, value);
|
|
3480
|
+
}
|
|
3481
|
+
/**
|
|
3482
|
+
* Not equal (is_not)
|
|
3483
|
+
*/
|
|
3484
|
+
neq(attribute, value) {
|
|
3485
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.neq, value);
|
|
3486
|
+
}
|
|
3487
|
+
/**
|
|
3488
|
+
* Greater than
|
|
3489
|
+
*/
|
|
3490
|
+
gt(attribute, value) {
|
|
3491
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.gt, value);
|
|
3492
|
+
}
|
|
3493
|
+
/**
|
|
3494
|
+
* Greater than or equal
|
|
3495
|
+
*/
|
|
3496
|
+
gte(attribute, value) {
|
|
3497
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.gte, value);
|
|
3498
|
+
}
|
|
3499
|
+
/**
|
|
3500
|
+
* Less than
|
|
3501
|
+
*/
|
|
3502
|
+
lt(attribute, value) {
|
|
3503
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.lt, value);
|
|
3504
|
+
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Less than or equal
|
|
3507
|
+
*/
|
|
3508
|
+
lte(attribute, value) {
|
|
3509
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.lte, value);
|
|
3510
|
+
}
|
|
3511
|
+
/**
|
|
3512
|
+
* Contains (text/relation)
|
|
3513
|
+
*/
|
|
3514
|
+
contains(attribute, value) {
|
|
3515
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.contains, value);
|
|
3516
|
+
}
|
|
3517
|
+
/**
|
|
3518
|
+
* Not contains
|
|
3519
|
+
*/
|
|
3520
|
+
notContains(attribute, value) {
|
|
3521
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.notContains, value);
|
|
3522
|
+
}
|
|
3523
|
+
/**
|
|
3524
|
+
* Starts with (text)
|
|
3525
|
+
*/
|
|
3526
|
+
startsWith(attribute, value) {
|
|
3527
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.startsWith, value);
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3530
|
+
* Ends with (text)
|
|
3531
|
+
*/
|
|
3532
|
+
endsWith(attribute, value) {
|
|
3533
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.endsWith, value);
|
|
3534
|
+
}
|
|
3535
|
+
/**
|
|
3536
|
+
* Is empty
|
|
3537
|
+
*/
|
|
3538
|
+
isEmpty(attribute) {
|
|
3539
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.isEmpty, null);
|
|
3540
|
+
}
|
|
3541
|
+
/**
|
|
3542
|
+
* Is not empty
|
|
3543
|
+
*/
|
|
3544
|
+
isNotEmpty(attribute) {
|
|
3545
|
+
return this.where(attribute, SHORTCUT_TO_FILTER_OPERATOR.isNotEmpty, null);
|
|
3546
|
+
}
|
|
3547
|
+
/**
|
|
3548
|
+
* In list (any_of)
|
|
3549
|
+
*/
|
|
3550
|
+
in(attribute, values) {
|
|
3551
|
+
return this.where(attribute, "any_of", values);
|
|
3552
|
+
}
|
|
3553
|
+
/**
|
|
3554
|
+
* Not in list (none_of)
|
|
3555
|
+
*/
|
|
3556
|
+
notIn(attribute, values) {
|
|
3557
|
+
return this.where(attribute, "none_of", values);
|
|
3558
|
+
}
|
|
3559
|
+
// ============================================================================
|
|
3560
|
+
// SORT & PAGINATION
|
|
3561
|
+
// ============================================================================
|
|
3562
|
+
/**
|
|
3563
|
+
* Add sort rule
|
|
3564
|
+
*/
|
|
3565
|
+
orderBy(attribute, direction = "asc") {
|
|
3566
|
+
return this.clone({
|
|
3567
|
+
sorts: [...this.state.sorts, { attribute, direction }]
|
|
3568
|
+
});
|
|
3569
|
+
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Set limit
|
|
3572
|
+
*/
|
|
3573
|
+
limit(count) {
|
|
3574
|
+
return this.clone({ limit: count });
|
|
3575
|
+
}
|
|
3576
|
+
/**
|
|
3577
|
+
* Set offset
|
|
3578
|
+
*/
|
|
3579
|
+
offset(count) {
|
|
3580
|
+
return this.clone({ offset: count });
|
|
3581
|
+
}
|
|
3582
|
+
/**
|
|
3583
|
+
* Include soft-deleted records
|
|
3584
|
+
*/
|
|
3585
|
+
withDeleted() {
|
|
3586
|
+
return this.clone({ includeDeleted: true });
|
|
3587
|
+
}
|
|
3588
|
+
// ============================================================================
|
|
3589
|
+
// GROUP BY (KANBAN)
|
|
3590
|
+
// ============================================================================
|
|
3591
|
+
/**
|
|
3592
|
+
* Group results by an attribute (for Kanban views).
|
|
3593
|
+
* Only status and select attributes are supported.
|
|
3594
|
+
*
|
|
3595
|
+
* @example
|
|
3596
|
+
* ```typescript
|
|
3597
|
+
* const grouped = await qb
|
|
3598
|
+
* .groupBy('status')
|
|
3599
|
+
* .fetchGrouped();
|
|
3600
|
+
* // → { groups: { 'pending': [...], 'active': [...] }, total: 50 }
|
|
3601
|
+
* ```
|
|
3602
|
+
*/
|
|
3603
|
+
groupBy(attribute) {
|
|
3604
|
+
return this.clone({ groupBy: attribute });
|
|
3605
|
+
}
|
|
3606
|
+
// ============================================================================
|
|
3607
|
+
// RAW MODE
|
|
3608
|
+
// ============================================================================
|
|
3609
|
+
/**
|
|
3610
|
+
* Return raw ObjectRecord instead of formatted record.
|
|
3611
|
+
* Includes all metadata (objectId, label, completionStatus, etc.)
|
|
3612
|
+
*/
|
|
3613
|
+
raw() {
|
|
3614
|
+
return this.clone({ raw: true });
|
|
3615
|
+
}
|
|
3616
|
+
// ============================================================================
|
|
3617
|
+
// TERMINATION METHODS (READ)
|
|
3618
|
+
// ============================================================================
|
|
3619
|
+
/**
|
|
3620
|
+
* Fetch records with current filters
|
|
3621
|
+
* Returns formatted records by default, raw ObjectRecords if raw() was called
|
|
3622
|
+
*/
|
|
3623
|
+
async fetch() {
|
|
3624
|
+
const objectId = await this.resolveObjectId();
|
|
3625
|
+
const filters = this.state.filters.length > 0 ? { combinator: this.state.combinator, rules: this.state.filters } : void 0;
|
|
3626
|
+
const result = await this.recordService.listRecords(objectId, {
|
|
3627
|
+
filters,
|
|
3628
|
+
sorts: this.state.sorts.length > 0 ? this.state.sorts : void 0,
|
|
3629
|
+
limit: this.state.limit,
|
|
3630
|
+
offset: this.state.offset,
|
|
3631
|
+
includeDeleted: this.state.includeDeleted
|
|
3632
|
+
});
|
|
3633
|
+
if (this.state.raw) {
|
|
3634
|
+
return result;
|
|
3635
|
+
}
|
|
3636
|
+
return {
|
|
3637
|
+
records: formatRecords(result.records),
|
|
3638
|
+
total: result.total
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Get a single record (throws if 0 or >1 results)
|
|
3643
|
+
*/
|
|
3644
|
+
async single() {
|
|
3645
|
+
const result = await this.limit(2).fetch();
|
|
3646
|
+
if (result.records.length === 0) {
|
|
3647
|
+
throw new QueryNoResultError(this.state.objectName, this.state.filters);
|
|
3648
|
+
}
|
|
3649
|
+
if (result.records.length > 1) {
|
|
3650
|
+
throw new QueryMultipleResultsError(this.state.objectName, result.total);
|
|
3651
|
+
}
|
|
3652
|
+
return result.records[0];
|
|
3653
|
+
}
|
|
3654
|
+
/**
|
|
3655
|
+
* Get first record or null
|
|
3656
|
+
*/
|
|
3657
|
+
async first() {
|
|
3658
|
+
const result = await this.limit(1).fetch();
|
|
3659
|
+
return result.records[0] ?? null;
|
|
3660
|
+
}
|
|
3661
|
+
/**
|
|
3662
|
+
* Get record by ID
|
|
3663
|
+
*/
|
|
3664
|
+
async findById(id) {
|
|
3665
|
+
const record = await this.recordService.getRecord(id);
|
|
3666
|
+
if (!record) return null;
|
|
3667
|
+
if (this.state.raw) {
|
|
3668
|
+
return record;
|
|
3669
|
+
}
|
|
3670
|
+
return formatRecord(record);
|
|
3671
|
+
}
|
|
3672
|
+
/**
|
|
3673
|
+
* Count records matching filters
|
|
3674
|
+
*/
|
|
3675
|
+
async count() {
|
|
3676
|
+
const result = await this.limit(0).fetch();
|
|
3677
|
+
return result.total;
|
|
3678
|
+
}
|
|
3679
|
+
/**
|
|
3680
|
+
* Fetch records grouped by the groupBy attribute (for Kanban views).
|
|
3681
|
+
* Requires groupBy() to be called first.
|
|
3682
|
+
*
|
|
3683
|
+
* @example
|
|
3684
|
+
* ```typescript
|
|
3685
|
+
* const result = await schema.from('tasks')
|
|
3686
|
+
* .groupBy('status')
|
|
3687
|
+
* .fetchGrouped();
|
|
3688
|
+
* // → { groups: { 'todo': [...], 'in_progress': [...], 'done': [...] }, total: 50 }
|
|
3689
|
+
* ```
|
|
3690
|
+
*/
|
|
3691
|
+
async fetchGrouped() {
|
|
3692
|
+
if (!this.state.groupBy) {
|
|
3693
|
+
throw new Error("groupBy() must be called before fetchGrouped()");
|
|
3694
|
+
}
|
|
3695
|
+
const result = await this.fetch();
|
|
3696
|
+
const groupByAttr = this.state.groupBy;
|
|
3697
|
+
const groups = {};
|
|
3698
|
+
for (const record of result.records) {
|
|
3699
|
+
const value = record[groupByAttr];
|
|
3700
|
+
const key = value != null ? String(value) : "__null__";
|
|
3701
|
+
if (!groups[key]) {
|
|
3702
|
+
groups[key] = [];
|
|
3703
|
+
}
|
|
3704
|
+
groups[key].push(record);
|
|
3705
|
+
}
|
|
3706
|
+
return {
|
|
3707
|
+
groups,
|
|
3708
|
+
total: result.total
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3711
|
+
// ============================================================================
|
|
3712
|
+
// TERMINATION METHODS (WRITE)
|
|
3713
|
+
// ============================================================================
|
|
3714
|
+
/**
|
|
3715
|
+
* Create a new record
|
|
3716
|
+
* System fields (id, createdAt, updatedAt) are managed automatically.
|
|
3717
|
+
*
|
|
3718
|
+
* @example
|
|
3719
|
+
* ```typescript
|
|
3720
|
+
* const product = await schema.from('products')
|
|
3721
|
+
* .insert({ name: 'iPhone', price: 999 });
|
|
3722
|
+
*
|
|
3723
|
+
* // Allow draft (missing required fields)
|
|
3724
|
+
* const draft = await schema.from('products')
|
|
3725
|
+
* .insert({ name: 'Draft' }, { allowDraft: true });
|
|
3726
|
+
*
|
|
3727
|
+
* // With custom metadata
|
|
3728
|
+
* const withMeta = await schema.from('products')
|
|
3729
|
+
* .insert({ name: 'iPhone' }, { metadata: { externalId: 'ext-123' } });
|
|
3730
|
+
* ```
|
|
3731
|
+
*/
|
|
3732
|
+
async insert(data, options) {
|
|
3733
|
+
const objectId = await this.resolveObjectId();
|
|
3734
|
+
const record = await this.recordService.createRecord(
|
|
3735
|
+
objectId,
|
|
3736
|
+
data,
|
|
3737
|
+
{
|
|
3738
|
+
allowDraft: options?.allowDraft,
|
|
3739
|
+
validate: options?.validate,
|
|
3740
|
+
metadata: options?.metadata
|
|
3741
|
+
}
|
|
3742
|
+
);
|
|
3743
|
+
if (this.state.raw) {
|
|
3744
|
+
return record;
|
|
3745
|
+
}
|
|
3746
|
+
return formatRecord(record);
|
|
3747
|
+
}
|
|
3748
|
+
/**
|
|
3749
|
+
* Update records matching filters (expects exactly 1 record by default)
|
|
3750
|
+
* Use with eq('id', ...) for single record update.
|
|
3751
|
+
* System fields (id, createdAt, updatedAt) are managed automatically.
|
|
3752
|
+
*
|
|
3753
|
+
* @example
|
|
3754
|
+
* ```typescript
|
|
3755
|
+
* const updated = await schema.from('products')
|
|
3756
|
+
* .eq('id', 'rec-123')
|
|
3757
|
+
* .update({ price: 899 });
|
|
3758
|
+
*
|
|
3759
|
+
* // Update with metadata (replaces existing metadata)
|
|
3760
|
+
* const withMeta = await schema.from('products')
|
|
3761
|
+
* .eq('id', 'rec-123')
|
|
3762
|
+
* .update({ price: 899 }, { metadata: { synced: true } });
|
|
3763
|
+
* ```
|
|
3764
|
+
*/
|
|
3765
|
+
async update(data, options) {
|
|
3766
|
+
const existing = await this.single();
|
|
3767
|
+
const recordId = this.state.raw ? existing.id : existing.id;
|
|
3768
|
+
const record = await this.recordService.updateRecord(
|
|
3769
|
+
recordId,
|
|
3770
|
+
data,
|
|
3771
|
+
{
|
|
3772
|
+
partial: true,
|
|
3773
|
+
metadata: options?.metadata
|
|
3774
|
+
}
|
|
3775
|
+
);
|
|
3776
|
+
if (this.state.raw) {
|
|
3777
|
+
return record;
|
|
3778
|
+
}
|
|
3779
|
+
return formatRecord(record);
|
|
3780
|
+
}
|
|
3781
|
+
/**
|
|
3782
|
+
* Delete record matching filters (expects exactly 1 record)
|
|
3783
|
+
*
|
|
3784
|
+
* @example
|
|
3785
|
+
* ```typescript
|
|
3786
|
+
* await schema.from('products')
|
|
3787
|
+
* .eq('id', 'rec-123')
|
|
3788
|
+
* .delete();
|
|
3789
|
+
* ```
|
|
3790
|
+
*/
|
|
3791
|
+
async delete() {
|
|
3792
|
+
const existing = await this.single();
|
|
3793
|
+
const recordId = this.state.raw ? existing.id : existing.id;
|
|
3794
|
+
await this.recordService.deleteRecord(recordId);
|
|
3795
|
+
}
|
|
3796
|
+
/**
|
|
3797
|
+
* Upsert (update if exists by ID, insert otherwise)
|
|
3798
|
+
* Provide 'id' for lookup. System fields (createdAt, updatedAt) are managed automatically.
|
|
3799
|
+
*
|
|
3800
|
+
* @example
|
|
3801
|
+
* ```typescript
|
|
3802
|
+
* // Insert if not exists, update if exists
|
|
3803
|
+
* const product = await schema.from('products')
|
|
3804
|
+
* .upsert({ id: 'rec-123', name: 'iPhone', price: 999 });
|
|
3805
|
+
*
|
|
3806
|
+
* // With metadata
|
|
3807
|
+
* const withMeta = await schema.from('products')
|
|
3808
|
+
* .upsert({ name: 'iPhone' }, { metadata: { source: 'import' } });
|
|
3809
|
+
* ```
|
|
3810
|
+
*/
|
|
3811
|
+
async upsert(data, options) {
|
|
3812
|
+
const { id, ...rest } = data;
|
|
3813
|
+
const writeData = rest;
|
|
3814
|
+
if (id) {
|
|
3815
|
+
const existing = await this.findById(id);
|
|
3816
|
+
if (existing) {
|
|
3817
|
+
return this.eq("id", id).update(writeData, {
|
|
3818
|
+
metadata: options?.metadata
|
|
3819
|
+
});
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
return this.insert(writeData, options);
|
|
3823
|
+
}
|
|
3824
|
+
};
|
|
3825
|
+
function createQueryBuilder(recordService, adapter, objectName) {
|
|
3826
|
+
return new QueryBuilder(recordService, adapter, objectName);
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3339
3829
|
// src/runtime/formula/evaluator.ts
|
|
3340
3830
|
var import_expr_eval = require("expr-eval");
|
|
3341
3831
|
function createFormulaParser() {
|
|
@@ -4177,6 +4667,7 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4177
4667
|
label: data.label,
|
|
4178
4668
|
completionStatus: data.completionStatus,
|
|
4179
4669
|
values: data.data,
|
|
4670
|
+
metadata: data.metadata ?? {},
|
|
4180
4671
|
createdAt: /* @__PURE__ */ new Date(),
|
|
4181
4672
|
updatedAt: /* @__PURE__ */ new Date()
|
|
4182
4673
|
};
|
|
@@ -4189,11 +4680,13 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4189
4680
|
if (!existing) {
|
|
4190
4681
|
return Promise.reject(new Error(`ObjectRecord ${id} not found`));
|
|
4191
4682
|
}
|
|
4192
|
-
const { __completionStatus, __label, ...valueData } = data;
|
|
4683
|
+
const { __completionStatus, __label, __metadata, ...valueData } = data;
|
|
4193
4684
|
const updated = {
|
|
4194
4685
|
...existing,
|
|
4195
4686
|
label: __label ?? existing.label,
|
|
4196
4687
|
completionStatus: __completionStatus ?? existing.completionStatus,
|
|
4688
|
+
// Metadata uses replace behavior (not merge)
|
|
4689
|
+
metadata: __metadata !== void 0 ? __metadata : existing.metadata,
|
|
4197
4690
|
values: { ...existing.values, ...valueData },
|
|
4198
4691
|
updatedAt: /* @__PURE__ */ new Date()
|
|
4199
4692
|
};
|
|
@@ -5871,7 +6364,7 @@ var GlobalSearchService = class {
|
|
|
5871
6364
|
};
|
|
5872
6365
|
|
|
5873
6366
|
// src/runtime/services/object-schema.service.ts
|
|
5874
|
-
var
|
|
6367
|
+
var _ObjectSchemaService = class _ObjectSchemaService {
|
|
5875
6368
|
constructor(adapter, nativeRegistry, options) {
|
|
5876
6369
|
this.adapter = adapter;
|
|
5877
6370
|
this.nativeRegistry = nativeRegistry;
|
|
@@ -6294,6 +6787,14 @@ var ObjectSchemaService = class {
|
|
|
6294
6787
|
"Invalid attribute name format.\nName must be a valid variable identifier:\n \u2705 Valid: 'firstName', 'first_name', 'FirstName', 'FIRST_NAME'\n \u274C Invalid: 'first-name', 'first name', '123name', 'first.name'"
|
|
6295
6788
|
);
|
|
6296
6789
|
}
|
|
6790
|
+
if (_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.includes(
|
|
6791
|
+
name
|
|
6792
|
+
)) {
|
|
6793
|
+
throw new Error(
|
|
6794
|
+
`"${name}" is a reserved name and cannot be used as an attribute name.
|
|
6795
|
+
Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
6796
|
+
);
|
|
6797
|
+
}
|
|
6297
6798
|
}
|
|
6298
6799
|
/**
|
|
6299
6800
|
* Get complete object schema (system + custom attributes)
|
|
@@ -6540,6 +7041,17 @@ var ObjectSchemaService = class {
|
|
|
6540
7041
|
return baseAttr;
|
|
6541
7042
|
}
|
|
6542
7043
|
};
|
|
7044
|
+
/**
|
|
7045
|
+
* Reserved attribute names that cannot be used.
|
|
7046
|
+
* These names conflict with system fields on ObjectRecord.
|
|
7047
|
+
*/
|
|
7048
|
+
_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES = [
|
|
7049
|
+
"id",
|
|
7050
|
+
"createdAt",
|
|
7051
|
+
"updatedAt",
|
|
7052
|
+
"metadata"
|
|
7053
|
+
];
|
|
7054
|
+
var ObjectSchemaService = _ObjectSchemaService;
|
|
6543
7055
|
|
|
6544
7056
|
// src/runtime/services/permission.service.ts
|
|
6545
7057
|
var PermissionService = class {
|
|
@@ -7501,6 +8013,71 @@ var RollupService = class {
|
|
|
7501
8013
|
}
|
|
7502
8014
|
return [...new Set(affectedIds)];
|
|
7503
8015
|
}
|
|
8016
|
+
/**
|
|
8017
|
+
* Find records that have forward rollups pointing to the modified record.
|
|
8018
|
+
*
|
|
8019
|
+
* Forward rollups are rollups where the record has a relation attribute
|
|
8020
|
+
* pointing to another object, and the rollup aggregates values from that target.
|
|
8021
|
+
* When the target record changes, we need to recalculate these rollups.
|
|
8022
|
+
*
|
|
8023
|
+
* Example: Order has relation "company" → Company, and rollup "capitalSocial"
|
|
8024
|
+
* aggregating from the Company. When Company.capitalSocial changes,
|
|
8025
|
+
* all Orders pointing to that Company need their rollup recalculated.
|
|
8026
|
+
*
|
|
8027
|
+
* @param changedRecord - The record that was modified
|
|
8028
|
+
* @param changedSchema - Schema of the changed record's object
|
|
8029
|
+
* @returns Array of records that need their forward rollups recalculated
|
|
8030
|
+
*/
|
|
8031
|
+
async findRecordsWithForwardRollup(changedRecord, changedSchema) {
|
|
8032
|
+
const affectedRecords = [];
|
|
8033
|
+
const recordObject = await this.adapter.objects.findById(changedRecord.objectId);
|
|
8034
|
+
if (!recordObject) {
|
|
8035
|
+
return affectedRecords;
|
|
8036
|
+
}
|
|
8037
|
+
const tenantId = recordObject.tenantId;
|
|
8038
|
+
const allObjects = await this.adapter.objects.list(tenantId);
|
|
8039
|
+
for (const obj of allObjects) {
|
|
8040
|
+
if (obj.id === changedRecord.objectId) {
|
|
8041
|
+
continue;
|
|
8042
|
+
}
|
|
8043
|
+
const attributes = await this.adapter.attributes.findByObjectId(obj.id);
|
|
8044
|
+
const rollupAttrs = attributes.filter((a) => a.type === "rollup");
|
|
8045
|
+
if (rollupAttrs.length === 0) {
|
|
8046
|
+
continue;
|
|
8047
|
+
}
|
|
8048
|
+
for (const rollupDbAttr of rollupAttrs) {
|
|
8049
|
+
const rollupConfig = rollupDbAttr.config;
|
|
8050
|
+
if (!rollupConfig?.relationAttribute) {
|
|
8051
|
+
continue;
|
|
8052
|
+
}
|
|
8053
|
+
const relationAttr = attributes.find(
|
|
8054
|
+
(a) => a.type === "relation" && a.name === rollupConfig.relationAttribute
|
|
8055
|
+
);
|
|
8056
|
+
if (!relationAttr) {
|
|
8057
|
+
continue;
|
|
8058
|
+
}
|
|
8059
|
+
const relationConfig = relationAttr.config;
|
|
8060
|
+
const targetsChangedObject = relationConfig?.targets?.some(
|
|
8061
|
+
(t) => t.object === changedSchema.name
|
|
8062
|
+
);
|
|
8063
|
+
if (!targetsChangedObject) {
|
|
8064
|
+
continue;
|
|
8065
|
+
}
|
|
8066
|
+
const recordsPointingToChanged = await this.adapter.objectRecords.findByRelation(
|
|
8067
|
+
tenantId,
|
|
8068
|
+
obj.id,
|
|
8069
|
+
relationAttr.name,
|
|
8070
|
+
changedRecord.id
|
|
8071
|
+
);
|
|
8072
|
+
affectedRecords.push(...recordsPointingToChanged);
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
8075
|
+
const uniqueRecords = /* @__PURE__ */ new Map();
|
|
8076
|
+
for (const record of affectedRecords) {
|
|
8077
|
+
uniqueRecords.set(record.id, record);
|
|
8078
|
+
}
|
|
8079
|
+
return Array.from(uniqueRecords.values());
|
|
8080
|
+
}
|
|
7504
8081
|
};
|
|
7505
8082
|
|
|
7506
8083
|
// src/runtime/services/user.service.ts
|
|
@@ -7625,7 +8202,11 @@ var RecordService = class {
|
|
|
7625
8202
|
constructor(adapter, tenantId, options) {
|
|
7626
8203
|
this.adapter = adapter;
|
|
7627
8204
|
this.tenantId = tenantId;
|
|
7628
|
-
this.schemaService = new ObjectSchemaService(adapter, registry
|
|
8205
|
+
this.schemaService = new ObjectSchemaService(adapter, registry, {
|
|
8206
|
+
userId: options?.userId,
|
|
8207
|
+
userEmail: options?.userEmail,
|
|
8208
|
+
auditService: options?.auditService
|
|
8209
|
+
});
|
|
7629
8210
|
this.relationService = new RelationService(adapter, registry);
|
|
7630
8211
|
this.userService = new UserService(adapter, tenantId);
|
|
7631
8212
|
this.rollupService = new RollupService(adapter);
|
|
@@ -7780,7 +8361,8 @@ var RecordService = class {
|
|
|
7780
8361
|
objectId,
|
|
7781
8362
|
data,
|
|
7782
8363
|
label,
|
|
7783
|
-
completionStatus
|
|
8364
|
+
completionStatus,
|
|
8365
|
+
metadata: options?.metadata
|
|
7784
8366
|
});
|
|
7785
8367
|
if (!options?.skipHooks) {
|
|
7786
8368
|
const afterCtx = {
|
|
@@ -7894,11 +8476,15 @@ var RecordService = class {
|
|
|
7894
8476
|
}
|
|
7895
8477
|
const completionStatus = computeRecordStatus(schema, mergedData);
|
|
7896
8478
|
const label = await this.computeLabel(schema, mergedData);
|
|
7897
|
-
const
|
|
8479
|
+
const updatePayload = {
|
|
7898
8480
|
...data,
|
|
7899
8481
|
__completionStatus: completionStatus,
|
|
7900
8482
|
__label: label
|
|
7901
|
-
}
|
|
8483
|
+
};
|
|
8484
|
+
if (options?.metadata !== void 0) {
|
|
8485
|
+
updatePayload.__metadata = options.metadata;
|
|
8486
|
+
}
|
|
8487
|
+
const updated = await this.adapter.objectRecords.update(recordId, updatePayload);
|
|
7902
8488
|
if (!options?.skipHooks) {
|
|
7903
8489
|
const afterCtx = {
|
|
7904
8490
|
...hookCtx,
|
|
@@ -8167,9 +8753,10 @@ var RecordService = class {
|
|
|
8167
8753
|
/**
|
|
8168
8754
|
* Recalculate rollups after a record changes
|
|
8169
8755
|
*
|
|
8170
|
-
* This handles
|
|
8756
|
+
* This handles three cases:
|
|
8171
8757
|
* 1. The record itself has rollups (e.g., aggregating from related records it points to)
|
|
8172
|
-
* 2. Parent records have rollups that aggregate from this record
|
|
8758
|
+
* 2. Parent records have rollups that aggregate from this record (reverse pattern)
|
|
8759
|
+
* 3. Records that have forward rollups pointing to this record (forward pattern)
|
|
8173
8760
|
*
|
|
8174
8761
|
* @param record - The record that was modified
|
|
8175
8762
|
* @param schema - Schema of the record's object
|
|
@@ -8183,19 +8770,26 @@ var RecordService = class {
|
|
|
8183
8770
|
await this.rollupService.recalculateAndUpdate(record, schema);
|
|
8184
8771
|
}
|
|
8185
8772
|
const affectedParentIds = await this.rollupService.findAffectedParentRecords(record, schema);
|
|
8186
|
-
if (affectedParentIds.length
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
(
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
await this.rollupService.recalculateAndUpdate(parentRecord, parentSchema);
|
|
8773
|
+
if (affectedParentIds.length > 0) {
|
|
8774
|
+
const parentRecords = await this.adapter.objectRecords.findByIds(affectedParentIds);
|
|
8775
|
+
for (const parentRecord of parentRecords) {
|
|
8776
|
+
const parentSchema = await this.schemaService.getObjectSchema(parentRecord.objectId);
|
|
8777
|
+
const rollupAttrs = parentSchema.attributes.filter(
|
|
8778
|
+
(a) => a.type === "rollup"
|
|
8779
|
+
);
|
|
8780
|
+
if (rollupAttrs.length > 0) {
|
|
8781
|
+
await this.rollupService.recalculateAndUpdate(parentRecord, parentSchema);
|
|
8782
|
+
}
|
|
8197
8783
|
}
|
|
8198
8784
|
}
|
|
8785
|
+
const affectedForwardRecords = await this.rollupService.findRecordsWithForwardRollup(
|
|
8786
|
+
record,
|
|
8787
|
+
schema
|
|
8788
|
+
);
|
|
8789
|
+
for (const forwardRecord of affectedForwardRecords) {
|
|
8790
|
+
const forwardSchema = await this.schemaService.getObjectSchema(forwardRecord.objectId);
|
|
8791
|
+
await this.rollupService.recalculateAndUpdate(forwardRecord, forwardSchema);
|
|
8792
|
+
}
|
|
8199
8793
|
}
|
|
8200
8794
|
/**
|
|
8201
8795
|
* Permanently delete a record (hard delete)
|
|
@@ -9323,6 +9917,9 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
9323
9917
|
PermissionService,
|
|
9324
9918
|
ProtectedResourceError,
|
|
9325
9919
|
ProtectedRoleError,
|
|
9920
|
+
QueryBuilder,
|
|
9921
|
+
QueryMultipleResultsError,
|
|
9922
|
+
QueryNoResultError,
|
|
9326
9923
|
RecordNotFoundError,
|
|
9327
9924
|
RecordReferencedError,
|
|
9328
9925
|
RecordService,
|
|
@@ -9331,6 +9928,8 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
9331
9928
|
RoleNotFoundError,
|
|
9332
9929
|
RollupScheduler,
|
|
9333
9930
|
RollupService,
|
|
9931
|
+
SHORTCUT_TO_FILTER_OPERATOR,
|
|
9932
|
+
SYSTEM_FIELD_NAMES,
|
|
9334
9933
|
SYSTEM_RESOURCES,
|
|
9335
9934
|
SYSTEM_RESOURCE_LABELS,
|
|
9336
9935
|
SchemaError,
|
|
@@ -9353,6 +9952,7 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
9353
9952
|
createCheckboxValidator,
|
|
9354
9953
|
createCurrencyValidator,
|
|
9355
9954
|
createDateValidator,
|
|
9955
|
+
createDefaultState,
|
|
9356
9956
|
createDraftValidator,
|
|
9357
9957
|
createFileValidator,
|
|
9358
9958
|
createFormulaValidator,
|
|
@@ -9363,6 +9963,7 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
9363
9963
|
createNumberValidator,
|
|
9364
9964
|
createObjectValidator,
|
|
9365
9965
|
createPhoneValidator,
|
|
9966
|
+
createQueryBuilder,
|
|
9366
9967
|
createRatingValidator,
|
|
9367
9968
|
createRelationValidator,
|
|
9368
9969
|
createRollupValidator,
|
|
@@ -9392,6 +9993,8 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
9392
9993
|
flow,
|
|
9393
9994
|
formatAttributeValue,
|
|
9394
9995
|
formatFormulaResult,
|
|
9996
|
+
formatRecord,
|
|
9997
|
+
formatRecords,
|
|
9395
9998
|
formula,
|
|
9396
9999
|
formulaConfigSchema,
|
|
9397
10000
|
generateId,
|