@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.
@@ -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: "Votre session a expir\xE9. Veuillez cliquer \xE0 nouveau sur le lien re\xE7u par email.",
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: "Lien invalide ou corrompu. Contactez l'exp\xE9diteur.",
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: "Format de token invalide.",
185
+ error: "MALFORMED",
186
186
  errorCode: "MALFORMED"
187
187
  };
188
188
  }
189
189
  }
190
190
  return {
191
191
  valid: false,
192
- error: "Authentification \xE9chou\xE9e.",
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
- /** Computed rollup value for a record */
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
- throw new Error(
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
- throw new Error(`FormNode "${node.id}" has no 'next' target defined`);
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 ?? "").replace(new RegExp(search, "g"), replacement);
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/format.ts
2566
- import { getCountryByIso3 } from "@stndrds/constants";
2567
- var EMPTY_VALUE_PLACEHOLDER = "\u2014";
2568
- function formatText(value) {
2569
- return String(value);
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 phone2.phoneNumber;
2628
+ return userId;
2627
2629
  }
2628
- function formatLocation(value, attribute) {
2629
- if (typeof value !== "object" || value === null) return String(value);
2630
- const loc = value;
2631
- const granularity = attribute.granularity ?? "full";
2632
- const parts = [];
2633
- switch (granularity) {
2634
- case "country":
2635
- if (loc.country) parts.push(loc.country);
2636
- break;
2637
- case "state":
2638
- if (loc.state) parts.push(loc.state);
2639
- if (loc.country) parts.push(loc.country);
2640
- break;
2641
- case "city":
2642
- if (loc.city) parts.push(loc.city);
2643
- if (loc.state) parts.push(loc.state);
2644
- if (loc.country) parts.push(loc.country);
2645
- break;
2646
- case "coordinates":
2647
- if (loc.latitude !== void 0 && loc.longitude !== void 0) {
2648
- parts.push(`${loc.latitude}, ${loc.longitude}`);
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
- break;
2651
- case "address":
2652
- if (loc.address) parts.push(loc.address);
2653
- if (loc.city) parts.push(loc.city);
2654
- if (loc.state) parts.push(loc.state);
2655
- if (loc.country) parts.push(loc.country);
2656
- break;
2657
- default:
2658
- if (loc.address) parts.push(loc.address);
2659
- if (loc.city) parts.push(loc.city);
2660
- if (loc.state) parts.push(loc.state);
2661
- if (loc.postalCode) parts.push(loc.postalCode);
2662
- if (loc.country) parts.push(loc.country);
2663
- break;
2664
- }
2665
- return parts.join(", ") || EMPTY_VALUE_PLACEHOLDER;
2666
- }
2667
- function formatSelect(value, attribute) {
2668
- if (typeof value !== "string") return String(value);
2669
- const option = attribute.options?.find((o) => o.value === value);
2670
- return option?.label ?? String(value);
2671
- }
2672
- function formatMultiselect(value, attribute) {
2673
- if (!Array.isArray(value)) return String(value);
2674
- if (attribute.options) {
2675
- const labels = value.map((v) => attribute.options.find((o) => o.value === v)?.label).filter(Boolean);
2676
- return labels.join(", ");
2677
- }
2678
- return value.join(", ");
2679
- }
2680
- function formatRating(value, attribute) {
2681
- if (typeof value !== "number") return String(value);
2682
- const max = attribute.max ?? 5;
2683
- return `${value}/${max}`;
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 formatAttributeValue(value, attribute) {
2686
- if (value === null || value === void 0 || value === "") {
2687
- return EMPTY_VALUE_PLACEHOLDER;
2688
- }
2689
- switch (attribute.type) {
2690
- case "text":
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
- findById(id) {
2880
- return Promise.resolve(stores.objects.get(id) ?? null);
2742
+ get() {
2743
+ const key = getKey();
2744
+ return Promise.resolve(stores.aiUserMemory.get(key) ?? null);
2881
2745
  },
2882
- findByName(name) {
2746
+ upsert(data) {
2747
+ const key = getKey();
2883
2748
  const tenantId = getTenantId();
2884
- for (const obj of stores.objects.values()) {
2885
- if (obj.tenantId === tenantId && obj.name === name) {
2886
- return Promise.resolve(obj);
2887
- }
2888
- }
2889
- return Promise.resolve(null);
2890
- },
2891
- findSystemByName(name) {
2892
- for (const obj of stores.objects.values()) {
2893
- if (obj.system && obj.name === name) {
2894
- return Promise.resolve(obj);
2895
- }
2896
- }
2897
- return Promise.resolve(null);
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
- create(data) {
2764
+ addFact(fact) {
2765
+ const key = getKey();
2900
2766
  const tenantId = getTenantId();
2901
- const obj = {
2902
- id: generateId(),
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
- name: data.name,
2905
- label: data.label,
2906
- pluralLabel: data.pluralLabel,
2907
- description: data.description,
2908
- icon: data.icon,
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.objects.set(obj.id, obj);
2917
- return Promise.resolve(obj);
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
- function createMockUserProfilesRepository(stores) {
3064
- return {
3065
- findById(id) {
3066
- return Promise.resolve(stores.userProfiles.get(id) ?? null);
3067
- },
3068
- findByIds(ids) {
3069
- const tenantId = getTenantId();
3070
- const results = [];
3071
- for (const id of ids) {
3072
- const profile = stores.userProfiles.get(id);
3073
- if (profile && profile.tenantId === tenantId) {
3074
- results.push(profile);
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
- return Promise.resolve(results);
3078
- },
3079
- findByAuthId(authId) {
3080
- for (const profile of stores.userProfiles.values()) {
3081
- if (profile.authId === authId) {
3082
- return Promise.resolve(profile);
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 Promise.resolve(null);
3086
- },
3087
- findByEmail(email) {
3088
- const tenantId = getTenantId();
3089
- for (const profile of stores.userProfiles.values()) {
3090
- if (profile.tenantId === tenantId && profile.email === email) {
3091
- return Promise.resolve(profile);
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 createMockFilesRepository(stores) {
3174
- return {
3175
- findById(id) {
3176
- const file2 = stores.files.get(id);
3177
- if (file2?.deletedAt) return Promise.resolve(null);
3178
- return Promise.resolve(file2 ?? null);
3179
- },
3180
- findByIds(ids) {
3181
- const files = ids.map((id) => stores.files.get(id)).filter((f) => f != null && !f.deletedAt);
3182
- return Promise.resolve(files);
3183
- },
3184
- create(data) {
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 { __completionStatus, __label, __metadata, __lastUpdatedBy, ...valueData } = data;
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
- function createMockViewsRepository(stores) {
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.views.get(id) ?? null);
4021
+ return Promise.resolve(stores.userProfiles.get(id) ?? null);
3539
4022
  },
3540
- findByName(objectName, viewName) {
4023
+ findByIds(ids) {
3541
4024
  const tenantId = getTenantId();
3542
- return Promise.resolve(
3543
- Array.from(stores.views.values()).find(
3544
- (v) => v.tenantId === tenantId && v.objectName === objectName && v.name === viewName
3545
- ) ?? null
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
- findByObjectName(objectName) {
3549
- const tenantId = getTenantId();
3550
- return Promise.resolve(
3551
- Array.from(stores.views.values()).filter(
3552
- (v) => v.tenantId === tenantId && v.objectName === objectName
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
- findAllForTenant() {
4042
+ findByEmail(email) {
3557
4043
  const tenantId = getTenantId();
3558
- return Promise.resolve(
3559
- Array.from(stores.views.values()).filter((v) => v.tenantId === tenantId)
3560
- );
3561
- },
3562
- findSystemByName(objectName, viewName) {
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 id = generateId();
3577
- const now = /* @__PURE__ */ new Date();
3578
- const dbView = {
3579
- id,
4053
+ const profile = {
4054
+ id: generateId(),
3580
4055
  tenantId,
3581
- objectName: data.objectName,
3582
- name: data.name,
3583
- label: data.label,
3584
- description: data.description,
3585
- icon: data.icon,
3586
- tabs: data.tabs,
3587
- default: data.default ?? false,
3588
- system: data.system ?? false,
3589
- metadata: data.metadata,
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.views.set(id, dbView);
3594
- return Promise.resolve(dbView);
4066
+ stores.userProfiles.set(profile.id, profile);
4067
+ return Promise.resolve(profile);
3595
4068
  },
3596
4069
  update(id, data) {
3597
- const view2 = stores.views.get(id);
3598
- if (!view2) {
3599
- return Promise.reject(new Error(`View not found: ${id}`));
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
- ...view2,
4075
+ ...existing,
3603
4076
  ...data,
3604
4077
  updatedAt: /* @__PURE__ */ new Date()
3605
4078
  };
3606
- stores.views.set(id, updated);
4079
+ stores.userProfiles.set(id, updated);
3607
4080
  return Promise.resolve(updated);
3608
4081
  },
3609
4082
  delete(id) {
3610
- stores.views.delete(id);
4083
+ stores.userProfiles.delete(id);
3611
4084
  return Promise.resolve();
3612
4085
  },
3613
- deleteNotIn(objectName, keepViewNames) {
3614
- let deleted = 0;
3615
- for (const [id, view2] of stores.views.entries()) {
3616
- if (view2.objectName === objectName && view2.system && !keepViewNames.includes(view2.name)) {
3617
- stores.views.delete(id);
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(deleted);
4092
+ return Promise.resolve(results);
3622
4093
  },
3623
- async upsert(data) {
3624
- const existing = data.system ? await this.findSystemByName(data.objectName, data.name) : await this.findByName(data.objectName, data.name);
3625
- if (existing) {
3626
- return this.update(existing.id, {
3627
- label: data.label,
3628
- description: data.description,
3629
- icon: data.icon,
3630
- tabs: data.tabs,
3631
- default: data.default,
3632
- metadata: data.metadata
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 this.create(data);
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 createMockAIUserMemoryRepository(stores) {
4282
- const getKey = () => {
4283
- const tenantId = getTenantId();
4284
- const userId = requireUserId();
4285
- return `${tenantId}:${userId}`;
4286
- };
4644
+ function createMockWorkflowInvitationsRepository(stores) {
4287
4645
  return {
4288
- get() {
4289
- const key = getKey();
4290
- return Promise.resolve(stores.aiUserMemory.get(key) ?? null);
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
- const userId = requireUserId();
4296
- const now = /* @__PURE__ */ new Date();
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
- addFact(fact) {
4311
- const key = getKey();
4653
+ findByInstanceId(instanceId) {
4312
4654
  const tenantId = getTenantId();
4313
- const userId = requireUserId();
4314
- const now = /* @__PURE__ */ new Date();
4315
- const existing = stores.aiUserMemory.get(key);
4316
- const memory = {
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
- removeFact(fact) {
4329
- const key = getKey();
4660
+ findByEmail(email) {
4330
4661
  const tenantId = getTenantId();
4331
- const userId = requireUserId();
4332
- const now = /* @__PURE__ */ new Date();
4333
- const existing = stores.aiUserMemory.get(key);
4334
- const memory = {
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
- setPreference(prefKey, value) {
4347
- const memoryKey = getKey();
4667
+ create(data) {
4348
4668
  const tenantId = getTenantId();
4349
- const userId = requireUserId();
4350
- const now = /* @__PURE__ */ new Date();
4351
- const existing = stores.aiUserMemory.get(memoryKey);
4352
- const memory = {
4353
- id: existing?.id ?? generateId(),
4354
- tenantId,
4355
- userId,
4356
- preferences: { ...existing?.preferences ?? {}, [prefKey]: value },
4357
- facts: existing?.facts ?? [],
4358
- createdAt: existing?.createdAt ?? now,
4359
- updatedAt: now
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.aiUserMemory.set(memoryKey, memory);
4362
- return Promise.resolve(memory);
4682
+ stores.workflowInvitations.set(invitation.id, invitation);
4683
+ return Promise.resolve(invitation);
4363
4684
  },
4364
- clear() {
4365
- const key = getKey();
4366
- stores.aiUserMemory.delete(key);
4367
- return Promise.resolve();
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 createMockAIUsageMetricsRepository(stores) {
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
- recordUsage(data) {
4703
+ findById(id) {
4704
+ const grant = stores.workflowAccessGrants.get(id);
4705
+ if (!grant) return Promise.resolve(null);
4379
4706
  const tenantId = getTenantId();
4380
- const now = /* @__PURE__ */ new Date();
4381
- const key = getDateKey(now);
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
- getByDateRange(startDate, endDate) {
4710
+ findByInvitationId(invitationId) {
4408
4711
  const tenantId = getTenantId();
4409
- const results = Array.from(stores.aiUsageMetrics.values()).filter((m) => {
4410
- if (m.tenantId !== tenantId) return false;
4411
- return m.date >= startDate && m.date <= endDate;
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
- getCurrentMonthUsage() {
4717
+ findByInstanceId(instanceId) {
4417
4718
  const tenantId = getTenantId();
4418
- const now = /* @__PURE__ */ new Date();
4419
- const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
4420
- const monthMetrics = Array.from(stores.aiUsageMetrics.values()).filter((m) => {
4421
- if (m.tenantId !== tenantId) return false;
4422
- return m.date >= startOfMonth;
4423
- });
4424
- const aggregated = {
4425
- requestCount: 0,
4426
- totalTokens: 0,
4427
- totalCost: 0,
4428
- providerBreakdown: {}
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
- for (const m of monthMetrics) {
4431
- aggregated.requestCount += m.requestCount;
4432
- aggregated.totalTokens += m.totalTokens;
4433
- aggregated.totalCost += m.totalCost;
4434
- for (const [provider, stats] of Object.entries(m.providerBreakdown)) {
4435
- if (!aggregated.providerBreakdown[provider]) {
4436
- aggregated.providerBreakdown[provider] = { requests: 0, tokens: 0, cost: 0 };
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
- return Promise.resolve(aggregated);
4444
- }
4445
- };
4446
- }
4447
- function createMockAdapter() {
4448
- const stores = {
4449
- objects: /* @__PURE__ */ new Map(),
4450
- attributes: /* @__PURE__ */ new Map(),
4451
- userProfiles: /* @__PURE__ */ new Map(),
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(new RegExp(attr.pattern), messages.invalidPattern(attr));
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).strict();
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).strict();
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
- async getObjectOwnerInfo(objectId) {
8366
- const dbObject = await this.adapter.objects.findById(objectId);
8367
- if (!dbObject) {
8368
- throw new Error(`Object with id "${objectId}" not found`);
8369
- }
8370
- return {
8371
- tenantId: dbObject.tenantId,
8372
- sharingMode: dbObject.sharingMode
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
- var RoleNotFoundError = class extends NotFoundError {
8778
- constructor(roleId) {
8779
- super("Role", roleId, SchemaErrorCode.ROLE_NOT_FOUND);
8780
- this.name = "RoleNotFoundError";
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(console.error);
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 Promise.all(
9563
- parentRecords.map(async (parentRecord) => {
9564
- const parentSchema = parentSchemas.get(parentRecord.objectId);
9565
- if (!parentSchema) return;
9566
- const rollupAttrs = parentSchema.attributes.filter(
9567
- (a) => a.type === "rollup"
9568
- );
9569
- if (rollupAttrs.length > 0) {
9570
- await rollupService.recalculateAndUpdate(parentRecord, parentSchema);
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 Promise.all(
9579
- affectedForwardRecords.map(async (forwardRecord) => {
9580
- const forwardSchema = forwardSchemas.get(forwardRecord.objectId);
9581
- if (!forwardSchema) return;
9582
- await rollupService.recalculateAndUpdate(forwardRecord, forwardSchema);
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
- filteredRecords = result.records.filter((record) => policy.canAccessRecord?.(ctx, record));
9662
- effectiveTotal = filteredRecords.length;
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 target of filteredTargets) {
10026
- const objectSchema = await this.schemaService.getObjectSchemaByName(target.object);
10027
- if (!objectSchema?.id) {
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
- for (const obj of allObjects) {
10578
- if (obj.id === changedRecord.objectId) {
10579
- continue;
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
- const attributes = await this.adapter.attributes.findByObjectId(obj.id);
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
- if (rollupAttrs.length === 0) {
10584
- continue;
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.invalidateLists("allRecordLists", objectId);
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
- await this.auditService.logRecordAction({
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) => existing.values[key] !== data[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.invalidateCache(cacheKeys.record(this.tenantId, recordId));
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
- await this.auditService.logRecordAction({
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.invalidateCache(cacheKeys.record(this.tenantId, recordId));
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
- await this.auditService.logRecordAction({
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.invalidateCache(cacheKeys.record(this.tenantId, recordId));
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.invalidateCache(cacheKeys.record(this.tenantId, recordId));
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
- await this.auditService.logRecordAction({
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 (error2) {
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 (error2) {
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
- const updatedInstance = await this.executeCurrentNode(instance);
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
- const result = await this.executeCurrentNode(updatedInstance, input.input);
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
- return this.convertDBInstanceToInstance(dbInstance);
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 instances2 = await this.getInstancesByWorkflow(options.workflowName);
12183
- let filtered = instances2;
12184
- if (options.status) {
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
- return { instances: paginated, total: total2 };
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
- let current = instance;
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
- let updatedContext = await this.persistSlots(current);
12306
- if (this.documentProcessingHook) {
12307
- updatedContext = await this.documentProcessingHook.process(
12308
- updatedContext,
12309
- current.workflowSnapshot,
12310
- current.startedBy
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: instance.id,
12551
- workflowId: instance.workflowId,
12552
- workflowVersion: instance.workflowVersion,
12553
- workflowSnapshot: instance.workflowSnapshot,
12554
- status: instance.status,
12555
- currentNodeId: instance.currentNodeId,
12556
- context: instance.context,
12557
- history: instance.history,
12558
- pendingAction: instance.pendingAction,
12559
- startedBy: instance.startedBy,
12560
- expiresAt: instance.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
- for (const fileId of fileIds) {
15040
- const file2 = await this.getFile(fileId);
15041
- if (!file2) {
15042
- continue;
15043
- }
15044
- if (options?.deleteFromStorage && this.adapter.storage) {
15045
- await this.adapter.storage.delete(file2.storagePath);
15046
- }
15047
- if (options?.hard) {
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 this.adapter.autocomplete({
15237
- ...params,
15238
- query: params.query.trim(),
15239
- limit: params.limit ?? 5
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: 100,
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
- console.info(`[ViewSync] Starting sync for ${nativeViews.length} native views...`);
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, options) {
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
- console.info(`[ViewSync] Deleted ${deletedCount} obsolete views for ${objectName}`);
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
- console.info(
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
- console.info(
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
- console.info(
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,