@fayz-ai/core 0.18.0 → 0.19.0

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.
Files changed (98) hide show
  1. package/dist/access/engine.d.ts +15 -1
  2. package/dist/access/engine.d.ts.map +1 -1
  3. package/dist/access/index.js +2 -2
  4. package/dist/access/index.js.map +1 -1
  5. package/dist/app/app-config.d.ts +25 -0
  6. package/dist/app/app-config.d.ts.map +1 -1
  7. package/dist/{chunk-ED6NBH6U.js → chunk-3TRR2YLM.js} +2 -2
  8. package/dist/{chunk-ED6NBH6U.js.map → chunk-3TRR2YLM.js.map} +1 -1
  9. package/dist/{chunk-AOIYRUTF.js → chunk-4SPEISNL.js} +19 -5
  10. package/dist/chunk-4SPEISNL.js.map +1 -0
  11. package/dist/chunk-6TVRNKWW.js +17 -0
  12. package/dist/chunk-6TVRNKWW.js.map +1 -0
  13. package/dist/{chunk-OHH26S4N.js → chunk-7M4NWLIT.js} +9 -8
  14. package/dist/chunk-7M4NWLIT.js.map +1 -0
  15. package/dist/{chunk-2VY5IJW7.js → chunk-H6XDQXN5.js} +42 -14
  16. package/dist/chunk-H6XDQXN5.js.map +1 -0
  17. package/dist/{chunk-BCI743RH.js → chunk-HVWFFFKN.js} +4 -3
  18. package/dist/chunk-HVWFFFKN.js.map +1 -0
  19. package/dist/chunk-RBPDK2WB.js +104 -0
  20. package/dist/chunk-RBPDK2WB.js.map +1 -0
  21. package/dist/{chunk-2ZAUJVYP.js → chunk-S3EEGDEK.js} +122 -48
  22. package/dist/chunk-S3EEGDEK.js.map +1 -0
  23. package/dist/{chunk-KJUD6JA5.js → chunk-ZGAGCTQM.js} +6 -5
  24. package/dist/chunk-ZGAGCTQM.js.map +1 -0
  25. package/dist/connector-runtime/index.js +1 -1
  26. package/dist/data/archetype.d.ts +16 -0
  27. package/dist/data/archetype.d.ts.map +1 -1
  28. package/dist/data/bulk.d.ts.map +1 -1
  29. package/dist/data/index.js +3 -2
  30. package/dist/data/resolve.d.ts.map +1 -1
  31. package/dist/data/supabase.d.ts.map +1 -1
  32. package/dist/data/types.d.ts +13 -0
  33. package/dist/data/types.d.ts.map +1 -1
  34. package/dist/events/log.d.ts +1 -1
  35. package/dist/events/log.d.ts.map +1 -1
  36. package/dist/fields/store.d.ts +1 -1
  37. package/dist/fields/store.d.ts.map +1 -1
  38. package/dist/i18n/index.d.ts +11 -0
  39. package/dist/i18n/index.d.ts.map +1 -1
  40. package/dist/i18n/index.js +1 -1
  41. package/dist/i18n/shell-translations.en.d.ts.map +1 -1
  42. package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -1
  43. package/dist/index.d.ts +9 -4
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +19 -12
  46. package/dist/index.js.map +1 -1
  47. package/dist/integrations/index.d.ts +8 -8
  48. package/dist/integrations/index.d.ts.map +1 -1
  49. package/dist/integrations/index.js +4 -3
  50. package/dist/integrations/request.d.ts +2 -2
  51. package/dist/integrations/request.d.ts.map +1 -1
  52. package/dist/integrations/runtime.d.ts +1 -1
  53. package/dist/integrations/runtime.d.ts.map +1 -1
  54. package/dist/integrations/settings.d.ts +1 -1
  55. package/dist/integrations/settings.d.ts.map +1 -1
  56. package/dist/integrations/store.d.ts +7 -7
  57. package/dist/integrations/store.d.ts.map +1 -1
  58. package/dist/interaction/haptics.d.ts +5 -0
  59. package/dist/interaction/haptics.d.ts.map +1 -0
  60. package/dist/interaction/index.d.ts +7 -0
  61. package/dist/interaction/index.d.ts.map +1 -0
  62. package/dist/interaction/index.js +3 -0
  63. package/dist/interaction/index.js.map +1 -0
  64. package/dist/interaction/keymap.d.ts +17 -0
  65. package/dist/interaction/keymap.d.ts.map +1 -0
  66. package/dist/interaction/roving.d.ts +9 -0
  67. package/dist/interaction/roving.d.ts.map +1 -0
  68. package/dist/lib/error-message.d.ts +21 -0
  69. package/dist/lib/error-message.d.ts.map +1 -0
  70. package/dist/orders/index.d.ts +114 -0
  71. package/dist/orders/index.d.ts.map +1 -0
  72. package/dist/orders/index.js +168 -0
  73. package/dist/orders/index.js.map +1 -0
  74. package/dist/plugin/index.js +2 -1
  75. package/dist/plugin/runtime.d.ts.map +1 -1
  76. package/dist/registry/extension-points.d.ts.map +1 -1
  77. package/dist/{shell-translations.pt-br-ZSSVWG6H.js → shell-translations.pt-br-GFBWBZZJ.js} +28 -12
  78. package/dist/shell-translations.pt-br-GFBWBZZJ.js.map +1 -0
  79. package/dist/testing/index.js +5 -4
  80. package/dist/testing.d.ts.map +1 -1
  81. package/dist/types/auth.d.ts +12 -0
  82. package/dist/types/auth.d.ts.map +1 -1
  83. package/dist/types/crud.d.ts +42 -0
  84. package/dist/types/crud.d.ts.map +1 -1
  85. package/dist/types/onboarding.d.ts +25 -0
  86. package/dist/types/onboarding.d.ts.map +1 -1
  87. package/dist/types/plugins.d.ts +43 -0
  88. package/dist/types/plugins.d.ts.map +1 -1
  89. package/dist/types/setup-guide.d.ts +120 -0
  90. package/dist/types/setup-guide.d.ts.map +1 -0
  91. package/package.json +12 -3
  92. package/dist/chunk-2VY5IJW7.js.map +0 -1
  93. package/dist/chunk-2ZAUJVYP.js.map +0 -1
  94. package/dist/chunk-AOIYRUTF.js.map +0 -1
  95. package/dist/chunk-BCI743RH.js.map +0 -1
  96. package/dist/chunk-KJUD6JA5.js.map +0 -1
  97. package/dist/chunk-OHH26S4N.js.map +0 -1
  98. package/dist/shell-translations.pt-br-ZSSVWG6H.js.map +0 -1
@@ -0,0 +1,104 @@
1
+ // src/interaction/haptics.ts
2
+ var PATTERN = {
3
+ /** Something registered: an item rung, a card picked. */
4
+ tap: 8,
5
+ /** Something finished: a bill closed, an order fired, a ticket bumped. */
6
+ commit: [12, 40, 18],
7
+ /** Something needs a decision: a PIN gate, a required modifier. */
8
+ warn: [10, 60, 10],
9
+ /** Something was refused: wrong PIN, blocked action. */
10
+ reject: [22, 50, 22]
11
+ };
12
+ var enabled = true;
13
+ function setHapticsEnabled(value) {
14
+ enabled = value;
15
+ }
16
+ function haptic(kind = "tap") {
17
+ if (!enabled) return;
18
+ if (typeof navigator === "undefined" || typeof navigator.vibrate !== "function") return;
19
+ try {
20
+ navigator.vibrate(PATTERN[kind]);
21
+ } catch {
22
+ }
23
+ }
24
+
25
+ // src/interaction/keymap.ts
26
+ function comboOf(event) {
27
+ const parts = [];
28
+ if (event.ctrlKey) parts.push("Ctrl");
29
+ if (event.altKey) parts.push("Alt");
30
+ if (event.shiftKey && event.key.length > 1) parts.push("Shift");
31
+ parts.push(event.key.length === 1 ? event.key.toUpperCase() : event.key);
32
+ return parts.join("+");
33
+ }
34
+ function createKeymap() {
35
+ const bindings = /* @__PURE__ */ new Map();
36
+ return {
37
+ register(binding) {
38
+ const key = binding.combo.toLowerCase();
39
+ bindings.set(key, binding);
40
+ return () => {
41
+ if (bindings.get(key) === binding) bindings.delete(key);
42
+ };
43
+ },
44
+ /** Returns true when the event was consumed. */
45
+ dispatch(event) {
46
+ const binding = bindings.get(comboOf(event).toLowerCase());
47
+ if (!binding) return false;
48
+ binding.handler();
49
+ return true;
50
+ },
51
+ comboFor(actionId) {
52
+ for (const binding of bindings.values()) if (binding.id === actionId) return binding.combo;
53
+ return void 0;
54
+ }
55
+ };
56
+ }
57
+
58
+ // src/interaction/roving.ts
59
+ var FOCUSABLE = 'button:not([disabled]), [role="button"]:not([aria-disabled="true"])';
60
+ function centreOf(el) {
61
+ const r = el.getBoundingClientRect();
62
+ return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
63
+ }
64
+ function moveFocus(container, direction) {
65
+ const candidates = Array.from(container.querySelectorAll(FOCUSABLE)).filter((el) => el.offsetParent !== null);
66
+ if (candidates.length === 0) return false;
67
+ const active = document.activeElement;
68
+ if (!active || !container.contains(active)) {
69
+ candidates[0].focus();
70
+ return true;
71
+ }
72
+ const from = centreOf(active);
73
+ const axis = direction === "up" || direction === "down" ? "y" : "x";
74
+ const sign = direction === "down" || direction === "right" ? 1 : -1;
75
+ let best;
76
+ let bestScore = Infinity;
77
+ for (const el of candidates) {
78
+ if (el === active) continue;
79
+ const to = centreOf(el);
80
+ const along = (to[axis] - from[axis]) * sign;
81
+ if (along <= 1) continue;
82
+ const across = Math.abs(axis === "y" ? to.x - from.x : to.y - from.y);
83
+ const score = along + across * 3;
84
+ if (score < bestScore) {
85
+ bestScore = score;
86
+ best = el;
87
+ }
88
+ }
89
+ if (!best) return false;
90
+ best.focus();
91
+ best.scrollIntoView?.({ block: "nearest", inline: "nearest" });
92
+ return true;
93
+ }
94
+ function directionOf(key) {
95
+ if (key === "ArrowUp") return "up";
96
+ if (key === "ArrowDown") return "down";
97
+ if (key === "ArrowLeft") return "left";
98
+ if (key === "ArrowRight") return "right";
99
+ return void 0;
100
+ }
101
+
102
+ export { comboOf, createKeymap, directionOf, haptic, moveFocus, setHapticsEnabled };
103
+ //# sourceMappingURL=chunk-RBPDK2WB.js.map
104
+ //# sourceMappingURL=chunk-RBPDK2WB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/interaction/haptics.ts","../src/interaction/keymap.ts","../src/interaction/roving.ts"],"names":[],"mappings":"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","file":"chunk-RBPDK2WB.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Haptics.\n//\n// Honest about its reach: the Vibration API exists on Android/Chrome and does\n// NOT exist in Safari on iPad, which is a large share of restaurant tablets.\n// So this is a bonus on the hardware that has it, never something the UI leans\n// on — every cue it accompanies is also visible.\n//\n// Patterns are short on purpose. An operator surface fires these hundreds of\n// times a shift; anything longer than a tap becomes noise the operator turns\n// off.\n// ---------------------------------------------------------------------------\n\nexport type Haptic = 'tap' | 'commit' | 'warn' | 'reject'\n\nconst PATTERN: Record<Haptic, number | number[]> = {\n /** Something registered: an item rung, a card picked. */\n tap: 8,\n /** Something finished: a bill closed, an order fired, a ticket bumped. */\n commit: [12, 40, 18],\n /** Something needs a decision: a PIN gate, a required modifier. */\n warn: [10, 60, 10],\n /** Something was refused: wrong PIN, blocked action. */\n reject: [22, 50, 22],\n}\n\nlet enabled = true\n\n/** Lets an app turn the whole thing off — some operators hate it. */\nexport function setHapticsEnabled(value: boolean): void {\n enabled = value\n}\n\nexport function haptic(kind: Haptic = 'tap'): void {\n if (!enabled) return\n if (typeof navigator === 'undefined' || typeof navigator.vibrate !== 'function') return\n // A page that never had a user gesture cannot vibrate; the throw is silent\n // by spec in most engines, but not all of them.\n try { navigator.vibrate(PATTERN[kind]) } catch { /* no haptics here, fine */ }\n}\n","// ---------------------------------------------------------------------------\n// The keymap registry.\n//\n// Bindings are declared once, in one table, for two consumers: the runtime that\n// dispatches them and the UI that DRAWS them on the button. A shortcut nobody\n// can see is a shortcut nobody uses — which is the gap between a terminal that\n// demos well and one a Bematech operator is fast on from day one.\n// ---------------------------------------------------------------------------\n\nexport interface KeyBinding {\n /** Action id — matches the id in the surface's own action table. */\n id: string\n /** Human form, also what gets drawn: 'F2', 'Ctrl+T', 'Esc'. */\n combo: string\n handler: () => void\n}\n\n/** Normalises a KeyboardEvent to the same shape `combo` is written in. */\nexport function comboOf(event: KeyboardEvent): string {\n const parts: string[] = []\n if (event.ctrlKey) parts.push('Ctrl')\n if (event.altKey) parts.push('Alt')\n if (event.shiftKey && event.key.length > 1) parts.push('Shift')\n parts.push(event.key.length === 1 ? event.key.toUpperCase() : event.key)\n return parts.join('+')\n}\n\nexport function createKeymap() {\n const bindings = new Map<string, KeyBinding>()\n\n return {\n register(binding: KeyBinding): () => void {\n const key = binding.combo.toLowerCase()\n bindings.set(key, binding)\n return () => {\n // Only unregister if still ours — a re-render that re-registers the same\n // combo must not have its binding removed by the previous cleanup.\n if (bindings.get(key) === binding) bindings.delete(key)\n }\n },\n /** Returns true when the event was consumed. */\n dispatch(event: KeyboardEvent): boolean {\n const binding = bindings.get(comboOf(event).toLowerCase())\n if (!binding) return false\n binding.handler()\n return true\n },\n comboFor(actionId: string): string | undefined {\n for (const binding of bindings.values()) if (binding.id === actionId) return binding.combo\n return undefined\n },\n }\n}\n\nexport type Keymap = ReturnType<typeof createKeymap>\n","// ---------------------------------------------------------------------------\n// Arrow-key movement across whatever is on screen.\n//\n// Geometric, not index-based, and that is the point: the surface can be a\n// responsive grid, an absolutely-positioned room plan, or a wall of kitchen\n// tickets, and a list is just a grid one column wide. Picking the nearest\n// target in the direction pressed works for all of them without any of them\n// declaring its own shape.\n// ---------------------------------------------------------------------------\n\nexport type Direction = 'up' | 'down' | 'left' | 'right'\n\nconst FOCUSABLE = 'button:not([disabled]), [role=\"button\"]:not([aria-disabled=\"true\"])'\n\n/** Centre point, which is what \"nearest in that direction\" is measured from. */\nfunction centreOf(el: Element): { x: number; y: number } {\n const r = el.getBoundingClientRect()\n return { x: r.left + r.width / 2, y: r.top + r.height / 2 }\n}\n\n/**\n * Moves focus to the nearest focusable inside `container` in `direction`.\n * Returns true when focus actually moved, so the caller knows whether to\n * swallow the key.\n */\nexport function moveFocus(container: HTMLElement, direction: Direction): boolean {\n const candidates = Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE))\n .filter((el) => el.offsetParent !== null)\n if (candidates.length === 0) return false\n\n const active = document.activeElement as HTMLElement | null\n // Nothing focused inside yet: enter at the first item rather than guessing a\n // direction from a cursor that is not there.\n if (!active || !container.contains(active)) {\n candidates[0]!.focus()\n return true\n }\n\n const from = centreOf(active)\n const axis = direction === 'up' || direction === 'down' ? 'y' : 'x'\n const sign = direction === 'down' || direction === 'right' ? 1 : -1\n\n let best: HTMLElement | undefined\n let bestScore = Infinity\n for (const el of candidates) {\n if (el === active) continue\n const to = centreOf(el)\n const along = (to[axis] - from[axis]) * sign\n // Must actually lie in the direction pressed; 1px of slack keeps a row of\n // pixel-misaligned cards from being invisible to the arrow.\n if (along <= 1) continue\n const across = Math.abs(axis === 'y' ? to.x - from.x : to.y - from.y)\n // Drift across the axis costs triple, so a grid steps to the cell below\n // instead of leaping to a far one that happens to be marginally closer.\n const score = along + across * 3\n if (score < bestScore) { bestScore = score; best = el }\n }\n\n if (!best) return false\n best.focus()\n // Keep the newly focused card on screen without yanking the whole page.\n // Guarded because not every embedded webview ships it, and losing arrow\n // navigation entirely is a far worse outcome than losing the scroll nudge.\n best.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n return true\n}\n\nexport function directionOf(key: string): Direction | undefined {\n if (key === 'ArrowUp') return 'up'\n if (key === 'ArrowDown') return 'down'\n if (key === 'ArrowLeft') return 'left'\n if (key === 'ArrowRight') return 'right'\n return undefined\n}\n"]}
@@ -1,5 +1,6 @@
1
- import { globalCache, getActiveTenantId, getSupabaseClientOptional, parseRangeFilter, matchesRange, applyUnitScope, applyRangeToQuery, isFilterObject, stableKey, DEFAULT_CACHE_TTL, getScopedResource, createSupabaseProvider, resolveUnitScope, clearGlobalCache } from './chunk-BCI743RH.js';
1
+ import { globalCache, getActiveTenantId, getSupabaseClientOptional, parseRangeFilter, matchesRange, stableKey, DEFAULT_CACHE_TTL, getScopedResource, createSupabaseProvider, resolveUnitScope, applyUnitScope, applyRangeToQuery, isFilterObject, clearGlobalCache } from './chunk-HVWFFFKN.js';
2
2
  import { declaredCustomFields, validateEntityRecordData } from './chunk-EWKX4VUM.js';
3
+ import { errorMessage } from './chunk-6TVRNKWW.js';
3
4
  import * as React from 'react';
4
5
 
5
6
  // src/data/count.ts
@@ -295,19 +296,25 @@ var ARCHETYPE_CONFIG = {
295
296
  person: { table: "people", fkColumn: "person_id" },
296
297
  category: { table: "categories", fkColumn: "category_id" },
297
298
  product: { table: "products", fkColumn: "product_id" },
298
- service: { table: "services", fkColumn: "service_id" },
299
- location: { table: "locations", fkColumn: "location_id" },
299
+ // A service IS a product with kind='service' (spine 191 retired the
300
+ // public.services mirror; 190 folded items.service_id into product_id).
301
+ service: { table: "products", fkColumn: "product_id" },
302
+ // A unit lives in schema `app`; `v_units` is the public bridge PostgREST can
303
+ // reach. Read-complete, but write-partial (most columns are metadata-derived)
304
+ // — creating a unit goes through the `unit_create` RPC.
305
+ location: { table: "v_units", fkColumn: "location_id" },
300
306
  order: { table: "orders", fkColumn: "order_id" },
301
307
  transaction: { table: "transactions", fkColumn: "transaction_id" },
302
308
  schedule: { table: "schedules", fkColumn: "schedule_id" }
303
309
  };
304
310
  var CUSTOM_FIELDS_COLUMN = "custom_fields";
305
311
  var CUSTOM_FIELD_FILTER_PREFIX = "custom.";
312
+ var COUNT_DEADLINE_MS = 2500;
306
313
  var UNIT_COLUMN = "unit_id";
307
314
  var ARCHETYPE_COLUMNS = {
308
315
  person: /* @__PURE__ */ new Set(["name", "email", "phone", "document_number", "avatar_url", "date_of_birth", "address", "city", "state", "country", "postal_code", "tags", "is_active", "is_bookable", "notes", "metadata", CUSTOM_FIELDS_COLUMN, UNIT_COLUMN]),
309
316
  category: /* @__PURE__ */ new Set(["name", "slug", "parent_id", "icon", "color", "sort_order", "is_active", "metadata", CUSTOM_FIELDS_COLUMN, UNIT_COLUMN]),
310
- product: /* @__PURE__ */ new Set(["name", "description", "sku", "price", "cost", "currency", "unit", "image_url", "stock", "min_stock", "status", "is_active", "tags", "metadata", "category_id", CUSTOM_FIELDS_COLUMN, UNIT_COLUMN]),
317
+ product: /* @__PURE__ */ new Set(["name", "description", "sku", "price", "cost", "currency", "unit", "image_url", "status", "is_active", "tags", "metadata", "category_id", CUSTOM_FIELDS_COLUMN, UNIT_COLUMN]),
311
318
  service: /* @__PURE__ */ new Set(["name", "description", "price", "cost", "currency", "duration_minutes", "image_url", "status", "is_active", "tags", "metadata", "category_id", CUSTOM_FIELDS_COLUMN, UNIT_COLUMN]),
312
319
  location: /* @__PURE__ */ new Set(["name", "email", "phone", "address", "city", "state", "country", "postal_code", "is_headquarters", "is_active", "tags", "notes", "metadata", CUSTOM_FIELDS_COLUMN]),
313
320
  order: /* @__PURE__ */ new Set(["reference_number", "status", "party_id", "assignee_id", "location_id", "subtotal", "discount", "tax", "total", "currency", "due_at", "completed_at", "notes", "tags", "metadata", CUSTOM_FIELDS_COLUMN]),
@@ -342,11 +349,17 @@ function createArchetypeProvider(config) {
342
349
  function unitScope() {
343
350
  return resolveUnitScope(config);
344
351
  }
352
+ const customKeys = new Set(config.customFields ?? []);
345
353
  function splitFields(data) {
346
354
  const archetypeData = {};
347
355
  const projectData = {};
356
+ const customData = {};
348
357
  for (const [key, value] of Object.entries(data)) {
349
358
  if (["id", "createdAt", "updatedAt", "createdBy", "ownerId", "tenantId", fkColumn].includes(key)) continue;
359
+ if (customKeys.has(key)) {
360
+ customData[key] = value;
361
+ continue;
362
+ }
350
363
  const snakeKey = camelToSnake(key);
351
364
  if (archetypeColumns.has(snakeKey)) {
352
365
  archetypeData[snakeKey] = value;
@@ -354,7 +367,17 @@ function createArchetypeProvider(config) {
354
367
  projectData[snakeKey] = value;
355
368
  }
356
369
  }
357
- return { archetypeData, projectData };
370
+ return { archetypeData, projectData, customData };
371
+ }
372
+ function liftCustom(row) {
373
+ if (customKeys.size === 0 || !row) return row;
374
+ const bag = row[CUSTOM_FIELDS_COLUMN];
375
+ if (!bag || typeof bag !== "object") return row;
376
+ const out = { ...row };
377
+ for (const k of customKeys) {
378
+ if (k in bag) out[k] = bag[k];
379
+ }
380
+ return out;
358
381
  }
359
382
  const VIEW_MAP = {
360
383
  clients: "v_clients",
@@ -362,40 +385,58 @@ function createArchetypeProvider(config) {
362
385
  };
363
386
  const viewName = VIEW_MAP[config.projectTable] ?? `v_${config.projectTable}`;
364
387
  const isPure = config.projectTable === ac.table;
388
+ function applyPredicates(q0, query) {
389
+ let q = q0;
390
+ q = applyUnitScope(q, unitScope());
391
+ if (isPure && HAS_KIND.has(config.archetype)) {
392
+ q = q.eq("kind", config.archetypeKind);
393
+ }
394
+ if (query.filters) {
395
+ for (const [key, val] of Object.entries(query.filters)) {
396
+ if (val == null || val === "") continue;
397
+ const col = key.startsWith(CUSTOM_FIELD_FILTER_PREFIX) ? `${CUSTOM_FIELDS_COLUMN}->>${key.slice(CUSTOM_FIELD_FILTER_PREFIX.length)}` : camelToSnake(key);
398
+ const range = parseRangeFilter(val);
399
+ if (range) {
400
+ q = applyRangeToQuery(q, col, range);
401
+ continue;
402
+ }
403
+ if (isFilterObject(val)) {
404
+ throw new Error(
405
+ `[@fayz-ai/core] Filter "${key}" is an object with no recognised bound. Use {from,to} / {gte,lte,gt,lt} for ranges, or a plain value for equality.`
406
+ );
407
+ }
408
+ q = q.eq(col, val);
409
+ }
410
+ }
411
+ if (query.search && config.searchColumns && config.searchColumns.length > 0) {
412
+ const term = `%${query.search}%`;
413
+ const orClauses = config.searchColumns.map((col) => camelToSnake(col)).map((col) => `${col}.ilike.${term}`);
414
+ if (orClauses.length > 0) {
415
+ q = q.or(orClauses.join(","));
416
+ }
417
+ }
418
+ return q;
419
+ }
420
+ async function countMatching(tenantId, query, mode) {
421
+ let q = isPure ? coreClient().from(ac.table).select("id", { count: mode, head: true }).eq("tenant_id", tenantId) : getClient2().from(viewName).select("id", { count: mode, head: true }).eq("tenant_id", tenantId);
422
+ q = applyPredicates(q, query);
423
+ if (mode === "planned") {
424
+ const { count: count2, error: error2 } = await q;
425
+ if (error2) throw error2;
426
+ return count2 ?? 0;
427
+ }
428
+ const prazo = new Promise((_, rej) => setTimeout(() => rej(new Error("count: prazo")), COUNT_DEADLINE_MS));
429
+ const { count, error } = await Promise.race([q, prazo]);
430
+ if (error) throw error;
431
+ return count ?? 0;
432
+ }
365
433
  return {
366
434
  async list(query) {
367
435
  const tenantId = config.tenantId();
368
436
  if (!tenantId) return { data: [], total: 0 };
369
- const countOpts = query.countMode === "none" ? void 0 : { count: "exact" };
370
- let q = isPure ? coreClient().from(ac.table).select("*", countOpts).eq("tenant_id", tenantId) : getClient2().from(viewName).select("*", countOpts).eq("tenant_id", tenantId);
371
- q = applyUnitScope(q, unitScope());
372
- if (isPure && HAS_KIND.has(config.archetype)) {
373
- q = q.eq("kind", config.archetypeKind);
374
- }
375
- if (query.filters) {
376
- for (const [key, val] of Object.entries(query.filters)) {
377
- if (val == null || val === "") continue;
378
- const col = key.startsWith(CUSTOM_FIELD_FILTER_PREFIX) ? `${CUSTOM_FIELDS_COLUMN}->>${key.slice(CUSTOM_FIELD_FILTER_PREFIX.length)}` : camelToSnake(key);
379
- const range = parseRangeFilter(val);
380
- if (range) {
381
- q = applyRangeToQuery(q, col, range);
382
- continue;
383
- }
384
- if (isFilterObject(val)) {
385
- throw new Error(
386
- `[@fayz-ai/core] Filter "${key}" is an object with no recognised bound. Use {from,to} / {gte,lte,gt,lt} for ranges, or a plain value for equality.`
387
- );
388
- }
389
- q = q.eq(col, val);
390
- }
391
- }
392
- if (query.search && config.searchColumns && config.searchColumns.length > 0) {
393
- const term = `%${query.search}%`;
394
- const orClauses = config.searchColumns.map((col) => camelToSnake(col)).map((col) => `${col}.ilike.${term}`);
395
- if (orClauses.length > 0) {
396
- q = q.or(orClauses.join(","));
397
- }
398
- }
437
+ const wantCount = query.countMode !== "none";
438
+ let q = isPure ? coreClient().from(ac.table).select("*").eq("tenant_id", tenantId) : getClient2().from(viewName).select("*").eq("tenant_id", tenantId);
439
+ q = applyPredicates(q, query);
399
440
  if (query.sortBy) {
400
441
  q = q.order(camelToSnake(query.sortBy), { ascending: query.sortDir !== "desc" });
401
442
  } else {
@@ -405,15 +446,35 @@ function createArchetypeProvider(config) {
405
446
  const pageSize = query.pageSize ?? 50;
406
447
  const from = (page - 1) * pageSize;
407
448
  q = q.range(from, from + pageSize - 1);
408
- const { data, error, count } = await q;
449
+ const { data, error } = await q;
409
450
  if (error) throw error;
410
- const rows = (data ?? []).map((row) => mapRow(row));
411
- return { data: rows, total: count ?? 0 };
451
+ const rows = (data ?? []).map((row) => mapRow(liftCustom(row)));
452
+ let total = 0;
453
+ let totalIsEstimate = false;
454
+ if (wantCount) {
455
+ try {
456
+ total = await countMatching(tenantId, query, "exact");
457
+ } catch {
458
+ try {
459
+ total = await countMatching(tenantId, query, "planned");
460
+ totalIsEstimate = total > 0;
461
+ } catch {
462
+ total = 0;
463
+ }
464
+ }
465
+ }
466
+ return totalIsEstimate ? { data: rows, total, totalIsEstimate } : { data: rows, total };
412
467
  },
413
468
  async create(data) {
414
469
  const tenantId = config.tenantId();
415
470
  if (!tenantId) throw new Error("No tenant selected");
416
- const { archetypeData, projectData } = splitFields(data);
471
+ const { archetypeData, projectData, customData } = splitFields(data);
472
+ if (Object.keys(customData).length > 0) {
473
+ archetypeData[CUSTOM_FIELDS_COLUMN] = {
474
+ ...archetypeData[CUSTOM_FIELDS_COLUMN],
475
+ ...customData
476
+ };
477
+ }
417
478
  archetypeData.tenant_id = tenantId;
418
479
  if (HAS_KIND.has(config.archetype)) {
419
480
  archetypeData.kind = config.archetypeKind;
@@ -434,10 +495,22 @@ function createArchetypeProvider(config) {
434
495
  throw projectError;
435
496
  }
436
497
  }
437
- return { id: archetypeId, ...archetypeRow, ...isPure ? {} : projectData };
498
+ return liftCustom({ id: archetypeId, ...archetypeRow, ...isPure ? {} : projectData });
438
499
  },
439
500
  async update(id, data) {
440
- const { archetypeData, projectData } = splitFields(data);
501
+ const { archetypeData, projectData, customData } = splitFields(data);
502
+ if (Object.keys(customData).length > 0) {
503
+ const noPatch = archetypeData[CUSTOM_FIELDS_COLUMN];
504
+ let base = {};
505
+ if (noPatch && typeof noPatch === "object") {
506
+ base = noPatch;
507
+ } else {
508
+ const { data: atual } = await coreClient().from(ac.table).select(CUSTOM_FIELDS_COLUMN).eq("id", id).single();
509
+ const anterior = atual?.[CUSTOM_FIELDS_COLUMN];
510
+ if (anterior && typeof anterior === "object") base = anterior;
511
+ }
512
+ archetypeData[CUSTOM_FIELDS_COLUMN] = { ...base, ...customData };
513
+ }
441
514
  if (Object.keys(archetypeData).length > 0) {
442
515
  const { error } = await coreClient().from(ac.table).update(archetypeData).eq("id", id);
443
516
  if (error) throw error;
@@ -448,7 +521,7 @@ function createArchetypeProvider(config) {
448
521
  }
449
522
  const { data: archetypeRow } = await coreClient().from(ac.table).select("*").eq("id", id).single();
450
523
  if (isPure) {
451
- return mapRow({ ...archetypeRow, id });
524
+ return mapRow(liftCustom({ ...archetypeRow, id }));
452
525
  }
453
526
  const { data: projectRow } = await getClient2().from(config.projectTable).select("*").eq(fkColumn, id).single();
454
527
  const flat = { ...archetypeRow, ...projectRow, id };
@@ -456,7 +529,7 @@ function createArchetypeProvider(config) {
456
529
  flat[CUSTOM_FIELDS_COLUMN] = archetypeRow[CUSTOM_FIELDS_COLUMN];
457
530
  }
458
531
  delete flat[fkColumn];
459
- return mapRow(flat);
532
+ return mapRow(liftCustom(flat));
460
533
  },
461
534
  async remove(id) {
462
535
  const { error } = await coreClient().from(ac.table).delete().eq("id", id);
@@ -1177,7 +1250,7 @@ function scheduleReplicaSync(options, delayMs) {
1177
1250
  }
1178
1251
 
1179
1252
  // src/fields/store.ts
1180
- var FIELDS_TABLE = "plg_entity_fields";
1253
+ var FIELDS_TABLE = "entity_fields";
1181
1254
  var CustomFieldsError = class extends Error {
1182
1255
  constructor(reference, issues) {
1183
1256
  super(`${reference} custom fields: ${issues.map((i) => i.message).join("; ")}`);
@@ -1388,6 +1461,7 @@ function resolveDataProvider(entityDef, mockData, options = {}) {
1388
1461
  archetype: entityDef.data.archetype,
1389
1462
  archetypeKind: entityDef.data.archetypeKind,
1390
1463
  projectTable: entityDef.data.table,
1464
+ customFields: entityDef.data.customFields,
1391
1465
  tenantId,
1392
1466
  searchColumns: entityDef.data.searchColumns ?? entityDef.fields.filter((field) => field.searchable).map((field) => field.key),
1393
1467
  ...unitConfig
@@ -1656,7 +1730,7 @@ async function bulkCreate(provider, rows, options) {
1656
1730
  await provider.create(rows[i]);
1657
1731
  created += 1;
1658
1732
  } catch (error) {
1659
- failed.push({ index: i, message: error instanceof Error ? error.message : String(error) });
1733
+ failed.push({ index: i, message: errorMessage(error) });
1660
1734
  }
1661
1735
  options?.onProgress?.(i + 1, rows.length);
1662
1736
  }
@@ -1665,7 +1739,7 @@ async function bulkCreate(provider, rows, options) {
1665
1739
  async function bulkRemove(provider, ids, options) {
1666
1740
  if (!ids.length) return { removed: 0, failed: [] };
1667
1741
  const failed = [];
1668
- const message = (error) => error instanceof Error ? error.message : String(error);
1742
+ const message = (error) => errorMessage(error);
1669
1743
  if (provider.removeMany) {
1670
1744
  const chunkSize = options?.chunkSize ?? 100;
1671
1745
  let removed2 = 0;
@@ -1693,5 +1767,5 @@ async function bulkRemove(provider, ids, options) {
1693
1767
  }
1694
1768
 
1695
1769
  export { CustomFieldsError, DATA_CHANGED_EVENT, DataError, FIELDS_TABLE, attachPersistentCache, bulkCreate, bulkRemove, cacheKeyFor, cachePrefixFor, clearCustomFieldsCache, countByTenant, createArchetypeProvider, createEventBus, createFayzApiProvider, createMockProvider, dataCall, dataQuery, emitDataChanged, eventBus, getActiveUnitId, getPersistentCache, invalidateCount, isKnownMissing, isUnitMode, localList, matchesDataChange, mergeCustomFields, registerDataTelemetry, rememberIfMissing, replicaKeyFor, resetActiveUnit, resetMissingObjects, resolveCustomFields, resolveDataProvider, setActiveUnitId, setUnitMode, subscribeActiveUnit, syncReplica, toDataError, useActiveUnitId, useDataChanged, useUnitMode, validateCustomFields, withCache, withOffline };
1696
- //# sourceMappingURL=chunk-2ZAUJVYP.js.map
1697
- //# sourceMappingURL=chunk-2ZAUJVYP.js.map
1770
+ //# sourceMappingURL=chunk-S3EEGDEK.js.map
1771
+ //# sourceMappingURL=chunk-S3EEGDEK.js.map