@stndrds/schema 0.1.0-alpha.47 → 0.1.0-alpha.49
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-CVMMZRH6.mjs → chunk-FDWX5X6L.mjs} +253 -6
- package/dist/{chunk-I6VF7DOR.js → chunk-LPCQM5VG.js} +342 -95
- 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-BOg0C4ev.d.mts → runtime-BbM_jyVU.d.mts} +108 -4
- package/dist/{runtime-BOg0C4ev.d.ts → runtime-BbM_jyVU.d.ts} +108 -4
- 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
|
@@ -323,7 +323,14 @@ function initializePinCodeService(salt) {
|
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
// src/runtime/cache.ts
|
|
326
|
-
|
|
326
|
+
function fnv1aHash(str) {
|
|
327
|
+
let hash = 2166136261;
|
|
328
|
+
for (let i = 0; i < str.length; i++) {
|
|
329
|
+
hash ^= str.charCodeAt(i);
|
|
330
|
+
hash = hash * 16777619 >>> 0;
|
|
331
|
+
}
|
|
332
|
+
return hash.toString(16).padStart(8, "0");
|
|
333
|
+
}
|
|
327
334
|
function hashOptions(options) {
|
|
328
335
|
if (options === null || options === void 0 || typeof options === "object" && Object.keys(options).length === 0) {
|
|
329
336
|
return "default";
|
|
@@ -340,7 +347,7 @@ function hashOptions(options) {
|
|
|
340
347
|
}
|
|
341
348
|
return value;
|
|
342
349
|
});
|
|
343
|
-
return
|
|
350
|
+
return fnv1aHash(sortedJson);
|
|
344
351
|
}
|
|
345
352
|
var cacheKeys = {
|
|
346
353
|
// -------------------------------------------------------------------------
|
|
@@ -369,6 +376,12 @@ var cacheKeys = {
|
|
|
369
376
|
// -------------------------------------------------------------------------
|
|
370
377
|
/** Relation options for an attribute */
|
|
371
378
|
relationOptions: (tenantId, attrId, hash) => `rel:${tenantId}:${attrId}:${hash}`,
|
|
379
|
+
/**
|
|
380
|
+
* Resolved relation by composite ID.
|
|
381
|
+
* Composite ID format: `${attributeId}:${recordId}`
|
|
382
|
+
* Used by cachedByMany in RelationService.resolveIds()
|
|
383
|
+
*/
|
|
384
|
+
resolvedRelation: (tenantId, compositeId) => `relres:${tenantId}:${compositeId}`,
|
|
372
385
|
// -------------------------------------------------------------------------
|
|
373
386
|
// Rollups - TTL: 2 minutes (high volatility)
|
|
374
387
|
// -------------------------------------------------------------------------
|
|
@@ -408,6 +421,19 @@ var cacheKeys = {
|
|
|
408
421
|
allPermissions: (tenantId) => `perms:${tenantId}:*`,
|
|
409
422
|
/** All relation cache for a tenant */
|
|
410
423
|
allRelations: (tenantId) => `rel:${tenantId}:*`,
|
|
424
|
+
/** All resolved relations cache for a tenant */
|
|
425
|
+
allResolvedRelations: (tenantId) => `relres:${tenantId}:*`,
|
|
426
|
+
/**
|
|
427
|
+
* Resolved relations for a specific record (all attributeId variants).
|
|
428
|
+
* Pattern matches `relres:${tenantId}:*:${recordId}` to invalidate
|
|
429
|
+
* all cached labels for a record regardless of which attribute resolved it.
|
|
430
|
+
*/
|
|
431
|
+
resolvedRelationsByRecord: (tenantId, recordId) => `relres:${tenantId}:*:${recordId}`,
|
|
432
|
+
/**
|
|
433
|
+
* Resolved relations for a specific attribute.
|
|
434
|
+
* Invalidates when a relation's displayTemplate changes.
|
|
435
|
+
*/
|
|
436
|
+
resolvedRelationsByAttr: (tenantId, attributeId) => `relres:${tenantId}:${attributeId}:*`,
|
|
411
437
|
/** All rollup cache for a tenant */
|
|
412
438
|
allRollups: (tenantId) => `rollup:${tenantId}:*`,
|
|
413
439
|
/** All rollups for a specific record */
|
|
@@ -460,6 +486,8 @@ var cacheTtl = {
|
|
|
460
486
|
permissions: 15 * 60 * 1e3,
|
|
461
487
|
/** Relation options - medium volatility (5 minutes) */
|
|
462
488
|
relations: 5 * 60 * 1e3,
|
|
489
|
+
/** Resolved relations - medium volatility (5 minutes) */
|
|
490
|
+
resolvedRelations: 5 * 60 * 1e3,
|
|
463
491
|
/** Rollup values - high volatility (2 minutes) */
|
|
464
492
|
rollup: 2 * 60 * 1e3,
|
|
465
493
|
/** Individual records - high volatility (1 minute) */
|
|
@@ -492,6 +520,7 @@ var defaultTtl = {
|
|
|
492
520
|
workflowById: cacheTtl.workflows,
|
|
493
521
|
workflowList: cacheTtl.workflows,
|
|
494
522
|
relationOptions: cacheTtl.relations,
|
|
523
|
+
resolvedRelation: cacheTtl.resolvedRelations,
|
|
495
524
|
rollupValue: cacheTtl.rollup,
|
|
496
525
|
userPermissions: cacheTtl.permissions,
|
|
497
526
|
recordList: cacheTtl.recordList,
|
|
@@ -4408,6 +4437,72 @@ var BaseService = class {
|
|
|
4408
4437
|
const pattern = patternFn(this.tenantId, id);
|
|
4409
4438
|
await this.cache.deletePattern(pattern);
|
|
4410
4439
|
}
|
|
4440
|
+
/**
|
|
4441
|
+
* Cache multiple resources by ID, fetching only missing ones.
|
|
4442
|
+
* Useful for batch operations with individual caching (e.g., resolveIds).
|
|
4443
|
+
*
|
|
4444
|
+
* Unlike `cachedBy` which caches a single resource, this method:
|
|
4445
|
+
* 1. Checks the cache for each ID in parallel
|
|
4446
|
+
* 2. Collects cache misses
|
|
4447
|
+
* 3. Fetches only missing items via the fetcher
|
|
4448
|
+
* 4. Caches new results individually
|
|
4449
|
+
* 5. Returns all results (cached + freshly fetched)
|
|
4450
|
+
*
|
|
4451
|
+
* @param keyType - Type of cache key (e.g., "resolvedRelation")
|
|
4452
|
+
* @param ids - Resource identifiers to fetch
|
|
4453
|
+
* @param fetcher - Function to fetch missing items (receives only cache-miss IDs)
|
|
4454
|
+
* @param getId - Function to extract ID from a fetched item
|
|
4455
|
+
* @param ttlMs - Optional TTL override
|
|
4456
|
+
*
|
|
4457
|
+
* @example
|
|
4458
|
+
* ```typescript
|
|
4459
|
+
* // Cache relations by composite ID (attributeId:recordId)
|
|
4460
|
+
* return this.cachedByMany(
|
|
4461
|
+
* "resolvedRelation",
|
|
4462
|
+
* ids.map(id => `${attributeId}:${id}`),
|
|
4463
|
+
* async (missingCompositeIds) => {
|
|
4464
|
+
* const missingRecordIds = missingCompositeIds.map(c => c.split(":")[1]);
|
|
4465
|
+
* return this.fetchRelations(missingRecordIds, attributeId);
|
|
4466
|
+
* },
|
|
4467
|
+
* (item) => `${attributeId}:${item.id}`
|
|
4468
|
+
* );
|
|
4469
|
+
* ```
|
|
4470
|
+
*/
|
|
4471
|
+
async cachedByMany(keyType, ids, fetcher, getId, ttlMs) {
|
|
4472
|
+
const cache = this.cache;
|
|
4473
|
+
if (!cache || ids.length === 0) {
|
|
4474
|
+
return fetcher(ids);
|
|
4475
|
+
}
|
|
4476
|
+
const keyFn = cacheKeys[keyType];
|
|
4477
|
+
const ttl = ttlMs ?? defaultTtl[keyType] ?? 6e4;
|
|
4478
|
+
const cacheChecks = await Promise.all(
|
|
4479
|
+
ids.map(async (id) => {
|
|
4480
|
+
const key = keyFn(this.tenantId, id);
|
|
4481
|
+
const cached = await cache.get(key);
|
|
4482
|
+
return { id, cached };
|
|
4483
|
+
})
|
|
4484
|
+
);
|
|
4485
|
+
const results = [];
|
|
4486
|
+
const missingIds = [];
|
|
4487
|
+
for (const { id, cached } of cacheChecks) {
|
|
4488
|
+
if (cached !== null) {
|
|
4489
|
+
results.push(cached);
|
|
4490
|
+
} else {
|
|
4491
|
+
missingIds.push(id);
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
if (missingIds.length > 0) {
|
|
4495
|
+
const fetched = await fetcher(missingIds);
|
|
4496
|
+
await Promise.all(
|
|
4497
|
+
fetched.map(async (item) => {
|
|
4498
|
+
const key = keyFn(this.tenantId, getId(item));
|
|
4499
|
+
await cache.set(key, item, ttl);
|
|
4500
|
+
})
|
|
4501
|
+
);
|
|
4502
|
+
results.push(...fetched);
|
|
4503
|
+
}
|
|
4504
|
+
return results;
|
|
4505
|
+
}
|
|
4411
4506
|
};
|
|
4412
4507
|
var BaseRepository = class {
|
|
4413
4508
|
/**
|
|
@@ -7717,6 +7812,7 @@ var ObjectSchemaService = class extends BaseService {
|
|
|
7717
7812
|
return new Map(records.map((r) => [r.id, r.label]));
|
|
7718
7813
|
});
|
|
7719
7814
|
});
|
|
7815
|
+
await this.invalidateCachePattern(cacheKeys.allResolvedRelations(this.tenantId));
|
|
7720
7816
|
}
|
|
7721
7817
|
return this.convertDBObjectToDefinition(updatedDbObject, dbAttributes);
|
|
7722
7818
|
}
|
|
@@ -9445,8 +9541,8 @@ var RelationService = class extends BaseService {
|
|
|
9445
9541
|
* Resolve record IDs to their display labels.
|
|
9446
9542
|
* Useful for displaying current values in the UI.
|
|
9447
9543
|
*
|
|
9448
|
-
* Uses
|
|
9449
|
-
*
|
|
9544
|
+
* Uses caching per individual record ID for optimal performance.
|
|
9545
|
+
* Cache key format: `${attributeId}:${recordId}` to handle different displayTemplates.
|
|
9450
9546
|
*
|
|
9451
9547
|
* @param ids - Record IDs to resolve
|
|
9452
9548
|
* @param attributeId - Relation attribute ID to use its displayTemplate for label rendering
|
|
@@ -9461,6 +9557,153 @@ var RelationService = class extends BaseService {
|
|
|
9461
9557
|
if (!ids || ids.length === 0) {
|
|
9462
9558
|
return [];
|
|
9463
9559
|
}
|
|
9560
|
+
const compositeIds = ids.map((id) => `${attributeId}:${id}`);
|
|
9561
|
+
return this.cachedByMany(
|
|
9562
|
+
"resolvedRelation",
|
|
9563
|
+
compositeIds,
|
|
9564
|
+
async (missingCompositeIds) => {
|
|
9565
|
+
const missingRecordIds = missingCompositeIds.map((c) => c.split(":")[1]);
|
|
9566
|
+
return this.fetchResolveIds(missingRecordIds, attributeId);
|
|
9567
|
+
},
|
|
9568
|
+
(item) => `${attributeId}:${item.id}`,
|
|
9569
|
+
cacheTtl.resolvedRelations
|
|
9570
|
+
);
|
|
9571
|
+
}
|
|
9572
|
+
/**
|
|
9573
|
+
* Resolve multiple attribute/IDs batches in a single operation.
|
|
9574
|
+
* Optimized for DataGrid scenarios with multiple relation columns.
|
|
9575
|
+
*
|
|
9576
|
+
* Benefits over multiple resolveIds() calls:
|
|
9577
|
+
* - Single DB query for all records across all attributes
|
|
9578
|
+
* - Deduplication of records referenced by multiple attributes
|
|
9579
|
+
* - Single schema lookup per objectId
|
|
9580
|
+
*
|
|
9581
|
+
* Uses caching per individual record ID for optimal performance.
|
|
9582
|
+
*
|
|
9583
|
+
* @param requests - Array of { attributeId, ids } to resolve
|
|
9584
|
+
* @returns Map of attributeId to resolved options
|
|
9585
|
+
*
|
|
9586
|
+
* @example
|
|
9587
|
+
* ```typescript
|
|
9588
|
+
* const results = await relationService.resolveIdsBatch([
|
|
9589
|
+
* { attributeId: "attr-company", ids: ["rec-1", "rec-2"] },
|
|
9590
|
+
* { attributeId: "attr-contact", ids: ["rec-3", "rec-4"] },
|
|
9591
|
+
* ]);
|
|
9592
|
+
* // { "attr-company": [...], "attr-contact": [...] }
|
|
9593
|
+
* ```
|
|
9594
|
+
*/
|
|
9595
|
+
async resolveIdsBatch(requests) {
|
|
9596
|
+
if (!requests || requests.length === 0) {
|
|
9597
|
+
return {};
|
|
9598
|
+
}
|
|
9599
|
+
const validRequests = requests.filter((r) => r.ids && r.ids.length > 0);
|
|
9600
|
+
if (validRequests.length === 0) {
|
|
9601
|
+
return {};
|
|
9602
|
+
}
|
|
9603
|
+
const allCompositeIds = [];
|
|
9604
|
+
const compositeIdsByAttr = /* @__PURE__ */ new Map();
|
|
9605
|
+
for (const req of validRequests) {
|
|
9606
|
+
const composites = req.ids.map((id) => `${req.attributeId}:${id}`);
|
|
9607
|
+
allCompositeIds.push(...composites);
|
|
9608
|
+
compositeIdsByAttr.set(req.attributeId, composites);
|
|
9609
|
+
}
|
|
9610
|
+
const allResolved = await this.cachedByMany(
|
|
9611
|
+
"resolvedRelation",
|
|
9612
|
+
allCompositeIds,
|
|
9613
|
+
(missingCompositeIds) => this.fetchResolveIdsBatch(missingCompositeIds),
|
|
9614
|
+
(item) => item._compositeId,
|
|
9615
|
+
cacheTtl.resolvedRelations
|
|
9616
|
+
);
|
|
9617
|
+
const response = {};
|
|
9618
|
+
const resolvedMap = new Map(allResolved.map((item) => [item._compositeId, item]));
|
|
9619
|
+
for (const req of validRequests) {
|
|
9620
|
+
const composites = compositeIdsByAttr.get(req.attributeId) ?? [];
|
|
9621
|
+
const options = [];
|
|
9622
|
+
for (const compositeId of composites) {
|
|
9623
|
+
const resolved = resolvedMap.get(compositeId);
|
|
9624
|
+
if (resolved) {
|
|
9625
|
+
const { _compositeId, ...option } = resolved;
|
|
9626
|
+
options.push(option);
|
|
9627
|
+
}
|
|
9628
|
+
}
|
|
9629
|
+
response[req.attributeId] = options;
|
|
9630
|
+
}
|
|
9631
|
+
return response;
|
|
9632
|
+
}
|
|
9633
|
+
/**
|
|
9634
|
+
* Internal method to fetch and resolve multiple composite IDs at once.
|
|
9635
|
+
* Optimized for batch operations - single DB query for all records.
|
|
9636
|
+
*/
|
|
9637
|
+
async fetchResolveIdsBatch(compositeIds) {
|
|
9638
|
+
if (compositeIds.length === 0) {
|
|
9639
|
+
return [];
|
|
9640
|
+
}
|
|
9641
|
+
const parsed = compositeIds.map((c) => {
|
|
9642
|
+
const [attributeId, recordId] = c.split(":");
|
|
9643
|
+
return { compositeId: c, attributeId, recordId };
|
|
9644
|
+
});
|
|
9645
|
+
const uniqueRecordIds = [...new Set(parsed.map((p) => p.recordId))];
|
|
9646
|
+
const uniqueAttributeIds = [...new Set(parsed.map((p) => p.attributeId))];
|
|
9647
|
+
const records = await this.adapter.objectRecords.findByIds(uniqueRecordIds);
|
|
9648
|
+
if (records.length === 0) {
|
|
9649
|
+
return [];
|
|
9650
|
+
}
|
|
9651
|
+
const recordMap = new Map(records.map((r) => [r.id, r]));
|
|
9652
|
+
const attributePromises = uniqueAttributeIds.map((id) => this.findAttributeById(id));
|
|
9653
|
+
const attributes = await Promise.all(attributePromises);
|
|
9654
|
+
const attributeMap = new Map(uniqueAttributeIds.map((id, i) => [id, attributes[i]]));
|
|
9655
|
+
const uniqueObjectIds = [...new Set(records.map((r) => r.objectId))];
|
|
9656
|
+
const schemaPromises = uniqueObjectIds.map((id) => this.schemaService.getObjectSchema(id));
|
|
9657
|
+
const schemas = await Promise.all(schemaPromises);
|
|
9658
|
+
const schemaMap = new Map(uniqueObjectIds.map((id, i) => [id, schemas[i]]));
|
|
9659
|
+
const resolved = [];
|
|
9660
|
+
for (const { compositeId, attributeId, recordId } of parsed) {
|
|
9661
|
+
const record = recordMap.get(recordId);
|
|
9662
|
+
if (!record) {
|
|
9663
|
+
continue;
|
|
9664
|
+
}
|
|
9665
|
+
const objectSchema = schemaMap.get(record.objectId);
|
|
9666
|
+
if (!objectSchema) {
|
|
9667
|
+
continue;
|
|
9668
|
+
}
|
|
9669
|
+
const attribute = attributeMap.get(attributeId);
|
|
9670
|
+
const targetConfig = attribute?.targets?.find((t) => t.object === objectSchema.name);
|
|
9671
|
+
const customTemplate = targetConfig?.displayTemplate;
|
|
9672
|
+
let label;
|
|
9673
|
+
if (customTemplate) {
|
|
9674
|
+
label = await computeLabelWithRelations(
|
|
9675
|
+
customTemplate,
|
|
9676
|
+
record.values,
|
|
9677
|
+
objectSchema.attributes,
|
|
9678
|
+
async (nestedIds) => {
|
|
9679
|
+
const linkedRecords = await this.adapter.objectRecords.findByIds(nestedIds);
|
|
9680
|
+
return new Map(linkedRecords.map((r) => [r.id, r.label]));
|
|
9681
|
+
}
|
|
9682
|
+
);
|
|
9683
|
+
} else {
|
|
9684
|
+
label = record.label;
|
|
9685
|
+
}
|
|
9686
|
+
resolved.push({
|
|
9687
|
+
_compositeId: compositeId,
|
|
9688
|
+
id: record.id,
|
|
9689
|
+
objectId: record.objectId,
|
|
9690
|
+
objectName: objectSchema.name,
|
|
9691
|
+
objectLabel: objectSchema.label,
|
|
9692
|
+
objectIcon: objectSchema.icon,
|
|
9693
|
+
label
|
|
9694
|
+
});
|
|
9695
|
+
}
|
|
9696
|
+
return resolved;
|
|
9697
|
+
}
|
|
9698
|
+
/**
|
|
9699
|
+
* Internal method to fetch and resolve relation IDs (no caching).
|
|
9700
|
+
* Uses batch fetching for performance - fetches all records in one query,
|
|
9701
|
+
* then groups by objectId to minimize schema lookups.
|
|
9702
|
+
*/
|
|
9703
|
+
async fetchResolveIds(ids, attributeId) {
|
|
9704
|
+
if (ids.length === 0) {
|
|
9705
|
+
return [];
|
|
9706
|
+
}
|
|
9464
9707
|
const attribute = await this.findAttributeById(attributeId);
|
|
9465
9708
|
const records = await this.adapter.objectRecords.findByIds(ids);
|
|
9466
9709
|
if (records.length === 0) {
|
|
@@ -9487,8 +9730,8 @@ var RelationService = class extends BaseService {
|
|
|
9487
9730
|
customTemplate,
|
|
9488
9731
|
record.values,
|
|
9489
9732
|
objectSchema.attributes,
|
|
9490
|
-
async (
|
|
9491
|
-
const linkedRecords = await this.adapter.objectRecords.findByIds(
|
|
9733
|
+
async (nestedIds) => {
|
|
9734
|
+
const linkedRecords = await this.adapter.objectRecords.findByIds(nestedIds);
|
|
9492
9735
|
return new Map(linkedRecords.map((r) => [r.id, r.label]));
|
|
9493
9736
|
}
|
|
9494
9737
|
);
|
|
@@ -10140,6 +10383,7 @@ var RecordService = class extends BaseService {
|
|
|
10140
10383
|
await this.invalidateCache(cacheKeys.record(this.tenantId, recordId));
|
|
10141
10384
|
await this.invalidateLists("allRecordLists", existing.objectId);
|
|
10142
10385
|
await this.invalidateLists("allSearchResults", existing.objectId);
|
|
10386
|
+
await this.invalidateCachePattern(cacheKeys.resolvedRelationsByRecord(this.tenantId, recordId));
|
|
10143
10387
|
if (!options?.skipHooks) {
|
|
10144
10388
|
const afterCtx = {
|
|
10145
10389
|
...hookCtx,
|
|
@@ -10207,6 +10451,7 @@ var RecordService = class extends BaseService {
|
|
|
10207
10451
|
await this.invalidateLists("allRecordLists", record.objectId);
|
|
10208
10452
|
await this.invalidateLists("allSearchResults", record.objectId);
|
|
10209
10453
|
await this.invalidateCachePattern(cacheKeys.allGlobalSearch(this.tenantId));
|
|
10454
|
+
await this.invalidateCachePattern(cacheKeys.resolvedRelationsByRecord(this.tenantId, recordId));
|
|
10210
10455
|
if (!options?.skipHooks) {
|
|
10211
10456
|
await this.hookRegistry.execute("afterDelete", schema.name, hookCtx);
|
|
10212
10457
|
}
|
|
@@ -10235,6 +10480,7 @@ var RecordService = class extends BaseService {
|
|
|
10235
10480
|
await this.invalidateLists("allRecordLists", record.objectId);
|
|
10236
10481
|
await this.invalidateLists("allSearchResults", record.objectId);
|
|
10237
10482
|
await this.invalidateCachePattern(cacheKeys.allGlobalSearch(this.tenantId));
|
|
10483
|
+
await this.invalidateCachePattern(cacheKeys.resolvedRelationsByRecord(this.tenantId, recordId));
|
|
10238
10484
|
}
|
|
10239
10485
|
// ============================================================================
|
|
10240
10486
|
// RESTORE
|
|
@@ -10262,6 +10508,7 @@ var RecordService = class extends BaseService {
|
|
|
10262
10508
|
await this.invalidateLists("allRecordLists", record.objectId);
|
|
10263
10509
|
await this.invalidateLists("allSearchResults", record.objectId);
|
|
10264
10510
|
await this.invalidateCachePattern(cacheKeys.allGlobalSearch(this.tenantId));
|
|
10511
|
+
await this.invalidateCachePattern(cacheKeys.resolvedRelationsByRecord(this.tenantId, recordId));
|
|
10265
10512
|
if (!options?.skipHooks) {
|
|
10266
10513
|
const afterCtx = {
|
|
10267
10514
|
...hookCtx,
|