@stndrds/schema 0.1.0-alpha.55 → 0.1.0-alpha.56
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-4NLKDJWD.mjs → chunk-LOOGQL6M.mjs} +1909 -1471
- package/dist/{chunk-QBDGRMSC.js → chunk-XBF4EUC4.js} +2215 -1777
- package/dist/index.d.mts +13 -3
- package/dist/index.d.ts +13 -3
- package/dist/index.js +6 -6
- package/dist/index.mjs +5 -5
- package/dist/{runtime-ka1bmD0F.d.mts → runtime-D5MuaVQs.d.mts} +275 -697
- package/dist/{runtime-ka1bmD0F.d.ts → runtime-D5MuaVQs.d.ts} +275 -697
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +2 -6
- package/dist/runtime.mjs +1 -5
- package/package.json +2 -2
|
@@ -168,28 +168,28 @@ var WorkflowJwtService = class _WorkflowJwtService {
|
|
|
168
168
|
if (message.includes("expired") || message.includes("exp")) {
|
|
169
169
|
return {
|
|
170
170
|
valid: false,
|
|
171
|
-
error: "
|
|
171
|
+
error: "EXPIRED",
|
|
172
172
|
errorCode: "EXPIRED"
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
175
|
if (message.includes("signature") || message.includes("verification")) {
|
|
176
176
|
return {
|
|
177
177
|
valid: false,
|
|
178
|
-
error: "
|
|
178
|
+
error: "INVALID_SIGNATURE",
|
|
179
179
|
errorCode: "INVALID_SIGNATURE"
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
182
|
if (message.includes("malformed") || message.includes("invalid")) {
|
|
183
183
|
return {
|
|
184
184
|
valid: false,
|
|
185
|
-
error: "
|
|
185
|
+
error: "MALFORMED",
|
|
186
186
|
errorCode: "MALFORMED"
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
return {
|
|
191
191
|
valid: false,
|
|
192
|
-
error: "
|
|
192
|
+
error: "AUTH_FAILED",
|
|
193
193
|
errorCode: "UNKNOWN"
|
|
194
194
|
};
|
|
195
195
|
}
|
|
@@ -249,6 +249,7 @@ var cacheKeys = {
|
|
|
249
249
|
objectSchemaByName: (tenantId, name) => `schema:${tenantId}:name:${name}`,
|
|
250
250
|
/** List of all object schemas */
|
|
251
251
|
objectSchemaList: (tenantId) => `schema:${tenantId}:list`,
|
|
252
|
+
objectOwnerInfo: (tenantId, objectId) => `schema:${tenantId}:owner:${objectId}`,
|
|
252
253
|
// -------------------------------------------------------------------------
|
|
253
254
|
// Attributes - TTL: 1 hour (rarely change)
|
|
254
255
|
// -------------------------------------------------------------------------
|
|
@@ -275,7 +276,15 @@ var cacheKeys = {
|
|
|
275
276
|
// -------------------------------------------------------------------------
|
|
276
277
|
// Rollups - TTL: 2 minutes (high volatility)
|
|
277
278
|
// -------------------------------------------------------------------------
|
|
278
|
-
/**
|
|
279
|
+
/**
|
|
280
|
+
* Computed rollup value for a record.
|
|
281
|
+
*
|
|
282
|
+
* NOTE: When used via `cachedBy("rollupValue", compositeId, ...)`, the compositeId
|
|
283
|
+
* is `${recordId}:${attrName}`. Since `cachedBy` calls `keyFn(tenantId, compositeId)`,
|
|
284
|
+
* the third parameter receives the composite string, producing the same key as
|
|
285
|
+
* calling `rollupValue(tenantId, recordId, attrName)` directly. This works because
|
|
286
|
+
* the colon separator in the composite matches the key format, but is intentional.
|
|
287
|
+
*/
|
|
279
288
|
rollupValue: (tenantId, recordId, attrName) => `rollup:${tenantId}:${recordId}:${attrName}`,
|
|
280
289
|
// -------------------------------------------------------------------------
|
|
281
290
|
// Records - TTL: 1 minute (high volatility)
|
|
@@ -419,6 +428,7 @@ var defaultTtl = {
|
|
|
419
428
|
objectSchema: cacheTtl.schema,
|
|
420
429
|
objectSchemaByName: cacheTtl.schema,
|
|
421
430
|
objectSchemaList: cacheTtl.schemaList,
|
|
431
|
+
objectOwnerInfo: cacheTtl.schema,
|
|
422
432
|
objectAttributes: cacheTtl.attributes,
|
|
423
433
|
attributeById: cacheTtl.attributes,
|
|
424
434
|
userProfileById: cacheTtl.userProfiles,
|
|
@@ -1344,6 +1354,9 @@ function getContextValue(context, path) {
|
|
|
1344
1354
|
if (current === null || current === void 0) {
|
|
1345
1355
|
return void 0;
|
|
1346
1356
|
}
|
|
1357
|
+
if (typeof current !== "object") {
|
|
1358
|
+
return void 0;
|
|
1359
|
+
}
|
|
1347
1360
|
current = current[part];
|
|
1348
1361
|
}
|
|
1349
1362
|
return current;
|
|
@@ -1900,7 +1913,8 @@ var ConditionExecutor = class {
|
|
|
1900
1913
|
};
|
|
1901
1914
|
const nextNodeId = result ? node.onTrue : node.onFalse;
|
|
1902
1915
|
if (!nextNodeId) {
|
|
1903
|
-
|
|
1916
|
+
return error(
|
|
1917
|
+
"MISSING_TARGET",
|
|
1904
1918
|
`ConditionNode "${node.id}" has no ${result ? "onTrue" : "onFalse"} target defined`
|
|
1905
1919
|
);
|
|
1906
1920
|
}
|
|
@@ -1954,6 +1968,7 @@ var DocumentExecutor = class {
|
|
|
1954
1968
|
filename: node.filename,
|
|
1955
1969
|
metadata: {
|
|
1956
1970
|
templateId: node.templateId,
|
|
1971
|
+
templateVersion: node.templateVersion ?? 1,
|
|
1957
1972
|
targetSlotIds: node.targetSlotIds ?? [],
|
|
1958
1973
|
status: "pending"
|
|
1959
1974
|
}
|
|
@@ -1976,6 +1991,36 @@ var DocumentExecutor = class {
|
|
|
1976
1991
|
if (!node.next) {
|
|
1977
1992
|
errors.push("DocumentNode must have a 'next' property");
|
|
1978
1993
|
}
|
|
1994
|
+
if (node.targetSlotIds && node.targetSlotIds.length > 0) {
|
|
1995
|
+
for (const slotId of node.targetSlotIds) {
|
|
1996
|
+
if (!slotId || typeof slotId !== "string" || slotId.trim() === "") {
|
|
1997
|
+
errors.push("DocumentNode targetSlotIds must contain non-empty string values");
|
|
1998
|
+
break;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
return errors;
|
|
2003
|
+
}
|
|
2004
|
+
/**
|
|
2005
|
+
* Validate that targetSlotIds reference existing slots in the workflow definition.
|
|
2006
|
+
* This is a context-aware validation that requires the workflow's slot definitions.
|
|
2007
|
+
*
|
|
2008
|
+
* @param node - The document node to validate
|
|
2009
|
+
* @param workflowSlots - All slots defined in the workflow
|
|
2010
|
+
* @returns Array of validation error messages
|
|
2011
|
+
*/
|
|
2012
|
+
validateSlotReferences(node, workflowSlots) {
|
|
2013
|
+
const errors = [];
|
|
2014
|
+
if (node.targetSlotIds && node.targetSlotIds.length > 0) {
|
|
2015
|
+
const slotIdSet = new Set(workflowSlots.map((s) => s.id));
|
|
2016
|
+
for (const slotId of node.targetSlotIds) {
|
|
2017
|
+
if (!slotIdSet.has(slotId)) {
|
|
2018
|
+
errors.push(
|
|
2019
|
+
`DocumentNode "${node.id}" references unknown slot "${slotId}" in targetSlotIds`
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
1979
2024
|
return errors;
|
|
1980
2025
|
}
|
|
1981
2026
|
};
|
|
@@ -2029,8 +2074,20 @@ var FormExecutor = class {
|
|
|
2029
2074
|
...slotInput
|
|
2030
2075
|
};
|
|
2031
2076
|
}
|
|
2077
|
+
if (context.objectDefinitions && context.objectDefinitions.length > 0) {
|
|
2078
|
+
const typedInput = input;
|
|
2079
|
+
const validationErrors = this.validateRequiredFields(
|
|
2080
|
+
node,
|
|
2081
|
+
typedInput,
|
|
2082
|
+
context.definition.slots,
|
|
2083
|
+
context.objectDefinitions
|
|
2084
|
+
);
|
|
2085
|
+
if (validationErrors.length > 0) {
|
|
2086
|
+
return error("VALIDATION_FAILED", validationErrors.join("; "));
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2032
2089
|
if (!node.next) {
|
|
2033
|
-
|
|
2090
|
+
return error("MISSING_NEXT", `FormNode "${node.id}" has no 'next' target defined`);
|
|
2034
2091
|
}
|
|
2035
2092
|
return success(node.next, contextUpdates);
|
|
2036
2093
|
}
|
|
@@ -2196,7 +2253,7 @@ function createFormulaParser() {
|
|
|
2196
2253
|
parser.functions.LENGTH = (value) => String(value ?? "").length;
|
|
2197
2254
|
parser.functions.LEFT = (value, count) => String(value ?? "").slice(0, count);
|
|
2198
2255
|
parser.functions.RIGHT = (value, count) => String(value ?? "").slice(-count);
|
|
2199
|
-
parser.functions.REPLACE = (value, search, replacement) => String(value ?? "").
|
|
2256
|
+
parser.functions.REPLACE = (value, search, replacement) => String(value ?? "").split(String(search)).join(replacement);
|
|
2200
2257
|
parser.functions.CONTAINS = (value, search) => String(value ?? "").toLowerCase().includes(String(search).toLowerCase());
|
|
2201
2258
|
parser.functions.ROUND = (value, decimals = 0) => {
|
|
2202
2259
|
if (typeof value !== "number" || Number.isNaN(value)) return null;
|
|
@@ -2562,359 +2619,418 @@ var NoopHookRegistry = class {
|
|
|
2562
2619
|
}
|
|
2563
2620
|
};
|
|
2564
2621
|
|
|
2565
|
-
// src/
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
}
|
|
2571
|
-
function formatCheckbox(value) {
|
|
2572
|
-
return value ? "Yes" : "No";
|
|
2573
|
-
}
|
|
2574
|
-
function formatNumber(value, attribute) {
|
|
2575
|
-
if (typeof value !== "number") return String(value);
|
|
2576
|
-
const decimals = attribute.decimals;
|
|
2577
|
-
if (attribute.unit === "integer") {
|
|
2578
|
-
return value.toLocaleString(void 0, {
|
|
2579
|
-
minimumFractionDigits: 0,
|
|
2580
|
-
maximumFractionDigits: 0
|
|
2581
|
-
});
|
|
2582
|
-
}
|
|
2583
|
-
if (attribute.unit === "percentage") {
|
|
2584
|
-
return (value / 100).toLocaleString(void 0, {
|
|
2585
|
-
style: "percent",
|
|
2586
|
-
minimumFractionDigits: decimals,
|
|
2587
|
-
maximumFractionDigits: decimals
|
|
2588
|
-
});
|
|
2589
|
-
}
|
|
2590
|
-
return value.toLocaleString(void 0, {
|
|
2591
|
-
minimumFractionDigits: decimals,
|
|
2592
|
-
maximumFractionDigits: decimals
|
|
2593
|
-
});
|
|
2594
|
-
}
|
|
2595
|
-
function formatCurrency(value, _attribute) {
|
|
2596
|
-
if (typeof value !== "object" || value === null) return String(value);
|
|
2597
|
-
const currency2 = value;
|
|
2598
|
-
if (!("value" in currency2 && "code" in currency2)) return String(value);
|
|
2599
|
-
const formattedValue = currency2.value.toLocaleString(void 0, {
|
|
2600
|
-
minimumFractionDigits: 2,
|
|
2601
|
-
maximumFractionDigits: 2
|
|
2602
|
-
});
|
|
2603
|
-
return `${formattedValue} ${currency2.code}`;
|
|
2604
|
-
}
|
|
2605
|
-
function formatDate(value) {
|
|
2606
|
-
if (value instanceof Date) {
|
|
2607
|
-
return value.toISOString().split("T")[0];
|
|
2608
|
-
}
|
|
2609
|
-
if (typeof value === "string") {
|
|
2610
|
-
const date2 = new Date(value);
|
|
2611
|
-
if (!Number.isNaN(date2.getTime())) {
|
|
2612
|
-
return date2.toISOString().split("T")[0];
|
|
2613
|
-
}
|
|
2614
|
-
}
|
|
2615
|
-
return String(value);
|
|
2616
|
-
}
|
|
2617
|
-
function formatPhone(value) {
|
|
2618
|
-
if (typeof value !== "object" || value === null) return String(value);
|
|
2619
|
-
const phone2 = value;
|
|
2620
|
-
if (!("phoneNumber" in phone2)) return String(value);
|
|
2621
|
-
if (phone2.countryCode) {
|
|
2622
|
-
const country = getCountryByIso3(phone2.countryCode);
|
|
2623
|
-
const dial = country?.phoneCode ?? "";
|
|
2624
|
-
return `${dial} ${phone2.phoneNumber}`.trim();
|
|
2622
|
+
// src/runtime/mock/mock-ai.ts
|
|
2623
|
+
function requireUserId() {
|
|
2624
|
+
const userId = getUserId();
|
|
2625
|
+
if (!userId) {
|
|
2626
|
+
throw new Error("User context required for AI operations");
|
|
2625
2627
|
}
|
|
2626
|
-
return
|
|
2628
|
+
return userId;
|
|
2627
2629
|
}
|
|
2628
|
-
function
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
if (
|
|
2648
|
-
|
|
2630
|
+
function createMockAIConversationsRepository(stores) {
|
|
2631
|
+
return {
|
|
2632
|
+
findById(id) {
|
|
2633
|
+
const conversation = stores.aiConversations.get(id);
|
|
2634
|
+
if (!conversation || conversation.deletedAt) return Promise.resolve(null);
|
|
2635
|
+
const tenantId = getTenantId();
|
|
2636
|
+
if (conversation.tenantId !== tenantId) return Promise.resolve(null);
|
|
2637
|
+
return Promise.resolve(conversation);
|
|
2638
|
+
},
|
|
2639
|
+
list(options) {
|
|
2640
|
+
const tenantId = getTenantId();
|
|
2641
|
+
const userId = requireUserId();
|
|
2642
|
+
let results = Array.from(stores.aiConversations.values()).filter((c) => {
|
|
2643
|
+
if (c.tenantId !== tenantId || c.userId !== userId) return false;
|
|
2644
|
+
if (!options?.includeDeleted && c.deletedAt) return false;
|
|
2645
|
+
return true;
|
|
2646
|
+
});
|
|
2647
|
+
results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
2648
|
+
const total = results.length;
|
|
2649
|
+
if (options?.limit) {
|
|
2650
|
+
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
2649
2651
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2652
|
+
return Promise.resolve({ conversations: results, total });
|
|
2653
|
+
},
|
|
2654
|
+
create(data) {
|
|
2655
|
+
const tenantId = getTenantId();
|
|
2656
|
+
const userId = requireUserId();
|
|
2657
|
+
const now = /* @__PURE__ */ new Date();
|
|
2658
|
+
const conversation = {
|
|
2659
|
+
id: generateId(),
|
|
2660
|
+
tenantId,
|
|
2661
|
+
userId,
|
|
2662
|
+
title: data.title ?? null,
|
|
2663
|
+
messageCount: 0,
|
|
2664
|
+
totalTokens: 0,
|
|
2665
|
+
totalCost: 0,
|
|
2666
|
+
createdAt: now,
|
|
2667
|
+
updatedAt: now,
|
|
2668
|
+
deletedAt: null
|
|
2669
|
+
};
|
|
2670
|
+
stores.aiConversations.set(conversation.id, conversation);
|
|
2671
|
+
return Promise.resolve(conversation);
|
|
2672
|
+
},
|
|
2673
|
+
updateTitle(id, title) {
|
|
2674
|
+
const conversation = stores.aiConversations.get(id);
|
|
2675
|
+
if (!conversation || conversation.deletedAt) return Promise.resolve(null);
|
|
2676
|
+
const tenantId = getTenantId();
|
|
2677
|
+
if (conversation.tenantId !== tenantId) return Promise.resolve(null);
|
|
2678
|
+
conversation.title = title;
|
|
2679
|
+
conversation.updatedAt = /* @__PURE__ */ new Date();
|
|
2680
|
+
stores.aiConversations.set(id, conversation);
|
|
2681
|
+
return Promise.resolve(conversation);
|
|
2682
|
+
},
|
|
2683
|
+
delete(id) {
|
|
2684
|
+
const conversation = stores.aiConversations.get(id);
|
|
2685
|
+
if (!conversation) return Promise.resolve(false);
|
|
2686
|
+
const tenantId = getTenantId();
|
|
2687
|
+
if (conversation.tenantId !== tenantId) return Promise.resolve(false);
|
|
2688
|
+
conversation.deletedAt = /* @__PURE__ */ new Date();
|
|
2689
|
+
stores.aiConversations.set(id, conversation);
|
|
2690
|
+
return Promise.resolve(true);
|
|
2691
|
+
},
|
|
2692
|
+
addMessage(input) {
|
|
2693
|
+
const now = /* @__PURE__ */ new Date();
|
|
2694
|
+
const message = {
|
|
2695
|
+
id: generateId(),
|
|
2696
|
+
conversationId: input.conversationId,
|
|
2697
|
+
role: input.role,
|
|
2698
|
+
content: input.content,
|
|
2699
|
+
thinkingLevel: input.thinkingLevel ?? null,
|
|
2700
|
+
thinkingSummary: input.thinkingSummary ?? null,
|
|
2701
|
+
toolCalls: input.toolCalls ?? null,
|
|
2702
|
+
inputTokens: input.inputTokens ?? null,
|
|
2703
|
+
outputTokens: input.outputTokens ?? null,
|
|
2704
|
+
cost: input.cost ?? null,
|
|
2705
|
+
provider: input.provider ?? null,
|
|
2706
|
+
model: input.model ?? null,
|
|
2707
|
+
attachmentIds: input.attachmentIds ?? null,
|
|
2708
|
+
createdAt: now
|
|
2709
|
+
};
|
|
2710
|
+
stores.aiMessages.set(message.id, message);
|
|
2711
|
+
const conversation = stores.aiConversations.get(input.conversationId);
|
|
2712
|
+
if (conversation) {
|
|
2713
|
+
conversation.messageCount++;
|
|
2714
|
+
conversation.totalTokens += (input.inputTokens ?? 0) + (input.outputTokens ?? 0);
|
|
2715
|
+
conversation.totalCost += input.cost ?? 0;
|
|
2716
|
+
conversation.updatedAt = now;
|
|
2717
|
+
stores.aiConversations.set(input.conversationId, conversation);
|
|
2718
|
+
}
|
|
2719
|
+
return Promise.resolve(message);
|
|
2720
|
+
},
|
|
2721
|
+
listMessages(conversationId, options) {
|
|
2722
|
+
let results = Array.from(stores.aiMessages.values()).filter((m) => m.conversationId === conversationId).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
2723
|
+
const total = results.length;
|
|
2724
|
+
if (options?.limit) {
|
|
2725
|
+
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
2726
|
+
}
|
|
2727
|
+
return Promise.resolve({ messages: results, total });
|
|
2728
|
+
},
|
|
2729
|
+
getRecentMessages(conversationId, count = 20) {
|
|
2730
|
+
const results = Array.from(stores.aiMessages.values()).filter((m) => m.conversationId === conversationId).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).slice(-count);
|
|
2731
|
+
return Promise.resolve(results);
|
|
2732
|
+
}
|
|
2733
|
+
};
|
|
2684
2734
|
}
|
|
2685
|
-
function
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
case "textarea":
|
|
2692
|
-
return formatText(value);
|
|
2693
|
-
case "checkbox":
|
|
2694
|
-
return formatCheckbox(value);
|
|
2695
|
-
case "number":
|
|
2696
|
-
return formatNumber(value, attribute);
|
|
2697
|
-
case "currency":
|
|
2698
|
-
return formatCurrency(value, attribute);
|
|
2699
|
-
case "date":
|
|
2700
|
-
return formatDate(value);
|
|
2701
|
-
case "phone":
|
|
2702
|
-
return formatPhone(value);
|
|
2703
|
-
case "location":
|
|
2704
|
-
return formatLocation(value, attribute);
|
|
2705
|
-
case "select":
|
|
2706
|
-
case "status":
|
|
2707
|
-
return formatSelect(value, attribute);
|
|
2708
|
-
case "multiselect":
|
|
2709
|
-
return formatMultiselect(value, attribute);
|
|
2710
|
-
case "rating":
|
|
2711
|
-
return formatRating(value, attribute);
|
|
2712
|
-
// Unsupported types - return value as-is or placeholder
|
|
2713
|
-
case "file":
|
|
2714
|
-
case "user":
|
|
2715
|
-
case "relation":
|
|
2716
|
-
if (Array.isArray(value)) {
|
|
2717
|
-
return value.join(", ");
|
|
2718
|
-
}
|
|
2719
|
-
return String(value);
|
|
2720
|
-
default: {
|
|
2721
|
-
if (Array.isArray(value)) {
|
|
2722
|
-
return value.join(", ");
|
|
2723
|
-
}
|
|
2724
|
-
return String(value);
|
|
2725
|
-
}
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
|
|
2729
|
-
// src/runtime/template.ts
|
|
2730
|
-
var simplePipes = {
|
|
2731
|
-
/** Convert to uppercase */
|
|
2732
|
-
UPPER: (v) => String(v).toUpperCase(),
|
|
2733
|
-
/** Convert to lowercase */
|
|
2734
|
-
LOWER: (v) => String(v).toLowerCase(),
|
|
2735
|
-
/** Capitalize first letter of each word */
|
|
2736
|
-
capitalize: (v) => String(v).replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
2737
|
-
/** Trim whitespace from both ends */
|
|
2738
|
-
trim: (v) => String(v).trim()
|
|
2739
|
-
};
|
|
2740
|
-
var pipesWithArgs = {
|
|
2741
|
-
/** Add prefix only if value is non-empty */
|
|
2742
|
-
prefix: (v, pre = "") => v ? `${pre}${v}` : "",
|
|
2743
|
-
/** Add suffix only if value is non-empty */
|
|
2744
|
-
suffix: (v, suf = "") => v ? `${v}${suf}` : "",
|
|
2745
|
-
/** Wrap value with prefix and suffix only if non-empty */
|
|
2746
|
-
wrap: (v, pre = "", suf = "") => v ? `${pre}${v}${suf}` : "",
|
|
2747
|
-
/** Show default value if empty */
|
|
2748
|
-
default: (v, def = "") => v || def
|
|
2749
|
-
};
|
|
2750
|
-
function getValue(obj, path) {
|
|
2751
|
-
return path.split(".").reduce((acc, key) => {
|
|
2752
|
-
if (acc == null || typeof acc !== "object") return void 0;
|
|
2753
|
-
return acc[key];
|
|
2754
|
-
}, obj);
|
|
2755
|
-
}
|
|
2756
|
-
var DEFAULT_LABEL_FALLBACK = "(Untitled)";
|
|
2757
|
-
function parsePipeExpression(pipeExpr) {
|
|
2758
|
-
const match = pipeExpr.match(/^(\w+)(?::(.*))?$/);
|
|
2759
|
-
if (!match) return { name: pipeExpr, args: [] };
|
|
2760
|
-
const name = match[1];
|
|
2761
|
-
const argsStr = match[2];
|
|
2762
|
-
if (!argsStr) return { name, args: [] };
|
|
2763
|
-
const args = [];
|
|
2764
|
-
const argRegex = /["']([^"']*?)["']/g;
|
|
2765
|
-
let argMatch;
|
|
2766
|
-
while ((argMatch = argRegex.exec(argsStr)) !== null) {
|
|
2767
|
-
args.push(argMatch[1]);
|
|
2768
|
-
}
|
|
2769
|
-
return { name, args };
|
|
2770
|
-
}
|
|
2771
|
-
function renderLabelExpression(template, values, fallback = DEFAULT_LABEL_FALLBACK) {
|
|
2772
|
-
const result = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_, expr) => {
|
|
2773
|
-
const parts = expr.split("|").map((s) => s.trim());
|
|
2774
|
-
const path = parts[0];
|
|
2775
|
-
let value = getValue(values, path);
|
|
2776
|
-
const isEmpty3 = value == null || value === "";
|
|
2777
|
-
if (isEmpty3 && parts.length === 1) return "";
|
|
2778
|
-
for (let i = 1; i < parts.length; i++) {
|
|
2779
|
-
const { name: pipeName, args } = parsePipeExpression(parts[i]);
|
|
2780
|
-
const simpleFn = simplePipes[pipeName];
|
|
2781
|
-
if (simpleFn) {
|
|
2782
|
-
if (value != null && value !== "") {
|
|
2783
|
-
value = simpleFn(String(value));
|
|
2784
|
-
}
|
|
2785
|
-
} else {
|
|
2786
|
-
const argFn = pipesWithArgs[pipeName];
|
|
2787
|
-
if (argFn) {
|
|
2788
|
-
value = argFn(String(value ?? ""), ...args);
|
|
2789
|
-
}
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
return String(value ?? "");
|
|
2793
|
-
}).trim();
|
|
2794
|
-
return result || fallback;
|
|
2795
|
-
}
|
|
2796
|
-
function isLabelExpression(value) {
|
|
2797
|
-
return /\{\{\s*\S+.*\}\}/.test(value);
|
|
2798
|
-
}
|
|
2799
|
-
function extractAttributeNames(template) {
|
|
2800
|
-
const names = [];
|
|
2801
|
-
const regex = /\{\{\s*([^|}]+)/g;
|
|
2802
|
-
let match;
|
|
2803
|
-
while ((match = regex.exec(template)) !== null) {
|
|
2804
|
-
const path = match[1].trim();
|
|
2805
|
-
const rootName = path.split(".")[0];
|
|
2806
|
-
if (rootName && !names.includes(rootName)) {
|
|
2807
|
-
names.push(rootName);
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
|
-
return names;
|
|
2811
|
-
}
|
|
2812
|
-
function hasOptions(attr) {
|
|
2813
|
-
return "options" in attr && Array.isArray(attr.options) && attr.options.length > 0;
|
|
2814
|
-
}
|
|
2815
|
-
var FORMATTABLE_TYPES = /* @__PURE__ */ new Set([
|
|
2816
|
-
"currency",
|
|
2817
|
-
"location",
|
|
2818
|
-
"phone",
|
|
2819
|
-
"date",
|
|
2820
|
-
"rating",
|
|
2821
|
-
"select",
|
|
2822
|
-
"status",
|
|
2823
|
-
"multiselect",
|
|
2824
|
-
"number"
|
|
2825
|
-
]);
|
|
2826
|
-
function enrichValuesForDisplay(values, attributes) {
|
|
2827
|
-
const enriched = { ...values };
|
|
2828
|
-
for (const attr of attributes) {
|
|
2829
|
-
const value = values[attr.name];
|
|
2830
|
-
if (value == null) continue;
|
|
2831
|
-
if (!FORMATTABLE_TYPES.has(attr.type)) continue;
|
|
2832
|
-
const isSelectLike = attr.type === "select" || attr.type === "status" || attr.type === "multiselect";
|
|
2833
|
-
if (isSelectLike && !hasOptions(attr)) continue;
|
|
2834
|
-
if (attr.type === "multiselect" && Array.isArray(value) && value.length === 0) continue;
|
|
2835
|
-
const formatted = formatAttributeValue(value, attr);
|
|
2836
|
-
if (formatted && formatted !== EMPTY_VALUE_PLACEHOLDER) {
|
|
2837
|
-
enriched[attr.name] = formatted;
|
|
2838
|
-
}
|
|
2839
|
-
}
|
|
2840
|
-
return enriched;
|
|
2841
|
-
}
|
|
2842
|
-
var enrichValuesWithSelectLabels = enrichValuesForDisplay;
|
|
2843
|
-
function extractRelationIds(val) {
|
|
2844
|
-
if (typeof val === "string") return [val];
|
|
2845
|
-
if (Array.isArray(val) && typeof val[0] === "string") return [val[0]];
|
|
2846
|
-
return [];
|
|
2847
|
-
}
|
|
2848
|
-
async function computeLabelWithRelations(template, values, attributes, resolveRelationIds) {
|
|
2849
|
-
let enrichedValues = enrichValuesForDisplay(values, attributes);
|
|
2850
|
-
const attrNames = extractAttributeNames(template);
|
|
2851
|
-
const relationAttrs = attributes.filter(
|
|
2852
|
-
(attr) => attr.type === "relation" && attrNames.includes(attr.name)
|
|
2853
|
-
);
|
|
2854
|
-
if (relationAttrs.length === 0) {
|
|
2855
|
-
return renderLabelExpression(template, enrichedValues);
|
|
2856
|
-
}
|
|
2857
|
-
const allIds = [];
|
|
2858
|
-
for (const attr of relationAttrs) {
|
|
2859
|
-
const ids = extractRelationIds(values[attr.name]);
|
|
2860
|
-
allIds.push(...ids);
|
|
2861
|
-
}
|
|
2862
|
-
if (allIds.length === 0) {
|
|
2863
|
-
return renderLabelExpression(template, enrichedValues);
|
|
2864
|
-
}
|
|
2865
|
-
const resolvedMap = await resolveRelationIds(allIds);
|
|
2866
|
-
enrichedValues = { ...enrichedValues };
|
|
2867
|
-
for (const attr of relationAttrs) {
|
|
2868
|
-
const ids = extractRelationIds(values[attr.name]);
|
|
2869
|
-
if (ids.length > 0 && resolvedMap.has(ids[0])) {
|
|
2870
|
-
enrichedValues[attr.name] = resolvedMap.get(ids[0]);
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
2873
|
-
return renderLabelExpression(template, enrichedValues);
|
|
2874
|
-
}
|
|
2875
|
-
|
|
2876
|
-
// src/runtime/mock-adapter.ts
|
|
2877
|
-
function createMockObjectsRepository(stores) {
|
|
2735
|
+
function createMockAIUserMemoryRepository(stores) {
|
|
2736
|
+
const getKey = () => {
|
|
2737
|
+
const tenantId = getTenantId();
|
|
2738
|
+
const userId = requireUserId();
|
|
2739
|
+
return `${tenantId}:${userId}`;
|
|
2740
|
+
};
|
|
2878
2741
|
return {
|
|
2879
|
-
|
|
2880
|
-
|
|
2742
|
+
get() {
|
|
2743
|
+
const key = getKey();
|
|
2744
|
+
return Promise.resolve(stores.aiUserMemory.get(key) ?? null);
|
|
2881
2745
|
},
|
|
2882
|
-
|
|
2746
|
+
upsert(data) {
|
|
2747
|
+
const key = getKey();
|
|
2883
2748
|
const tenantId = getTenantId();
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
return Promise.resolve(
|
|
2749
|
+
const userId = requireUserId();
|
|
2750
|
+
const now = /* @__PURE__ */ new Date();
|
|
2751
|
+
const existing = stores.aiUserMemory.get(key);
|
|
2752
|
+
const memory = {
|
|
2753
|
+
id: existing?.id ?? generateId(),
|
|
2754
|
+
tenantId,
|
|
2755
|
+
userId,
|
|
2756
|
+
preferences: data.preferences ?? existing?.preferences ?? {},
|
|
2757
|
+
facts: data.facts ?? existing?.facts ?? [],
|
|
2758
|
+
createdAt: existing?.createdAt ?? now,
|
|
2759
|
+
updatedAt: now
|
|
2760
|
+
};
|
|
2761
|
+
stores.aiUserMemory.set(key, memory);
|
|
2762
|
+
return Promise.resolve(memory);
|
|
2898
2763
|
},
|
|
2899
|
-
|
|
2764
|
+
addFact(fact) {
|
|
2765
|
+
const key = getKey();
|
|
2900
2766
|
const tenantId = getTenantId();
|
|
2901
|
-
const
|
|
2902
|
-
|
|
2767
|
+
const userId = requireUserId();
|
|
2768
|
+
const now = /* @__PURE__ */ new Date();
|
|
2769
|
+
const existing = stores.aiUserMemory.get(key);
|
|
2770
|
+
const memory = {
|
|
2771
|
+
id: existing?.id ?? generateId(),
|
|
2903
2772
|
tenantId,
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
labelExpression: data.labelExpression,
|
|
2910
|
-
system: data.system ?? false,
|
|
2911
|
-
sharingMode: data.sharingMode ?? "private",
|
|
2912
|
-
metadata: data.metadata,
|
|
2913
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
2914
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
2773
|
+
userId,
|
|
2774
|
+
preferences: existing?.preferences ?? {},
|
|
2775
|
+
facts: [...existing?.facts ?? [], fact],
|
|
2776
|
+
createdAt: existing?.createdAt ?? now,
|
|
2777
|
+
updatedAt: now
|
|
2915
2778
|
};
|
|
2916
|
-
stores.
|
|
2917
|
-
return Promise.resolve(
|
|
2779
|
+
stores.aiUserMemory.set(key, memory);
|
|
2780
|
+
return Promise.resolve(memory);
|
|
2781
|
+
},
|
|
2782
|
+
removeFact(fact) {
|
|
2783
|
+
const key = getKey();
|
|
2784
|
+
const tenantId = getTenantId();
|
|
2785
|
+
const userId = requireUserId();
|
|
2786
|
+
const now = /* @__PURE__ */ new Date();
|
|
2787
|
+
const existing = stores.aiUserMemory.get(key);
|
|
2788
|
+
const memory = {
|
|
2789
|
+
id: existing?.id ?? generateId(),
|
|
2790
|
+
tenantId,
|
|
2791
|
+
userId,
|
|
2792
|
+
preferences: existing?.preferences ?? {},
|
|
2793
|
+
facts: (existing?.facts ?? []).filter((f) => f !== fact),
|
|
2794
|
+
createdAt: existing?.createdAt ?? now,
|
|
2795
|
+
updatedAt: now
|
|
2796
|
+
};
|
|
2797
|
+
stores.aiUserMemory.set(key, memory);
|
|
2798
|
+
return Promise.resolve(memory);
|
|
2799
|
+
},
|
|
2800
|
+
setPreference(prefKey, value) {
|
|
2801
|
+
const memoryKey = getKey();
|
|
2802
|
+
const tenantId = getTenantId();
|
|
2803
|
+
const userId = requireUserId();
|
|
2804
|
+
const now = /* @__PURE__ */ new Date();
|
|
2805
|
+
const existing = stores.aiUserMemory.get(memoryKey);
|
|
2806
|
+
const memory = {
|
|
2807
|
+
id: existing?.id ?? generateId(),
|
|
2808
|
+
tenantId,
|
|
2809
|
+
userId,
|
|
2810
|
+
preferences: { ...existing?.preferences ?? {}, [prefKey]: value },
|
|
2811
|
+
facts: existing?.facts ?? [],
|
|
2812
|
+
createdAt: existing?.createdAt ?? now,
|
|
2813
|
+
updatedAt: now
|
|
2814
|
+
};
|
|
2815
|
+
stores.aiUserMemory.set(memoryKey, memory);
|
|
2816
|
+
return Promise.resolve(memory);
|
|
2817
|
+
},
|
|
2818
|
+
clear() {
|
|
2819
|
+
const key = getKey();
|
|
2820
|
+
stores.aiUserMemory.delete(key);
|
|
2821
|
+
return Promise.resolve();
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2825
|
+
function createMockAIUsageMetricsRepository(stores) {
|
|
2826
|
+
const getDateKey = (date2) => {
|
|
2827
|
+
const tenantId = getTenantId();
|
|
2828
|
+
const dateStr = date2.toISOString().split("T")[0];
|
|
2829
|
+
return `${tenantId}:${dateStr}`;
|
|
2830
|
+
};
|
|
2831
|
+
return {
|
|
2832
|
+
recordUsage(data) {
|
|
2833
|
+
const tenantId = getTenantId();
|
|
2834
|
+
const now = /* @__PURE__ */ new Date();
|
|
2835
|
+
const key = getDateKey(now);
|
|
2836
|
+
const existing = stores.aiUsageMetrics.get(key);
|
|
2837
|
+
const providerBreakdown = existing?.providerBreakdown ?? {};
|
|
2838
|
+
if (!providerBreakdown[data.provider]) {
|
|
2839
|
+
providerBreakdown[data.provider] = { requests: 0, tokens: 0, cost: 0 };
|
|
2840
|
+
}
|
|
2841
|
+
providerBreakdown[data.provider].requests++;
|
|
2842
|
+
providerBreakdown[data.provider].tokens += data.tokens;
|
|
2843
|
+
providerBreakdown[data.provider].cost += data.cost;
|
|
2844
|
+
const toolUsage = existing?.toolUsage ?? {};
|
|
2845
|
+
if (data.toolName) {
|
|
2846
|
+
toolUsage[data.toolName] = (toolUsage[data.toolName] ?? 0) + 1;
|
|
2847
|
+
}
|
|
2848
|
+
const metrics = {
|
|
2849
|
+
id: existing?.id ?? generateId(),
|
|
2850
|
+
tenantId,
|
|
2851
|
+
date: new Date(now.toISOString().split("T")[0] ?? now.toISOString()),
|
|
2852
|
+
requestCount: (existing?.requestCount ?? 0) + 1,
|
|
2853
|
+
totalTokens: (existing?.totalTokens ?? 0) + data.tokens,
|
|
2854
|
+
totalCost: (existing?.totalCost ?? 0) + data.cost,
|
|
2855
|
+
providerBreakdown,
|
|
2856
|
+
toolUsage
|
|
2857
|
+
};
|
|
2858
|
+
stores.aiUsageMetrics.set(key, metrics);
|
|
2859
|
+
return Promise.resolve();
|
|
2860
|
+
},
|
|
2861
|
+
getByDateRange(startDate, endDate) {
|
|
2862
|
+
const tenantId = getTenantId();
|
|
2863
|
+
const results = Array.from(stores.aiUsageMetrics.values()).filter((m) => {
|
|
2864
|
+
if (m.tenantId !== tenantId) return false;
|
|
2865
|
+
return m.date >= startDate && m.date <= endDate;
|
|
2866
|
+
});
|
|
2867
|
+
results.sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
2868
|
+
return Promise.resolve(results);
|
|
2869
|
+
},
|
|
2870
|
+
getCurrentMonthUsage() {
|
|
2871
|
+
const tenantId = getTenantId();
|
|
2872
|
+
const now = /* @__PURE__ */ new Date();
|
|
2873
|
+
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
2874
|
+
const monthMetrics = Array.from(stores.aiUsageMetrics.values()).filter((m) => {
|
|
2875
|
+
if (m.tenantId !== tenantId) return false;
|
|
2876
|
+
return m.date >= startOfMonth;
|
|
2877
|
+
});
|
|
2878
|
+
const aggregated = {
|
|
2879
|
+
requestCount: 0,
|
|
2880
|
+
totalTokens: 0,
|
|
2881
|
+
totalCost: 0,
|
|
2882
|
+
providerBreakdown: {}
|
|
2883
|
+
};
|
|
2884
|
+
for (const m of monthMetrics) {
|
|
2885
|
+
aggregated.requestCount += m.requestCount;
|
|
2886
|
+
aggregated.totalTokens += m.totalTokens;
|
|
2887
|
+
aggregated.totalCost += m.totalCost;
|
|
2888
|
+
for (const [provider, stats] of Object.entries(m.providerBreakdown)) {
|
|
2889
|
+
if (!aggregated.providerBreakdown[provider]) {
|
|
2890
|
+
aggregated.providerBreakdown[provider] = { requests: 0, tokens: 0, cost: 0 };
|
|
2891
|
+
}
|
|
2892
|
+
aggregated.providerBreakdown[provider].requests += stats.requests;
|
|
2893
|
+
aggregated.providerBreakdown[provider].tokens += stats.tokens;
|
|
2894
|
+
aggregated.providerBreakdown[provider].cost += stats.cost;
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
return Promise.resolve(aggregated);
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
// src/runtime/mock/mock-documents.ts
|
|
2903
|
+
function createMockFilesRepository(stores) {
|
|
2904
|
+
return {
|
|
2905
|
+
findById(id) {
|
|
2906
|
+
const file2 = stores.files.get(id);
|
|
2907
|
+
if (file2?.deletedAt) return Promise.resolve(null);
|
|
2908
|
+
return Promise.resolve(file2 ?? null);
|
|
2909
|
+
},
|
|
2910
|
+
findByIds(ids) {
|
|
2911
|
+
const files = ids.map((id) => stores.files.get(id)).filter((f) => f != null && !f.deletedAt);
|
|
2912
|
+
return Promise.resolve(files);
|
|
2913
|
+
},
|
|
2914
|
+
create(data) {
|
|
2915
|
+
const tenantId = getTenantId();
|
|
2916
|
+
const file2 = {
|
|
2917
|
+
id: generateId(),
|
|
2918
|
+
tenantId,
|
|
2919
|
+
name: data.name,
|
|
2920
|
+
originalName: data.originalName,
|
|
2921
|
+
mimeType: data.mimeType,
|
|
2922
|
+
size: data.size,
|
|
2923
|
+
storageProvider: data.storageProvider,
|
|
2924
|
+
storagePath: data.storagePath,
|
|
2925
|
+
storageBucket: data.storageBucket,
|
|
2926
|
+
url: data.url,
|
|
2927
|
+
uploadedBy: data.uploadedBy,
|
|
2928
|
+
folderPath: data.folderPath,
|
|
2929
|
+
tags: data.tags,
|
|
2930
|
+
visibility: data.visibility ?? "private",
|
|
2931
|
+
allowedUsers: data.allowedUsers,
|
|
2932
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
2933
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2934
|
+
};
|
|
2935
|
+
stores.files.set(file2.id, file2);
|
|
2936
|
+
return Promise.resolve(file2);
|
|
2937
|
+
},
|
|
2938
|
+
update(id, data) {
|
|
2939
|
+
const existing = stores.files.get(id);
|
|
2940
|
+
if (!existing) {
|
|
2941
|
+
return Promise.reject(new Error(`File ${id} not found`));
|
|
2942
|
+
}
|
|
2943
|
+
const updated = {
|
|
2944
|
+
...existing,
|
|
2945
|
+
...data,
|
|
2946
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2947
|
+
};
|
|
2948
|
+
stores.files.set(id, updated);
|
|
2949
|
+
return Promise.resolve(updated);
|
|
2950
|
+
},
|
|
2951
|
+
delete(id) {
|
|
2952
|
+
const file2 = stores.files.get(id);
|
|
2953
|
+
if (file2) {
|
|
2954
|
+
file2.deletedAt = /* @__PURE__ */ new Date();
|
|
2955
|
+
stores.files.set(id, file2);
|
|
2956
|
+
}
|
|
2957
|
+
return Promise.resolve();
|
|
2958
|
+
},
|
|
2959
|
+
hardDelete(id) {
|
|
2960
|
+
stores.files.delete(id);
|
|
2961
|
+
return Promise.resolve();
|
|
2962
|
+
},
|
|
2963
|
+
list(options) {
|
|
2964
|
+
const tenantId = getTenantId();
|
|
2965
|
+
let results = Array.from(stores.files.values()).filter(
|
|
2966
|
+
(f) => f.tenantId === tenantId && !f.deletedAt
|
|
2967
|
+
);
|
|
2968
|
+
if (options?.mimeType) {
|
|
2969
|
+
results = results.filter((f) => f.mimeType === options.mimeType);
|
|
2970
|
+
}
|
|
2971
|
+
if (options?.limit) {
|
|
2972
|
+
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
2973
|
+
}
|
|
2974
|
+
return Promise.resolve(results);
|
|
2975
|
+
},
|
|
2976
|
+
findByFolder(folderPath) {
|
|
2977
|
+
const tenantId = getTenantId();
|
|
2978
|
+
const results = Array.from(stores.files.values()).filter(
|
|
2979
|
+
(f) => f.tenantId === tenantId && f.folderPath === folderPath && !f.deletedAt
|
|
2980
|
+
);
|
|
2981
|
+
return Promise.resolve(results);
|
|
2982
|
+
},
|
|
2983
|
+
findByUploader(uploadedBy) {
|
|
2984
|
+
const results = Array.from(stores.files.values()).filter(
|
|
2985
|
+
(f) => f.uploadedBy === uploadedBy && !f.deletedAt
|
|
2986
|
+
);
|
|
2987
|
+
return Promise.resolve(results);
|
|
2988
|
+
}
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
// src/runtime/mock/mock-object-definitions.ts
|
|
2993
|
+
function createMockObjectsRepository(stores) {
|
|
2994
|
+
return {
|
|
2995
|
+
findById(id) {
|
|
2996
|
+
return Promise.resolve(stores.objects.get(id) ?? null);
|
|
2997
|
+
},
|
|
2998
|
+
findByName(name) {
|
|
2999
|
+
const tenantId = getTenantId();
|
|
3000
|
+
for (const obj of stores.objects.values()) {
|
|
3001
|
+
if (obj.tenantId === tenantId && obj.name === name) {
|
|
3002
|
+
return Promise.resolve(obj);
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
return Promise.resolve(null);
|
|
3006
|
+
},
|
|
3007
|
+
findSystemByName(name) {
|
|
3008
|
+
for (const obj of stores.objects.values()) {
|
|
3009
|
+
if (obj.system && obj.name === name) {
|
|
3010
|
+
return Promise.resolve(obj);
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
return Promise.resolve(null);
|
|
3014
|
+
},
|
|
3015
|
+
create(data) {
|
|
3016
|
+
const tenantId = getTenantId();
|
|
3017
|
+
const obj = {
|
|
3018
|
+
id: generateId(),
|
|
3019
|
+
tenantId,
|
|
3020
|
+
name: data.name,
|
|
3021
|
+
label: data.label,
|
|
3022
|
+
pluralLabel: data.pluralLabel,
|
|
3023
|
+
description: data.description,
|
|
3024
|
+
icon: data.icon,
|
|
3025
|
+
labelExpression: data.labelExpression,
|
|
3026
|
+
system: data.system ?? false,
|
|
3027
|
+
sharingMode: data.sharingMode ?? "private",
|
|
3028
|
+
metadata: data.metadata,
|
|
3029
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
3030
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3031
|
+
};
|
|
3032
|
+
stores.objects.set(obj.id, obj);
|
|
3033
|
+
return Promise.resolve(obj);
|
|
2918
3034
|
},
|
|
2919
3035
|
update(id, data) {
|
|
2920
3036
|
const existing = stores.objects.get(id);
|
|
@@ -3060,204 +3176,533 @@ function createMockAttributesRepository(stores) {
|
|
|
3060
3176
|
}
|
|
3061
3177
|
};
|
|
3062
3178
|
}
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3179
|
+
|
|
3180
|
+
// src/exceptions.ts
|
|
3181
|
+
var SchemaErrorCode = {
|
|
3182
|
+
// Generic
|
|
3183
|
+
UNKNOWN: "SCHEMA_UNKNOWN_ERROR",
|
|
3184
|
+
// Not Found
|
|
3185
|
+
OBJECT_NOT_FOUND: "SCHEMA_OBJECT_NOT_FOUND",
|
|
3186
|
+
ATTRIBUTE_NOT_FOUND: "SCHEMA_ATTRIBUTE_NOT_FOUND",
|
|
3187
|
+
RECORD_NOT_FOUND: "SCHEMA_RECORD_NOT_FOUND",
|
|
3188
|
+
USER_PROFILE_NOT_FOUND: "SCHEMA_USER_PROFILE_NOT_FOUND",
|
|
3189
|
+
FILE_NOT_FOUND: "SCHEMA_FILE_NOT_FOUND",
|
|
3190
|
+
ROLE_NOT_FOUND: "SCHEMA_ROLE_NOT_FOUND",
|
|
3191
|
+
// Validation
|
|
3192
|
+
VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED",
|
|
3193
|
+
INVALID_ATTRIBUTE_NAME: "SCHEMA_INVALID_ATTRIBUTE_NAME",
|
|
3194
|
+
INVALID_OBJECT_NAME: "SCHEMA_INVALID_OBJECT_NAME",
|
|
3195
|
+
// Protected Resources
|
|
3196
|
+
PROTECTED_OBJECT: "SCHEMA_PROTECTED_OBJECT",
|
|
3197
|
+
PROTECTED_ATTRIBUTE: "SCHEMA_PROTECTED_ATTRIBUTE",
|
|
3198
|
+
PROTECTED_VIEW: "SCHEMA_PROTECTED_VIEW",
|
|
3199
|
+
PROTECTED_ROLE: "SCHEMA_PROTECTED_ROLE",
|
|
3200
|
+
// Permissions
|
|
3201
|
+
FORBIDDEN: "SCHEMA_FORBIDDEN",
|
|
3202
|
+
// Sync
|
|
3203
|
+
SYNC_FAILED: "SCHEMA_SYNC_FAILED",
|
|
3204
|
+
NOT_SYSTEM_OBJECT: "SCHEMA_NOT_SYSTEM_OBJECT",
|
|
3205
|
+
// Duplicates
|
|
3206
|
+
DUPLICATE_OBJECT: "SCHEMA_DUPLICATE_OBJECT",
|
|
3207
|
+
DUPLICATE_ATTRIBUTE: "SCHEMA_DUPLICATE_ATTRIBUTE",
|
|
3208
|
+
// Concurrency
|
|
3209
|
+
CONFLICT: "SCHEMA_CONFLICT"
|
|
3210
|
+
};
|
|
3211
|
+
var SchemaError = class extends Error {
|
|
3212
|
+
constructor(message, code = SchemaErrorCode.UNKNOWN, details) {
|
|
3213
|
+
super(message);
|
|
3214
|
+
this.name = "SchemaError";
|
|
3215
|
+
this.code = code;
|
|
3216
|
+
this.details = details;
|
|
3217
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
3218
|
+
}
|
|
3219
|
+
toJSON() {
|
|
3220
|
+
return {
|
|
3221
|
+
name: this.name,
|
|
3222
|
+
code: this.code,
|
|
3223
|
+
message: this.message,
|
|
3224
|
+
details: this.details
|
|
3225
|
+
};
|
|
3226
|
+
}
|
|
3227
|
+
};
|
|
3228
|
+
var NotFoundError = class extends SchemaError {
|
|
3229
|
+
constructor(resourceType, resourceId, code = SchemaErrorCode.RECORD_NOT_FOUND) {
|
|
3230
|
+
super(`${resourceType} with id "${resourceId}" not found`, code, {
|
|
3231
|
+
resourceType,
|
|
3232
|
+
resourceId
|
|
3233
|
+
});
|
|
3234
|
+
this.name = "NotFoundError";
|
|
3235
|
+
this.resourceType = resourceType;
|
|
3236
|
+
this.resourceId = resourceId;
|
|
3237
|
+
}
|
|
3238
|
+
};
|
|
3239
|
+
var ObjectNotFoundError = class extends NotFoundError {
|
|
3240
|
+
constructor(objectId) {
|
|
3241
|
+
super("Object", objectId, SchemaErrorCode.OBJECT_NOT_FOUND);
|
|
3242
|
+
this.name = "ObjectNotFoundError";
|
|
3243
|
+
}
|
|
3244
|
+
};
|
|
3245
|
+
var AttributeNotFoundError = class extends NotFoundError {
|
|
3246
|
+
constructor(attributeId) {
|
|
3247
|
+
super("Attribute", attributeId, SchemaErrorCode.ATTRIBUTE_NOT_FOUND);
|
|
3248
|
+
this.name = "AttributeNotFoundError";
|
|
3249
|
+
}
|
|
3250
|
+
};
|
|
3251
|
+
var RecordNotFoundError = class extends NotFoundError {
|
|
3252
|
+
constructor(recordId) {
|
|
3253
|
+
super("Record", recordId, SchemaErrorCode.RECORD_NOT_FOUND);
|
|
3254
|
+
this.name = "RecordNotFoundError";
|
|
3255
|
+
}
|
|
3256
|
+
};
|
|
3257
|
+
var UserProfileNotFoundError = class extends NotFoundError {
|
|
3258
|
+
constructor(identifier) {
|
|
3259
|
+
super("UserProfile", identifier, SchemaErrorCode.USER_PROFILE_NOT_FOUND);
|
|
3260
|
+
this.name = "UserProfileNotFoundError";
|
|
3261
|
+
}
|
|
3262
|
+
};
|
|
3263
|
+
var FileNotFoundError = class extends NotFoundError {
|
|
3264
|
+
constructor(fileId) {
|
|
3265
|
+
super("File", fileId, SchemaErrorCode.FILE_NOT_FOUND);
|
|
3266
|
+
this.name = "FileNotFoundError";
|
|
3267
|
+
}
|
|
3268
|
+
};
|
|
3269
|
+
var ValidationError = class _ValidationError extends SchemaError {
|
|
3270
|
+
constructor(message, errors) {
|
|
3271
|
+
super(message, SchemaErrorCode.VALIDATION_FAILED, { errors });
|
|
3272
|
+
this.name = "ValidationError";
|
|
3273
|
+
this.errors = errors;
|
|
3274
|
+
}
|
|
3275
|
+
/**
|
|
3276
|
+
* Create a validation error from Zod-style errors
|
|
3277
|
+
*/
|
|
3278
|
+
static fromZodErrors(errors) {
|
|
3279
|
+
const details = errors.map((err) => ({
|
|
3280
|
+
path: err.path.map(String),
|
|
3281
|
+
message: err.message
|
|
3282
|
+
}));
|
|
3283
|
+
const message = `Validation failed: ${details.map((d) => `${d.path.join(".")}: ${d.message}`).join(", ")}`;
|
|
3284
|
+
return new _ValidationError(message, details);
|
|
3285
|
+
}
|
|
3286
|
+
};
|
|
3287
|
+
var ProtectedResourceError = class extends SchemaError {
|
|
3288
|
+
constructor(resourceType, resourceName, operation) {
|
|
3289
|
+
const code = resourceType === "object" ? SchemaErrorCode.PROTECTED_OBJECT : resourceType === "view" ? SchemaErrorCode.PROTECTED_VIEW : SchemaErrorCode.PROTECTED_ATTRIBUTE;
|
|
3290
|
+
super(`Cannot ${operation} system ${resourceType} "${resourceName}"`, code, {
|
|
3291
|
+
resourceType,
|
|
3292
|
+
resourceName,
|
|
3293
|
+
operation
|
|
3294
|
+
});
|
|
3295
|
+
this.name = "ProtectedResourceError";
|
|
3296
|
+
this.resourceType = resourceType;
|
|
3297
|
+
this.resourceName = resourceName;
|
|
3298
|
+
this.operation = operation;
|
|
3299
|
+
}
|
|
3300
|
+
};
|
|
3301
|
+
var SyncError = class extends SchemaError {
|
|
3302
|
+
constructor(objectName, message, cause) {
|
|
3303
|
+
super(`Failed to sync object "${objectName}": ${message}`, SchemaErrorCode.SYNC_FAILED, {
|
|
3304
|
+
objectName,
|
|
3305
|
+
cause: cause?.message
|
|
3306
|
+
});
|
|
3307
|
+
this.name = "SyncError";
|
|
3308
|
+
this.objectName = objectName;
|
|
3309
|
+
this.cause = cause;
|
|
3310
|
+
}
|
|
3311
|
+
};
|
|
3312
|
+
var NotSystemObjectError = class extends SchemaError {
|
|
3313
|
+
constructor(objectName) {
|
|
3314
|
+
super(
|
|
3315
|
+
`Object "${objectName}" is not marked as system. Native objects must have system=true.`,
|
|
3316
|
+
SchemaErrorCode.NOT_SYSTEM_OBJECT,
|
|
3317
|
+
{ objectName }
|
|
3318
|
+
);
|
|
3319
|
+
this.name = "NotSystemObjectError";
|
|
3320
|
+
this.objectName = objectName;
|
|
3321
|
+
}
|
|
3322
|
+
};
|
|
3323
|
+
var DuplicateError = class extends SchemaError {
|
|
3324
|
+
constructor(resourceType, resourceName) {
|
|
3325
|
+
const code = resourceType === "object" ? SchemaErrorCode.DUPLICATE_OBJECT : SchemaErrorCode.DUPLICATE_ATTRIBUTE;
|
|
3326
|
+
super(
|
|
3327
|
+
`${resourceType === "object" ? "Object" : "Attribute"} "${resourceName}" already exists`,
|
|
3328
|
+
code,
|
|
3329
|
+
{ resourceType, resourceName }
|
|
3330
|
+
);
|
|
3331
|
+
this.name = "DuplicateError";
|
|
3332
|
+
this.resourceType = resourceType;
|
|
3333
|
+
this.resourceName = resourceName;
|
|
3334
|
+
}
|
|
3335
|
+
};
|
|
3336
|
+
function isSchemaError(error2) {
|
|
3337
|
+
return error2 instanceof SchemaError;
|
|
3338
|
+
}
|
|
3339
|
+
function isNotFoundError(error2) {
|
|
3340
|
+
return error2 instanceof NotFoundError;
|
|
3341
|
+
}
|
|
3342
|
+
function isValidationError(error2) {
|
|
3343
|
+
return error2 instanceof ValidationError;
|
|
3344
|
+
}
|
|
3345
|
+
function isProtectedResourceError(error2) {
|
|
3346
|
+
return error2 instanceof ProtectedResourceError;
|
|
3347
|
+
}
|
|
3348
|
+
var ForbiddenError = class extends SchemaError {
|
|
3349
|
+
constructor(objectName, action, userId) {
|
|
3350
|
+
super(`No ${action} permission on object "${objectName}"`, SchemaErrorCode.FORBIDDEN, {
|
|
3351
|
+
objectName,
|
|
3352
|
+
action,
|
|
3353
|
+
userId
|
|
3354
|
+
});
|
|
3355
|
+
this.name = "ForbiddenError";
|
|
3356
|
+
this.objectName = objectName;
|
|
3357
|
+
this.action = action;
|
|
3358
|
+
this.userId = userId;
|
|
3359
|
+
}
|
|
3360
|
+
};
|
|
3361
|
+
var ProtectedRoleError = class extends SchemaError {
|
|
3362
|
+
constructor(roleName, operation) {
|
|
3363
|
+
super(`Cannot ${operation} system role "${roleName}"`, SchemaErrorCode.PROTECTED_ROLE, {
|
|
3364
|
+
roleName,
|
|
3365
|
+
operation
|
|
3366
|
+
});
|
|
3367
|
+
this.name = "ProtectedRoleError";
|
|
3368
|
+
this.roleName = roleName;
|
|
3369
|
+
this.operation = operation;
|
|
3370
|
+
}
|
|
3371
|
+
};
|
|
3372
|
+
var RoleNotFoundError = class extends NotFoundError {
|
|
3373
|
+
constructor(roleId) {
|
|
3374
|
+
super("Role", roleId, SchemaErrorCode.ROLE_NOT_FOUND);
|
|
3375
|
+
this.name = "RoleNotFoundError";
|
|
3376
|
+
}
|
|
3377
|
+
};
|
|
3378
|
+
function isForbiddenError(error2) {
|
|
3379
|
+
return error2 instanceof ForbiddenError;
|
|
3380
|
+
}
|
|
3381
|
+
var ConcurrentModificationError = class extends SchemaError {
|
|
3382
|
+
constructor(recordId) {
|
|
3383
|
+
super(
|
|
3384
|
+
`Record ${recordId} was modified by another request. Please refresh and try again.`,
|
|
3385
|
+
SchemaErrorCode.CONFLICT,
|
|
3386
|
+
{ recordId }
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
};
|
|
3390
|
+
function isConcurrentModificationError(error2) {
|
|
3391
|
+
return error2 instanceof ConcurrentModificationError;
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
// src/format.ts
|
|
3395
|
+
import { getCountryByIso3 } from "@stndrds/constants";
|
|
3396
|
+
var EMPTY_VALUE_PLACEHOLDER = "\u2014";
|
|
3397
|
+
function formatText(value) {
|
|
3398
|
+
return String(value);
|
|
3399
|
+
}
|
|
3400
|
+
function formatCheckbox(value) {
|
|
3401
|
+
return value ? "Yes" : "No";
|
|
3402
|
+
}
|
|
3403
|
+
function formatNumber(value, attribute) {
|
|
3404
|
+
if (typeof value !== "number") return String(value);
|
|
3405
|
+
const decimals = attribute.decimals;
|
|
3406
|
+
if (attribute.unit === "integer") {
|
|
3407
|
+
return value.toLocaleString(void 0, {
|
|
3408
|
+
minimumFractionDigits: 0,
|
|
3409
|
+
maximumFractionDigits: 0
|
|
3410
|
+
});
|
|
3411
|
+
}
|
|
3412
|
+
if (attribute.unit === "percentage") {
|
|
3413
|
+
return (value / 100).toLocaleString(void 0, {
|
|
3414
|
+
style: "percent",
|
|
3415
|
+
minimumFractionDigits: decimals,
|
|
3416
|
+
maximumFractionDigits: decimals
|
|
3417
|
+
});
|
|
3418
|
+
}
|
|
3419
|
+
return value.toLocaleString(void 0, {
|
|
3420
|
+
minimumFractionDigits: decimals,
|
|
3421
|
+
maximumFractionDigits: decimals
|
|
3422
|
+
});
|
|
3423
|
+
}
|
|
3424
|
+
function formatCurrency(value, _attribute) {
|
|
3425
|
+
if (typeof value !== "object" || value === null) return String(value);
|
|
3426
|
+
const currency2 = value;
|
|
3427
|
+
if (!("value" in currency2 && "code" in currency2)) return String(value);
|
|
3428
|
+
const formattedValue = currency2.value.toLocaleString(void 0, {
|
|
3429
|
+
minimumFractionDigits: 2,
|
|
3430
|
+
maximumFractionDigits: 2
|
|
3431
|
+
});
|
|
3432
|
+
return `${formattedValue} ${currency2.code}`;
|
|
3433
|
+
}
|
|
3434
|
+
function formatDate(value) {
|
|
3435
|
+
if (value instanceof Date) {
|
|
3436
|
+
return value.toISOString().split("T")[0];
|
|
3437
|
+
}
|
|
3438
|
+
if (typeof value === "string") {
|
|
3439
|
+
const date2 = new Date(value);
|
|
3440
|
+
if (!Number.isNaN(date2.getTime())) {
|
|
3441
|
+
return date2.toISOString().split("T")[0];
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
return String(value);
|
|
3445
|
+
}
|
|
3446
|
+
function formatPhone(value) {
|
|
3447
|
+
if (typeof value !== "object" || value === null) return String(value);
|
|
3448
|
+
const phone2 = value;
|
|
3449
|
+
if (!("phoneNumber" in phone2)) return String(value);
|
|
3450
|
+
if (phone2.countryCode) {
|
|
3451
|
+
const country = getCountryByIso3(phone2.countryCode);
|
|
3452
|
+
const dial = country?.phoneCode ?? "";
|
|
3453
|
+
return `${dial} ${phone2.phoneNumber}`.trim();
|
|
3454
|
+
}
|
|
3455
|
+
return phone2.phoneNumber;
|
|
3456
|
+
}
|
|
3457
|
+
function formatLocation(value, attribute) {
|
|
3458
|
+
if (typeof value !== "object" || value === null) return String(value);
|
|
3459
|
+
const loc = value;
|
|
3460
|
+
const granularity = attribute.granularity ?? "full";
|
|
3461
|
+
const parts = [];
|
|
3462
|
+
switch (granularity) {
|
|
3463
|
+
case "country":
|
|
3464
|
+
if (loc.country) parts.push(loc.country);
|
|
3465
|
+
break;
|
|
3466
|
+
case "state":
|
|
3467
|
+
if (loc.state) parts.push(loc.state);
|
|
3468
|
+
if (loc.country) parts.push(loc.country);
|
|
3469
|
+
break;
|
|
3470
|
+
case "city":
|
|
3471
|
+
if (loc.city) parts.push(loc.city);
|
|
3472
|
+
if (loc.state) parts.push(loc.state);
|
|
3473
|
+
if (loc.country) parts.push(loc.country);
|
|
3474
|
+
break;
|
|
3475
|
+
case "coordinates":
|
|
3476
|
+
if (loc.latitude !== void 0 && loc.longitude !== void 0) {
|
|
3477
|
+
parts.push(`${loc.latitude}, ${loc.longitude}`);
|
|
3076
3478
|
}
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3479
|
+
break;
|
|
3480
|
+
case "address":
|
|
3481
|
+
if (loc.address) parts.push(loc.address);
|
|
3482
|
+
if (loc.city) parts.push(loc.city);
|
|
3483
|
+
if (loc.state) parts.push(loc.state);
|
|
3484
|
+
if (loc.country) parts.push(loc.country);
|
|
3485
|
+
break;
|
|
3486
|
+
default:
|
|
3487
|
+
if (loc.address) parts.push(loc.address);
|
|
3488
|
+
if (loc.city) parts.push(loc.city);
|
|
3489
|
+
if (loc.state) parts.push(loc.state);
|
|
3490
|
+
if (loc.postalCode) parts.push(loc.postalCode);
|
|
3491
|
+
if (loc.country) parts.push(loc.country);
|
|
3492
|
+
break;
|
|
3493
|
+
}
|
|
3494
|
+
return parts.join(", ") || EMPTY_VALUE_PLACEHOLDER;
|
|
3495
|
+
}
|
|
3496
|
+
function formatSelect(value, attribute) {
|
|
3497
|
+
if (typeof value !== "string") return String(value);
|
|
3498
|
+
const option = attribute.options?.find((o) => o.value === value);
|
|
3499
|
+
return option?.label ?? String(value);
|
|
3500
|
+
}
|
|
3501
|
+
function formatMultiselect(value, attribute) {
|
|
3502
|
+
if (!Array.isArray(value)) return String(value);
|
|
3503
|
+
if (attribute.options) {
|
|
3504
|
+
const labels = value.map((v) => attribute.options.find((o) => o.value === v)?.label).filter(Boolean);
|
|
3505
|
+
return labels.join(", ");
|
|
3506
|
+
}
|
|
3507
|
+
return value.join(", ");
|
|
3508
|
+
}
|
|
3509
|
+
function formatRating(value, attribute) {
|
|
3510
|
+
if (typeof value !== "number") return String(value);
|
|
3511
|
+
const max = attribute.max ?? 5;
|
|
3512
|
+
return `${value}/${max}`;
|
|
3513
|
+
}
|
|
3514
|
+
function formatAttributeValue(value, attribute) {
|
|
3515
|
+
if (value === null || value === void 0 || value === "") {
|
|
3516
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
3517
|
+
}
|
|
3518
|
+
switch (attribute.type) {
|
|
3519
|
+
case "text":
|
|
3520
|
+
case "textarea":
|
|
3521
|
+
return formatText(value);
|
|
3522
|
+
case "checkbox":
|
|
3523
|
+
return formatCheckbox(value);
|
|
3524
|
+
case "number":
|
|
3525
|
+
return formatNumber(value, attribute);
|
|
3526
|
+
case "currency":
|
|
3527
|
+
return formatCurrency(value, attribute);
|
|
3528
|
+
case "date":
|
|
3529
|
+
return formatDate(value);
|
|
3530
|
+
case "phone":
|
|
3531
|
+
return formatPhone(value);
|
|
3532
|
+
case "location":
|
|
3533
|
+
return formatLocation(value, attribute);
|
|
3534
|
+
case "select":
|
|
3535
|
+
case "status":
|
|
3536
|
+
return formatSelect(value, attribute);
|
|
3537
|
+
case "multiselect":
|
|
3538
|
+
return formatMultiselect(value, attribute);
|
|
3539
|
+
case "rating":
|
|
3540
|
+
return formatRating(value, attribute);
|
|
3541
|
+
// Unsupported types - return value as-is or placeholder
|
|
3542
|
+
case "file":
|
|
3543
|
+
case "user":
|
|
3544
|
+
case "relation":
|
|
3545
|
+
if (Array.isArray(value)) {
|
|
3546
|
+
return value.join(", ");
|
|
3084
3547
|
}
|
|
3085
|
-
return
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3548
|
+
return String(value);
|
|
3549
|
+
default: {
|
|
3550
|
+
if (Array.isArray(value)) {
|
|
3551
|
+
return value.join(", ");
|
|
3552
|
+
}
|
|
3553
|
+
return String(value);
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
// src/runtime/template.ts
|
|
3559
|
+
var simplePipes = {
|
|
3560
|
+
/** Convert to uppercase */
|
|
3561
|
+
UPPER: (v) => String(v).toUpperCase(),
|
|
3562
|
+
/** Convert to lowercase */
|
|
3563
|
+
LOWER: (v) => String(v).toLowerCase(),
|
|
3564
|
+
/** Capitalize first letter of each word */
|
|
3565
|
+
capitalize: (v) => String(v).replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
3566
|
+
/** Trim whitespace from both ends */
|
|
3567
|
+
trim: (v) => String(v).trim()
|
|
3568
|
+
};
|
|
3569
|
+
var pipesWithArgs = {
|
|
3570
|
+
/** Add prefix only if value is non-empty */
|
|
3571
|
+
prefix: (v, pre = "") => v ? `${pre}${v}` : "",
|
|
3572
|
+
/** Add suffix only if value is non-empty */
|
|
3573
|
+
suffix: (v, suf = "") => v ? `${v}${suf}` : "",
|
|
3574
|
+
/** Wrap value with prefix and suffix only if non-empty */
|
|
3575
|
+
wrap: (v, pre = "", suf = "") => v ? `${pre}${v}${suf}` : "",
|
|
3576
|
+
/** Show default value if empty */
|
|
3577
|
+
default: (v, def = "") => v || def
|
|
3578
|
+
};
|
|
3579
|
+
function getValue(obj, path) {
|
|
3580
|
+
return path.split(".").reduce((acc, key) => {
|
|
3581
|
+
if (acc == null || typeof acc !== "object") return void 0;
|
|
3582
|
+
return acc[key];
|
|
3583
|
+
}, obj);
|
|
3584
|
+
}
|
|
3585
|
+
var DEFAULT_LABEL_FALLBACK = "(Untitled)";
|
|
3586
|
+
function parsePipeExpression(pipeExpr) {
|
|
3587
|
+
const match = pipeExpr.match(/^(\w+)(?::(.*))?$/);
|
|
3588
|
+
if (!match) return { name: pipeExpr, args: [] };
|
|
3589
|
+
const name = match[1];
|
|
3590
|
+
const argsStr = match[2];
|
|
3591
|
+
if (!argsStr) return { name, args: [] };
|
|
3592
|
+
const args = [];
|
|
3593
|
+
const argRegex = /["']([^"']*?)["']/g;
|
|
3594
|
+
let argMatch;
|
|
3595
|
+
while ((argMatch = argRegex.exec(argsStr)) !== null) {
|
|
3596
|
+
args.push(argMatch[1]);
|
|
3597
|
+
}
|
|
3598
|
+
return { name, args };
|
|
3599
|
+
}
|
|
3600
|
+
function renderLabelExpression(template, values, fallback = DEFAULT_LABEL_FALLBACK) {
|
|
3601
|
+
const result = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_, expr) => {
|
|
3602
|
+
const parts = expr.split("|").map((s) => s.trim());
|
|
3603
|
+
const path = parts[0];
|
|
3604
|
+
let value = getValue(values, path);
|
|
3605
|
+
const isEmpty3 = value == null || value === "";
|
|
3606
|
+
if (isEmpty3 && parts.length === 1) return "";
|
|
3607
|
+
for (let i = 1; i < parts.length; i++) {
|
|
3608
|
+
const { name: pipeName, args } = parsePipeExpression(parts[i]);
|
|
3609
|
+
const simpleFn = simplePipes[pipeName];
|
|
3610
|
+
if (simpleFn) {
|
|
3611
|
+
if (value != null && value !== "") {
|
|
3612
|
+
value = simpleFn(String(value));
|
|
3613
|
+
}
|
|
3614
|
+
} else {
|
|
3615
|
+
const argFn = pipesWithArgs[pipeName];
|
|
3616
|
+
if (argFn) {
|
|
3617
|
+
value = argFn(String(value ?? ""), ...args);
|
|
3092
3618
|
}
|
|
3093
3619
|
}
|
|
3094
|
-
return Promise.resolve(null);
|
|
3095
|
-
},
|
|
3096
|
-
create(data) {
|
|
3097
|
-
const tenantId = getTenantId();
|
|
3098
|
-
const profile = {
|
|
3099
|
-
id: generateId(),
|
|
3100
|
-
tenantId,
|
|
3101
|
-
authId: data.authId,
|
|
3102
|
-
email: data.email,
|
|
3103
|
-
firstName: data.firstName,
|
|
3104
|
-
lastName: data.lastName,
|
|
3105
|
-
avatarUrl: data.avatarUrl,
|
|
3106
|
-
role: data.role ?? "member",
|
|
3107
|
-
status: data.status ?? "active",
|
|
3108
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
3109
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
3110
|
-
};
|
|
3111
|
-
stores.userProfiles.set(profile.id, profile);
|
|
3112
|
-
return Promise.resolve(profile);
|
|
3113
|
-
},
|
|
3114
|
-
update(id, data) {
|
|
3115
|
-
const existing = stores.userProfiles.get(id);
|
|
3116
|
-
if (!existing) {
|
|
3117
|
-
return Promise.reject(new Error(`UserProfile ${id} not found`));
|
|
3118
|
-
}
|
|
3119
|
-
const updated = {
|
|
3120
|
-
...existing,
|
|
3121
|
-
...data,
|
|
3122
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
3123
|
-
};
|
|
3124
|
-
stores.userProfiles.set(id, updated);
|
|
3125
|
-
return Promise.resolve(updated);
|
|
3126
|
-
},
|
|
3127
|
-
delete(id) {
|
|
3128
|
-
stores.userProfiles.delete(id);
|
|
3129
|
-
return Promise.resolve();
|
|
3130
|
-
},
|
|
3131
|
-
list(options) {
|
|
3132
|
-
const tenantId = getTenantId();
|
|
3133
|
-
let results = Array.from(stores.userProfiles.values()).filter((p) => p.tenantId === tenantId);
|
|
3134
|
-
if (options?.limit) {
|
|
3135
|
-
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
3136
|
-
}
|
|
3137
|
-
return Promise.resolve(results);
|
|
3138
|
-
},
|
|
3139
|
-
countByRole(role) {
|
|
3140
|
-
const tenantId = getTenantId();
|
|
3141
|
-
const count = Array.from(stores.userProfiles.values()).filter(
|
|
3142
|
-
(profile) => profile.tenantId === tenantId && profile.role === role
|
|
3143
|
-
).length;
|
|
3144
|
-
return Promise.resolve(count);
|
|
3145
|
-
},
|
|
3146
|
-
updateLastLogin(id) {
|
|
3147
|
-
const profile = stores.userProfiles.get(id);
|
|
3148
|
-
if (profile) {
|
|
3149
|
-
profile.lastLoginAt = /* @__PURE__ */ new Date();
|
|
3150
|
-
stores.userProfiles.set(id, profile);
|
|
3151
|
-
}
|
|
3152
|
-
return Promise.resolve();
|
|
3153
|
-
},
|
|
3154
|
-
invite(data) {
|
|
3155
|
-
const tenantId = getTenantId();
|
|
3156
|
-
const profile = {
|
|
3157
|
-
id: generateId(),
|
|
3158
|
-
tenantId,
|
|
3159
|
-
authId: `invited-${generateId()}`,
|
|
3160
|
-
email: data.email,
|
|
3161
|
-
firstName: data.firstName,
|
|
3162
|
-
lastName: data.lastName,
|
|
3163
|
-
role: data.role ?? "member",
|
|
3164
|
-
status: "pending",
|
|
3165
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
3166
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
3167
|
-
};
|
|
3168
|
-
stores.userProfiles.set(profile.id, profile);
|
|
3169
|
-
return Promise.resolve(profile);
|
|
3170
3620
|
}
|
|
3171
|
-
|
|
3621
|
+
return String(value ?? "");
|
|
3622
|
+
}).trim();
|
|
3623
|
+
return result || fallback;
|
|
3172
3624
|
}
|
|
3173
|
-
function
|
|
3174
|
-
return {
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
const tenantId = getTenantId();
|
|
3186
|
-
const file2 = {
|
|
3187
|
-
id: generateId(),
|
|
3188
|
-
tenantId,
|
|
3189
|
-
name: data.name,
|
|
3190
|
-
originalName: data.originalName,
|
|
3191
|
-
mimeType: data.mimeType,
|
|
3192
|
-
size: data.size,
|
|
3193
|
-
storageProvider: data.storageProvider,
|
|
3194
|
-
storagePath: data.storagePath,
|
|
3195
|
-
storageBucket: data.storageBucket,
|
|
3196
|
-
url: data.url,
|
|
3197
|
-
uploadedBy: data.uploadedBy,
|
|
3198
|
-
folderPath: data.folderPath,
|
|
3199
|
-
tags: data.tags,
|
|
3200
|
-
visibility: data.visibility ?? "private",
|
|
3201
|
-
allowedUsers: data.allowedUsers,
|
|
3202
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
3203
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
3204
|
-
};
|
|
3205
|
-
stores.files.set(file2.id, file2);
|
|
3206
|
-
return Promise.resolve(file2);
|
|
3207
|
-
},
|
|
3208
|
-
update(id, data) {
|
|
3209
|
-
const existing = stores.files.get(id);
|
|
3210
|
-
if (!existing) {
|
|
3211
|
-
return Promise.reject(new Error(`File ${id} not found`));
|
|
3212
|
-
}
|
|
3213
|
-
const updated = {
|
|
3214
|
-
...existing,
|
|
3215
|
-
...data,
|
|
3216
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
3217
|
-
};
|
|
3218
|
-
stores.files.set(id, updated);
|
|
3219
|
-
return Promise.resolve(updated);
|
|
3220
|
-
},
|
|
3221
|
-
delete(id) {
|
|
3222
|
-
const file2 = stores.files.get(id);
|
|
3223
|
-
if (file2) {
|
|
3224
|
-
file2.deletedAt = /* @__PURE__ */ new Date();
|
|
3225
|
-
stores.files.set(id, file2);
|
|
3226
|
-
}
|
|
3227
|
-
return Promise.resolve();
|
|
3228
|
-
},
|
|
3229
|
-
hardDelete(id) {
|
|
3230
|
-
stores.files.delete(id);
|
|
3231
|
-
return Promise.resolve();
|
|
3232
|
-
},
|
|
3233
|
-
list(options) {
|
|
3234
|
-
const tenantId = getTenantId();
|
|
3235
|
-
let results = Array.from(stores.files.values()).filter(
|
|
3236
|
-
(f) => f.tenantId === tenantId && !f.deletedAt
|
|
3237
|
-
);
|
|
3238
|
-
if (options?.mimeType) {
|
|
3239
|
-
results = results.filter((f) => f.mimeType === options.mimeType);
|
|
3240
|
-
}
|
|
3241
|
-
if (options?.limit) {
|
|
3242
|
-
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
3243
|
-
}
|
|
3244
|
-
return Promise.resolve(results);
|
|
3245
|
-
},
|
|
3246
|
-
findByFolder(folderPath) {
|
|
3247
|
-
const tenantId = getTenantId();
|
|
3248
|
-
const results = Array.from(stores.files.values()).filter(
|
|
3249
|
-
(f) => f.tenantId === tenantId && f.folderPath === folderPath && !f.deletedAt
|
|
3250
|
-
);
|
|
3251
|
-
return Promise.resolve(results);
|
|
3252
|
-
},
|
|
3253
|
-
findByUploader(uploadedBy) {
|
|
3254
|
-
const results = Array.from(stores.files.values()).filter(
|
|
3255
|
-
(f) => f.uploadedBy === uploadedBy && !f.deletedAt
|
|
3256
|
-
);
|
|
3257
|
-
return Promise.resolve(results);
|
|
3625
|
+
function isLabelExpression(value) {
|
|
3626
|
+
return /\{\{\s*\S+.*\}\}/.test(value);
|
|
3627
|
+
}
|
|
3628
|
+
function extractAttributeNames(template) {
|
|
3629
|
+
const names = [];
|
|
3630
|
+
const regex = /\{\{\s*([^|}]+)/g;
|
|
3631
|
+
let match;
|
|
3632
|
+
while ((match = regex.exec(template)) !== null) {
|
|
3633
|
+
const path = match[1].trim();
|
|
3634
|
+
const rootName = path.split(".")[0];
|
|
3635
|
+
if (rootName && !names.includes(rootName)) {
|
|
3636
|
+
names.push(rootName);
|
|
3258
3637
|
}
|
|
3259
|
-
}
|
|
3638
|
+
}
|
|
3639
|
+
return names;
|
|
3640
|
+
}
|
|
3641
|
+
function hasOptions(attr) {
|
|
3642
|
+
return "options" in attr && Array.isArray(attr.options) && attr.options.length > 0;
|
|
3643
|
+
}
|
|
3644
|
+
var FORMATTABLE_TYPES = /* @__PURE__ */ new Set([
|
|
3645
|
+
"currency",
|
|
3646
|
+
"location",
|
|
3647
|
+
"phone",
|
|
3648
|
+
"date",
|
|
3649
|
+
"rating",
|
|
3650
|
+
"select",
|
|
3651
|
+
"status",
|
|
3652
|
+
"multiselect",
|
|
3653
|
+
"number"
|
|
3654
|
+
]);
|
|
3655
|
+
function enrichValuesForDisplay(values, attributes) {
|
|
3656
|
+
const enriched = { ...values };
|
|
3657
|
+
for (const attr of attributes) {
|
|
3658
|
+
const value = values[attr.name];
|
|
3659
|
+
if (value == null) continue;
|
|
3660
|
+
if (!FORMATTABLE_TYPES.has(attr.type)) continue;
|
|
3661
|
+
const isSelectLike = attr.type === "select" || attr.type === "status" || attr.type === "multiselect";
|
|
3662
|
+
if (isSelectLike && !hasOptions(attr)) continue;
|
|
3663
|
+
if (attr.type === "multiselect" && Array.isArray(value) && value.length === 0) continue;
|
|
3664
|
+
const formatted = formatAttributeValue(value, attr);
|
|
3665
|
+
if (formatted && formatted !== EMPTY_VALUE_PLACEHOLDER) {
|
|
3666
|
+
enriched[attr.name] = formatted;
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
return enriched;
|
|
3670
|
+
}
|
|
3671
|
+
var enrichValuesWithSelectLabels = enrichValuesForDisplay;
|
|
3672
|
+
function extractRelationIds(val) {
|
|
3673
|
+
if (typeof val === "string") return [val];
|
|
3674
|
+
if (Array.isArray(val) && typeof val[0] === "string") return [val[0]];
|
|
3675
|
+
return [];
|
|
3676
|
+
}
|
|
3677
|
+
async function computeLabelWithRelations(template, values, attributes, resolveRelationIds) {
|
|
3678
|
+
let enrichedValues = enrichValuesForDisplay(values, attributes);
|
|
3679
|
+
const attrNames = extractAttributeNames(template);
|
|
3680
|
+
const relationAttrs = attributes.filter(
|
|
3681
|
+
(attr) => attr.type === "relation" && attrNames.includes(attr.name)
|
|
3682
|
+
);
|
|
3683
|
+
if (relationAttrs.length === 0) {
|
|
3684
|
+
return renderLabelExpression(template, enrichedValues);
|
|
3685
|
+
}
|
|
3686
|
+
const allIds = [];
|
|
3687
|
+
for (const attr of relationAttrs) {
|
|
3688
|
+
const ids = extractRelationIds(values[attr.name]);
|
|
3689
|
+
allIds.push(...ids);
|
|
3690
|
+
}
|
|
3691
|
+
if (allIds.length === 0) {
|
|
3692
|
+
return renderLabelExpression(template, enrichedValues);
|
|
3693
|
+
}
|
|
3694
|
+
const resolvedMap = await resolveRelationIds(allIds);
|
|
3695
|
+
enrichedValues = { ...enrichedValues };
|
|
3696
|
+
for (const attr of relationAttrs) {
|
|
3697
|
+
const ids = extractRelationIds(values[attr.name]);
|
|
3698
|
+
if (ids.length > 0 && resolvedMap.has(ids[0])) {
|
|
3699
|
+
enrichedValues[attr.name] = resolvedMap.get(ids[0]);
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
return renderLabelExpression(template, enrichedValues);
|
|
3260
3703
|
}
|
|
3704
|
+
|
|
3705
|
+
// src/runtime/mock/mock-object-records.ts
|
|
3261
3706
|
function createMockObjectRecordsRepository(stores) {
|
|
3262
3707
|
return {
|
|
3263
3708
|
hardDelete(id) {
|
|
@@ -3315,7 +3760,20 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
3315
3760
|
if (!existing) {
|
|
3316
3761
|
return Promise.reject(new Error(`ObjectRecord ${id} not found`));
|
|
3317
3762
|
}
|
|
3318
|
-
const {
|
|
3763
|
+
const {
|
|
3764
|
+
__completionStatus,
|
|
3765
|
+
__label,
|
|
3766
|
+
__metadata,
|
|
3767
|
+
__lastUpdatedBy,
|
|
3768
|
+
__expectedUpdatedAt,
|
|
3769
|
+
...valueData
|
|
3770
|
+
} = data;
|
|
3771
|
+
if (__expectedUpdatedAt) {
|
|
3772
|
+
const existingUpdatedAt = existing.updatedAt instanceof Date ? existing.updatedAt.toISOString() : String(existing.updatedAt);
|
|
3773
|
+
if (existingUpdatedAt !== __expectedUpdatedAt) {
|
|
3774
|
+
return Promise.reject(new ConcurrentModificationError(id));
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3319
3777
|
const updated = {
|
|
3320
3778
|
...existing,
|
|
3321
3779
|
label: __label ?? existing.label,
|
|
@@ -3532,107 +3990,138 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
3532
3990
|
}
|
|
3533
3991
|
};
|
|
3534
3992
|
}
|
|
3535
|
-
|
|
3993
|
+
|
|
3994
|
+
// src/runtime/mock/mock-stores.ts
|
|
3995
|
+
function createEmptyStores() {
|
|
3996
|
+
return {
|
|
3997
|
+
objects: /* @__PURE__ */ new Map(),
|
|
3998
|
+
attributes: /* @__PURE__ */ new Map(),
|
|
3999
|
+
userProfiles: /* @__PURE__ */ new Map(),
|
|
4000
|
+
files: /* @__PURE__ */ new Map(),
|
|
4001
|
+
objectRecords: /* @__PURE__ */ new Map(),
|
|
4002
|
+
views: /* @__PURE__ */ new Map(),
|
|
4003
|
+
roles: /* @__PURE__ */ new Map(),
|
|
4004
|
+
permissions: /* @__PURE__ */ new Map(),
|
|
4005
|
+
userRoles: /* @__PURE__ */ new Map(),
|
|
4006
|
+
workflows: /* @__PURE__ */ new Map(),
|
|
4007
|
+
workflowInstances: /* @__PURE__ */ new Map(),
|
|
4008
|
+
workflowInvitations: /* @__PURE__ */ new Map(),
|
|
4009
|
+
workflowAccessGrants: /* @__PURE__ */ new Map(),
|
|
4010
|
+
aiConversations: /* @__PURE__ */ new Map(),
|
|
4011
|
+
aiMessages: /* @__PURE__ */ new Map(),
|
|
4012
|
+
aiUserMemory: /* @__PURE__ */ new Map(),
|
|
4013
|
+
aiUsageMetrics: /* @__PURE__ */ new Map()
|
|
4014
|
+
};
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
// src/runtime/mock/mock-users.ts
|
|
4018
|
+
function createMockUserProfilesRepository(stores) {
|
|
3536
4019
|
return {
|
|
3537
4020
|
findById(id) {
|
|
3538
|
-
return Promise.resolve(stores.
|
|
4021
|
+
return Promise.resolve(stores.userProfiles.get(id) ?? null);
|
|
3539
4022
|
},
|
|
3540
|
-
|
|
4023
|
+
findByIds(ids) {
|
|
3541
4024
|
const tenantId = getTenantId();
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
)
|
|
3546
|
-
|
|
4025
|
+
const results = [];
|
|
4026
|
+
for (const id of ids) {
|
|
4027
|
+
const profile = stores.userProfiles.get(id);
|
|
4028
|
+
if (profile && profile.tenantId === tenantId) {
|
|
4029
|
+
results.push(profile);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
return Promise.resolve(results);
|
|
3547
4033
|
},
|
|
3548
|
-
|
|
3549
|
-
const
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
);
|
|
4034
|
+
findByAuthId(authId) {
|
|
4035
|
+
for (const profile of stores.userProfiles.values()) {
|
|
4036
|
+
if (profile.authId === authId) {
|
|
4037
|
+
return Promise.resolve(profile);
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
return Promise.resolve(null);
|
|
3555
4041
|
},
|
|
3556
|
-
|
|
4042
|
+
findByEmail(email) {
|
|
3557
4043
|
const tenantId = getTenantId();
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
return Promise.resolve(
|
|
3564
|
-
Array.from(stores.views.values()).find(
|
|
3565
|
-
(v) => v.objectName === objectName && v.name === viewName && v.system
|
|
3566
|
-
) ?? null
|
|
3567
|
-
);
|
|
3568
|
-
},
|
|
3569
|
-
findSystemByObjectName(objectName) {
|
|
3570
|
-
return Promise.resolve(
|
|
3571
|
-
Array.from(stores.views.values()).filter((v) => v.objectName === objectName && v.system)
|
|
3572
|
-
);
|
|
4044
|
+
for (const profile of stores.userProfiles.values()) {
|
|
4045
|
+
if (profile.tenantId === tenantId && profile.email === email) {
|
|
4046
|
+
return Promise.resolve(profile);
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
return Promise.resolve(null);
|
|
3573
4050
|
},
|
|
3574
4051
|
create(data) {
|
|
3575
4052
|
const tenantId = getTenantId();
|
|
3576
|
-
const
|
|
3577
|
-
|
|
3578
|
-
const dbView = {
|
|
3579
|
-
id,
|
|
4053
|
+
const profile = {
|
|
4054
|
+
id: generateId(),
|
|
3580
4055
|
tenantId,
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
createdAt: now,
|
|
3591
|
-
updatedAt: now
|
|
4056
|
+
authId: data.authId,
|
|
4057
|
+
email: data.email,
|
|
4058
|
+
firstName: data.firstName,
|
|
4059
|
+
lastName: data.lastName,
|
|
4060
|
+
avatarUrl: data.avatarUrl,
|
|
4061
|
+
role: data.role ?? "member",
|
|
4062
|
+
status: data.status ?? "active",
|
|
4063
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
4064
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3592
4065
|
};
|
|
3593
|
-
stores.
|
|
3594
|
-
return Promise.resolve(
|
|
4066
|
+
stores.userProfiles.set(profile.id, profile);
|
|
4067
|
+
return Promise.resolve(profile);
|
|
3595
4068
|
},
|
|
3596
4069
|
update(id, data) {
|
|
3597
|
-
const
|
|
3598
|
-
if (!
|
|
3599
|
-
return Promise.reject(new Error(`
|
|
4070
|
+
const existing = stores.userProfiles.get(id);
|
|
4071
|
+
if (!existing) {
|
|
4072
|
+
return Promise.reject(new Error(`UserProfile ${id} not found`));
|
|
3600
4073
|
}
|
|
3601
4074
|
const updated = {
|
|
3602
|
-
...
|
|
4075
|
+
...existing,
|
|
3603
4076
|
...data,
|
|
3604
4077
|
updatedAt: /* @__PURE__ */ new Date()
|
|
3605
4078
|
};
|
|
3606
|
-
stores.
|
|
4079
|
+
stores.userProfiles.set(id, updated);
|
|
3607
4080
|
return Promise.resolve(updated);
|
|
3608
4081
|
},
|
|
3609
4082
|
delete(id) {
|
|
3610
|
-
stores.
|
|
4083
|
+
stores.userProfiles.delete(id);
|
|
3611
4084
|
return Promise.resolve();
|
|
3612
4085
|
},
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
deleted++;
|
|
3619
|
-
}
|
|
4086
|
+
list(options) {
|
|
4087
|
+
const tenantId = getTenantId();
|
|
4088
|
+
let results = Array.from(stores.userProfiles.values()).filter((p) => p.tenantId === tenantId);
|
|
4089
|
+
if (options?.limit) {
|
|
4090
|
+
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
3620
4091
|
}
|
|
3621
|
-
return Promise.resolve(
|
|
4092
|
+
return Promise.resolve(results);
|
|
3622
4093
|
},
|
|
3623
|
-
|
|
3624
|
-
const
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
4094
|
+
countByRole(role) {
|
|
4095
|
+
const tenantId = getTenantId();
|
|
4096
|
+
const count = Array.from(stores.userProfiles.values()).filter(
|
|
4097
|
+
(profile) => profile.tenantId === tenantId && profile.role === role
|
|
4098
|
+
).length;
|
|
4099
|
+
return Promise.resolve(count);
|
|
4100
|
+
},
|
|
4101
|
+
updateLastLogin(id) {
|
|
4102
|
+
const profile = stores.userProfiles.get(id);
|
|
4103
|
+
if (profile) {
|
|
4104
|
+
profile.lastLoginAt = /* @__PURE__ */ new Date();
|
|
4105
|
+
stores.userProfiles.set(id, profile);
|
|
3634
4106
|
}
|
|
3635
|
-
return
|
|
4107
|
+
return Promise.resolve();
|
|
4108
|
+
},
|
|
4109
|
+
invite(data) {
|
|
4110
|
+
const tenantId = getTenantId();
|
|
4111
|
+
const profile = {
|
|
4112
|
+
id: generateId(),
|
|
4113
|
+
tenantId,
|
|
4114
|
+
authId: `invited-${generateId()}`,
|
|
4115
|
+
email: data.email,
|
|
4116
|
+
firstName: data.firstName,
|
|
4117
|
+
lastName: data.lastName,
|
|
4118
|
+
role: data.role ?? "member",
|
|
4119
|
+
status: "pending",
|
|
4120
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
4121
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
4122
|
+
};
|
|
4123
|
+
stores.userProfiles.set(profile.id, profile);
|
|
4124
|
+
return Promise.resolve(profile);
|
|
3636
4125
|
}
|
|
3637
4126
|
};
|
|
3638
4127
|
}
|
|
@@ -3789,10 +4278,118 @@ function createMockPermissionsRepository(stores) {
|
|
|
3789
4278
|
}
|
|
3790
4279
|
}
|
|
3791
4280
|
}
|
|
3792
|
-
return Promise.resolve({ isAdmin, objectPermissions, systemPermissions });
|
|
4281
|
+
return Promise.resolve({ isAdmin, objectPermissions, systemPermissions });
|
|
4282
|
+
}
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
// src/runtime/mock/mock-views.ts
|
|
4287
|
+
function createMockViewsRepository(stores) {
|
|
4288
|
+
return {
|
|
4289
|
+
findById(id) {
|
|
4290
|
+
return Promise.resolve(stores.views.get(id) ?? null);
|
|
4291
|
+
},
|
|
4292
|
+
findByName(objectName, viewName) {
|
|
4293
|
+
const tenantId = getTenantId();
|
|
4294
|
+
return Promise.resolve(
|
|
4295
|
+
Array.from(stores.views.values()).find(
|
|
4296
|
+
(v) => v.tenantId === tenantId && v.objectName === objectName && v.name === viewName
|
|
4297
|
+
) ?? null
|
|
4298
|
+
);
|
|
4299
|
+
},
|
|
4300
|
+
findByObjectName(objectName) {
|
|
4301
|
+
const tenantId = getTenantId();
|
|
4302
|
+
return Promise.resolve(
|
|
4303
|
+
Array.from(stores.views.values()).filter(
|
|
4304
|
+
(v) => v.tenantId === tenantId && v.objectName === objectName
|
|
4305
|
+
)
|
|
4306
|
+
);
|
|
4307
|
+
},
|
|
4308
|
+
findAllForTenant() {
|
|
4309
|
+
const tenantId = getTenantId();
|
|
4310
|
+
return Promise.resolve(
|
|
4311
|
+
Array.from(stores.views.values()).filter((v) => v.tenantId === tenantId)
|
|
4312
|
+
);
|
|
4313
|
+
},
|
|
4314
|
+
findSystemByName(objectName, viewName) {
|
|
4315
|
+
return Promise.resolve(
|
|
4316
|
+
Array.from(stores.views.values()).find(
|
|
4317
|
+
(v) => v.objectName === objectName && v.name === viewName && v.system
|
|
4318
|
+
) ?? null
|
|
4319
|
+
);
|
|
4320
|
+
},
|
|
4321
|
+
findSystemByObjectName(objectName) {
|
|
4322
|
+
return Promise.resolve(
|
|
4323
|
+
Array.from(stores.views.values()).filter((v) => v.objectName === objectName && v.system)
|
|
4324
|
+
);
|
|
4325
|
+
},
|
|
4326
|
+
create(data) {
|
|
4327
|
+
const tenantId = getTenantId();
|
|
4328
|
+
const id = generateId();
|
|
4329
|
+
const now = /* @__PURE__ */ new Date();
|
|
4330
|
+
const dbView = {
|
|
4331
|
+
id,
|
|
4332
|
+
tenantId,
|
|
4333
|
+
objectName: data.objectName,
|
|
4334
|
+
name: data.name,
|
|
4335
|
+
label: data.label,
|
|
4336
|
+
description: data.description,
|
|
4337
|
+
icon: data.icon,
|
|
4338
|
+
tabs: data.tabs,
|
|
4339
|
+
default: data.default ?? false,
|
|
4340
|
+
system: data.system ?? false,
|
|
4341
|
+
metadata: data.metadata,
|
|
4342
|
+
createdAt: now,
|
|
4343
|
+
updatedAt: now
|
|
4344
|
+
};
|
|
4345
|
+
stores.views.set(id, dbView);
|
|
4346
|
+
return Promise.resolve(dbView);
|
|
4347
|
+
},
|
|
4348
|
+
update(id, data) {
|
|
4349
|
+
const view2 = stores.views.get(id);
|
|
4350
|
+
if (!view2) {
|
|
4351
|
+
return Promise.reject(new Error(`View not found: ${id}`));
|
|
4352
|
+
}
|
|
4353
|
+
const updated = {
|
|
4354
|
+
...view2,
|
|
4355
|
+
...data,
|
|
4356
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
4357
|
+
};
|
|
4358
|
+
stores.views.set(id, updated);
|
|
4359
|
+
return Promise.resolve(updated);
|
|
4360
|
+
},
|
|
4361
|
+
delete(id) {
|
|
4362
|
+
stores.views.delete(id);
|
|
4363
|
+
return Promise.resolve();
|
|
4364
|
+
},
|
|
4365
|
+
deleteNotIn(objectName, keepViewNames) {
|
|
4366
|
+
let deleted = 0;
|
|
4367
|
+
for (const [id, view2] of stores.views.entries()) {
|
|
4368
|
+
if (view2.objectName === objectName && view2.system && !keepViewNames.includes(view2.name)) {
|
|
4369
|
+
stores.views.delete(id);
|
|
4370
|
+
deleted++;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
return Promise.resolve(deleted);
|
|
4374
|
+
},
|
|
4375
|
+
async upsert(data) {
|
|
4376
|
+
const existing = data.system ? await this.findSystemByName(data.objectName, data.name) : await this.findByName(data.objectName, data.name);
|
|
4377
|
+
if (existing) {
|
|
4378
|
+
return this.update(existing.id, {
|
|
4379
|
+
label: data.label,
|
|
4380
|
+
description: data.description,
|
|
4381
|
+
icon: data.icon,
|
|
4382
|
+
tabs: data.tabs,
|
|
4383
|
+
default: data.default,
|
|
4384
|
+
metadata: data.metadata
|
|
4385
|
+
});
|
|
4386
|
+
}
|
|
4387
|
+
return this.create(data);
|
|
3793
4388
|
}
|
|
3794
4389
|
};
|
|
3795
4390
|
}
|
|
4391
|
+
|
|
4392
|
+
// src/runtime/mock/mock-workflows.ts
|
|
3796
4393
|
function createMockWorkflowsRepository(stores) {
|
|
3797
4394
|
return {
|
|
3798
4395
|
findById(id) {
|
|
@@ -4038,433 +4635,138 @@ function createMockWorkflowInstancesRepository(stores) {
|
|
|
4038
4635
|
if (options?.offset !== void 0 || options?.limit !== void 0) {
|
|
4039
4636
|
const start = options?.offset ?? 0;
|
|
4040
4637
|
const end = options?.limit ? start + options.limit : void 0;
|
|
4041
|
-
results = results.slice(start, end);
|
|
4042
|
-
}
|
|
4043
|
-
return Promise.resolve({ instances: results, total });
|
|
4044
|
-
}
|
|
4045
|
-
};
|
|
4046
|
-
}
|
|
4047
|
-
function createMockWorkflowInvitationsRepository(stores) {
|
|
4048
|
-
return {
|
|
4049
|
-
findById(id) {
|
|
4050
|
-
const invitation = stores.workflowInvitations.get(id);
|
|
4051
|
-
if (!invitation) return Promise.resolve(null);
|
|
4052
|
-
const tenantId = getTenantId();
|
|
4053
|
-
if (invitation.tenant_id !== tenantId) return Promise.resolve(null);
|
|
4054
|
-
return Promise.resolve(invitation);
|
|
4055
|
-
},
|
|
4056
|
-
findByInstanceId(instanceId) {
|
|
4057
|
-
const tenantId = getTenantId();
|
|
4058
|
-
const results = Array.from(stores.workflowInvitations.values()).filter(
|
|
4059
|
-
(inv) => inv.tenant_id === tenantId && inv.instance_id === instanceId
|
|
4060
|
-
);
|
|
4061
|
-
return Promise.resolve(results);
|
|
4062
|
-
},
|
|
4063
|
-
findByEmail(email) {
|
|
4064
|
-
const tenantId = getTenantId();
|
|
4065
|
-
const results = Array.from(stores.workflowInvitations.values()).filter(
|
|
4066
|
-
(inv) => inv.tenant_id === tenantId && inv.recipient_email === email
|
|
4067
|
-
);
|
|
4068
|
-
return Promise.resolve(results);
|
|
4069
|
-
},
|
|
4070
|
-
create(data) {
|
|
4071
|
-
const tenantId = getTenantId();
|
|
4072
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4073
|
-
const invitation = {
|
|
4074
|
-
id: generateId(),
|
|
4075
|
-
tenant_id: tenantId,
|
|
4076
|
-
instance_id: data.instanceId,
|
|
4077
|
-
recipient_email: data.recipientEmail,
|
|
4078
|
-
recipient_name: data.recipientName ?? null,
|
|
4079
|
-
status: data.status ?? "pending",
|
|
4080
|
-
created_by: data.createdBy,
|
|
4081
|
-
created_at: now,
|
|
4082
|
-
accepted_at: null,
|
|
4083
|
-
expires_at: data.expiresAt.toISOString()
|
|
4084
|
-
};
|
|
4085
|
-
stores.workflowInvitations.set(invitation.id, invitation);
|
|
4086
|
-
return Promise.resolve(invitation);
|
|
4087
|
-
},
|
|
4088
|
-
update(id, data) {
|
|
4089
|
-
const existing = stores.workflowInvitations.get(id);
|
|
4090
|
-
if (!existing) {
|
|
4091
|
-
return Promise.reject(new Error(`WorkflowInvitation ${id} not found`));
|
|
4092
|
-
}
|
|
4093
|
-
const updated = {
|
|
4094
|
-
...existing,
|
|
4095
|
-
status: data.status ?? existing.status,
|
|
4096
|
-
accepted_at: data.acceptedAt !== void 0 ? data.acceptedAt?.toISOString() ?? null : existing.accepted_at,
|
|
4097
|
-
expires_at: data.expiresAt !== void 0 ? data.expiresAt.toISOString() : existing.expires_at
|
|
4098
|
-
};
|
|
4099
|
-
stores.workflowInvitations.set(id, updated);
|
|
4100
|
-
return Promise.resolve(updated);
|
|
4101
|
-
}
|
|
4102
|
-
};
|
|
4103
|
-
}
|
|
4104
|
-
function createMockWorkflowAccessGrantsRepository(stores) {
|
|
4105
|
-
return {
|
|
4106
|
-
findById(id) {
|
|
4107
|
-
const grant = stores.workflowAccessGrants.get(id);
|
|
4108
|
-
if (!grant) return Promise.resolve(null);
|
|
4109
|
-
const tenantId = getTenantId();
|
|
4110
|
-
if (grant.tenant_id !== tenantId) return Promise.resolve(null);
|
|
4111
|
-
return Promise.resolve(grant);
|
|
4112
|
-
},
|
|
4113
|
-
findByInvitationId(invitationId) {
|
|
4114
|
-
const tenantId = getTenantId();
|
|
4115
|
-
const results = Array.from(stores.workflowAccessGrants.values()).filter(
|
|
4116
|
-
(g) => g.tenant_id === tenantId && g.invitation_id === invitationId
|
|
4117
|
-
);
|
|
4118
|
-
return Promise.resolve(results);
|
|
4119
|
-
},
|
|
4120
|
-
findByInstanceId(instanceId) {
|
|
4121
|
-
const tenantId = getTenantId();
|
|
4122
|
-
const results = Array.from(stores.workflowAccessGrants.values()).filter(
|
|
4123
|
-
(g) => g.tenant_id === tenantId && g.instance_id === instanceId
|
|
4124
|
-
);
|
|
4125
|
-
return Promise.resolve(results);
|
|
4126
|
-
},
|
|
4127
|
-
findByEmail(email) {
|
|
4128
|
-
const tenantId = getTenantId();
|
|
4129
|
-
const results = Array.from(stores.workflowAccessGrants.values()).filter(
|
|
4130
|
-
(g) => g.tenant_id === tenantId && g.granted_to === email
|
|
4131
|
-
);
|
|
4132
|
-
return Promise.resolve(results);
|
|
4133
|
-
},
|
|
4134
|
-
create(data) {
|
|
4135
|
-
const tenantId = getTenantId();
|
|
4136
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4137
|
-
const grant = {
|
|
4138
|
-
id: generateId(),
|
|
4139
|
-
tenant_id: tenantId,
|
|
4140
|
-
invitation_id: data.invitationId,
|
|
4141
|
-
instance_id: data.instanceId,
|
|
4142
|
-
granted_to: data.grantedTo,
|
|
4143
|
-
scope: data.scope ?? ["*"],
|
|
4144
|
-
revoked_token_jtis: data.revokedTokenJtis ?? [],
|
|
4145
|
-
created_at: now,
|
|
4146
|
-
last_used_at: null,
|
|
4147
|
-
valid_until: data.validUntil.toISOString(),
|
|
4148
|
-
revoked_at: null
|
|
4149
|
-
};
|
|
4150
|
-
stores.workflowAccessGrants.set(grant.id, grant);
|
|
4151
|
-
return Promise.resolve(grant);
|
|
4152
|
-
},
|
|
4153
|
-
update(id, data) {
|
|
4154
|
-
const existing = stores.workflowAccessGrants.get(id);
|
|
4155
|
-
if (!existing) {
|
|
4156
|
-
return Promise.reject(new Error(`WorkflowAccessGrant ${id} not found`));
|
|
4157
|
-
}
|
|
4158
|
-
const updated = {
|
|
4159
|
-
...existing,
|
|
4160
|
-
last_used_at: data.lastUsedAt !== void 0 ? data.lastUsedAt.toISOString() : existing.last_used_at,
|
|
4161
|
-
revoked_token_jtis: data.revokedTokenJtis ?? existing.revoked_token_jtis,
|
|
4162
|
-
revoked_at: data.revokedAt !== void 0 ? data.revokedAt?.toISOString() ?? null : existing.revoked_at
|
|
4163
|
-
};
|
|
4164
|
-
stores.workflowAccessGrants.set(id, updated);
|
|
4165
|
-
return Promise.resolve(updated);
|
|
4166
|
-
}
|
|
4167
|
-
};
|
|
4168
|
-
}
|
|
4169
|
-
function requireUserId() {
|
|
4170
|
-
const userId = getUserId();
|
|
4171
|
-
if (!userId) {
|
|
4172
|
-
throw new Error("User context required for AI operations");
|
|
4173
|
-
}
|
|
4174
|
-
return userId;
|
|
4175
|
-
}
|
|
4176
|
-
function createMockAIConversationsRepository(stores) {
|
|
4177
|
-
return {
|
|
4178
|
-
findById(id) {
|
|
4179
|
-
const conversation = stores.aiConversations.get(id);
|
|
4180
|
-
if (!conversation || conversation.deletedAt) return Promise.resolve(null);
|
|
4181
|
-
const tenantId = getTenantId();
|
|
4182
|
-
if (conversation.tenantId !== tenantId) return Promise.resolve(null);
|
|
4183
|
-
return Promise.resolve(conversation);
|
|
4184
|
-
},
|
|
4185
|
-
list(options) {
|
|
4186
|
-
const tenantId = getTenantId();
|
|
4187
|
-
const userId = requireUserId();
|
|
4188
|
-
let results = Array.from(stores.aiConversations.values()).filter((c) => {
|
|
4189
|
-
if (c.tenantId !== tenantId || c.userId !== userId) return false;
|
|
4190
|
-
if (!options?.includeDeleted && c.deletedAt) return false;
|
|
4191
|
-
return true;
|
|
4192
|
-
});
|
|
4193
|
-
results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
4194
|
-
const total = results.length;
|
|
4195
|
-
if (options?.limit) {
|
|
4196
|
-
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
4197
|
-
}
|
|
4198
|
-
return Promise.resolve({ conversations: results, total });
|
|
4199
|
-
},
|
|
4200
|
-
create(data) {
|
|
4201
|
-
const tenantId = getTenantId();
|
|
4202
|
-
const userId = requireUserId();
|
|
4203
|
-
const now = /* @__PURE__ */ new Date();
|
|
4204
|
-
const conversation = {
|
|
4205
|
-
id: generateId(),
|
|
4206
|
-
tenantId,
|
|
4207
|
-
userId,
|
|
4208
|
-
title: data.title ?? null,
|
|
4209
|
-
messageCount: 0,
|
|
4210
|
-
totalTokens: 0,
|
|
4211
|
-
totalCost: 0,
|
|
4212
|
-
createdAt: now,
|
|
4213
|
-
updatedAt: now,
|
|
4214
|
-
deletedAt: null
|
|
4215
|
-
};
|
|
4216
|
-
stores.aiConversations.set(conversation.id, conversation);
|
|
4217
|
-
return Promise.resolve(conversation);
|
|
4218
|
-
},
|
|
4219
|
-
updateTitle(id, title) {
|
|
4220
|
-
const conversation = stores.aiConversations.get(id);
|
|
4221
|
-
if (!conversation || conversation.deletedAt) return Promise.resolve(null);
|
|
4222
|
-
const tenantId = getTenantId();
|
|
4223
|
-
if (conversation.tenantId !== tenantId) return Promise.resolve(null);
|
|
4224
|
-
conversation.title = title;
|
|
4225
|
-
conversation.updatedAt = /* @__PURE__ */ new Date();
|
|
4226
|
-
stores.aiConversations.set(id, conversation);
|
|
4227
|
-
return Promise.resolve(conversation);
|
|
4228
|
-
},
|
|
4229
|
-
delete(id) {
|
|
4230
|
-
const conversation = stores.aiConversations.get(id);
|
|
4231
|
-
if (!conversation) return Promise.resolve(false);
|
|
4232
|
-
const tenantId = getTenantId();
|
|
4233
|
-
if (conversation.tenantId !== tenantId) return Promise.resolve(false);
|
|
4234
|
-
conversation.deletedAt = /* @__PURE__ */ new Date();
|
|
4235
|
-
stores.aiConversations.set(id, conversation);
|
|
4236
|
-
return Promise.resolve(true);
|
|
4237
|
-
},
|
|
4238
|
-
addMessage(input) {
|
|
4239
|
-
const now = /* @__PURE__ */ new Date();
|
|
4240
|
-
const message = {
|
|
4241
|
-
id: generateId(),
|
|
4242
|
-
conversationId: input.conversationId,
|
|
4243
|
-
role: input.role,
|
|
4244
|
-
content: input.content,
|
|
4245
|
-
thinkingLevel: input.thinkingLevel ?? null,
|
|
4246
|
-
thinkingSummary: input.thinkingSummary ?? null,
|
|
4247
|
-
toolCalls: input.toolCalls ?? null,
|
|
4248
|
-
inputTokens: input.inputTokens ?? null,
|
|
4249
|
-
outputTokens: input.outputTokens ?? null,
|
|
4250
|
-
cost: input.cost ?? null,
|
|
4251
|
-
provider: input.provider ?? null,
|
|
4252
|
-
model: input.model ?? null,
|
|
4253
|
-
attachmentIds: input.attachmentIds ?? null,
|
|
4254
|
-
createdAt: now
|
|
4255
|
-
};
|
|
4256
|
-
stores.aiMessages.set(message.id, message);
|
|
4257
|
-
const conversation = stores.aiConversations.get(input.conversationId);
|
|
4258
|
-
if (conversation) {
|
|
4259
|
-
conversation.messageCount++;
|
|
4260
|
-
conversation.totalTokens += (input.inputTokens ?? 0) + (input.outputTokens ?? 0);
|
|
4261
|
-
conversation.totalCost += input.cost ?? 0;
|
|
4262
|
-
conversation.updatedAt = now;
|
|
4263
|
-
stores.aiConversations.set(input.conversationId, conversation);
|
|
4264
|
-
}
|
|
4265
|
-
return Promise.resolve(message);
|
|
4266
|
-
},
|
|
4267
|
-
listMessages(conversationId, options) {
|
|
4268
|
-
let results = Array.from(stores.aiMessages.values()).filter((m) => m.conversationId === conversationId).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
|
4269
|
-
const total = results.length;
|
|
4270
|
-
if (options?.limit) {
|
|
4271
|
-
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
4272
|
-
}
|
|
4273
|
-
return Promise.resolve({ messages: results, total });
|
|
4274
|
-
},
|
|
4275
|
-
getRecentMessages(conversationId, count = 20) {
|
|
4276
|
-
const results = Array.from(stores.aiMessages.values()).filter((m) => m.conversationId === conversationId).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).slice(-count);
|
|
4277
|
-
return Promise.resolve(results);
|
|
4638
|
+
results = results.slice(start, end);
|
|
4639
|
+
}
|
|
4640
|
+
return Promise.resolve({ instances: results, total });
|
|
4278
4641
|
}
|
|
4279
4642
|
};
|
|
4280
4643
|
}
|
|
4281
|
-
function
|
|
4282
|
-
const getKey = () => {
|
|
4283
|
-
const tenantId = getTenantId();
|
|
4284
|
-
const userId = requireUserId();
|
|
4285
|
-
return `${tenantId}:${userId}`;
|
|
4286
|
-
};
|
|
4644
|
+
function createMockWorkflowInvitationsRepository(stores) {
|
|
4287
4645
|
return {
|
|
4288
|
-
|
|
4289
|
-
const
|
|
4290
|
-
return Promise.resolve(
|
|
4291
|
-
},
|
|
4292
|
-
upsert(data) {
|
|
4293
|
-
const key = getKey();
|
|
4646
|
+
findById(id) {
|
|
4647
|
+
const invitation = stores.workflowInvitations.get(id);
|
|
4648
|
+
if (!invitation) return Promise.resolve(null);
|
|
4294
4649
|
const tenantId = getTenantId();
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
const existing = stores.aiUserMemory.get(key);
|
|
4298
|
-
const memory = {
|
|
4299
|
-
id: existing?.id ?? generateId(),
|
|
4300
|
-
tenantId,
|
|
4301
|
-
userId,
|
|
4302
|
-
preferences: data.preferences ?? existing?.preferences ?? {},
|
|
4303
|
-
facts: data.facts ?? existing?.facts ?? [],
|
|
4304
|
-
createdAt: existing?.createdAt ?? now,
|
|
4305
|
-
updatedAt: now
|
|
4306
|
-
};
|
|
4307
|
-
stores.aiUserMemory.set(key, memory);
|
|
4308
|
-
return Promise.resolve(memory);
|
|
4650
|
+
if (invitation.tenant_id !== tenantId) return Promise.resolve(null);
|
|
4651
|
+
return Promise.resolve(invitation);
|
|
4309
4652
|
},
|
|
4310
|
-
|
|
4311
|
-
const key = getKey();
|
|
4653
|
+
findByInstanceId(instanceId) {
|
|
4312
4654
|
const tenantId = getTenantId();
|
|
4313
|
-
const
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
id: existing?.id ?? generateId(),
|
|
4318
|
-
tenantId,
|
|
4319
|
-
userId,
|
|
4320
|
-
preferences: existing?.preferences ?? {},
|
|
4321
|
-
facts: [...existing?.facts ?? [], fact],
|
|
4322
|
-
createdAt: existing?.createdAt ?? now,
|
|
4323
|
-
updatedAt: now
|
|
4324
|
-
};
|
|
4325
|
-
stores.aiUserMemory.set(key, memory);
|
|
4326
|
-
return Promise.resolve(memory);
|
|
4655
|
+
const results = Array.from(stores.workflowInvitations.values()).filter(
|
|
4656
|
+
(inv) => inv.tenant_id === tenantId && inv.instance_id === instanceId
|
|
4657
|
+
);
|
|
4658
|
+
return Promise.resolve(results);
|
|
4327
4659
|
},
|
|
4328
|
-
|
|
4329
|
-
const key = getKey();
|
|
4660
|
+
findByEmail(email) {
|
|
4330
4661
|
const tenantId = getTenantId();
|
|
4331
|
-
const
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
id: existing?.id ?? generateId(),
|
|
4336
|
-
tenantId,
|
|
4337
|
-
userId,
|
|
4338
|
-
preferences: existing?.preferences ?? {},
|
|
4339
|
-
facts: (existing?.facts ?? []).filter((f) => f !== fact),
|
|
4340
|
-
createdAt: existing?.createdAt ?? now,
|
|
4341
|
-
updatedAt: now
|
|
4342
|
-
};
|
|
4343
|
-
stores.aiUserMemory.set(key, memory);
|
|
4344
|
-
return Promise.resolve(memory);
|
|
4662
|
+
const results = Array.from(stores.workflowInvitations.values()).filter(
|
|
4663
|
+
(inv) => inv.tenant_id === tenantId && inv.recipient_email === email
|
|
4664
|
+
);
|
|
4665
|
+
return Promise.resolve(results);
|
|
4345
4666
|
},
|
|
4346
|
-
|
|
4347
|
-
const memoryKey = getKey();
|
|
4667
|
+
create(data) {
|
|
4348
4668
|
const tenantId = getTenantId();
|
|
4349
|
-
const
|
|
4350
|
-
const
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4669
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4670
|
+
const invitation = {
|
|
4671
|
+
id: generateId(),
|
|
4672
|
+
tenant_id: tenantId,
|
|
4673
|
+
instance_id: data.instanceId,
|
|
4674
|
+
recipient_email: data.recipientEmail,
|
|
4675
|
+
recipient_name: data.recipientName ?? null,
|
|
4676
|
+
status: data.status ?? "pending",
|
|
4677
|
+
created_by: data.createdBy,
|
|
4678
|
+
created_at: now,
|
|
4679
|
+
accepted_at: null,
|
|
4680
|
+
expires_at: data.expiresAt.toISOString()
|
|
4360
4681
|
};
|
|
4361
|
-
stores.
|
|
4362
|
-
return Promise.resolve(
|
|
4682
|
+
stores.workflowInvitations.set(invitation.id, invitation);
|
|
4683
|
+
return Promise.resolve(invitation);
|
|
4363
4684
|
},
|
|
4364
|
-
|
|
4365
|
-
const
|
|
4366
|
-
|
|
4367
|
-
|
|
4685
|
+
update(id, data) {
|
|
4686
|
+
const existing = stores.workflowInvitations.get(id);
|
|
4687
|
+
if (!existing) {
|
|
4688
|
+
return Promise.reject(new Error(`WorkflowInvitation ${id} not found`));
|
|
4689
|
+
}
|
|
4690
|
+
const updated = {
|
|
4691
|
+
...existing,
|
|
4692
|
+
status: data.status ?? existing.status,
|
|
4693
|
+
accepted_at: data.acceptedAt !== void 0 ? data.acceptedAt?.toISOString() ?? null : existing.accepted_at,
|
|
4694
|
+
expires_at: data.expiresAt !== void 0 ? data.expiresAt.toISOString() : existing.expires_at
|
|
4695
|
+
};
|
|
4696
|
+
stores.workflowInvitations.set(id, updated);
|
|
4697
|
+
return Promise.resolve(updated);
|
|
4368
4698
|
}
|
|
4369
4699
|
};
|
|
4370
4700
|
}
|
|
4371
|
-
function
|
|
4372
|
-
const getDateKey = (date2) => {
|
|
4373
|
-
const tenantId = getTenantId();
|
|
4374
|
-
const dateStr = date2.toISOString().split("T")[0];
|
|
4375
|
-
return `${tenantId}:${dateStr}`;
|
|
4376
|
-
};
|
|
4701
|
+
function createMockWorkflowAccessGrantsRepository(stores) {
|
|
4377
4702
|
return {
|
|
4378
|
-
|
|
4703
|
+
findById(id) {
|
|
4704
|
+
const grant = stores.workflowAccessGrants.get(id);
|
|
4705
|
+
if (!grant) return Promise.resolve(null);
|
|
4379
4706
|
const tenantId = getTenantId();
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
const existing = stores.aiUsageMetrics.get(key);
|
|
4383
|
-
const providerBreakdown = existing?.providerBreakdown ?? {};
|
|
4384
|
-
if (!providerBreakdown[data.provider]) {
|
|
4385
|
-
providerBreakdown[data.provider] = { requests: 0, tokens: 0, cost: 0 };
|
|
4386
|
-
}
|
|
4387
|
-
providerBreakdown[data.provider].requests++;
|
|
4388
|
-
providerBreakdown[data.provider].tokens += data.tokens;
|
|
4389
|
-
providerBreakdown[data.provider].cost += data.cost;
|
|
4390
|
-
const toolUsage = existing?.toolUsage ?? {};
|
|
4391
|
-
if (data.toolName) {
|
|
4392
|
-
toolUsage[data.toolName] = (toolUsage[data.toolName] ?? 0) + 1;
|
|
4393
|
-
}
|
|
4394
|
-
const metrics = {
|
|
4395
|
-
id: existing?.id ?? generateId(),
|
|
4396
|
-
tenantId,
|
|
4397
|
-
date: new Date(now.toISOString().split("T")[0] ?? now.toISOString()),
|
|
4398
|
-
requestCount: (existing?.requestCount ?? 0) + 1,
|
|
4399
|
-
totalTokens: (existing?.totalTokens ?? 0) + data.tokens,
|
|
4400
|
-
totalCost: (existing?.totalCost ?? 0) + data.cost,
|
|
4401
|
-
providerBreakdown,
|
|
4402
|
-
toolUsage
|
|
4403
|
-
};
|
|
4404
|
-
stores.aiUsageMetrics.set(key, metrics);
|
|
4405
|
-
return Promise.resolve();
|
|
4707
|
+
if (grant.tenant_id !== tenantId) return Promise.resolve(null);
|
|
4708
|
+
return Promise.resolve(grant);
|
|
4406
4709
|
},
|
|
4407
|
-
|
|
4710
|
+
findByInvitationId(invitationId) {
|
|
4408
4711
|
const tenantId = getTenantId();
|
|
4409
|
-
const results = Array.from(stores.
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
});
|
|
4413
|
-
results.sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
4712
|
+
const results = Array.from(stores.workflowAccessGrants.values()).filter(
|
|
4713
|
+
(g) => g.tenant_id === tenantId && g.invitation_id === invitationId
|
|
4714
|
+
);
|
|
4414
4715
|
return Promise.resolve(results);
|
|
4415
4716
|
},
|
|
4416
|
-
|
|
4717
|
+
findByInstanceId(instanceId) {
|
|
4417
4718
|
const tenantId = getTenantId();
|
|
4418
|
-
const
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
const
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4719
|
+
const results = Array.from(stores.workflowAccessGrants.values()).filter(
|
|
4720
|
+
(g) => g.tenant_id === tenantId && g.instance_id === instanceId
|
|
4721
|
+
);
|
|
4722
|
+
return Promise.resolve(results);
|
|
4723
|
+
},
|
|
4724
|
+
findByEmail(email) {
|
|
4725
|
+
const tenantId = getTenantId();
|
|
4726
|
+
const results = Array.from(stores.workflowAccessGrants.values()).filter(
|
|
4727
|
+
(g) => g.tenant_id === tenantId && g.granted_to === email
|
|
4728
|
+
);
|
|
4729
|
+
return Promise.resolve(results);
|
|
4730
|
+
},
|
|
4731
|
+
create(data) {
|
|
4732
|
+
const tenantId = getTenantId();
|
|
4733
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4734
|
+
const grant = {
|
|
4735
|
+
id: generateId(),
|
|
4736
|
+
tenant_id: tenantId,
|
|
4737
|
+
invitation_id: data.invitationId,
|
|
4738
|
+
instance_id: data.instanceId,
|
|
4739
|
+
granted_to: data.grantedTo,
|
|
4740
|
+
scope: data.scope ?? ["*"],
|
|
4741
|
+
revoked_token_jtis: data.revokedTokenJtis ?? [],
|
|
4742
|
+
created_at: now,
|
|
4743
|
+
last_used_at: null,
|
|
4744
|
+
valid_until: data.validUntil.toISOString(),
|
|
4745
|
+
revoked_at: null
|
|
4429
4746
|
};
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
}
|
|
4438
|
-
aggregated.providerBreakdown[provider].requests += stats.requests;
|
|
4439
|
-
aggregated.providerBreakdown[provider].tokens += stats.tokens;
|
|
4440
|
-
aggregated.providerBreakdown[provider].cost += stats.cost;
|
|
4441
|
-
}
|
|
4747
|
+
stores.workflowAccessGrants.set(grant.id, grant);
|
|
4748
|
+
return Promise.resolve(grant);
|
|
4749
|
+
},
|
|
4750
|
+
update(id, data) {
|
|
4751
|
+
const existing = stores.workflowAccessGrants.get(id);
|
|
4752
|
+
if (!existing) {
|
|
4753
|
+
return Promise.reject(new Error(`WorkflowAccessGrant ${id} not found`));
|
|
4442
4754
|
}
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
files: /* @__PURE__ */ new Map(),
|
|
4453
|
-
objectRecords: /* @__PURE__ */ new Map(),
|
|
4454
|
-
views: /* @__PURE__ */ new Map(),
|
|
4455
|
-
roles: /* @__PURE__ */ new Map(),
|
|
4456
|
-
permissions: /* @__PURE__ */ new Map(),
|
|
4457
|
-
userRoles: /* @__PURE__ */ new Map(),
|
|
4458
|
-
workflows: /* @__PURE__ */ new Map(),
|
|
4459
|
-
workflowInstances: /* @__PURE__ */ new Map(),
|
|
4460
|
-
workflowInvitations: /* @__PURE__ */ new Map(),
|
|
4461
|
-
workflowAccessGrants: /* @__PURE__ */ new Map(),
|
|
4462
|
-
// AI stores
|
|
4463
|
-
aiConversations: /* @__PURE__ */ new Map(),
|
|
4464
|
-
aiMessages: /* @__PURE__ */ new Map(),
|
|
4465
|
-
aiUserMemory: /* @__PURE__ */ new Map(),
|
|
4466
|
-
aiUsageMetrics: /* @__PURE__ */ new Map()
|
|
4755
|
+
const updated = {
|
|
4756
|
+
...existing,
|
|
4757
|
+
last_used_at: data.lastUsedAt !== void 0 ? data.lastUsedAt.toISOString() : existing.last_used_at,
|
|
4758
|
+
revoked_token_jtis: data.revokedTokenJtis ?? existing.revoked_token_jtis,
|
|
4759
|
+
revoked_at: data.revokedAt !== void 0 ? data.revokedAt?.toISOString() ?? null : existing.revoked_at
|
|
4760
|
+
};
|
|
4761
|
+
stores.workflowAccessGrants.set(id, updated);
|
|
4762
|
+
return Promise.resolve(updated);
|
|
4763
|
+
}
|
|
4467
4764
|
};
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
// src/runtime/mock/mock-adapter.ts
|
|
4768
|
+
function createMockAdapter() {
|
|
4769
|
+
const stores = createEmptyStores();
|
|
4468
4770
|
const adapter = {
|
|
4469
4771
|
objects: createMockObjectsRepository(stores),
|
|
4470
4772
|
attributes: createMockAttributesRepository(stores),
|
|
@@ -4871,8 +5173,6 @@ var SchemaContextAwareRepository = class extends BaseRepository {
|
|
|
4871
5173
|
return getSchemaByNameFromContext(objectName);
|
|
4872
5174
|
}
|
|
4873
5175
|
};
|
|
4874
|
-
var TenantAwareRepository = BaseRepository;
|
|
4875
|
-
var TenantAwareService = BaseService;
|
|
4876
5176
|
|
|
4877
5177
|
// src/types/attributes.ts
|
|
4878
5178
|
var RELATION_TARGET_ANY = "*";
|
|
@@ -7306,6 +7606,15 @@ function isSystemAttributeObject(attr) {
|
|
|
7306
7606
|
|
|
7307
7607
|
// src/validation/validators.ts
|
|
7308
7608
|
import { z as z5 } from "zod";
|
|
7609
|
+
var regexPatternCache = /* @__PURE__ */ new Map();
|
|
7610
|
+
function getCachedRegex(pattern) {
|
|
7611
|
+
let cached = regexPatternCache.get(pattern);
|
|
7612
|
+
if (!cached) {
|
|
7613
|
+
cached = new RegExp(pattern);
|
|
7614
|
+
regexPatternCache.set(pattern, cached);
|
|
7615
|
+
}
|
|
7616
|
+
return cached;
|
|
7617
|
+
}
|
|
7309
7618
|
var DEFAULT_VALIDATION_MESSAGES = {
|
|
7310
7619
|
required: (attr) => `${attr.label} is required`,
|
|
7311
7620
|
invalidType: (attr, expected) => `${attr.label} must be a ${expected}`,
|
|
@@ -7527,7 +7836,7 @@ function createTextValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
|
|
|
7527
7836
|
schema = schema.max(attr.maxLength, messages.maxLength(attr, attr.maxLength));
|
|
7528
7837
|
}
|
|
7529
7838
|
if (attr.pattern) {
|
|
7530
|
-
schema = schema.regex(
|
|
7839
|
+
schema = schema.regex(getCachedRegex(attr.pattern), messages.invalidPattern(attr));
|
|
7531
7840
|
}
|
|
7532
7841
|
return schema;
|
|
7533
7842
|
}
|
|
@@ -7740,7 +8049,7 @@ function createObjectValidator(objectDef) {
|
|
|
7740
8049
|
const validator = createAttributeValidator(attr);
|
|
7741
8050
|
shape[attr.name] = attr.required ? validator : withEmptyToNull(validator);
|
|
7742
8051
|
}
|
|
7743
|
-
return z5.object(shape).
|
|
8052
|
+
return z5.object(shape).passthrough();
|
|
7744
8053
|
}
|
|
7745
8054
|
function validateAttribute(attr, value) {
|
|
7746
8055
|
const validator = createAttributeValidator(attr);
|
|
@@ -7794,7 +8103,7 @@ function createDraftValidator(objectDef) {
|
|
|
7794
8103
|
const validator = createAttributeValidator(attr);
|
|
7795
8104
|
shape[attr.name] = withEmptyToNull(validator);
|
|
7796
8105
|
}
|
|
7797
|
-
return z5.object(shape).
|
|
8106
|
+
return z5.object(shape).passthrough();
|
|
7798
8107
|
}
|
|
7799
8108
|
function validateDraft(objectDef, data) {
|
|
7800
8109
|
const validator = createDraftValidator(objectDef);
|
|
@@ -8362,15 +8671,17 @@ Reserved names: ${RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
8362
8671
|
* @param objectId - Object UUID
|
|
8363
8672
|
* @returns Object ownership info with tenantId and sharingMode
|
|
8364
8673
|
*/
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8674
|
+
getObjectOwnerInfo(objectId) {
|
|
8675
|
+
return this.cachedBy("objectOwnerInfo", objectId, async () => {
|
|
8676
|
+
const dbObject = await this.adapter.objects.findById(objectId);
|
|
8677
|
+
if (!dbObject) {
|
|
8678
|
+
throw new Error(`Object with id "${objectId}" not found`);
|
|
8679
|
+
}
|
|
8680
|
+
return {
|
|
8681
|
+
tenantId: dbObject.tenantId,
|
|
8682
|
+
sharingMode: dbObject.sharingMode
|
|
8683
|
+
};
|
|
8684
|
+
});
|
|
8374
8685
|
}
|
|
8375
8686
|
/**
|
|
8376
8687
|
* Invalidate all schema-related cache for the current tenant.
|
|
@@ -8537,252 +8848,52 @@ Reserved names: ${RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
8537
8848
|
const exists = await this.objectExists(target.object);
|
|
8538
8849
|
if (!exists) {
|
|
8539
8850
|
throw new Error(
|
|
8540
|
-
`Relation target object "${target.object}" does not exist. Make sure the object is created before adding a relation to it.`
|
|
8541
|
-
);
|
|
8542
|
-
}
|
|
8543
|
-
}
|
|
8544
|
-
}
|
|
8545
|
-
/**
|
|
8546
|
-
* Find objects that have a relation attribute targeting the given object.
|
|
8547
|
-
* Used to prevent deletion of objects that are relation targets.
|
|
8548
|
-
* Automatically uses tenant context from AsyncLocalStorage.
|
|
8549
|
-
* @internal
|
|
8550
|
-
*/
|
|
8551
|
-
async findObjectsWithRelationTo(targetObjectName) {
|
|
8552
|
-
const allObjects = await this.adapter.objects.list();
|
|
8553
|
-
const referencing = [];
|
|
8554
|
-
for (const obj of allObjects) {
|
|
8555
|
-
if (obj.name === targetObjectName) continue;
|
|
8556
|
-
const attrs = await this.adapter.attributes.findByObjectId(obj.id);
|
|
8557
|
-
const hasRelationToTarget = attrs.some((attr) => {
|
|
8558
|
-
if (attr.type !== "relation") return false;
|
|
8559
|
-
const config = attr.config;
|
|
8560
|
-
return config?.targets?.some((t) => t.object === targetObjectName) ?? false;
|
|
8561
|
-
});
|
|
8562
|
-
if (hasRelationToTarget) {
|
|
8563
|
-
referencing.push(obj.name);
|
|
8564
|
-
}
|
|
8565
|
-
}
|
|
8566
|
-
return referencing;
|
|
8567
|
-
}
|
|
8568
|
-
/**
|
|
8569
|
-
* Convert DB attribute to Attribute type
|
|
8570
|
-
* @internal
|
|
8571
|
-
*/
|
|
8572
|
-
convertDBAttributeToAttribute(dbAttr) {
|
|
8573
|
-
const baseAttr = {
|
|
8574
|
-
...dbAttr.config,
|
|
8575
|
-
id: dbAttr.id,
|
|
8576
|
-
name: dbAttr.name,
|
|
8577
|
-
label: dbAttr.label,
|
|
8578
|
-
type: dbAttr.type,
|
|
8579
|
-
required: dbAttr.required,
|
|
8580
|
-
system: dbAttr.system,
|
|
8581
|
-
unique: dbAttr.unique
|
|
8582
|
-
};
|
|
8583
|
-
return baseAttr;
|
|
8584
|
-
}
|
|
8585
|
-
};
|
|
8586
|
-
|
|
8587
|
-
// src/exceptions.ts
|
|
8588
|
-
var SchemaErrorCode = {
|
|
8589
|
-
// Generic
|
|
8590
|
-
UNKNOWN: "SCHEMA_UNKNOWN_ERROR",
|
|
8591
|
-
// Not Found
|
|
8592
|
-
OBJECT_NOT_FOUND: "SCHEMA_OBJECT_NOT_FOUND",
|
|
8593
|
-
ATTRIBUTE_NOT_FOUND: "SCHEMA_ATTRIBUTE_NOT_FOUND",
|
|
8594
|
-
RECORD_NOT_FOUND: "SCHEMA_RECORD_NOT_FOUND",
|
|
8595
|
-
USER_PROFILE_NOT_FOUND: "SCHEMA_USER_PROFILE_NOT_FOUND",
|
|
8596
|
-
FILE_NOT_FOUND: "SCHEMA_FILE_NOT_FOUND",
|
|
8597
|
-
ROLE_NOT_FOUND: "SCHEMA_ROLE_NOT_FOUND",
|
|
8598
|
-
// Validation
|
|
8599
|
-
VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED",
|
|
8600
|
-
INVALID_ATTRIBUTE_NAME: "SCHEMA_INVALID_ATTRIBUTE_NAME",
|
|
8601
|
-
INVALID_OBJECT_NAME: "SCHEMA_INVALID_OBJECT_NAME",
|
|
8602
|
-
// Protected Resources
|
|
8603
|
-
PROTECTED_OBJECT: "SCHEMA_PROTECTED_OBJECT",
|
|
8604
|
-
PROTECTED_ATTRIBUTE: "SCHEMA_PROTECTED_ATTRIBUTE",
|
|
8605
|
-
PROTECTED_VIEW: "SCHEMA_PROTECTED_VIEW",
|
|
8606
|
-
PROTECTED_ROLE: "SCHEMA_PROTECTED_ROLE",
|
|
8607
|
-
// Permissions
|
|
8608
|
-
FORBIDDEN: "SCHEMA_FORBIDDEN",
|
|
8609
|
-
// Sync
|
|
8610
|
-
SYNC_FAILED: "SCHEMA_SYNC_FAILED",
|
|
8611
|
-
NOT_SYSTEM_OBJECT: "SCHEMA_NOT_SYSTEM_OBJECT",
|
|
8612
|
-
// Duplicates
|
|
8613
|
-
DUPLICATE_OBJECT: "SCHEMA_DUPLICATE_OBJECT",
|
|
8614
|
-
DUPLICATE_ATTRIBUTE: "SCHEMA_DUPLICATE_ATTRIBUTE"
|
|
8615
|
-
};
|
|
8616
|
-
var SchemaError = class extends Error {
|
|
8617
|
-
constructor(message, code = SchemaErrorCode.UNKNOWN, details) {
|
|
8618
|
-
super(message);
|
|
8619
|
-
this.name = "SchemaError";
|
|
8620
|
-
this.code = code;
|
|
8621
|
-
this.details = details;
|
|
8622
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
8623
|
-
}
|
|
8624
|
-
toJSON() {
|
|
8625
|
-
return {
|
|
8626
|
-
name: this.name,
|
|
8627
|
-
code: this.code,
|
|
8628
|
-
message: this.message,
|
|
8629
|
-
details: this.details
|
|
8630
|
-
};
|
|
8631
|
-
}
|
|
8632
|
-
};
|
|
8633
|
-
var NotFoundError = class extends SchemaError {
|
|
8634
|
-
constructor(resourceType, resourceId, code = SchemaErrorCode.RECORD_NOT_FOUND) {
|
|
8635
|
-
super(`${resourceType} with id "${resourceId}" not found`, code, {
|
|
8636
|
-
resourceType,
|
|
8637
|
-
resourceId
|
|
8638
|
-
});
|
|
8639
|
-
this.name = "NotFoundError";
|
|
8640
|
-
this.resourceType = resourceType;
|
|
8641
|
-
this.resourceId = resourceId;
|
|
8642
|
-
}
|
|
8643
|
-
};
|
|
8644
|
-
var ObjectNotFoundError = class extends NotFoundError {
|
|
8645
|
-
constructor(objectId) {
|
|
8646
|
-
super("Object", objectId, SchemaErrorCode.OBJECT_NOT_FOUND);
|
|
8647
|
-
this.name = "ObjectNotFoundError";
|
|
8648
|
-
}
|
|
8649
|
-
};
|
|
8650
|
-
var AttributeNotFoundError = class extends NotFoundError {
|
|
8651
|
-
constructor(attributeId) {
|
|
8652
|
-
super("Attribute", attributeId, SchemaErrorCode.ATTRIBUTE_NOT_FOUND);
|
|
8653
|
-
this.name = "AttributeNotFoundError";
|
|
8654
|
-
}
|
|
8655
|
-
};
|
|
8656
|
-
var RecordNotFoundError = class extends NotFoundError {
|
|
8657
|
-
constructor(recordId) {
|
|
8658
|
-
super("Record", recordId, SchemaErrorCode.RECORD_NOT_FOUND);
|
|
8659
|
-
this.name = "RecordNotFoundError";
|
|
8660
|
-
}
|
|
8661
|
-
};
|
|
8662
|
-
var UserProfileNotFoundError = class extends NotFoundError {
|
|
8663
|
-
constructor(identifier) {
|
|
8664
|
-
super("UserProfile", identifier, SchemaErrorCode.USER_PROFILE_NOT_FOUND);
|
|
8665
|
-
this.name = "UserProfileNotFoundError";
|
|
8666
|
-
}
|
|
8667
|
-
};
|
|
8668
|
-
var FileNotFoundError = class extends NotFoundError {
|
|
8669
|
-
constructor(fileId) {
|
|
8670
|
-
super("File", fileId, SchemaErrorCode.FILE_NOT_FOUND);
|
|
8671
|
-
this.name = "FileNotFoundError";
|
|
8672
|
-
}
|
|
8673
|
-
};
|
|
8674
|
-
var ValidationError = class _ValidationError extends SchemaError {
|
|
8675
|
-
constructor(message, errors) {
|
|
8676
|
-
super(message, SchemaErrorCode.VALIDATION_FAILED, { errors });
|
|
8677
|
-
this.name = "ValidationError";
|
|
8678
|
-
this.errors = errors;
|
|
8679
|
-
}
|
|
8680
|
-
/**
|
|
8681
|
-
* Create a validation error from Zod-style errors
|
|
8682
|
-
*/
|
|
8683
|
-
static fromZodErrors(errors) {
|
|
8684
|
-
const details = errors.map((err) => ({
|
|
8685
|
-
path: err.path.map(String),
|
|
8686
|
-
message: err.message
|
|
8687
|
-
}));
|
|
8688
|
-
const message = `Validation failed: ${details.map((d) => `${d.path.join(".")}: ${d.message}`).join(", ")}`;
|
|
8689
|
-
return new _ValidationError(message, details);
|
|
8690
|
-
}
|
|
8691
|
-
};
|
|
8692
|
-
var ProtectedResourceError = class extends SchemaError {
|
|
8693
|
-
constructor(resourceType, resourceName, operation) {
|
|
8694
|
-
const code = resourceType === "object" ? SchemaErrorCode.PROTECTED_OBJECT : resourceType === "view" ? SchemaErrorCode.PROTECTED_VIEW : SchemaErrorCode.PROTECTED_ATTRIBUTE;
|
|
8695
|
-
super(`Cannot ${operation} system ${resourceType} "${resourceName}"`, code, {
|
|
8696
|
-
resourceType,
|
|
8697
|
-
resourceName,
|
|
8698
|
-
operation
|
|
8699
|
-
});
|
|
8700
|
-
this.name = "ProtectedResourceError";
|
|
8701
|
-
this.resourceType = resourceType;
|
|
8702
|
-
this.resourceName = resourceName;
|
|
8703
|
-
this.operation = operation;
|
|
8704
|
-
}
|
|
8705
|
-
};
|
|
8706
|
-
var SyncError = class extends SchemaError {
|
|
8707
|
-
constructor(objectName, message, cause) {
|
|
8708
|
-
super(`Failed to sync object "${objectName}": ${message}`, SchemaErrorCode.SYNC_FAILED, {
|
|
8709
|
-
objectName,
|
|
8710
|
-
cause: cause?.message
|
|
8711
|
-
});
|
|
8712
|
-
this.name = "SyncError";
|
|
8713
|
-
this.objectName = objectName;
|
|
8714
|
-
this.cause = cause;
|
|
8715
|
-
}
|
|
8716
|
-
};
|
|
8717
|
-
var NotSystemObjectError = class extends SchemaError {
|
|
8718
|
-
constructor(objectName) {
|
|
8719
|
-
super(
|
|
8720
|
-
`Object "${objectName}" is not marked as system. Native objects must have system=true.`,
|
|
8721
|
-
SchemaErrorCode.NOT_SYSTEM_OBJECT,
|
|
8722
|
-
{ objectName }
|
|
8723
|
-
);
|
|
8724
|
-
this.name = "NotSystemObjectError";
|
|
8725
|
-
this.objectName = objectName;
|
|
8726
|
-
}
|
|
8727
|
-
};
|
|
8728
|
-
var DuplicateError = class extends SchemaError {
|
|
8729
|
-
constructor(resourceType, resourceName) {
|
|
8730
|
-
const code = resourceType === "object" ? SchemaErrorCode.DUPLICATE_OBJECT : SchemaErrorCode.DUPLICATE_ATTRIBUTE;
|
|
8731
|
-
super(
|
|
8732
|
-
`${resourceType === "object" ? "Object" : "Attribute"} "${resourceName}" already exists`,
|
|
8733
|
-
code,
|
|
8734
|
-
{ resourceType, resourceName }
|
|
8735
|
-
);
|
|
8736
|
-
this.name = "DuplicateError";
|
|
8737
|
-
this.resourceType = resourceType;
|
|
8738
|
-
this.resourceName = resourceName;
|
|
8739
|
-
}
|
|
8740
|
-
};
|
|
8741
|
-
function isSchemaError(error2) {
|
|
8742
|
-
return error2 instanceof SchemaError;
|
|
8743
|
-
}
|
|
8744
|
-
function isNotFoundError(error2) {
|
|
8745
|
-
return error2 instanceof NotFoundError;
|
|
8746
|
-
}
|
|
8747
|
-
function isValidationError(error2) {
|
|
8748
|
-
return error2 instanceof ValidationError;
|
|
8749
|
-
}
|
|
8750
|
-
function isProtectedResourceError(error2) {
|
|
8751
|
-
return error2 instanceof ProtectedResourceError;
|
|
8752
|
-
}
|
|
8753
|
-
var ForbiddenError = class extends SchemaError {
|
|
8754
|
-
constructor(objectName, action, userId) {
|
|
8755
|
-
super(`No ${action} permission on object "${objectName}"`, SchemaErrorCode.FORBIDDEN, {
|
|
8756
|
-
objectName,
|
|
8757
|
-
action,
|
|
8758
|
-
userId
|
|
8759
|
-
});
|
|
8760
|
-
this.name = "ForbiddenError";
|
|
8761
|
-
this.objectName = objectName;
|
|
8762
|
-
this.action = action;
|
|
8763
|
-
this.userId = userId;
|
|
8764
|
-
}
|
|
8765
|
-
};
|
|
8766
|
-
var ProtectedRoleError = class extends SchemaError {
|
|
8767
|
-
constructor(roleName, operation) {
|
|
8768
|
-
super(`Cannot ${operation} system role "${roleName}"`, SchemaErrorCode.PROTECTED_ROLE, {
|
|
8769
|
-
roleName,
|
|
8770
|
-
operation
|
|
8771
|
-
});
|
|
8772
|
-
this.name = "ProtectedRoleError";
|
|
8773
|
-
this.roleName = roleName;
|
|
8774
|
-
this.operation = operation;
|
|
8851
|
+
`Relation target object "${target.object}" does not exist. Make sure the object is created before adding a relation to it.`
|
|
8852
|
+
);
|
|
8853
|
+
}
|
|
8854
|
+
}
|
|
8775
8855
|
}
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8856
|
+
/**
|
|
8857
|
+
* Find objects that have a relation attribute targeting the given object.
|
|
8858
|
+
* Used to prevent deletion of objects that are relation targets.
|
|
8859
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
8860
|
+
* @internal
|
|
8861
|
+
*/
|
|
8862
|
+
async findObjectsWithRelationTo(targetObjectName) {
|
|
8863
|
+
const allObjects = await this.adapter.objects.list();
|
|
8864
|
+
const referencing = [];
|
|
8865
|
+
for (const obj of allObjects) {
|
|
8866
|
+
if (obj.name === targetObjectName) continue;
|
|
8867
|
+
const attrs = await this.adapter.attributes.findByObjectId(obj.id);
|
|
8868
|
+
const hasRelationToTarget = attrs.some((attr) => {
|
|
8869
|
+
if (attr.type !== "relation") return false;
|
|
8870
|
+
const config = attr.config;
|
|
8871
|
+
return config?.targets?.some((t) => t.object === targetObjectName) ?? false;
|
|
8872
|
+
});
|
|
8873
|
+
if (hasRelationToTarget) {
|
|
8874
|
+
referencing.push(obj.name);
|
|
8875
|
+
}
|
|
8876
|
+
}
|
|
8877
|
+
return referencing;
|
|
8878
|
+
}
|
|
8879
|
+
/**
|
|
8880
|
+
* Convert DB attribute to Attribute type
|
|
8881
|
+
* @internal
|
|
8882
|
+
*/
|
|
8883
|
+
convertDBAttributeToAttribute(dbAttr) {
|
|
8884
|
+
const baseAttr = {
|
|
8885
|
+
...dbAttr.config,
|
|
8886
|
+
id: dbAttr.id,
|
|
8887
|
+
name: dbAttr.name,
|
|
8888
|
+
label: dbAttr.label,
|
|
8889
|
+
type: dbAttr.type,
|
|
8890
|
+
required: dbAttr.required,
|
|
8891
|
+
system: dbAttr.system,
|
|
8892
|
+
unique: dbAttr.unique
|
|
8893
|
+
};
|
|
8894
|
+
return baseAttr;
|
|
8781
8895
|
}
|
|
8782
8896
|
};
|
|
8783
|
-
function isForbiddenError(error2) {
|
|
8784
|
-
return error2 instanceof ForbiddenError;
|
|
8785
|
-
}
|
|
8786
8897
|
|
|
8787
8898
|
// src/native/registry.ts
|
|
8788
8899
|
var NativeObjectRegistryClass = class {
|
|
@@ -9165,7 +9276,8 @@ var AuditService = class extends BaseService {
|
|
|
9165
9276
|
startFlushTimer() {
|
|
9166
9277
|
const intervalMs = this.options?.flushIntervalMs ?? 1e3;
|
|
9167
9278
|
this.flushTimer = setInterval(() => {
|
|
9168
|
-
this.flush().catch(
|
|
9279
|
+
this.flush().catch(() => {
|
|
9280
|
+
});
|
|
9169
9281
|
}, intervalMs);
|
|
9170
9282
|
}
|
|
9171
9283
|
/**
|
|
@@ -9535,6 +9647,11 @@ function createContextForRestore(schema, tenantId, record, metadata) {
|
|
|
9535
9647
|
}
|
|
9536
9648
|
|
|
9537
9649
|
// src/runtime/services/record/helpers/rollup-cascade.ts
|
|
9650
|
+
async function batchProcess(items, fn, batchSize = 10) {
|
|
9651
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
9652
|
+
await Promise.all(items.slice(i, i + batchSize).map(fn));
|
|
9653
|
+
}
|
|
9654
|
+
}
|
|
9538
9655
|
async function preloadSchemas(records, schemaService) {
|
|
9539
9656
|
const schemasByObjectId = /* @__PURE__ */ new Map();
|
|
9540
9657
|
if (records.length === 0) {
|
|
@@ -9559,29 +9676,25 @@ async function recalculateParentRollups(record, schema, ctx) {
|
|
|
9559
9676
|
if (affectedParentIds.length > 0) {
|
|
9560
9677
|
const parentRecords = await findRecordsByIds(affectedParentIds);
|
|
9561
9678
|
const parentSchemas = await preloadSchemas(parentRecords, schemaService);
|
|
9562
|
-
await
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
})
|
|
9573
|
-
);
|
|
9679
|
+
await batchProcess(parentRecords, async (parentRecord) => {
|
|
9680
|
+
const parentSchema = parentSchemas.get(parentRecord.objectId);
|
|
9681
|
+
if (!parentSchema) return;
|
|
9682
|
+
const rollupAttrs = parentSchema.attributes.filter(
|
|
9683
|
+
(a) => a.type === "rollup"
|
|
9684
|
+
);
|
|
9685
|
+
if (rollupAttrs.length > 0) {
|
|
9686
|
+
await rollupService.recalculateAndUpdate(parentRecord, parentSchema);
|
|
9687
|
+
}
|
|
9688
|
+
});
|
|
9574
9689
|
}
|
|
9575
9690
|
const affectedForwardRecords = await rollupService.findRecordsWithForwardRollup(record, schema);
|
|
9576
9691
|
if (affectedForwardRecords.length > 0) {
|
|
9577
9692
|
const forwardSchemas = await preloadSchemas(affectedForwardRecords, schemaService);
|
|
9578
|
-
await
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
})
|
|
9584
|
-
);
|
|
9693
|
+
await batchProcess(affectedForwardRecords, async (forwardRecord) => {
|
|
9694
|
+
const forwardSchema = forwardSchemas.get(forwardRecord.objectId);
|
|
9695
|
+
if (!forwardSchema) return;
|
|
9696
|
+
await rollupService.recalculateAndUpdate(forwardRecord, forwardSchema);
|
|
9697
|
+
});
|
|
9585
9698
|
}
|
|
9586
9699
|
}
|
|
9587
9700
|
|
|
@@ -9658,8 +9771,40 @@ var RecordQueryService = class extends BaseService {
|
|
|
9658
9771
|
let effectiveTotal = result.total;
|
|
9659
9772
|
if (policy?.canAccessRecord && this.userId) {
|
|
9660
9773
|
const ctx = buildPolicyContext(schema.name, this.userId, this.tenantId);
|
|
9661
|
-
|
|
9662
|
-
|
|
9774
|
+
const requestedLimit = effectiveOptions?.limit ?? 20;
|
|
9775
|
+
const requestedOffset = effectiveOptions?.offset ?? 0;
|
|
9776
|
+
const overfetchMultiplier = 5;
|
|
9777
|
+
const batchSize = requestedLimit * overfetchMultiplier;
|
|
9778
|
+
const maxScanRecords = 1e4;
|
|
9779
|
+
const collected = [];
|
|
9780
|
+
let dbOffset = 0;
|
|
9781
|
+
let totalScanned = 0;
|
|
9782
|
+
let exhausted = false;
|
|
9783
|
+
const target = requestedOffset + requestedLimit;
|
|
9784
|
+
while (collected.length < target && totalScanned < maxScanRecords) {
|
|
9785
|
+
const batch = await runWithSchemaContext(
|
|
9786
|
+
[schema],
|
|
9787
|
+
() => this.adapter.objectRecords.list(objectId, {
|
|
9788
|
+
...effectiveOptions,
|
|
9789
|
+
limit: batchSize,
|
|
9790
|
+
offset: dbOffset
|
|
9791
|
+
})
|
|
9792
|
+
);
|
|
9793
|
+
if (batch.records.length === 0) {
|
|
9794
|
+
exhausted = true;
|
|
9795
|
+
break;
|
|
9796
|
+
}
|
|
9797
|
+
const filtered = batch.records.filter((record) => policy.canAccessRecord?.(ctx, record));
|
|
9798
|
+
collected.push(...filtered);
|
|
9799
|
+
dbOffset += batch.records.length;
|
|
9800
|
+
totalScanned += batch.records.length;
|
|
9801
|
+
if (batch.records.length < batchSize) {
|
|
9802
|
+
exhausted = true;
|
|
9803
|
+
break;
|
|
9804
|
+
}
|
|
9805
|
+
}
|
|
9806
|
+
effectiveTotal = exhausted ? collected.length : Math.max(collected.length, result.total);
|
|
9807
|
+
filteredRecords = collected.slice(requestedOffset, requestedOffset + requestedLimit);
|
|
9663
9808
|
}
|
|
9664
9809
|
if (!options?.skipFormulas) {
|
|
9665
9810
|
return {
|
|
@@ -10020,32 +10165,39 @@ var RelationService = class extends BaseService {
|
|
|
10020
10165
|
if (filteredTargets.length === 0) {
|
|
10021
10166
|
return { options: [], hasMore: false, total: 0 };
|
|
10022
10167
|
}
|
|
10168
|
+
const queryService = this.getQueryServiceOrThrow();
|
|
10169
|
+
const queryOptions = {
|
|
10170
|
+
limit: pageSize,
|
|
10171
|
+
offset: (page - 1) * pageSize,
|
|
10172
|
+
filters: filter
|
|
10173
|
+
};
|
|
10174
|
+
const targetResults = await Promise.all(
|
|
10175
|
+
filteredTargets.map(async (target) => {
|
|
10176
|
+
const objectSchema = await this.schemaService.getObjectSchemaByName(target.object);
|
|
10177
|
+
if (!objectSchema?.id) return { options: [], total: 0 };
|
|
10178
|
+
const objectId = objectSchema.id;
|
|
10179
|
+
const result = query ? await queryService.searchRecords(objectId, query, queryOptions) : await queryService.listRecords(objectId, queryOptions);
|
|
10180
|
+
const options = await Promise.all(
|
|
10181
|
+
result.records.map(async (record) => {
|
|
10182
|
+
const label = await this.resolveLabel(record, objectSchema, target.displayTemplate);
|
|
10183
|
+
return {
|
|
10184
|
+
id: record.id,
|
|
10185
|
+
objectId,
|
|
10186
|
+
objectName: objectSchema.name,
|
|
10187
|
+
objectLabel: objectSchema.label,
|
|
10188
|
+
objectIcon: objectSchema.icon,
|
|
10189
|
+
label
|
|
10190
|
+
};
|
|
10191
|
+
})
|
|
10192
|
+
);
|
|
10193
|
+
return { options, total: result.total };
|
|
10194
|
+
})
|
|
10195
|
+
);
|
|
10023
10196
|
const allOptions = [];
|
|
10024
10197
|
let totalCount = 0;
|
|
10025
|
-
for (const
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
continue;
|
|
10029
|
-
}
|
|
10030
|
-
const queryOptions = {
|
|
10031
|
-
limit: pageSize,
|
|
10032
|
-
offset: (page - 1) * pageSize,
|
|
10033
|
-
filters: filter
|
|
10034
|
-
};
|
|
10035
|
-
const queryService = this.getQueryServiceOrThrow();
|
|
10036
|
-
const result = query ? await queryService.searchRecords(objectSchema.id, query, queryOptions) : await queryService.listRecords(objectSchema.id, queryOptions);
|
|
10037
|
-
totalCount += result.total;
|
|
10038
|
-
for (const record of result.records) {
|
|
10039
|
-
const label = await this.resolveLabel(record, objectSchema, target.displayTemplate);
|
|
10040
|
-
allOptions.push({
|
|
10041
|
-
id: record.id,
|
|
10042
|
-
objectId: objectSchema.id,
|
|
10043
|
-
objectName: objectSchema.name,
|
|
10044
|
-
objectLabel: objectSchema.label,
|
|
10045
|
-
objectIcon: objectSchema.icon,
|
|
10046
|
-
label
|
|
10047
|
-
});
|
|
10048
|
-
}
|
|
10198
|
+
for (const { options: opts, total } of targetResults) {
|
|
10199
|
+
allOptions.push(...opts);
|
|
10200
|
+
totalCount += total;
|
|
10049
10201
|
}
|
|
10050
10202
|
const hasMore = totalCount > page * pageSize;
|
|
10051
10203
|
return {
|
|
@@ -10574,33 +10726,43 @@ var RollupService = class extends BaseService {
|
|
|
10574
10726
|
async findRecordsWithForwardRollup(changedRecord, changedSchema) {
|
|
10575
10727
|
const affectedRecords = [];
|
|
10576
10728
|
const allObjects = await this.adapter.objects.list();
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10729
|
+
const objectMap = new Map(allObjects.map((obj) => [obj.id, obj]));
|
|
10730
|
+
const candidateObjectIds = allObjects.filter((obj) => obj.id !== changedRecord.objectId).map((obj) => obj.id);
|
|
10731
|
+
if (candidateObjectIds.length === 0) {
|
|
10732
|
+
return [];
|
|
10733
|
+
}
|
|
10734
|
+
const attrResults = await Promise.all(
|
|
10735
|
+
candidateObjectIds.map(async (objId) => {
|
|
10736
|
+
const attributes = await this.adapter.attributes.findByObjectId(objId);
|
|
10737
|
+
return { objId, attributes };
|
|
10738
|
+
})
|
|
10739
|
+
);
|
|
10740
|
+
const objectsWithRollups = [];
|
|
10741
|
+
for (const { objId, attributes } of attrResults) {
|
|
10742
|
+
const hasRollup = attributes.some((a) => a.type === "rollup");
|
|
10743
|
+
if (hasRollup) {
|
|
10744
|
+
objectsWithRollups.push({ objId, attributes });
|
|
10580
10745
|
}
|
|
10581
|
-
|
|
10746
|
+
}
|
|
10747
|
+
if (objectsWithRollups.length === 0) {
|
|
10748
|
+
return [];
|
|
10749
|
+
}
|
|
10750
|
+
for (const { objId, attributes } of objectsWithRollups) {
|
|
10582
10751
|
const rollupAttrs = attributes.filter((a) => a.type === "rollup");
|
|
10583
|
-
|
|
10584
|
-
|
|
10585
|
-
}
|
|
10752
|
+
const obj = objectMap.get(objId);
|
|
10753
|
+
if (!obj) continue;
|
|
10586
10754
|
for (const rollupDbAttr of rollupAttrs) {
|
|
10587
10755
|
const rollupConfig = rollupDbAttr.config;
|
|
10588
|
-
if (!rollupConfig?.relationAttribute)
|
|
10589
|
-
continue;
|
|
10590
|
-
}
|
|
10756
|
+
if (!rollupConfig?.relationAttribute) continue;
|
|
10591
10757
|
const relationAttr = attributes.find(
|
|
10592
10758
|
(a) => a.type === "relation" && a.name === rollupConfig.relationAttribute
|
|
10593
10759
|
);
|
|
10594
|
-
if (!relationAttr)
|
|
10595
|
-
continue;
|
|
10596
|
-
}
|
|
10760
|
+
if (!relationAttr) continue;
|
|
10597
10761
|
const relationConfig = relationAttr.config;
|
|
10598
10762
|
const targetsChangedObject = relationConfig?.targets?.some(
|
|
10599
10763
|
(t) => t.object === changedSchema.name
|
|
10600
10764
|
);
|
|
10601
|
-
if (!targetsChangedObject)
|
|
10602
|
-
continue;
|
|
10603
|
-
}
|
|
10765
|
+
if (!targetsChangedObject) continue;
|
|
10604
10766
|
const recordsPointingToChanged = await this.adapter.objectRecords.findByRelation(
|
|
10605
10767
|
obj.id,
|
|
10606
10768
|
relationAttr.name,
|
|
@@ -10712,11 +10874,9 @@ var RecordService = class extends BaseService {
|
|
|
10712
10874
|
await this.hookRegistry.execute("afterCreate", schema.name, afterCtx);
|
|
10713
10875
|
}
|
|
10714
10876
|
await recalculateParentRollups(record, schema, this.rollupContext);
|
|
10715
|
-
await this.
|
|
10716
|
-
await this.invalidateLists("allSearchResults", objectId);
|
|
10717
|
-
await this.invalidateCachePattern(cacheKeys.allGlobalSearch(this.tenantId));
|
|
10877
|
+
await this.invalidateRecordCaches(record.id, objectId);
|
|
10718
10878
|
if (this.auditService && this.userId) {
|
|
10719
|
-
|
|
10879
|
+
this.auditService.logRecordAction({
|
|
10720
10880
|
action: "record.created",
|
|
10721
10881
|
actorId: this.userId,
|
|
10722
10882
|
objectName: schema.name,
|
|
@@ -10724,6 +10884,11 @@ var RecordService = class extends BaseService {
|
|
|
10724
10884
|
recordId: record.id,
|
|
10725
10885
|
recordLabel: record.label,
|
|
10726
10886
|
metadata: options?.hookMetadata
|
|
10887
|
+
}).catch((err) => {
|
|
10888
|
+
console.error(
|
|
10889
|
+
"Audit log failed (record.created):",
|
|
10890
|
+
err instanceof Error ? err.message : err
|
|
10891
|
+
);
|
|
10727
10892
|
});
|
|
10728
10893
|
}
|
|
10729
10894
|
return record;
|
|
@@ -10800,7 +10965,19 @@ var RecordService = class extends BaseService {
|
|
|
10800
10965
|
checkRecordModifyOrThrow(policy, existing, ctx);
|
|
10801
10966
|
}
|
|
10802
10967
|
const mergedData = { ...existing.values, ...data };
|
|
10803
|
-
const changedAttributes = Object.keys(data).filter((key) =>
|
|
10968
|
+
const changedAttributes = Object.keys(data).filter((key) => {
|
|
10969
|
+
const oldVal = existing.values[key];
|
|
10970
|
+
const newVal = data[key];
|
|
10971
|
+
if (oldVal === newVal) return false;
|
|
10972
|
+
if (oldVal !== null && newVal !== null && typeof oldVal === "object" && typeof newVal === "object") {
|
|
10973
|
+
try {
|
|
10974
|
+
return JSON.stringify(oldVal) !== JSON.stringify(newVal);
|
|
10975
|
+
} catch {
|
|
10976
|
+
return true;
|
|
10977
|
+
}
|
|
10978
|
+
}
|
|
10979
|
+
return true;
|
|
10980
|
+
});
|
|
10804
10981
|
const hookCtx = createContextForUpdate(
|
|
10805
10982
|
schema,
|
|
10806
10983
|
this.tenantId,
|
|
@@ -10844,7 +11021,8 @@ var RecordService = class extends BaseService {
|
|
|
10844
11021
|
...hookModifiedValues,
|
|
10845
11022
|
__completionStatus: completionStatus,
|
|
10846
11023
|
__label: label,
|
|
10847
|
-
__lastUpdatedBy: this.userId
|
|
11024
|
+
__lastUpdatedBy: this.userId,
|
|
11025
|
+
__expectedUpdatedAt: existing.updatedAt instanceof Date ? existing.updatedAt.toISOString() : existing.updatedAt
|
|
10848
11026
|
};
|
|
10849
11027
|
if (options?.metadata !== void 0) {
|
|
10850
11028
|
const existingMetadata = existing.metadata ?? {};
|
|
@@ -10855,10 +11033,7 @@ var RecordService = class extends BaseService {
|
|
|
10855
11033
|
updatePayload.__metadata = cleanedMetadata;
|
|
10856
11034
|
}
|
|
10857
11035
|
const updated = await this.adapter.objectRecords.update(recordId, updatePayload);
|
|
10858
|
-
await this.
|
|
10859
|
-
await this.invalidateLists("allRecordLists", existing.objectId);
|
|
10860
|
-
await this.invalidateLists("allSearchResults", existing.objectId);
|
|
10861
|
-
await this.invalidateCachePattern(cacheKeys.resolvedRelationsByRecord(this.tenantId, recordId));
|
|
11036
|
+
await this.invalidateRecordCaches(recordId, existing.objectId);
|
|
10862
11037
|
if (!options?.skipHooks) {
|
|
10863
11038
|
const afterCtx = {
|
|
10864
11039
|
...hookCtx,
|
|
@@ -10877,7 +11052,7 @@ var RecordService = class extends BaseService {
|
|
|
10877
11052
|
oldValue: hookCtx.oldValues?.[attr],
|
|
10878
11053
|
newValue: hookCtx.newValues[attr]
|
|
10879
11054
|
}));
|
|
10880
|
-
|
|
11055
|
+
this.auditService.logRecordAction({
|
|
10881
11056
|
action: "record.updated",
|
|
10882
11057
|
actorId: this.userId,
|
|
10883
11058
|
objectName: schema.name,
|
|
@@ -10886,6 +11061,11 @@ var RecordService = class extends BaseService {
|
|
|
10886
11061
|
recordLabel: updated.label,
|
|
10887
11062
|
changes,
|
|
10888
11063
|
metadata: options?.hookMetadata
|
|
11064
|
+
}).catch((err) => {
|
|
11065
|
+
console.error(
|
|
11066
|
+
"Audit log failed (record.updated):",
|
|
11067
|
+
err instanceof Error ? err.message : err
|
|
11068
|
+
);
|
|
10889
11069
|
});
|
|
10890
11070
|
}
|
|
10891
11071
|
return updated;
|
|
@@ -10929,17 +11109,13 @@ var RecordService = class extends BaseService {
|
|
|
10929
11109
|
await this.hookRegistry.execute("beforeDelete", schema.name, hookCtx);
|
|
10930
11110
|
}
|
|
10931
11111
|
await this.adapter.objectRecords.delete(recordId);
|
|
10932
|
-
await this.
|
|
10933
|
-
await this.invalidateLists("allRecordLists", record.objectId);
|
|
10934
|
-
await this.invalidateLists("allSearchResults", record.objectId);
|
|
10935
|
-
await this.invalidateCachePattern(cacheKeys.allGlobalSearch(this.tenantId));
|
|
10936
|
-
await this.invalidateCachePattern(cacheKeys.resolvedRelationsByRecord(this.tenantId, recordId));
|
|
11112
|
+
await this.invalidateRecordCaches(recordId, record.objectId);
|
|
10937
11113
|
if (!options?.skipHooks) {
|
|
10938
11114
|
await this.hookRegistry.execute("afterDelete", schema.name, hookCtx);
|
|
10939
11115
|
}
|
|
10940
11116
|
await recalculateParentRollups(record, schema, this.rollupContext);
|
|
10941
11117
|
if (this.auditService && this.userId) {
|
|
10942
|
-
|
|
11118
|
+
this.auditService.logRecordAction({
|
|
10943
11119
|
action: "record.deleted",
|
|
10944
11120
|
actorId: this.userId,
|
|
10945
11121
|
objectName: schema.name,
|
|
@@ -10947,6 +11123,11 @@ var RecordService = class extends BaseService {
|
|
|
10947
11123
|
recordId: record.id,
|
|
10948
11124
|
recordLabel: record.label,
|
|
10949
11125
|
metadata: options?.hookMetadata
|
|
11126
|
+
}).catch((err) => {
|
|
11127
|
+
console.error(
|
|
11128
|
+
"Audit log failed (record.deleted):",
|
|
11129
|
+
err instanceof Error ? err.message : err
|
|
11130
|
+
);
|
|
10950
11131
|
});
|
|
10951
11132
|
}
|
|
10952
11133
|
}
|
|
@@ -10958,11 +11139,7 @@ var RecordService = class extends BaseService {
|
|
|
10958
11139
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
10959
11140
|
await checkPermission(this.permissionService, this.userId, schema.name, "delete");
|
|
10960
11141
|
await this.adapter.objectRecords.hardDelete(recordId);
|
|
10961
|
-
await this.
|
|
10962
|
-
await this.invalidateLists("allRecordLists", record.objectId);
|
|
10963
|
-
await this.invalidateLists("allSearchResults", record.objectId);
|
|
10964
|
-
await this.invalidateCachePattern(cacheKeys.allGlobalSearch(this.tenantId));
|
|
10965
|
-
await this.invalidateCachePattern(cacheKeys.resolvedRelationsByRecord(this.tenantId, recordId));
|
|
11142
|
+
await this.invalidateRecordCaches(recordId, record.objectId);
|
|
10966
11143
|
}
|
|
10967
11144
|
// ============================================================================
|
|
10968
11145
|
// RESTORE
|
|
@@ -10993,11 +11170,7 @@ var RecordService = class extends BaseService {
|
|
|
10993
11170
|
await this.hookRegistry.execute("beforeRestore", schema.name, hookCtx);
|
|
10994
11171
|
}
|
|
10995
11172
|
const restored = await this.adapter.objectRecords.restore(recordId);
|
|
10996
|
-
await this.
|
|
10997
|
-
await this.invalidateLists("allRecordLists", record.objectId);
|
|
10998
|
-
await this.invalidateLists("allSearchResults", record.objectId);
|
|
10999
|
-
await this.invalidateCachePattern(cacheKeys.allGlobalSearch(this.tenantId));
|
|
11000
|
-
await this.invalidateCachePattern(cacheKeys.resolvedRelationsByRecord(this.tenantId, recordId));
|
|
11173
|
+
await this.invalidateRecordCaches(recordId, record.objectId);
|
|
11001
11174
|
if (!options?.skipHooks) {
|
|
11002
11175
|
const afterCtx = {
|
|
11003
11176
|
...hookCtx,
|
|
@@ -11006,7 +11179,7 @@ var RecordService = class extends BaseService {
|
|
|
11006
11179
|
await this.hookRegistry.execute("afterRestore", schema.name, afterCtx);
|
|
11007
11180
|
}
|
|
11008
11181
|
if (this.auditService && this.userId) {
|
|
11009
|
-
|
|
11182
|
+
this.auditService.logRecordAction({
|
|
11010
11183
|
action: "record.restored",
|
|
11011
11184
|
actorId: this.userId,
|
|
11012
11185
|
objectName: schema.name,
|
|
@@ -11014,11 +11187,30 @@ var RecordService = class extends BaseService {
|
|
|
11014
11187
|
recordId: restored.id,
|
|
11015
11188
|
recordLabel: restored.label,
|
|
11016
11189
|
metadata: options?.hookMetadata
|
|
11190
|
+
}).catch((err) => {
|
|
11191
|
+
console.error(
|
|
11192
|
+
"Audit log failed (record.restored):",
|
|
11193
|
+
err instanceof Error ? err.message : err
|
|
11194
|
+
);
|
|
11017
11195
|
});
|
|
11018
11196
|
}
|
|
11019
11197
|
return restored;
|
|
11020
11198
|
}
|
|
11021
11199
|
// ============================================================================
|
|
11200
|
+
// PRIVATE HELPERS
|
|
11201
|
+
// ============================================================================
|
|
11202
|
+
/**
|
|
11203
|
+
* Invalidate all caches related to a record (record cache + lists + global search)
|
|
11204
|
+
* @private
|
|
11205
|
+
*/
|
|
11206
|
+
async invalidateRecordCaches(recordId, objectId) {
|
|
11207
|
+
await this.invalidateCache(cacheKeys.record(this.tenantId, recordId));
|
|
11208
|
+
await this.invalidateLists("allRecordLists", objectId);
|
|
11209
|
+
await this.invalidateLists("allSearchResults", objectId);
|
|
11210
|
+
await this.invalidateCachePattern(cacheKeys.allGlobalSearch(this.tenantId));
|
|
11211
|
+
await this.invalidateCachePattern(cacheKeys.resolvedRelationsByRecord(this.tenantId, recordId));
|
|
11212
|
+
}
|
|
11213
|
+
// ============================================================================
|
|
11022
11214
|
// LIST & SEARCH (delegated to RecordQueryService)
|
|
11023
11215
|
// ============================================================================
|
|
11024
11216
|
/**
|
|
@@ -11426,8 +11618,7 @@ var DocumentRendererService = class {
|
|
|
11426
11618
|
const field = fields.find((f) => f.id === fieldId);
|
|
11427
11619
|
resolved.set(fieldId, labels.join(", ") || field?.fallback || "");
|
|
11428
11620
|
}
|
|
11429
|
-
} catch
|
|
11430
|
-
console.warn("Failed to resolve relations:", error2);
|
|
11621
|
+
} catch {
|
|
11431
11622
|
for (const { fieldId, ids } of relationBatch) {
|
|
11432
11623
|
const field = fields.find((f) => f.id === fieldId);
|
|
11433
11624
|
resolved.set(fieldId, ids.join(", ") || field?.fallback || "");
|
|
@@ -11482,7 +11673,6 @@ var DocumentRendererService = class {
|
|
|
11482
11673
|
drawField(pages, field, value, font, fontBold) {
|
|
11483
11674
|
const page = pages[field.page];
|
|
11484
11675
|
if (!page) {
|
|
11485
|
-
console.warn(`Field "${field.id}" references non-existent page ${field.page}`);
|
|
11486
11676
|
return;
|
|
11487
11677
|
}
|
|
11488
11678
|
if (!value) {
|
|
@@ -11637,7 +11827,6 @@ var DocumentProcessingHook = class extends BaseService {
|
|
|
11637
11827
|
...doc,
|
|
11638
11828
|
metadata: { ...metadata, status: "failed", error: errorMessage }
|
|
11639
11829
|
};
|
|
11640
|
-
console.error(`Failed to process document for node ${nodeId}:`, error2);
|
|
11641
11830
|
}
|
|
11642
11831
|
}
|
|
11643
11832
|
return {
|
|
@@ -11708,13 +11897,11 @@ var DocumentProcessingHook = class extends BaseService {
|
|
|
11708
11897
|
try {
|
|
11709
11898
|
const recordId = context.createdRecordIds?.[slotId];
|
|
11710
11899
|
if (!recordId) {
|
|
11711
|
-
console.warn(`No record ID found for slot "${slotId}", skipping attachment`);
|
|
11712
11900
|
continue;
|
|
11713
11901
|
}
|
|
11714
11902
|
const slotDef = workflow2.slots?.find((s) => s.id === slotId);
|
|
11715
11903
|
const objectName = slotDef?.objectName;
|
|
11716
11904
|
if (!objectName) {
|
|
11717
|
-
console.warn(`No object name found for slot "${slotId}", skipping attachment`);
|
|
11718
11905
|
continue;
|
|
11719
11906
|
}
|
|
11720
11907
|
const result = await documentService.createRecordDocument({
|
|
@@ -11737,8 +11924,7 @@ var DocumentProcessingHook = class extends BaseService {
|
|
|
11737
11924
|
{ partial: true }
|
|
11738
11925
|
);
|
|
11739
11926
|
}
|
|
11740
|
-
} catch
|
|
11741
|
-
console.error(`Failed to attach document to slot "${slotId}":`, error2);
|
|
11927
|
+
} catch {
|
|
11742
11928
|
}
|
|
11743
11929
|
}
|
|
11744
11930
|
return attachedDocumentIds;
|
|
@@ -12045,6 +12231,7 @@ var WorkflowAccessGrantService = class extends BaseService {
|
|
|
12045
12231
|
};
|
|
12046
12232
|
|
|
12047
12233
|
// src/runtime/services/workflow/instance.service.ts
|
|
12234
|
+
import { randomUUID } from "crypto";
|
|
12048
12235
|
var WorkflowInstanceService = class extends BaseService {
|
|
12049
12236
|
constructor(adapter, workflowService, options) {
|
|
12050
12237
|
super(adapter);
|
|
@@ -12094,7 +12281,24 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12094
12281
|
createdAt: /* @__PURE__ */ new Date(),
|
|
12095
12282
|
updatedAt: /* @__PURE__ */ new Date()
|
|
12096
12283
|
};
|
|
12097
|
-
|
|
12284
|
+
let updatedInstance;
|
|
12285
|
+
try {
|
|
12286
|
+
updatedInstance = await this.executeCurrentNode(instance);
|
|
12287
|
+
} catch (error2) {
|
|
12288
|
+
updatedInstance = {
|
|
12289
|
+
...instance,
|
|
12290
|
+
status: "failed",
|
|
12291
|
+
error: {
|
|
12292
|
+
code: "UNEXPECTED_ERROR",
|
|
12293
|
+
message: error2 instanceof Error ? error2.message : String(error2),
|
|
12294
|
+
nodeId: instance.currentNodeId,
|
|
12295
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
12296
|
+
},
|
|
12297
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
12298
|
+
};
|
|
12299
|
+
await this.saveInstance(updatedInstance);
|
|
12300
|
+
throw error2;
|
|
12301
|
+
}
|
|
12098
12302
|
await this.saveInstance(updatedInstance);
|
|
12099
12303
|
return updatedInstance;
|
|
12100
12304
|
}
|
|
@@ -12106,6 +12310,12 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12106
12310
|
if (!instance) {
|
|
12107
12311
|
throw new SchemaError(`Instance "${instanceId}" not found`, SchemaErrorCode.RECORD_NOT_FOUND);
|
|
12108
12312
|
}
|
|
12313
|
+
if (instance.expiresAt && new Date(instance.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
12314
|
+
throw new SchemaError(
|
|
12315
|
+
`Instance "${instanceId}" has expired (expiresAt: ${instance.expiresAt.toISOString()})`,
|
|
12316
|
+
SchemaErrorCode.VALIDATION_FAILED
|
|
12317
|
+
);
|
|
12318
|
+
}
|
|
12109
12319
|
if (instance.status !== "waiting") {
|
|
12110
12320
|
throw new SchemaError(
|
|
12111
12321
|
`Instance "${instanceId}" is not waiting (status: ${instance.status})`,
|
|
@@ -12117,7 +12327,24 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12117
12327
|
status: "running",
|
|
12118
12328
|
updatedAt: /* @__PURE__ */ new Date()
|
|
12119
12329
|
};
|
|
12120
|
-
|
|
12330
|
+
let result;
|
|
12331
|
+
try {
|
|
12332
|
+
result = await this.executeCurrentNode(updatedInstance, input.input);
|
|
12333
|
+
} catch (error2) {
|
|
12334
|
+
result = {
|
|
12335
|
+
...updatedInstance,
|
|
12336
|
+
status: "failed",
|
|
12337
|
+
error: {
|
|
12338
|
+
code: "UNEXPECTED_ERROR",
|
|
12339
|
+
message: error2 instanceof Error ? error2.message : String(error2),
|
|
12340
|
+
nodeId: updatedInstance.currentNodeId,
|
|
12341
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
12342
|
+
},
|
|
12343
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
12344
|
+
};
|
|
12345
|
+
await this.saveInstance(result);
|
|
12346
|
+
throw error2;
|
|
12347
|
+
}
|
|
12121
12348
|
await this.saveInstance(result);
|
|
12122
12349
|
return result;
|
|
12123
12350
|
}
|
|
@@ -12146,7 +12373,8 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12146
12373
|
return updatedInstance;
|
|
12147
12374
|
}
|
|
12148
12375
|
/**
|
|
12149
|
-
* Get an instance by ID
|
|
12376
|
+
* Get an instance by ID.
|
|
12377
|
+
* Automatically marks expired instances as "failed" if their expiresAt has passed.
|
|
12150
12378
|
*/
|
|
12151
12379
|
async getInstance(id) {
|
|
12152
12380
|
if (!this.adapter.workflowInstances) {
|
|
@@ -12156,7 +12384,22 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12156
12384
|
if (!dbInstance) {
|
|
12157
12385
|
return null;
|
|
12158
12386
|
}
|
|
12159
|
-
|
|
12387
|
+
let instance = this.convertDBInstanceToInstance(dbInstance);
|
|
12388
|
+
if (instance.expiresAt && new Date(instance.expiresAt) < /* @__PURE__ */ new Date() && !["completed", "failed", "cancelled"].includes(instance.status)) {
|
|
12389
|
+
instance = {
|
|
12390
|
+
...instance,
|
|
12391
|
+
status: "failed",
|
|
12392
|
+
error: {
|
|
12393
|
+
code: "WORKFLOW_EXPIRED",
|
|
12394
|
+
message: `Workflow instance expired (expiresAt: ${instance.expiresAt.toISOString()})`,
|
|
12395
|
+
nodeId: instance.currentNodeId,
|
|
12396
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
12397
|
+
},
|
|
12398
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
12399
|
+
};
|
|
12400
|
+
await this.saveInstance(instance);
|
|
12401
|
+
}
|
|
12402
|
+
return instance;
|
|
12160
12403
|
}
|
|
12161
12404
|
/**
|
|
12162
12405
|
* Get all instances for a workflow
|
|
@@ -12179,17 +12422,17 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12179
12422
|
return { instances: [], total: 0 };
|
|
12180
12423
|
}
|
|
12181
12424
|
if (options?.workflowName) {
|
|
12182
|
-
const
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
filtered = instances2.filter((i) => i.status === options.status);
|
|
12186
|
-
}
|
|
12187
|
-
const total2 = filtered.length;
|
|
12188
|
-
const paginated = filtered.slice(
|
|
12189
|
-
options.offset ?? 0,
|
|
12190
|
-
options.limit ? (options.offset ?? 0) + options.limit : void 0
|
|
12425
|
+
const allDbInstances = await this.adapter.workflowInstances.findByWorkflowName(
|
|
12426
|
+
options.workflowName,
|
|
12427
|
+
{ status: options.status }
|
|
12191
12428
|
);
|
|
12192
|
-
|
|
12429
|
+
const total2 = allDbInstances.length;
|
|
12430
|
+
const offset = options.offset ?? 0;
|
|
12431
|
+
const limit = options.limit ?? total2;
|
|
12432
|
+
const paged = allDbInstances.slice(offset, offset + limit);
|
|
12433
|
+
let instances2 = paged.map((db) => this.convertDBInstanceToInstance(db));
|
|
12434
|
+
instances2 = await this.markExpiredInstances(instances2);
|
|
12435
|
+
return { instances: instances2, total: total2 };
|
|
12193
12436
|
}
|
|
12194
12437
|
const { instances: dbInstances, total } = await this.adapter.workflowInstances.list({
|
|
12195
12438
|
limit: options?.limit,
|
|
@@ -12199,6 +12442,7 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12199
12442
|
if (options?.status) {
|
|
12200
12443
|
instances = instances.filter((i) => i.status === options.status);
|
|
12201
12444
|
}
|
|
12445
|
+
instances = await this.markExpiredInstances(instances);
|
|
12202
12446
|
return { instances, total };
|
|
12203
12447
|
}
|
|
12204
12448
|
/**
|
|
@@ -12240,7 +12484,30 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12240
12484
|
* Execute the current node and continue until wait/complete/error
|
|
12241
12485
|
*/
|
|
12242
12486
|
async executeCurrentNode(instance, input) {
|
|
12243
|
-
|
|
12487
|
+
if (instance.expiresAt && new Date(instance.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
12488
|
+
return {
|
|
12489
|
+
...instance,
|
|
12490
|
+
status: "failed",
|
|
12491
|
+
error: {
|
|
12492
|
+
code: "WORKFLOW_EXPIRED",
|
|
12493
|
+
message: `Workflow instance expired (expiresAt: ${instance.expiresAt.toISOString()})`,
|
|
12494
|
+
nodeId: instance.currentNodeId,
|
|
12495
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
12496
|
+
},
|
|
12497
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
12498
|
+
};
|
|
12499
|
+
}
|
|
12500
|
+
const executionId = randomUUID();
|
|
12501
|
+
let current = {
|
|
12502
|
+
...instance,
|
|
12503
|
+
context: {
|
|
12504
|
+
...instance.context,
|
|
12505
|
+
variables: {
|
|
12506
|
+
...instance.context.variables,
|
|
12507
|
+
__lastExecutionId: executionId
|
|
12508
|
+
}
|
|
12509
|
+
}
|
|
12510
|
+
};
|
|
12244
12511
|
let nodeInput = input;
|
|
12245
12512
|
while (current.status === "running") {
|
|
12246
12513
|
const node = current.workflowSnapshot.nodes[current.currentNodeId];
|
|
@@ -12256,11 +12523,26 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12256
12523
|
}
|
|
12257
12524
|
};
|
|
12258
12525
|
}
|
|
12526
|
+
let objectDefinitions;
|
|
12527
|
+
if (this.schemaService) {
|
|
12528
|
+
try {
|
|
12529
|
+
const schemas = await Promise.all(
|
|
12530
|
+
current.workflowSnapshot.slots.map(
|
|
12531
|
+
(slot) => this.schemaService?.getObjectSchemaByName(slot.objectName)
|
|
12532
|
+
)
|
|
12533
|
+
);
|
|
12534
|
+
objectDefinitions = schemas.filter(
|
|
12535
|
+
(s) => s !== void 0
|
|
12536
|
+
);
|
|
12537
|
+
} catch {
|
|
12538
|
+
}
|
|
12539
|
+
}
|
|
12259
12540
|
const executorContext = {
|
|
12260
12541
|
instance: current,
|
|
12261
12542
|
definition: current.workflowSnapshot,
|
|
12262
12543
|
executionContext: current.context,
|
|
12263
|
-
input: nodeInput
|
|
12544
|
+
input: nodeInput,
|
|
12545
|
+
objectDefinitions
|
|
12264
12546
|
};
|
|
12265
12547
|
const startTime = Date.now();
|
|
12266
12548
|
const result = await this.executorRegistry.execute(node, executorContext);
|
|
@@ -12302,22 +12584,36 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12302
12584
|
break;
|
|
12303
12585
|
}
|
|
12304
12586
|
case "complete": {
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
updatedContext
|
|
12309
|
-
|
|
12310
|
-
|
|
12311
|
-
|
|
12587
|
+
try {
|
|
12588
|
+
let updatedContext = await this.persistSlots(current);
|
|
12589
|
+
if (this.documentProcessingHook) {
|
|
12590
|
+
updatedContext = await this.documentProcessingHook.process(
|
|
12591
|
+
updatedContext,
|
|
12592
|
+
current.workflowSnapshot,
|
|
12593
|
+
current.startedBy
|
|
12594
|
+
);
|
|
12595
|
+
}
|
|
12596
|
+
current = {
|
|
12597
|
+
...current,
|
|
12598
|
+
context: updatedContext,
|
|
12599
|
+
status: "completed",
|
|
12600
|
+
pendingAction: void 0,
|
|
12601
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
12602
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
12603
|
+
};
|
|
12604
|
+
} catch (persistError) {
|
|
12605
|
+
current = {
|
|
12606
|
+
...current,
|
|
12607
|
+
status: "failed",
|
|
12608
|
+
error: {
|
|
12609
|
+
code: "PERSIST_SLOTS_FAILED",
|
|
12610
|
+
message: persistError instanceof Error ? persistError.message : String(persistError),
|
|
12611
|
+
nodeId: current.currentNodeId,
|
|
12612
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
12613
|
+
},
|
|
12614
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
12615
|
+
};
|
|
12312
12616
|
}
|
|
12313
|
-
current = {
|
|
12314
|
-
...current,
|
|
12315
|
-
context: updatedContext,
|
|
12316
|
-
status: "completed",
|
|
12317
|
-
pendingAction: void 0,
|
|
12318
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
12319
|
-
completedAt: /* @__PURE__ */ new Date()
|
|
12320
|
-
};
|
|
12321
12617
|
break;
|
|
12322
12618
|
}
|
|
12323
12619
|
case "error": {
|
|
@@ -12341,6 +12637,34 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12341
12637
|
// ============================================================================
|
|
12342
12638
|
// HELPERS
|
|
12343
12639
|
// ============================================================================
|
|
12640
|
+
/**
|
|
12641
|
+
* Check a list of instances for expiration and mark any expired non-terminal
|
|
12642
|
+
* instances as "failed". Saves updated instances to the database.
|
|
12643
|
+
*/
|
|
12644
|
+
async markExpiredInstances(instances) {
|
|
12645
|
+
const now = /* @__PURE__ */ new Date();
|
|
12646
|
+
const terminalStatuses = ["completed", "failed", "cancelled"];
|
|
12647
|
+
return await Promise.all(
|
|
12648
|
+
instances.map(async (instance) => {
|
|
12649
|
+
if (instance.expiresAt && new Date(instance.expiresAt) < now && !terminalStatuses.includes(instance.status)) {
|
|
12650
|
+
const expired = {
|
|
12651
|
+
...instance,
|
|
12652
|
+
status: "failed",
|
|
12653
|
+
error: {
|
|
12654
|
+
code: "WORKFLOW_EXPIRED",
|
|
12655
|
+
message: `Workflow instance expired (expiresAt: ${instance.expiresAt.toISOString()})`,
|
|
12656
|
+
nodeId: instance.currentNodeId,
|
|
12657
|
+
timestamp: now
|
|
12658
|
+
},
|
|
12659
|
+
updatedAt: now
|
|
12660
|
+
};
|
|
12661
|
+
await this.saveInstance(expired);
|
|
12662
|
+
return expired;
|
|
12663
|
+
}
|
|
12664
|
+
return instance;
|
|
12665
|
+
})
|
|
12666
|
+
);
|
|
12667
|
+
}
|
|
12344
12668
|
mergeContext(base, updates) {
|
|
12345
12669
|
if (!updates) {
|
|
12346
12670
|
return base;
|
|
@@ -12364,11 +12688,15 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12364
12688
|
// SLOT PERSISTENCE
|
|
12365
12689
|
// ============================================================================
|
|
12366
12690
|
/**
|
|
12367
|
-
* Persist all slots as records in the database.
|
|
12691
|
+
* Persist all slots as records in the database using a saga pattern.
|
|
12368
12692
|
* - Slots with mode "create" create new records
|
|
12369
12693
|
* - Slots with mode "select" or "optional" with existing ID update the record
|
|
12370
12694
|
* - Slots with mode "optional" without ID create new records
|
|
12371
12695
|
*
|
|
12696
|
+
* If any slot fails to persist, all previously persisted slots in this batch
|
|
12697
|
+
* are rolled back (best-effort): created records are deleted, updated records
|
|
12698
|
+
* are restored to their previous state.
|
|
12699
|
+
*
|
|
12372
12700
|
* Returns updated context with createdRecordIds populated.
|
|
12373
12701
|
*/
|
|
12374
12702
|
async persistSlots(instance) {
|
|
@@ -12379,6 +12707,7 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12379
12707
|
const context = { ...instance.context };
|
|
12380
12708
|
const createdRecordIds = { ...context.createdRecordIds };
|
|
12381
12709
|
const sortedSlots = this.sortSlotsByDependencies(slots, context);
|
|
12710
|
+
const completedOperations = [];
|
|
12382
12711
|
for (const slot of sortedSlots) {
|
|
12383
12712
|
const slotData = context.slots[slot.id];
|
|
12384
12713
|
if (!slotData) continue;
|
|
@@ -12398,17 +12727,36 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12398
12727
|
}
|
|
12399
12728
|
});
|
|
12400
12729
|
createdRecordIds[slot.id] = record.id;
|
|
12730
|
+
completedOperations.push({
|
|
12731
|
+
slotId: slot.id,
|
|
12732
|
+
recordId: record.id,
|
|
12733
|
+
operation: "create"
|
|
12734
|
+
});
|
|
12401
12735
|
} else if (slot.mode === "select" && existingId) {
|
|
12736
|
+
const previousData = await this.snapshotRecord(existingId);
|
|
12402
12737
|
await this.recordService.updateRecord(existingId, dataToSave, {
|
|
12403
12738
|
partial: true
|
|
12404
12739
|
});
|
|
12405
12740
|
createdRecordIds[slot.id] = existingId;
|
|
12741
|
+
completedOperations.push({
|
|
12742
|
+
slotId: slot.id,
|
|
12743
|
+
recordId: existingId,
|
|
12744
|
+
operation: "update",
|
|
12745
|
+
previousData
|
|
12746
|
+
});
|
|
12406
12747
|
} else if (slot.mode === "optional") {
|
|
12407
12748
|
if (existingId) {
|
|
12749
|
+
const previousData = await this.snapshotRecord(existingId);
|
|
12408
12750
|
await this.recordService.updateRecord(existingId, dataToSave, {
|
|
12409
12751
|
partial: true
|
|
12410
12752
|
});
|
|
12411
12753
|
createdRecordIds[slot.id] = existingId;
|
|
12754
|
+
completedOperations.push({
|
|
12755
|
+
slotId: slot.id,
|
|
12756
|
+
recordId: existingId,
|
|
12757
|
+
operation: "update",
|
|
12758
|
+
previousData
|
|
12759
|
+
});
|
|
12412
12760
|
} else {
|
|
12413
12761
|
const record = await this.recordService.createRecord(objectId, dataToSave, {
|
|
12414
12762
|
allowDraft: true,
|
|
@@ -12418,11 +12766,18 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12418
12766
|
}
|
|
12419
12767
|
});
|
|
12420
12768
|
createdRecordIds[slot.id] = record.id;
|
|
12769
|
+
completedOperations.push({
|
|
12770
|
+
slotId: slot.id,
|
|
12771
|
+
recordId: record.id,
|
|
12772
|
+
operation: "create"
|
|
12773
|
+
});
|
|
12421
12774
|
}
|
|
12422
12775
|
}
|
|
12423
12776
|
} catch (error2) {
|
|
12777
|
+
const rolledBackSlots = await this.rollbackSlotOperations(completedOperations);
|
|
12778
|
+
const rollbackInfo = rolledBackSlots.length > 0 ? ` Rolled back slots: [${rolledBackSlots.join(", ")}].` : "";
|
|
12424
12779
|
throw new SchemaError(
|
|
12425
|
-
`Failed to persist slot "${slot.id}" (${slot.objectName}): ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
12780
|
+
`Failed to persist slot "${slot.id}" (${slot.objectName}): ${error2 instanceof Error ? error2.message : String(error2)}.${rollbackInfo}`,
|
|
12426
12781
|
SchemaErrorCode.VALIDATION_FAILED
|
|
12427
12782
|
);
|
|
12428
12783
|
}
|
|
@@ -12432,6 +12787,48 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12432
12787
|
createdRecordIds
|
|
12433
12788
|
};
|
|
12434
12789
|
}
|
|
12790
|
+
/**
|
|
12791
|
+
* Snapshot a record's current data for potential rollback.
|
|
12792
|
+
* Returns the record data or undefined if the record cannot be read.
|
|
12793
|
+
*/
|
|
12794
|
+
async snapshotRecord(recordId) {
|
|
12795
|
+
try {
|
|
12796
|
+
const record = await this.recordService?.getRecord(recordId, { skipPolicyCheck: true });
|
|
12797
|
+
return record?.values;
|
|
12798
|
+
} catch {
|
|
12799
|
+
return void 0;
|
|
12800
|
+
}
|
|
12801
|
+
}
|
|
12802
|
+
/**
|
|
12803
|
+
* Rollback completed slot persistence operations in reverse order (saga compensation).
|
|
12804
|
+
*
|
|
12805
|
+
* This is BEST EFFORT: errors during rollback are logged but never re-thrown.
|
|
12806
|
+
* - For "create" operations: deletes the created record
|
|
12807
|
+
* - For "update" operations: restores the previous data snapshot
|
|
12808
|
+
*
|
|
12809
|
+
* @returns Array of slot IDs that were successfully rolled back
|
|
12810
|
+
*/
|
|
12811
|
+
async rollbackSlotOperations(operations) {
|
|
12812
|
+
const rolledBack = [];
|
|
12813
|
+
for (const op of [...operations].reverse()) {
|
|
12814
|
+
try {
|
|
12815
|
+
if (op.operation === "create") {
|
|
12816
|
+
await this.recordService?.deleteRecord(op.recordId, {
|
|
12817
|
+
skipHooks: true,
|
|
12818
|
+
skipReferenceCheck: true
|
|
12819
|
+
});
|
|
12820
|
+
rolledBack.push(op.slotId);
|
|
12821
|
+
} else if (op.operation === "update" && op.previousData) {
|
|
12822
|
+
await this.recordService?.updateRecord(op.recordId, op.previousData, {
|
|
12823
|
+
partial: false
|
|
12824
|
+
});
|
|
12825
|
+
rolledBack.push(op.slotId);
|
|
12826
|
+
}
|
|
12827
|
+
} catch {
|
|
12828
|
+
}
|
|
12829
|
+
}
|
|
12830
|
+
return rolledBack;
|
|
12831
|
+
}
|
|
12435
12832
|
/**
|
|
12436
12833
|
* Clean slot data by removing undefined and null values.
|
|
12437
12834
|
* This prevents form submissions from overwriting existing record values
|
|
@@ -12546,18 +12943,30 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12546
12943
|
if (!this.adapter.workflowInstances) {
|
|
12547
12944
|
return;
|
|
12548
12945
|
}
|
|
12946
|
+
const currentVersion = instance.context.variables?.__version ?? 0;
|
|
12947
|
+
const nextVersion = currentVersion + 1;
|
|
12948
|
+
const instanceWithVersion = {
|
|
12949
|
+
...instance,
|
|
12950
|
+
context: {
|
|
12951
|
+
...instance.context,
|
|
12952
|
+
variables: {
|
|
12953
|
+
...instance.context.variables,
|
|
12954
|
+
__version: nextVersion
|
|
12955
|
+
}
|
|
12956
|
+
}
|
|
12957
|
+
};
|
|
12549
12958
|
await this.adapter.workflowInstances.upsert({
|
|
12550
|
-
id:
|
|
12551
|
-
workflowId:
|
|
12552
|
-
workflowVersion:
|
|
12553
|
-
workflowSnapshot:
|
|
12554
|
-
status:
|
|
12555
|
-
currentNodeId:
|
|
12556
|
-
context:
|
|
12557
|
-
history:
|
|
12558
|
-
pendingAction:
|
|
12559
|
-
startedBy:
|
|
12560
|
-
expiresAt:
|
|
12959
|
+
id: instanceWithVersion.id,
|
|
12960
|
+
workflowId: instanceWithVersion.workflowId,
|
|
12961
|
+
workflowVersion: instanceWithVersion.workflowVersion,
|
|
12962
|
+
workflowSnapshot: instanceWithVersion.workflowSnapshot,
|
|
12963
|
+
status: instanceWithVersion.status,
|
|
12964
|
+
currentNodeId: instanceWithVersion.currentNodeId,
|
|
12965
|
+
context: instanceWithVersion.context,
|
|
12966
|
+
history: instanceWithVersion.history,
|
|
12967
|
+
pendingAction: instanceWithVersion.pendingAction,
|
|
12968
|
+
startedBy: instanceWithVersion.startedBy,
|
|
12969
|
+
expiresAt: instanceWithVersion.expiresAt
|
|
12561
12970
|
});
|
|
12562
12971
|
}
|
|
12563
12972
|
convertDBInstanceToInstance(db) {
|
|
@@ -14823,6 +15232,13 @@ var FileService = class extends BaseService {
|
|
|
14823
15232
|
"StorageAdapter is not configured. Provide adapter.storage to use uploadFile()."
|
|
14824
15233
|
);
|
|
14825
15234
|
}
|
|
15235
|
+
if (input.folderPath) {
|
|
15236
|
+
let sanitized = input.folderPath;
|
|
15237
|
+
while (sanitized.includes("..")) {
|
|
15238
|
+
sanitized = sanitized.replace(/\.\./g, "");
|
|
15239
|
+
}
|
|
15240
|
+
input.folderPath = sanitized.replace(/^\/+/, "").replace(/\/+/g, "/");
|
|
15241
|
+
}
|
|
14826
15242
|
const uploadResult = await this.adapter.storage.upload({
|
|
14827
15243
|
content: input.content,
|
|
14828
15244
|
fileName: input.fileName,
|
|
@@ -15036,29 +15452,36 @@ var FileService = class extends BaseService {
|
|
|
15036
15452
|
* @param options - Delete options
|
|
15037
15453
|
*/
|
|
15038
15454
|
async bulkDelete(fileIds, options) {
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
await this.adapter.files.hardDelete(fileId);
|
|
15049
|
-
} else {
|
|
15050
|
-
await this.adapter.files.delete(fileId);
|
|
15051
|
-
}
|
|
15052
|
-
if (this.auditService && this.userId) {
|
|
15053
|
-
await this.auditService.logFileAction({
|
|
15054
|
-
action: "file.deleted",
|
|
15055
|
-
actorId: this.userId,
|
|
15056
|
-
fileId,
|
|
15057
|
-
fileName: file2.name,
|
|
15058
|
-
metadata: { deletedFromStorage: options?.deleteFromStorage ?? false }
|
|
15059
|
-
});
|
|
15455
|
+
if (fileIds.length === 0) return;
|
|
15456
|
+
const fileResults = await Promise.all(fileIds.map((id) => this.getFile(id)));
|
|
15457
|
+
const files = fileResults.filter((f) => f !== null);
|
|
15458
|
+
if (files.length === 0) return;
|
|
15459
|
+
if (options?.deleteFromStorage && this.adapter.storage) {
|
|
15460
|
+
const BATCH_SIZE = 10;
|
|
15461
|
+
for (let i = 0; i < files.length; i += BATCH_SIZE) {
|
|
15462
|
+
const batch = files.slice(i, i + BATCH_SIZE);
|
|
15463
|
+
await Promise.all(batch.map((file2) => this.adapter.storage?.delete(file2.storagePath)));
|
|
15060
15464
|
}
|
|
15061
15465
|
}
|
|
15466
|
+
const idsToDelete = files.map((f) => f.id);
|
|
15467
|
+
if (options?.hard) {
|
|
15468
|
+
await Promise.all(idsToDelete.map((id) => this.adapter.files.hardDelete(id)));
|
|
15469
|
+
} else {
|
|
15470
|
+
await Promise.all(idsToDelete.map((id) => this.adapter.files.delete(id)));
|
|
15471
|
+
}
|
|
15472
|
+
if (this.auditService && this.userId) {
|
|
15473
|
+
await Promise.all(
|
|
15474
|
+
files.map(
|
|
15475
|
+
(file2) => this.auditService?.logFileAction({
|
|
15476
|
+
action: "file.deleted",
|
|
15477
|
+
actorId: this.userId ?? "",
|
|
15478
|
+
fileId: file2.id,
|
|
15479
|
+
fileName: file2.name,
|
|
15480
|
+
metadata: { deletedFromStorage: options?.deleteFromStorage ?? false }
|
|
15481
|
+
})
|
|
15482
|
+
)
|
|
15483
|
+
);
|
|
15484
|
+
}
|
|
15062
15485
|
}
|
|
15063
15486
|
// ============================================================================
|
|
15064
15487
|
// LIST
|
|
@@ -15127,11 +15550,14 @@ var FileService = class extends BaseService {
|
|
|
15127
15550
|
* @param userId - User ID to check
|
|
15128
15551
|
* @returns true if user can access the file
|
|
15129
15552
|
*/
|
|
15130
|
-
async checkAccess(fileId, userId) {
|
|
15553
|
+
async checkAccess(fileId, userId, options) {
|
|
15131
15554
|
const file2 = await this.getFile(fileId);
|
|
15132
15555
|
if (!file2) {
|
|
15133
15556
|
return false;
|
|
15134
15557
|
}
|
|
15558
|
+
if (options?.isAdmin) {
|
|
15559
|
+
return true;
|
|
15560
|
+
}
|
|
15135
15561
|
if (file2.visibility === "public") {
|
|
15136
15562
|
return true;
|
|
15137
15563
|
}
|
|
@@ -15222,9 +15648,19 @@ var FileService = class extends BaseService {
|
|
|
15222
15648
|
};
|
|
15223
15649
|
|
|
15224
15650
|
// src/runtime/services/geocoding.service.ts
|
|
15651
|
+
var GEOCODING_TIMEOUT_MS = 5e3;
|
|
15652
|
+
function withTimeout(promise, ms, label) {
|
|
15653
|
+
return Promise.race([
|
|
15654
|
+
promise,
|
|
15655
|
+
new Promise(
|
|
15656
|
+
(_resolve, reject) => setTimeout(() => reject(new Error(`Geocoding ${label} timed out after ${ms}ms`)), ms)
|
|
15657
|
+
)
|
|
15658
|
+
]);
|
|
15659
|
+
}
|
|
15225
15660
|
var GeocodingService = class {
|
|
15226
|
-
constructor(adapter) {
|
|
15661
|
+
constructor(adapter, options) {
|
|
15227
15662
|
this.adapter = adapter;
|
|
15663
|
+
this.timeoutMs = options?.timeoutMs ?? GEOCODING_TIMEOUT_MS;
|
|
15228
15664
|
}
|
|
15229
15665
|
/**
|
|
15230
15666
|
* Search for address suggestions as the user types
|
|
@@ -15233,11 +15669,15 @@ var GeocodingService = class {
|
|
|
15233
15669
|
if (!params.query || params.query.trim().length < 2) {
|
|
15234
15670
|
return [];
|
|
15235
15671
|
}
|
|
15236
|
-
return await
|
|
15237
|
-
|
|
15238
|
-
|
|
15239
|
-
|
|
15240
|
-
|
|
15672
|
+
return await withTimeout(
|
|
15673
|
+
this.adapter.autocomplete({
|
|
15674
|
+
...params,
|
|
15675
|
+
query: params.query.trim(),
|
|
15676
|
+
limit: params.limit ?? 5
|
|
15677
|
+
}),
|
|
15678
|
+
this.timeoutMs,
|
|
15679
|
+
"autocomplete"
|
|
15680
|
+
);
|
|
15241
15681
|
}
|
|
15242
15682
|
/**
|
|
15243
15683
|
* Reverse geocode coordinates to an address
|
|
@@ -15246,7 +15686,7 @@ var GeocodingService = class {
|
|
|
15246
15686
|
if (!this.adapter.reverse) {
|
|
15247
15687
|
throw new Error("Reverse geocoding is not supported by the configured adapter");
|
|
15248
15688
|
}
|
|
15249
|
-
return await this.adapter.reverse(params);
|
|
15689
|
+
return await withTimeout(this.adapter.reverse(params), this.timeoutMs, "reverse");
|
|
15250
15690
|
}
|
|
15251
15691
|
/**
|
|
15252
15692
|
* Geocode a structured address to coordinates
|
|
@@ -15255,7 +15695,7 @@ var GeocodingService = class {
|
|
|
15255
15695
|
if (!this.adapter.geocode) {
|
|
15256
15696
|
throw new Error("Geocoding is not supported by the configured adapter");
|
|
15257
15697
|
}
|
|
15258
|
-
return await this.adapter.geocode(params);
|
|
15698
|
+
return await withTimeout(this.adapter.geocode(params), this.timeoutMs, "geocode");
|
|
15259
15699
|
}
|
|
15260
15700
|
};
|
|
15261
15701
|
|
|
@@ -15318,10 +15758,12 @@ var GlobalSearchService = class extends BaseService {
|
|
|
15318
15758
|
* @returns Results grouped by object name
|
|
15319
15759
|
*/
|
|
15320
15760
|
async searchGrouped(query, options) {
|
|
15761
|
+
const limitPerGroup = options?.limitPerGroup ?? 5;
|
|
15762
|
+
const estimatedGroupCount = 10;
|
|
15763
|
+
const fetchLimit = Math.min(limitPerGroup * estimatedGroupCount, 100);
|
|
15321
15764
|
const { results, total } = await this.search(query, {
|
|
15322
15765
|
...options,
|
|
15323
|
-
limit:
|
|
15324
|
-
// Get more for grouping
|
|
15766
|
+
limit: fetchLimit,
|
|
15325
15767
|
offset: 0
|
|
15326
15768
|
});
|
|
15327
15769
|
const groupMap = /* @__PURE__ */ new Map();
|
|
@@ -15339,6 +15781,7 @@ var GlobalSearchService = class extends BaseService {
|
|
|
15339
15781
|
}
|
|
15340
15782
|
const groups = Array.from(groupMap.values()).map((g) => ({
|
|
15341
15783
|
...g,
|
|
15784
|
+
results: g.results.slice(0, limitPerGroup),
|
|
15342
15785
|
count: g.results.length
|
|
15343
15786
|
}));
|
|
15344
15787
|
groups.sort((a, b) => b.count - a.count);
|
|
@@ -15551,6 +15994,7 @@ var PermissionService = class extends BaseService {
|
|
|
15551
15994
|
async updateRole(roleId, updates) {
|
|
15552
15995
|
const oldRole = await this.permissionsRepo.getRoleById(roleId);
|
|
15553
15996
|
const role = await this.permissionsRepo.updateRole(roleId, updates);
|
|
15997
|
+
await this.invalidateAllCache();
|
|
15554
15998
|
if (this.auditService && this.userId && oldRole) {
|
|
15555
15999
|
const changes = [];
|
|
15556
16000
|
if (updates.label !== void 0 && updates.label !== oldRole.label) {
|
|
@@ -15581,7 +16025,7 @@ var PermissionService = class extends BaseService {
|
|
|
15581
16025
|
async deleteRole(roleId) {
|
|
15582
16026
|
const role = await this.permissionsRepo.getRoleById(roleId);
|
|
15583
16027
|
await this.permissionsRepo.deleteRole(roleId);
|
|
15584
|
-
this.invalidateAllCache();
|
|
16028
|
+
await this.invalidateAllCache();
|
|
15585
16029
|
if (this.auditService && this.userId && role) {
|
|
15586
16030
|
await this.auditService.logRoleAction({
|
|
15587
16031
|
action: "role.deleted",
|
|
@@ -15605,7 +16049,7 @@ var PermissionService = class extends BaseService {
|
|
|
15605
16049
|
*/
|
|
15606
16050
|
async setPermissions(roleId, permissions) {
|
|
15607
16051
|
await this.permissionsRepo.setPermissions(roleId, permissions);
|
|
15608
|
-
this.invalidateAllCache();
|
|
16052
|
+
await this.invalidateAllCache();
|
|
15609
16053
|
if (this.auditService && this.userId) {
|
|
15610
16054
|
const role = await this.permissionsRepo.getRoleById(roleId);
|
|
15611
16055
|
await this.auditService.logRoleAction({
|
|
@@ -15635,7 +16079,7 @@ var PermissionService = class extends BaseService {
|
|
|
15635
16079
|
roleId,
|
|
15636
16080
|
assignedBy
|
|
15637
16081
|
});
|
|
15638
|
-
this.invalidateCache(userProfileId);
|
|
16082
|
+
await this.invalidateCache(userProfileId);
|
|
15639
16083
|
if (this.auditService && this.userId) {
|
|
15640
16084
|
const role = await this.permissionsRepo.getRoleById(roleId);
|
|
15641
16085
|
await this.auditService.logRoleAction({
|
|
@@ -15654,7 +16098,7 @@ var PermissionService = class extends BaseService {
|
|
|
15654
16098
|
async revokeRole(userProfileId, roleId) {
|
|
15655
16099
|
const role = await this.permissionsRepo.getRoleById(roleId);
|
|
15656
16100
|
await this.permissionsRepo.revokeRole(userProfileId, roleId);
|
|
15657
|
-
this.invalidateCache(userProfileId);
|
|
16101
|
+
await this.invalidateCache(userProfileId);
|
|
15658
16102
|
if (this.auditService && this.userId) {
|
|
15659
16103
|
await this.auditService.logRoleAction({
|
|
15660
16104
|
action: "role.revoked",
|
|
@@ -15995,8 +16439,8 @@ async function syncNativeViews(adapter, nativeViewRegistry, options = {}) {
|
|
|
15995
16439
|
errors: []
|
|
15996
16440
|
};
|
|
15997
16441
|
const nativeViews = nativeViewRegistry.getAll();
|
|
15998
|
-
if (options.verbose) {
|
|
15999
|
-
|
|
16442
|
+
if (options.verbose && options.logger) {
|
|
16443
|
+
options.logger.info(`[ViewSync] Starting sync for ${nativeViews.length} native views...`);
|
|
16000
16444
|
}
|
|
16001
16445
|
try {
|
|
16002
16446
|
await adapter.transaction(async (tx) => {
|
|
@@ -16026,24 +16470,21 @@ function trackViewByObject(viewsByObject, nativeView) {
|
|
|
16026
16470
|
objectViews.push(nativeView.name);
|
|
16027
16471
|
viewsByObject.set(nativeView.object, objectViews);
|
|
16028
16472
|
}
|
|
16029
|
-
function handleViewSyncError(result, nativeView, error2,
|
|
16473
|
+
function handleViewSyncError(result, nativeView, error2, _options) {
|
|
16030
16474
|
result.success = false;
|
|
16031
16475
|
result.errors.push({
|
|
16032
16476
|
viewName: nativeView.name,
|
|
16033
16477
|
objectName: nativeView.object,
|
|
16034
16478
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
16035
16479
|
});
|
|
16036
|
-
if (options.verbose) {
|
|
16037
|
-
console.error(`[ViewSync] \u2717 Failed to sync ${nativeView.object}:${nativeView.name}:`, error2);
|
|
16038
|
-
}
|
|
16039
16480
|
}
|
|
16040
16481
|
async function cleanupRemovedViews(tx, viewsByObject, result, options) {
|
|
16041
16482
|
if (options.dryRun) return;
|
|
16042
16483
|
for (const [objectName, viewNames] of viewsByObject) {
|
|
16043
16484
|
const deletedCount = await tx.views.deleteNotIn(objectName, viewNames);
|
|
16044
16485
|
result.viewsDeleted += deletedCount;
|
|
16045
|
-
if (options.verbose && deletedCount > 0) {
|
|
16046
|
-
|
|
16486
|
+
if (options.verbose && deletedCount > 0 && options.logger) {
|
|
16487
|
+
options.logger.info(`[ViewSync] Deleted ${deletedCount} obsolete views for ${objectName}`);
|
|
16047
16488
|
}
|
|
16048
16489
|
}
|
|
16049
16490
|
}
|
|
@@ -16056,8 +16497,8 @@ function handleTransactionError(result, error2) {
|
|
|
16056
16497
|
});
|
|
16057
16498
|
}
|
|
16058
16499
|
function logSyncComplete(result, options) {
|
|
16059
|
-
if (options.verbose) {
|
|
16060
|
-
|
|
16500
|
+
if (options.verbose && options.logger) {
|
|
16501
|
+
options.logger.info(
|
|
16061
16502
|
`[ViewSync] ${result.success ? "\u2713" : "\u2717"} Sync complete:
|
|
16062
16503
|
Views: ${result.viewsCreated} created, ${result.viewsUpdated} updated, ${result.viewsDeleted} deleted
|
|
16063
16504
|
Errors: ${result.errors.length}`
|
|
@@ -16077,8 +16518,8 @@ async function syncSingleView(adapter, nativeView, result, options) {
|
|
|
16077
16518
|
}
|
|
16078
16519
|
result.viewsSynced++;
|
|
16079
16520
|
if (options.dryRun) {
|
|
16080
|
-
if (options.verbose) {
|
|
16081
|
-
|
|
16521
|
+
if (options.verbose && options.logger) {
|
|
16522
|
+
options.logger.info(
|
|
16082
16523
|
`[ViewSync] Would ${isNew ? "create" : "update"} view: ${nativeView.object}:${nativeView.name}`
|
|
16083
16524
|
);
|
|
16084
16525
|
}
|
|
@@ -16095,8 +16536,8 @@ async function syncSingleView(adapter, nativeView, result, options) {
|
|
|
16095
16536
|
system: true,
|
|
16096
16537
|
metadata: nativeView.metadata
|
|
16097
16538
|
});
|
|
16098
|
-
if (options.verbose) {
|
|
16099
|
-
|
|
16539
|
+
if (options.verbose && options.logger) {
|
|
16540
|
+
options.logger.info(
|
|
16100
16541
|
`[ViewSync] \u2713 Synced ${nativeView.object}:${nativeView.name}: ${nativeView.tabs.length} tabs`
|
|
16101
16542
|
);
|
|
16102
16543
|
}
|
|
@@ -16161,9 +16602,6 @@ async function syncNativeObjects(adapter, nativeRegistry, options = {}) {
|
|
|
16161
16602
|
objectName: nativeObject.name,
|
|
16162
16603
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
16163
16604
|
});
|
|
16164
|
-
if (options.verbose) {
|
|
16165
|
-
console.error(`[SyncService] \u2717 Failed to sync ${nativeObject.name}:`, error2);
|
|
16166
|
-
}
|
|
16167
16605
|
}
|
|
16168
16606
|
}
|
|
16169
16607
|
});
|
|
@@ -16449,6 +16887,8 @@ export {
|
|
|
16449
16887
|
ProtectedRoleError,
|
|
16450
16888
|
RoleNotFoundError,
|
|
16451
16889
|
isForbiddenError,
|
|
16890
|
+
ConcurrentModificationError,
|
|
16891
|
+
isConcurrentModificationError,
|
|
16452
16892
|
text,
|
|
16453
16893
|
textarea,
|
|
16454
16894
|
richtext,
|
|
@@ -16640,8 +17080,6 @@ export {
|
|
|
16640
17080
|
BaseService,
|
|
16641
17081
|
BaseRepository,
|
|
16642
17082
|
SchemaContextAwareRepository,
|
|
16643
|
-
TenantAwareRepository,
|
|
16644
|
-
TenantAwareService,
|
|
16645
17083
|
buildAuditChanges,
|
|
16646
17084
|
ObjectSchemaService,
|
|
16647
17085
|
AuditService,
|