@stndrds/schema 0.1.0-alpha.41 → 0.1.0-alpha.43
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-4ZUAND4E.mjs → chunk-MXYJTCA3.mjs} +323 -477
- package/dist/{chunk-6ID63D4M.js → chunk-SWD6OPOV.js} +323 -477
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -6
- package/dist/index.mjs +1 -1
- package/dist/{runtime-Vg_IW6IO.d.mts → runtime-CvRbtIhb.d.mts} +9 -306
- package/dist/{runtime-Vg_IW6IO.d.ts → runtime-CvRbtIhb.d.ts} +9 -306
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +2 -2
- package/dist/runtime.mjs +1 -1
- package/package.json +2 -2
|
@@ -3300,11 +3300,11 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
3300
3300
|
}
|
|
3301
3301
|
return Promise.resolve(updated);
|
|
3302
3302
|
},
|
|
3303
|
-
async batchRefreshLabels(objectId,
|
|
3303
|
+
async batchRefreshLabels(objectId, computeLabel2) {
|
|
3304
3304
|
let updated = 0;
|
|
3305
3305
|
for (const record of stores.objectRecords.values()) {
|
|
3306
3306
|
if (record.objectId === objectId) {
|
|
3307
|
-
const newLabel = await
|
|
3307
|
+
const newLabel = await computeLabel2(record.values);
|
|
3308
3308
|
if (newLabel !== record.label) {
|
|
3309
3309
|
record.label = newLabel;
|
|
3310
3310
|
record.updatedAt = /* @__PURE__ */ new Date();
|
|
@@ -10212,7 +10212,7 @@ var UserService = class extends TenantAwareService {
|
|
|
10212
10212
|
}
|
|
10213
10213
|
};
|
|
10214
10214
|
|
|
10215
|
-
// src/runtime/services/record.
|
|
10215
|
+
// src/runtime/services/record/defaults.ts
|
|
10216
10216
|
function applyDefaultValues(schema, data) {
|
|
10217
10217
|
const result = { ...data };
|
|
10218
10218
|
for (const attr of schema.attributes) {
|
|
@@ -10225,170 +10225,267 @@ function applyDefaultValues(schema, data) {
|
|
|
10225
10225
|
}
|
|
10226
10226
|
return result;
|
|
10227
10227
|
}
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
auditService: options?.auditService
|
|
10234
|
-
});
|
|
10235
|
-
this.relationService = new RelationService(adapter, registry);
|
|
10236
|
-
this.userService = new UserService(adapter);
|
|
10237
|
-
this.rollupService = new RollupService(adapter);
|
|
10238
|
-
this.hookRegistry = options?.hookRegistry ?? new NoopHookRegistry();
|
|
10239
|
-
this.permissionService = options?.permissionService;
|
|
10240
|
-
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter) : void 0);
|
|
10241
|
-
this.policyRegistry = options?.policyRegistry === null ? null : options?.policyRegistry ?? defaultPolicyRegistry;
|
|
10228
|
+
|
|
10229
|
+
// src/runtime/services/record/access.ts
|
|
10230
|
+
async function checkPermission(permissionService, userId, objectName, action) {
|
|
10231
|
+
if (permissionService && userId) {
|
|
10232
|
+
await permissionService.checkObjectAccess(userId, objectName, action);
|
|
10242
10233
|
}
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
*/
|
|
10248
|
-
async checkPermission(objectName, action) {
|
|
10249
|
-
if (this.permissionService && this.userId) {
|
|
10250
|
-
await this.permissionService.checkObjectAccess(this.userId, objectName, action);
|
|
10251
|
-
}
|
|
10234
|
+
}
|
|
10235
|
+
function getPolicy(policyRegistry, userId, objectName) {
|
|
10236
|
+
if (!(policyRegistry && userId)) {
|
|
10237
|
+
return void 0;
|
|
10252
10238
|
}
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
return this.policyRegistry.get(objectName);
|
|
10239
|
+
return policyRegistry.get(objectName);
|
|
10240
|
+
}
|
|
10241
|
+
function buildPolicyContext(objectName, userId, tenantId) {
|
|
10242
|
+
return { userId, tenantId, objectName };
|
|
10243
|
+
}
|
|
10244
|
+
function checkRecordAccess(policy, record, context) {
|
|
10245
|
+
if (!policy.canAccessRecord) {
|
|
10246
|
+
return true;
|
|
10262
10247
|
}
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
return {
|
|
10269
|
-
// biome-ignore lint/style/noNonNullAssertion: userId is guaranteed to be set
|
|
10270
|
-
userId: this.userId,
|
|
10271
|
-
tenantId: this.tenantId,
|
|
10272
|
-
objectName
|
|
10273
|
-
};
|
|
10248
|
+
return policy.canAccessRecord(context, record);
|
|
10249
|
+
}
|
|
10250
|
+
function checkRecordModifyOrThrow(policy, record, context) {
|
|
10251
|
+
if (!policy.canModifyRecord) {
|
|
10252
|
+
return;
|
|
10274
10253
|
}
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
* Returns true if no policy exists or user can access.
|
|
10278
|
-
* @internal
|
|
10279
|
-
*/
|
|
10280
|
-
checkRecordAccess(policy, record) {
|
|
10281
|
-
if (!policy.canAccessRecord) {
|
|
10282
|
-
return true;
|
|
10283
|
-
}
|
|
10284
|
-
return policy.canAccessRecord(this.buildPolicyContext(policy.objectName), record);
|
|
10254
|
+
if (!policy.canModifyRecord(context, record)) {
|
|
10255
|
+
throw new PolicyViolationError(policy.objectName, "update", record.id);
|
|
10285
10256
|
}
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
*/
|
|
10291
|
-
checkRecordModify(policy, record) {
|
|
10292
|
-
if (!policy.canModifyRecord) {
|
|
10293
|
-
return;
|
|
10294
|
-
}
|
|
10295
|
-
const canModify = policy.canModifyRecord(this.buildPolicyContext(policy.objectName), record);
|
|
10296
|
-
if (!canModify) {
|
|
10297
|
-
throw new PolicyViolationError(policy.objectName, "update", record.id);
|
|
10298
|
-
}
|
|
10257
|
+
}
|
|
10258
|
+
function checkRecordDeleteOrThrow(policy, record, context) {
|
|
10259
|
+
if (!policy.canDeleteRecord) {
|
|
10260
|
+
return;
|
|
10299
10261
|
}
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10262
|
+
if (!policy.canDeleteRecord(context, record)) {
|
|
10263
|
+
throw new PolicyViolationError(policy.objectName, "delete", record.id);
|
|
10264
|
+
}
|
|
10265
|
+
}
|
|
10266
|
+
|
|
10267
|
+
// src/runtime/services/record/label.ts
|
|
10268
|
+
function extractRelationIds2(val) {
|
|
10269
|
+
if (typeof val === "string") return [val];
|
|
10270
|
+
if (Array.isArray(val) && typeof val[0] === "string") return [val[0]];
|
|
10271
|
+
return [];
|
|
10272
|
+
}
|
|
10273
|
+
async function resolveRelationLabels(relationAttrs, values, resolver) {
|
|
10274
|
+
const resolvedMap = /* @__PURE__ */ new Map();
|
|
10275
|
+
const idsWithAttrId = [];
|
|
10276
|
+
const idsWithoutAttrId = [];
|
|
10277
|
+
for (const attr of relationAttrs) {
|
|
10278
|
+
const ids = extractRelationIds2(values[attr.name]);
|
|
10279
|
+
if (ids.length > 0) {
|
|
10280
|
+
if (attr.id) {
|
|
10281
|
+
idsWithAttrId.push({ attrId: attr.id, ids });
|
|
10282
|
+
} else {
|
|
10283
|
+
idsWithoutAttrId.push(...ids);
|
|
10284
|
+
}
|
|
10312
10285
|
}
|
|
10313
10286
|
}
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
|
|
10329
|
-
idsWithoutAttrId.push(...ids);
|
|
10287
|
+
await Promise.all(
|
|
10288
|
+
idsWithAttrId.map(async ({ attrId, ids }) => {
|
|
10289
|
+
const resolved = await resolver.resolveRelationIds(ids, attrId);
|
|
10290
|
+
for (const r of resolved) {
|
|
10291
|
+
resolvedMap.set(r.id, r.label);
|
|
10292
|
+
}
|
|
10293
|
+
})
|
|
10294
|
+
);
|
|
10295
|
+
if (idsWithoutAttrId.length > 0) {
|
|
10296
|
+
const uniqueIds = [...new Set(idsWithoutAttrId)].filter((id) => !resolvedMap.has(id));
|
|
10297
|
+
if (uniqueIds.length > 0) {
|
|
10298
|
+
const records = await resolver.findRecordLabels(uniqueIds);
|
|
10299
|
+
for (const record of records) {
|
|
10300
|
+
if (record.label) {
|
|
10301
|
+
resolvedMap.set(record.id, record.label);
|
|
10330
10302
|
}
|
|
10331
10303
|
}
|
|
10332
10304
|
}
|
|
10305
|
+
}
|
|
10306
|
+
return resolvedMap;
|
|
10307
|
+
}
|
|
10308
|
+
async function computeLabel(schema, values, resolver) {
|
|
10309
|
+
const attrNames = extractAttributeNames(schema.labelExpression);
|
|
10310
|
+
let enrichedValues = enrichValuesForDisplay(values, schema.attributes);
|
|
10311
|
+
const relationAttrs = schema.attributes.filter(
|
|
10312
|
+
(attr) => attr.type === "relation" && attrNames.includes(attr.name)
|
|
10313
|
+
);
|
|
10314
|
+
if (relationAttrs.length === 0) {
|
|
10315
|
+
return renderLabelExpression(schema.labelExpression, enrichedValues);
|
|
10316
|
+
}
|
|
10317
|
+
const resolvedMap = await resolveRelationLabels(relationAttrs, values, resolver);
|
|
10318
|
+
if (resolvedMap.size === 0) {
|
|
10319
|
+
return renderLabelExpression(schema.labelExpression, enrichedValues);
|
|
10320
|
+
}
|
|
10321
|
+
enrichedValues = { ...enrichedValues };
|
|
10322
|
+
for (const attr of relationAttrs) {
|
|
10323
|
+
const ids = extractRelationIds2(values[attr.name]);
|
|
10324
|
+
if (ids.length > 0 && resolvedMap.has(ids[0])) {
|
|
10325
|
+
enrichedValues[attr.name] = resolvedMap.get(ids[0]);
|
|
10326
|
+
}
|
|
10327
|
+
}
|
|
10328
|
+
return renderLabelExpression(schema.labelExpression, enrichedValues);
|
|
10329
|
+
}
|
|
10330
|
+
|
|
10331
|
+
// src/runtime/services/record/formula.ts
|
|
10332
|
+
function enrichWithFormulas(record, schema) {
|
|
10333
|
+
const formulaAttrs = schema.attributes.filter((a) => a.type === "formula");
|
|
10334
|
+
if (formulaAttrs.length === 0) {
|
|
10335
|
+
return record;
|
|
10336
|
+
}
|
|
10337
|
+
const enrichedValues = { ...record.values };
|
|
10338
|
+
for (const attr of formulaAttrs) {
|
|
10339
|
+
enrichedValues[attr.name] = evaluateFormulaAttribute(attr, record.values);
|
|
10340
|
+
}
|
|
10341
|
+
return {
|
|
10342
|
+
...record,
|
|
10343
|
+
values: enrichedValues
|
|
10344
|
+
};
|
|
10345
|
+
}
|
|
10346
|
+
function enrichRecordsWithFormulas(records, schema) {
|
|
10347
|
+
const formulaAttrs = schema.attributes.filter((a) => a.type === "formula");
|
|
10348
|
+
if (formulaAttrs.length === 0) {
|
|
10349
|
+
return records;
|
|
10350
|
+
}
|
|
10351
|
+
return records.map((record) => enrichWithFormulas(record, schema));
|
|
10352
|
+
}
|
|
10353
|
+
|
|
10354
|
+
// src/runtime/services/record/hook-context.ts
|
|
10355
|
+
function createGetChange(oldValues, newValues, changedAttributes) {
|
|
10356
|
+
return (attr) => ({
|
|
10357
|
+
oldValue: oldValues[attr],
|
|
10358
|
+
newValue: newValues[attr],
|
|
10359
|
+
changed: changedAttributes.includes(attr)
|
|
10360
|
+
});
|
|
10361
|
+
}
|
|
10362
|
+
function createContextForCreate(schema, tenantId, data, metadata) {
|
|
10363
|
+
const attributeNames = Object.keys(data);
|
|
10364
|
+
return {
|
|
10365
|
+
objectId: schema.id,
|
|
10366
|
+
objectName: schema.name,
|
|
10367
|
+
recordId: "",
|
|
10368
|
+
// Will be set after creation
|
|
10369
|
+
tenantId,
|
|
10370
|
+
record: null,
|
|
10371
|
+
// Will be set after creation
|
|
10372
|
+
oldValues: {},
|
|
10373
|
+
newValues: data,
|
|
10374
|
+
changedAttributes: attributeNames,
|
|
10375
|
+
// All attributes are "new"
|
|
10376
|
+
getChange: createGetChange({}, data, attributeNames),
|
|
10377
|
+
metadata: metadata ?? {},
|
|
10378
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
10379
|
+
};
|
|
10380
|
+
}
|
|
10381
|
+
function createContextForUpdate(schema, tenantId, existing, mergedData, changedAttributes, metadata) {
|
|
10382
|
+
const oldValuesSnapshot = { ...existing.values };
|
|
10383
|
+
return {
|
|
10384
|
+
objectId: schema.id,
|
|
10385
|
+
objectName: schema.name,
|
|
10386
|
+
recordId: existing.id,
|
|
10387
|
+
tenantId,
|
|
10388
|
+
record: existing,
|
|
10389
|
+
oldValues: oldValuesSnapshot,
|
|
10390
|
+
newValues: mergedData,
|
|
10391
|
+
changedAttributes,
|
|
10392
|
+
getChange: createGetChange(oldValuesSnapshot, mergedData, changedAttributes),
|
|
10393
|
+
metadata: metadata ?? {},
|
|
10394
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
10395
|
+
};
|
|
10396
|
+
}
|
|
10397
|
+
function createContextForDelete(schema, tenantId, record, metadata) {
|
|
10398
|
+
const attributeNames = Object.keys(record.values);
|
|
10399
|
+
return {
|
|
10400
|
+
objectId: schema.id,
|
|
10401
|
+
objectName: schema.name,
|
|
10402
|
+
recordId: record.id,
|
|
10403
|
+
tenantId,
|
|
10404
|
+
record,
|
|
10405
|
+
oldValues: record.values,
|
|
10406
|
+
newValues: {},
|
|
10407
|
+
changedAttributes: attributeNames,
|
|
10408
|
+
// All attributes are being "removed"
|
|
10409
|
+
getChange: createGetChange(record.values, {}, attributeNames),
|
|
10410
|
+
metadata: metadata ?? {},
|
|
10411
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
10412
|
+
};
|
|
10413
|
+
}
|
|
10414
|
+
function createContextForRestore(schema, tenantId, record, metadata) {
|
|
10415
|
+
return {
|
|
10416
|
+
objectId: schema.id,
|
|
10417
|
+
objectName: schema.name,
|
|
10418
|
+
recordId: record.id,
|
|
10419
|
+
tenantId,
|
|
10420
|
+
record,
|
|
10421
|
+
oldValues: record.values,
|
|
10422
|
+
newValues: record.values,
|
|
10423
|
+
changedAttributes: [],
|
|
10424
|
+
getChange: createGetChange(record.values, record.values, []),
|
|
10425
|
+
metadata: metadata ?? {},
|
|
10426
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
10427
|
+
};
|
|
10428
|
+
}
|
|
10429
|
+
|
|
10430
|
+
// src/runtime/services/record/rollup-cascade.ts
|
|
10431
|
+
async function recalculateParentRollups(record, schema, ctx) {
|
|
10432
|
+
const { rollupService, schemaService, findRecordsByIds } = ctx;
|
|
10433
|
+
const ownRollupAttrs = schema.attributes.filter((a) => a.type === "rollup");
|
|
10434
|
+
if (ownRollupAttrs.length > 0) {
|
|
10435
|
+
await rollupService.recalculateAndUpdate(record, schema);
|
|
10436
|
+
}
|
|
10437
|
+
const affectedParentIds = await rollupService.findAffectedParentRecords(record, schema);
|
|
10438
|
+
if (affectedParentIds.length > 0) {
|
|
10439
|
+
const parentRecords = await findRecordsByIds(affectedParentIds);
|
|
10333
10440
|
await Promise.all(
|
|
10334
|
-
|
|
10335
|
-
const
|
|
10336
|
-
|
|
10337
|
-
|
|
10441
|
+
parentRecords.map(async (parentRecord) => {
|
|
10442
|
+
const parentSchema = await schemaService.getObjectSchema(parentRecord.objectId);
|
|
10443
|
+
const rollupAttrs = parentSchema.attributes.filter(
|
|
10444
|
+
(a) => a.type === "rollup"
|
|
10445
|
+
);
|
|
10446
|
+
if (rollupAttrs.length > 0) {
|
|
10447
|
+
await rollupService.recalculateAndUpdate(parentRecord, parentSchema);
|
|
10338
10448
|
}
|
|
10339
10449
|
})
|
|
10340
10450
|
);
|
|
10341
|
-
if (idsWithoutAttrId.length > 0) {
|
|
10342
|
-
const uniqueIds = [...new Set(idsWithoutAttrId)].filter((id) => !resolvedMap.has(id));
|
|
10343
|
-
if (uniqueIds.length > 0) {
|
|
10344
|
-
const records = await this.adapter.objectRecords.findByIds(uniqueIds);
|
|
10345
|
-
for (const record of records) {
|
|
10346
|
-
if (record.label) {
|
|
10347
|
-
resolvedMap.set(record.id, record.label);
|
|
10348
|
-
}
|
|
10349
|
-
}
|
|
10350
|
-
}
|
|
10351
|
-
}
|
|
10352
|
-
return resolvedMap;
|
|
10353
10451
|
}
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
);
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
}
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
enrichedValues[attr.name] = resolvedMap.get(ids[0]);
|
|
10388
|
-
}
|
|
10389
|
-
}
|
|
10390
|
-
return renderLabelExpression(schema.labelExpression, enrichedValues);
|
|
10452
|
+
const affectedForwardRecords = await rollupService.findRecordsWithForwardRollup(record, schema);
|
|
10453
|
+
await Promise.all(
|
|
10454
|
+
affectedForwardRecords.map(async (forwardRecord) => {
|
|
10455
|
+
const forwardSchema = await schemaService.getObjectSchema(forwardRecord.objectId);
|
|
10456
|
+
await rollupService.recalculateAndUpdate(forwardRecord, forwardSchema);
|
|
10457
|
+
})
|
|
10458
|
+
);
|
|
10459
|
+
}
|
|
10460
|
+
|
|
10461
|
+
// src/runtime/services/record.service.ts
|
|
10462
|
+
var RecordService = class extends TenantAwareService {
|
|
10463
|
+
constructor(adapter, options) {
|
|
10464
|
+
super();
|
|
10465
|
+
this.adapter = adapter;
|
|
10466
|
+
this.schemaService = new ObjectSchemaService(adapter, registry, {
|
|
10467
|
+
auditService: options?.auditService
|
|
10468
|
+
});
|
|
10469
|
+
this.relationService = new RelationService(adapter, registry);
|
|
10470
|
+
this.userService = new UserService(adapter);
|
|
10471
|
+
this.rollupService = new RollupService(adapter);
|
|
10472
|
+
this.hookRegistry = options?.hookRegistry ?? new NoopHookRegistry();
|
|
10473
|
+
this.permissionService = options?.permissionService;
|
|
10474
|
+
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter) : void 0);
|
|
10475
|
+
this.policyRegistry = options?.policyRegistry === null ? null : options?.policyRegistry ?? defaultPolicyRegistry;
|
|
10476
|
+
this.labelResolver = {
|
|
10477
|
+
resolveRelationIds: (ids, attrId) => this.relationService.resolveIds(ids, attrId),
|
|
10478
|
+
findRecordLabels: (ids) => this.adapter.objectRecords.findByIds(ids)
|
|
10479
|
+
};
|
|
10480
|
+
this.rollupContext = {
|
|
10481
|
+
rollupService: this.rollupService,
|
|
10482
|
+
schemaService: this.schemaService,
|
|
10483
|
+
findRecordsByIds: (ids) => this.adapter.objectRecords.findByIds(ids)
|
|
10484
|
+
};
|
|
10391
10485
|
}
|
|
10486
|
+
// ============================================================================
|
|
10487
|
+
// CREATE
|
|
10488
|
+
// ============================================================================
|
|
10392
10489
|
/**
|
|
10393
10490
|
* Create a new record with validation
|
|
10394
10491
|
*
|
|
@@ -10398,31 +10495,17 @@ var RecordService = class extends TenantAwareService {
|
|
|
10398
10495
|
* @param data - Record data (attribute values)
|
|
10399
10496
|
* @param options - Creation options
|
|
10400
10497
|
* @returns Created record with computed completionStatus
|
|
10401
|
-
*
|
|
10402
|
-
* @example
|
|
10403
|
-
* ```typescript
|
|
10404
|
-
* const service = new RecordService(adapter, "tenant-123");
|
|
10405
|
-
*
|
|
10406
|
-
* // Create a complete record (strict validation)
|
|
10407
|
-
* const product = await service.createRecord("obj-product", {
|
|
10408
|
-
* name: "Nike Air Max",
|
|
10409
|
-
* price: 129.99,
|
|
10410
|
-
* status: "active"
|
|
10411
|
-
* });
|
|
10412
|
-
* // → product.completionStatus = "complete"
|
|
10413
|
-
*
|
|
10414
|
-
* // Create a draft record (allows missing required fields)
|
|
10415
|
-
* const draft = await service.createRecord("obj-product", {
|
|
10416
|
-
* name: "Draft Product"
|
|
10417
|
-
* }, { allowDraft: true });
|
|
10418
|
-
* // → draft.completionStatus = "draft"
|
|
10419
|
-
* ```
|
|
10420
10498
|
*/
|
|
10421
10499
|
async createRecord(objectId, data, options) {
|
|
10422
10500
|
const schema = await this.schemaService.getObjectSchema(objectId);
|
|
10423
10501
|
const dataWithDefaults = applyDefaultValues(schema, data);
|
|
10424
|
-
await this.
|
|
10425
|
-
const hookCtx =
|
|
10502
|
+
await checkPermission(this.permissionService, this.userId, schema.name, "create");
|
|
10503
|
+
const hookCtx = createContextForCreate(
|
|
10504
|
+
schema,
|
|
10505
|
+
this.tenantId,
|
|
10506
|
+
dataWithDefaults,
|
|
10507
|
+
options?.hookMetadata
|
|
10508
|
+
);
|
|
10426
10509
|
if (!options?.skipHooks) {
|
|
10427
10510
|
await this.hookRegistry.execute("beforeCreate", schema.name, hookCtx);
|
|
10428
10511
|
}
|
|
@@ -10440,7 +10523,7 @@ var RecordService = class extends TenantAwareService {
|
|
|
10440
10523
|
}
|
|
10441
10524
|
}
|
|
10442
10525
|
const completionStatus = computeRecordStatus(schema, dataWithDefaults);
|
|
10443
|
-
const label = await
|
|
10526
|
+
const label = await computeLabel(schema, dataWithDefaults, this.labelResolver);
|
|
10444
10527
|
const record = await this.adapter.objectRecords.create({
|
|
10445
10528
|
objectId,
|
|
10446
10529
|
data: dataWithDefaults,
|
|
@@ -10457,7 +10540,7 @@ var RecordService = class extends TenantAwareService {
|
|
|
10457
10540
|
};
|
|
10458
10541
|
await this.hookRegistry.execute("afterCreate", schema.name, afterCtx);
|
|
10459
10542
|
}
|
|
10460
|
-
await
|
|
10543
|
+
await recalculateParentRollups(record, schema, this.rollupContext);
|
|
10461
10544
|
if (this.auditService && this.userId) {
|
|
10462
10545
|
await this.auditService.logRecordAction({
|
|
10463
10546
|
action: "record.created",
|
|
@@ -10471,12 +10554,11 @@ var RecordService = class extends TenantAwareService {
|
|
|
10471
10554
|
}
|
|
10472
10555
|
return record;
|
|
10473
10556
|
}
|
|
10557
|
+
// ============================================================================
|
|
10558
|
+
// READ
|
|
10559
|
+
// ============================================================================
|
|
10474
10560
|
/**
|
|
10475
10561
|
* Get a record by ID
|
|
10476
|
-
*
|
|
10477
|
-
* @param recordId - Record UUID
|
|
10478
|
-
* @param options - Query options
|
|
10479
|
-
* @returns Record or null if not found
|
|
10480
10562
|
*/
|
|
10481
10563
|
async getRecord(recordId, options) {
|
|
10482
10564
|
const record = await this.adapter.objectRecords.findById(recordId);
|
|
@@ -10485,14 +10567,17 @@ var RecordService = class extends TenantAwareService {
|
|
|
10485
10567
|
}
|
|
10486
10568
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
10487
10569
|
if (!options?.skipPolicyCheck) {
|
|
10488
|
-
const policy = this.
|
|
10489
|
-
if (policy
|
|
10490
|
-
|
|
10570
|
+
const policy = getPolicy(this.policyRegistry, this.userId, schema.name);
|
|
10571
|
+
if (policy) {
|
|
10572
|
+
const ctx = buildPolicyContext(schema.name, this.userId, this.tenantId);
|
|
10573
|
+
if (!checkRecordAccess(policy, record, ctx)) {
|
|
10574
|
+
return null;
|
|
10575
|
+
}
|
|
10491
10576
|
}
|
|
10492
10577
|
}
|
|
10493
10578
|
let enrichedRecord = record;
|
|
10494
10579
|
if (!options?.skipFormulas) {
|
|
10495
|
-
enrichedRecord =
|
|
10580
|
+
enrichedRecord = enrichWithFormulas(record, schema);
|
|
10496
10581
|
}
|
|
10497
10582
|
if (options?.includeSchema) {
|
|
10498
10583
|
const recordWithSchema = enrichedRecord;
|
|
@@ -10511,28 +10596,11 @@ var RecordService = class extends TenantAwareService {
|
|
|
10511
10596
|
}
|
|
10512
10597
|
return record;
|
|
10513
10598
|
}
|
|
10599
|
+
// ============================================================================
|
|
10600
|
+
// UPDATE
|
|
10601
|
+
// ============================================================================
|
|
10514
10602
|
/**
|
|
10515
10603
|
* Update a record with validation
|
|
10516
|
-
*
|
|
10517
|
-
* The completion status is automatically recalculated after each update.
|
|
10518
|
-
* A draft record becomes complete when all required fields are filled.
|
|
10519
|
-
*
|
|
10520
|
-
* Triggers beforeUpdate and afterUpdate hooks if a HookRegistry is configured.
|
|
10521
|
-
*
|
|
10522
|
-
* @param recordId - Record UUID
|
|
10523
|
-
* @param data - Partial data to update
|
|
10524
|
-
* @param options - Update options
|
|
10525
|
-
* @returns Updated record with recalculated completionStatus
|
|
10526
|
-
*
|
|
10527
|
-
* @example
|
|
10528
|
-
* ```typescript
|
|
10529
|
-
* // Update a draft record to make it complete
|
|
10530
|
-
* const updated = await service.updateRecord(draftId, {
|
|
10531
|
-
* price: 99.99,
|
|
10532
|
-
* status: "active"
|
|
10533
|
-
* });
|
|
10534
|
-
* // → updated.completionStatus = "complete" if all required fields now present
|
|
10535
|
-
* ```
|
|
10536
10604
|
*/
|
|
10537
10605
|
async updateRecord(recordId, data, options) {
|
|
10538
10606
|
const existing = await this.adapter.objectRecords.findById(recordId);
|
|
@@ -10540,17 +10608,18 @@ var RecordService = class extends TenantAwareService {
|
|
|
10540
10608
|
throw new RecordNotFoundError(recordId);
|
|
10541
10609
|
}
|
|
10542
10610
|
const schema = await this.schemaService.getObjectSchema(existing.objectId);
|
|
10543
|
-
await this.
|
|
10544
|
-
const policy = this.
|
|
10545
|
-
if (policy) {
|
|
10546
|
-
this.
|
|
10611
|
+
await checkPermission(this.permissionService, this.userId, schema.name, "update");
|
|
10612
|
+
const policy = getPolicy(this.policyRegistry, this.userId, schema.name);
|
|
10613
|
+
if (policy && this.userId) {
|
|
10614
|
+
const ctx = buildPolicyContext(schema.name, this.userId, this.tenantId);
|
|
10615
|
+
checkRecordModifyOrThrow(policy, existing, ctx);
|
|
10547
10616
|
}
|
|
10548
10617
|
const mergedData = { ...existing.values, ...data };
|
|
10549
10618
|
const changedAttributes = Object.keys(data).filter((key) => existing.values[key] !== data[key]);
|
|
10550
|
-
const hookCtx =
|
|
10619
|
+
const hookCtx = createContextForUpdate(
|
|
10551
10620
|
schema,
|
|
10621
|
+
this.tenantId,
|
|
10552
10622
|
existing,
|
|
10553
|
-
data,
|
|
10554
10623
|
mergedData,
|
|
10555
10624
|
changedAttributes,
|
|
10556
10625
|
options?.hookMetadata
|
|
@@ -10584,7 +10653,7 @@ var RecordService = class extends TenantAwareService {
|
|
|
10584
10653
|
}
|
|
10585
10654
|
}
|
|
10586
10655
|
const completionStatus = computeRecordStatus(schema, mergedData);
|
|
10587
|
-
const label = await
|
|
10656
|
+
const label = await computeLabel(schema, mergedData, this.labelResolver);
|
|
10588
10657
|
const updatePayload = {
|
|
10589
10658
|
...data,
|
|
10590
10659
|
...hookModifiedValues,
|
|
@@ -10608,7 +10677,7 @@ var RecordService = class extends TenantAwareService {
|
|
|
10608
10677
|
};
|
|
10609
10678
|
await this.hookRegistry.execute("afterUpdate", schema.name, afterCtx);
|
|
10610
10679
|
}
|
|
10611
|
-
await
|
|
10680
|
+
await recalculateParentRollups(updated, schema, this.rollupContext);
|
|
10612
10681
|
const allChangedAttributes = [
|
|
10613
10682
|
...changedAttributes,
|
|
10614
10683
|
...Object.keys(hookModifiedValues).filter((k) => !changedAttributes.includes(k))
|
|
@@ -10632,89 +10701,11 @@ var RecordService = class extends TenantAwareService {
|
|
|
10632
10701
|
}
|
|
10633
10702
|
return updated;
|
|
10634
10703
|
}
|
|
10704
|
+
// ============================================================================
|
|
10705
|
+
// DELETE
|
|
10706
|
+
// ============================================================================
|
|
10635
10707
|
/**
|
|
10636
|
-
*
|
|
10637
|
-
* @internal
|
|
10638
|
-
*/
|
|
10639
|
-
buildHookContext(schema, existing, newData, mergedData, changedAttributes, metadata) {
|
|
10640
|
-
const oldValuesSnapshot = { ...existing.values };
|
|
10641
|
-
return {
|
|
10642
|
-
objectId: schema.id,
|
|
10643
|
-
objectName: schema.name,
|
|
10644
|
-
recordId: existing.id,
|
|
10645
|
-
tenantId: this.tenantId,
|
|
10646
|
-
record: existing,
|
|
10647
|
-
oldValues: oldValuesSnapshot,
|
|
10648
|
-
newValues: mergedData,
|
|
10649
|
-
changedAttributes,
|
|
10650
|
-
getChange: (attr) => ({
|
|
10651
|
-
oldValue: oldValuesSnapshot[attr],
|
|
10652
|
-
newValue: newData[attr] ?? oldValuesSnapshot[attr],
|
|
10653
|
-
changed: changedAttributes.includes(attr)
|
|
10654
|
-
}),
|
|
10655
|
-
metadata: metadata ?? {},
|
|
10656
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
10657
|
-
};
|
|
10658
|
-
}
|
|
10659
|
-
/**
|
|
10660
|
-
* Build hook context for create operations (no existing record)
|
|
10661
|
-
* @internal
|
|
10662
|
-
*/
|
|
10663
|
-
buildCreateHookContext(schema, data, metadata) {
|
|
10664
|
-
const attributeNames = Object.keys(data);
|
|
10665
|
-
return {
|
|
10666
|
-
objectId: schema.id,
|
|
10667
|
-
objectName: schema.name,
|
|
10668
|
-
recordId: "",
|
|
10669
|
-
// Will be set after creation
|
|
10670
|
-
tenantId: this.tenantId,
|
|
10671
|
-
record: null,
|
|
10672
|
-
// Will be set after creation
|
|
10673
|
-
oldValues: {},
|
|
10674
|
-
newValues: data,
|
|
10675
|
-
changedAttributes: attributeNames,
|
|
10676
|
-
// All attributes are "new"
|
|
10677
|
-
getChange: (attr) => ({
|
|
10678
|
-
oldValue: void 0,
|
|
10679
|
-
newValue: data[attr],
|
|
10680
|
-
changed: attributeNames.includes(attr)
|
|
10681
|
-
}),
|
|
10682
|
-
metadata: metadata ?? {},
|
|
10683
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
10684
|
-
};
|
|
10685
|
-
}
|
|
10686
|
-
/**
|
|
10687
|
-
* Build hook context for delete operations
|
|
10688
|
-
* @internal
|
|
10689
|
-
*/
|
|
10690
|
-
buildDeleteHookContext(schema, record, metadata) {
|
|
10691
|
-
const attributeNames = Object.keys(record.values);
|
|
10692
|
-
return {
|
|
10693
|
-
objectId: schema.id,
|
|
10694
|
-
objectName: schema.name,
|
|
10695
|
-
recordId: record.id,
|
|
10696
|
-
tenantId: this.tenantId,
|
|
10697
|
-
record,
|
|
10698
|
-
oldValues: record.values,
|
|
10699
|
-
newValues: {},
|
|
10700
|
-
changedAttributes: attributeNames,
|
|
10701
|
-
// All attributes are being "removed"
|
|
10702
|
-
getChange: (attr) => ({
|
|
10703
|
-
oldValue: record.values[attr],
|
|
10704
|
-
newValue: void 0,
|
|
10705
|
-
changed: attributeNames.includes(attr)
|
|
10706
|
-
}),
|
|
10707
|
-
metadata: metadata ?? {},
|
|
10708
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
10709
|
-
};
|
|
10710
|
-
}
|
|
10711
|
-
/**
|
|
10712
|
-
* Delete a record
|
|
10713
|
-
*
|
|
10714
|
-
* Triggers beforeDelete and afterDelete hooks if a HookRegistry is configured.
|
|
10715
|
-
*
|
|
10716
|
-
* @param recordId - Record UUID
|
|
10717
|
-
* @param options - Delete options
|
|
10708
|
+
* Delete a record (soft delete)
|
|
10718
10709
|
*/
|
|
10719
10710
|
async deleteRecord(recordId, options) {
|
|
10720
10711
|
const record = await this.adapter.objectRecords.findById(recordId);
|
|
@@ -10722,15 +10713,14 @@ var RecordService = class extends TenantAwareService {
|
|
|
10722
10713
|
throw new RecordNotFoundError(recordId);
|
|
10723
10714
|
}
|
|
10724
10715
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
10725
|
-
await this.
|
|
10726
|
-
const policy = this.
|
|
10727
|
-
if (policy) {
|
|
10728
|
-
this.
|
|
10716
|
+
await checkPermission(this.permissionService, this.userId, schema.name, "delete");
|
|
10717
|
+
const policy = getPolicy(this.policyRegistry, this.userId, schema.name);
|
|
10718
|
+
if (policy && this.userId) {
|
|
10719
|
+
const ctx = buildPolicyContext(schema.name, this.userId, this.tenantId);
|
|
10720
|
+
checkRecordDeleteOrThrow(policy, record, ctx);
|
|
10729
10721
|
}
|
|
10730
|
-
if (options?.checkSystem) {
|
|
10731
|
-
|
|
10732
|
-
throw new ProtectedResourceError("object", schema.name, "delete");
|
|
10733
|
-
}
|
|
10722
|
+
if (options?.checkSystem && schema.system) {
|
|
10723
|
+
throw new ProtectedResourceError("object", schema.name, "delete");
|
|
10734
10724
|
}
|
|
10735
10725
|
if (!options?.skipReferenceCheck) {
|
|
10736
10726
|
const references = await this.adapter.objectRecords.countRecordsReferencingId(recordId);
|
|
@@ -10738,7 +10728,7 @@ var RecordService = class extends TenantAwareService {
|
|
|
10738
10728
|
throw new RecordReferencedError(recordId, references);
|
|
10739
10729
|
}
|
|
10740
10730
|
}
|
|
10741
|
-
const hookCtx =
|
|
10731
|
+
const hookCtx = createContextForDelete(schema, this.tenantId, record, options?.hookMetadata);
|
|
10742
10732
|
if (!options?.skipHooks) {
|
|
10743
10733
|
await this.hookRegistry.execute("beforeDelete", schema.name, hookCtx);
|
|
10744
10734
|
}
|
|
@@ -10746,7 +10736,7 @@ var RecordService = class extends TenantAwareService {
|
|
|
10746
10736
|
if (!options?.skipHooks) {
|
|
10747
10737
|
await this.hookRegistry.execute("afterDelete", schema.name, hookCtx);
|
|
10748
10738
|
}
|
|
10749
|
-
await
|
|
10739
|
+
await recalculateParentRollups(record, schema, this.rollupContext);
|
|
10750
10740
|
if (this.auditService && this.userId) {
|
|
10751
10741
|
await this.auditService.logRecordAction({
|
|
10752
10742
|
action: "record.deleted",
|
|
@@ -10759,21 +10749,20 @@ var RecordService = class extends TenantAwareService {
|
|
|
10759
10749
|
});
|
|
10760
10750
|
}
|
|
10761
10751
|
}
|
|
10752
|
+
/**
|
|
10753
|
+
* Permanently delete a record (hard delete)
|
|
10754
|
+
*/
|
|
10755
|
+
async hardDeleteRecord(recordId) {
|
|
10756
|
+
const record = await this.getRecordOrThrow(recordId);
|
|
10757
|
+
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
10758
|
+
await checkPermission(this.permissionService, this.userId, schema.name, "delete");
|
|
10759
|
+
await this.adapter.objectRecords.hardDelete(recordId);
|
|
10760
|
+
}
|
|
10761
|
+
// ============================================================================
|
|
10762
|
+
// RESTORE
|
|
10763
|
+
// ============================================================================
|
|
10762
10764
|
/**
|
|
10763
10765
|
* Restore a soft-deleted record
|
|
10764
|
-
*
|
|
10765
|
-
* Triggers beforeRestore and afterRestore hooks if a HookRegistry is configured.
|
|
10766
|
-
*
|
|
10767
|
-
* @param recordId - Record UUID
|
|
10768
|
-
* @param options - Restore options
|
|
10769
|
-
* @returns Restored record
|
|
10770
|
-
*
|
|
10771
|
-
* @example
|
|
10772
|
-
* ```typescript
|
|
10773
|
-
* // Restore a deleted record
|
|
10774
|
-
* const restored = await service.restoreRecord("rec-123");
|
|
10775
|
-
* console.log(restored.deletedAt); // null
|
|
10776
|
-
* ```
|
|
10777
10766
|
*/
|
|
10778
10767
|
async restoreRecord(recordId, options) {
|
|
10779
10768
|
const record = await this.getRecordOrThrow(recordId);
|
|
@@ -10785,8 +10774,8 @@ var RecordService = class extends TenantAwareService {
|
|
|
10785
10774
|
);
|
|
10786
10775
|
}
|
|
10787
10776
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
10788
|
-
await this.
|
|
10789
|
-
const hookCtx =
|
|
10777
|
+
await checkPermission(this.permissionService, this.userId, schema.name, "update");
|
|
10778
|
+
const hookCtx = createContextForRestore(schema, this.tenantId, record, options?.hookMetadata);
|
|
10790
10779
|
if (!options?.skipHooks) {
|
|
10791
10780
|
await this.hookRegistry.execute("beforeRestore", schema.name, hookCtx);
|
|
10792
10781
|
}
|
|
@@ -10811,150 +10800,22 @@ var RecordService = class extends TenantAwareService {
|
|
|
10811
10800
|
}
|
|
10812
10801
|
return restored;
|
|
10813
10802
|
}
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
*/
|
|
10818
|
-
buildRestoreHookContext(schema, record, metadata) {
|
|
10819
|
-
return {
|
|
10820
|
-
objectId: schema.id,
|
|
10821
|
-
objectName: schema.name,
|
|
10822
|
-
recordId: record.id,
|
|
10823
|
-
tenantId: this.tenantId,
|
|
10824
|
-
record,
|
|
10825
|
-
oldValues: record.values,
|
|
10826
|
-
newValues: record.values,
|
|
10827
|
-
changedAttributes: [],
|
|
10828
|
-
getChange: (attr) => ({
|
|
10829
|
-
oldValue: record.values[attr],
|
|
10830
|
-
newValue: record.values[attr],
|
|
10831
|
-
changed: false
|
|
10832
|
-
}),
|
|
10833
|
-
metadata: metadata ?? {},
|
|
10834
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
10835
|
-
};
|
|
10836
|
-
}
|
|
10837
|
-
/**
|
|
10838
|
-
* Enrich a record with computed formula values
|
|
10839
|
-
*
|
|
10840
|
-
* Formula attributes are calculated at read-time from the record's values.
|
|
10841
|
-
* This method adds the computed values to the record's values object.
|
|
10842
|
-
*
|
|
10843
|
-
* @param record - The record to enrich
|
|
10844
|
-
* @param schema - The object schema containing attribute definitions
|
|
10845
|
-
* @returns Record with formula values computed
|
|
10846
|
-
* @internal
|
|
10847
|
-
*/
|
|
10848
|
-
enrichWithFormulas(record, schema) {
|
|
10849
|
-
const formulaAttrs = schema.attributes.filter(
|
|
10850
|
-
(a) => a.type === "formula"
|
|
10851
|
-
);
|
|
10852
|
-
if (formulaAttrs.length === 0) {
|
|
10853
|
-
return record;
|
|
10854
|
-
}
|
|
10855
|
-
const enrichedValues = { ...record.values };
|
|
10856
|
-
for (const attr of formulaAttrs) {
|
|
10857
|
-
enrichedValues[attr.name] = evaluateFormulaAttribute(attr, record.values);
|
|
10858
|
-
}
|
|
10859
|
-
return {
|
|
10860
|
-
...record,
|
|
10861
|
-
values: enrichedValues
|
|
10862
|
-
};
|
|
10863
|
-
}
|
|
10864
|
-
/**
|
|
10865
|
-
* Enrich multiple records with computed formula values
|
|
10866
|
-
* @internal
|
|
10867
|
-
*/
|
|
10868
|
-
enrichRecordsWithFormulas(records, schema) {
|
|
10869
|
-
const formulaAttrs = schema.attributes.filter(
|
|
10870
|
-
(a) => a.type === "formula"
|
|
10871
|
-
);
|
|
10872
|
-
if (formulaAttrs.length === 0) {
|
|
10873
|
-
return records;
|
|
10874
|
-
}
|
|
10875
|
-
return records.map((record) => this.enrichWithFormulas(record, schema));
|
|
10876
|
-
}
|
|
10877
|
-
/**
|
|
10878
|
-
* Recalculate rollups after a record changes
|
|
10879
|
-
*
|
|
10880
|
-
* This handles three cases:
|
|
10881
|
-
* 1. The record itself has rollups (e.g., aggregating from related records it points to)
|
|
10882
|
-
* 2. Parent records have rollups that aggregate from this record (reverse pattern)
|
|
10883
|
-
* 3. Records that have forward rollups pointing to this record (forward pattern)
|
|
10884
|
-
*
|
|
10885
|
-
* @param record - The record that was modified
|
|
10886
|
-
* @param schema - Schema of the record's object
|
|
10887
|
-
* @internal
|
|
10888
|
-
*/
|
|
10889
|
-
async recalculateParentRollups(record, schema) {
|
|
10890
|
-
const ownRollupAttrs = schema.attributes.filter(
|
|
10891
|
-
(a) => a.type === "rollup"
|
|
10892
|
-
);
|
|
10893
|
-
if (ownRollupAttrs.length > 0) {
|
|
10894
|
-
await this.rollupService.recalculateAndUpdate(record, schema);
|
|
10895
|
-
}
|
|
10896
|
-
const affectedParentIds = await this.rollupService.findAffectedParentRecords(record, schema);
|
|
10897
|
-
if (affectedParentIds.length > 0) {
|
|
10898
|
-
const parentRecords = await this.adapter.objectRecords.findByIds(affectedParentIds);
|
|
10899
|
-
await Promise.all(
|
|
10900
|
-
parentRecords.map(async (parentRecord) => {
|
|
10901
|
-
const parentSchema = await this.schemaService.getObjectSchema(parentRecord.objectId);
|
|
10902
|
-
const rollupAttrs = parentSchema.attributes.filter(
|
|
10903
|
-
(a) => a.type === "rollup"
|
|
10904
|
-
);
|
|
10905
|
-
if (rollupAttrs.length > 0) {
|
|
10906
|
-
await this.rollupService.recalculateAndUpdate(parentRecord, parentSchema);
|
|
10907
|
-
}
|
|
10908
|
-
})
|
|
10909
|
-
);
|
|
10910
|
-
}
|
|
10911
|
-
const affectedForwardRecords = await this.rollupService.findRecordsWithForwardRollup(
|
|
10912
|
-
record,
|
|
10913
|
-
schema
|
|
10914
|
-
);
|
|
10915
|
-
await Promise.all(
|
|
10916
|
-
affectedForwardRecords.map(async (forwardRecord) => {
|
|
10917
|
-
const forwardSchema = await this.schemaService.getObjectSchema(forwardRecord.objectId);
|
|
10918
|
-
await this.rollupService.recalculateAndUpdate(forwardRecord, forwardSchema);
|
|
10919
|
-
})
|
|
10920
|
-
);
|
|
10921
|
-
}
|
|
10922
|
-
/**
|
|
10923
|
-
* Permanently delete a record (hard delete)
|
|
10924
|
-
*
|
|
10925
|
-
* This cannot be undone. Use with caution - prefer soft delete for data safety.
|
|
10926
|
-
* Does NOT trigger delete hooks (already triggered on soft delete).
|
|
10927
|
-
*
|
|
10928
|
-
* @param recordId - Record UUID
|
|
10929
|
-
*
|
|
10930
|
-
* @example
|
|
10931
|
-
* ```typescript
|
|
10932
|
-
* // Permanently delete a record
|
|
10933
|
-
* await service.hardDeleteRecord("rec-123");
|
|
10934
|
-
* ```
|
|
10935
|
-
*/
|
|
10936
|
-
async hardDeleteRecord(recordId) {
|
|
10937
|
-
const record = await this.getRecordOrThrow(recordId);
|
|
10938
|
-
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
10939
|
-
await this.checkPermission(schema.name, "delete");
|
|
10940
|
-
await this.adapter.objectRecords.hardDelete(recordId);
|
|
10941
|
-
}
|
|
10803
|
+
// ============================================================================
|
|
10804
|
+
// LIST & SEARCH
|
|
10805
|
+
// ============================================================================
|
|
10942
10806
|
/**
|
|
10943
10807
|
* List records for an object with pagination
|
|
10944
|
-
*
|
|
10945
|
-
* @param objectId - Object UUID
|
|
10946
|
-
* @param options - List options
|
|
10947
|
-
* @returns Records and total count
|
|
10948
10808
|
*/
|
|
10949
10809
|
async listRecords(objectId, options) {
|
|
10950
10810
|
const schema = await this.schemaService.getObjectSchema(objectId);
|
|
10951
10811
|
if (this.permissionService && this.userId) {
|
|
10952
|
-
await this.
|
|
10812
|
+
await checkPermission(this.permissionService, this.userId, schema.name, "read");
|
|
10953
10813
|
}
|
|
10954
|
-
const policy = options?.skipPolicyFilter ? void 0 : this.
|
|
10814
|
+
const policy = options?.skipPolicyFilter ? void 0 : getPolicy(this.policyRegistry, this.userId, schema.name);
|
|
10955
10815
|
let effectiveOptions = options;
|
|
10956
|
-
if (policy?.applyListFilter) {
|
|
10957
|
-
|
|
10816
|
+
if (policy?.applyListFilter && this.userId) {
|
|
10817
|
+
const ctx = buildPolicyContext(schema.name, this.userId, this.tenantId);
|
|
10818
|
+
effectiveOptions = policy.applyListFilter(ctx, options);
|
|
10958
10819
|
}
|
|
10959
10820
|
const result = await runWithSchemaContext(
|
|
10960
10821
|
[schema],
|
|
@@ -10962,14 +10823,14 @@ var RecordService = class extends TenantAwareService {
|
|
|
10962
10823
|
);
|
|
10963
10824
|
let filteredRecords = result.records;
|
|
10964
10825
|
let effectiveTotal = result.total;
|
|
10965
|
-
if (policy?.canAccessRecord) {
|
|
10966
|
-
const ctx =
|
|
10826
|
+
if (policy?.canAccessRecord && this.userId) {
|
|
10827
|
+
const ctx = buildPolicyContext(schema.name, this.userId, this.tenantId);
|
|
10967
10828
|
filteredRecords = result.records.filter((record) => policy.canAccessRecord?.(ctx, record));
|
|
10968
10829
|
effectiveTotal = filteredRecords.length;
|
|
10969
10830
|
}
|
|
10970
10831
|
if (!options?.skipFormulas) {
|
|
10971
10832
|
return {
|
|
10972
|
-
records:
|
|
10833
|
+
records: enrichRecordsWithFormulas(filteredRecords, schema),
|
|
10973
10834
|
total: effectiveTotal
|
|
10974
10835
|
};
|
|
10975
10836
|
}
|
|
@@ -10980,16 +10841,11 @@ var RecordService = class extends TenantAwareService {
|
|
|
10980
10841
|
}
|
|
10981
10842
|
/**
|
|
10982
10843
|
* Search records using full-text search
|
|
10983
|
-
*
|
|
10984
|
-
* @param objectId - Object UUID
|
|
10985
|
-
* @param query - Search query
|
|
10986
|
-
* @param options - Search options
|
|
10987
|
-
* @returns Matching records and total count
|
|
10988
10844
|
*/
|
|
10989
10845
|
async searchRecords(objectId, query, options) {
|
|
10990
10846
|
const schema = await this.schemaService.getObjectSchema(objectId);
|
|
10991
10847
|
if (this.permissionService && this.userId) {
|
|
10992
|
-
await this.
|
|
10848
|
+
await checkPermission(this.permissionService, this.userId, schema.name, "read");
|
|
10993
10849
|
}
|
|
10994
10850
|
const result = await runWithSchemaContext(
|
|
10995
10851
|
[schema],
|
|
@@ -10997,41 +10853,31 @@ var RecordService = class extends TenantAwareService {
|
|
|
10997
10853
|
);
|
|
10998
10854
|
if (!options?.skipFormulas) {
|
|
10999
10855
|
return {
|
|
11000
|
-
records:
|
|
10856
|
+
records: enrichRecordsWithFormulas(result.records, schema),
|
|
11001
10857
|
total: result.total
|
|
11002
10858
|
};
|
|
11003
10859
|
}
|
|
11004
10860
|
return result;
|
|
11005
10861
|
}
|
|
10862
|
+
// ============================================================================
|
|
10863
|
+
// VALIDATION & STATUS
|
|
10864
|
+
// ============================================================================
|
|
11006
10865
|
/**
|
|
11007
10866
|
* Validate data against object schema without saving
|
|
11008
|
-
*
|
|
11009
|
-
* @param objectId - Object UUID
|
|
11010
|
-
* @param data - Data to validate
|
|
11011
|
-
* @returns Validation result
|
|
11012
10867
|
*/
|
|
11013
10868
|
async validateData(objectId, data) {
|
|
11014
10869
|
const schema = await this.schemaService.getObjectSchema(objectId);
|
|
11015
10870
|
return validateObject(schema, data);
|
|
11016
10871
|
}
|
|
11017
10872
|
/**
|
|
11018
|
-
* Compute the completion status for given data without saving
|
|
11019
|
-
* Useful for UI to show draft/complete status before submitting.
|
|
11020
|
-
*
|
|
11021
|
-
* @param objectId - Object UUID
|
|
11022
|
-
* @param data - Data to check
|
|
11023
|
-
* @returns Computed completion status
|
|
10873
|
+
* Compute the completion status for given data without saving
|
|
11024
10874
|
*/
|
|
11025
10875
|
async computeStatus(objectId, data) {
|
|
11026
10876
|
const schema = await this.schemaService.getObjectSchema(objectId);
|
|
11027
10877
|
return computeRecordStatus(schema, data);
|
|
11028
10878
|
}
|
|
11029
10879
|
/**
|
|
11030
|
-
* Refresh the completion status of an existing record
|
|
11031
|
-
* Useful when schema changes and you need to recompute statuses.
|
|
11032
|
-
*
|
|
11033
|
-
* @param recordId - Record UUID
|
|
11034
|
-
* @returns Updated completion status
|
|
10880
|
+
* Refresh the completion status of an existing record
|
|
11035
10881
|
*/
|
|
11036
10882
|
async refreshRecordStatus(recordId) {
|
|
11037
10883
|
const record = await this.getRecordOrThrow(recordId);
|