@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.
- package/dist/access/engine.d.ts +15 -1
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/index.js +2 -2
- package/dist/access/index.js.map +1 -1
- package/dist/app/app-config.d.ts +25 -0
- package/dist/app/app-config.d.ts.map +1 -1
- package/dist/{chunk-ED6NBH6U.js → chunk-3TRR2YLM.js} +2 -2
- package/dist/{chunk-ED6NBH6U.js.map → chunk-3TRR2YLM.js.map} +1 -1
- package/dist/{chunk-AOIYRUTF.js → chunk-4SPEISNL.js} +19 -5
- package/dist/chunk-4SPEISNL.js.map +1 -0
- package/dist/chunk-6TVRNKWW.js +17 -0
- package/dist/chunk-6TVRNKWW.js.map +1 -0
- package/dist/{chunk-OHH26S4N.js → chunk-7M4NWLIT.js} +9 -8
- package/dist/chunk-7M4NWLIT.js.map +1 -0
- package/dist/{chunk-2VY5IJW7.js → chunk-H6XDQXN5.js} +42 -14
- package/dist/chunk-H6XDQXN5.js.map +1 -0
- package/dist/{chunk-BCI743RH.js → chunk-HVWFFFKN.js} +4 -3
- package/dist/chunk-HVWFFFKN.js.map +1 -0
- package/dist/chunk-RBPDK2WB.js +104 -0
- package/dist/chunk-RBPDK2WB.js.map +1 -0
- package/dist/{chunk-2ZAUJVYP.js → chunk-S3EEGDEK.js} +122 -48
- package/dist/chunk-S3EEGDEK.js.map +1 -0
- package/dist/{chunk-KJUD6JA5.js → chunk-ZGAGCTQM.js} +6 -5
- package/dist/chunk-ZGAGCTQM.js.map +1 -0
- package/dist/connector-runtime/index.js +1 -1
- package/dist/data/archetype.d.ts +16 -0
- package/dist/data/archetype.d.ts.map +1 -1
- package/dist/data/bulk.d.ts.map +1 -1
- package/dist/data/index.js +3 -2
- package/dist/data/resolve.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/types.d.ts +13 -0
- package/dist/data/types.d.ts.map +1 -1
- package/dist/events/log.d.ts +1 -1
- package/dist/events/log.d.ts.map +1 -1
- package/dist/fields/store.d.ts +1 -1
- package/dist/fields/store.d.ts.map +1 -1
- package/dist/i18n/index.d.ts +11 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/shell-translations.en.d.ts.map +1 -1
- package/dist/i18n/shell-translations.pt-br.d.ts.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -12
- package/dist/index.js.map +1 -1
- package/dist/integrations/index.d.ts +8 -8
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +4 -3
- package/dist/integrations/request.d.ts +2 -2
- package/dist/integrations/request.d.ts.map +1 -1
- package/dist/integrations/runtime.d.ts +1 -1
- package/dist/integrations/runtime.d.ts.map +1 -1
- package/dist/integrations/settings.d.ts +1 -1
- package/dist/integrations/settings.d.ts.map +1 -1
- package/dist/integrations/store.d.ts +7 -7
- package/dist/integrations/store.d.ts.map +1 -1
- package/dist/interaction/haptics.d.ts +5 -0
- package/dist/interaction/haptics.d.ts.map +1 -0
- package/dist/interaction/index.d.ts +7 -0
- package/dist/interaction/index.d.ts.map +1 -0
- package/dist/interaction/index.js +3 -0
- package/dist/interaction/index.js.map +1 -0
- package/dist/interaction/keymap.d.ts +17 -0
- package/dist/interaction/keymap.d.ts.map +1 -0
- package/dist/interaction/roving.d.ts +9 -0
- package/dist/interaction/roving.d.ts.map +1 -0
- package/dist/lib/error-message.d.ts +21 -0
- package/dist/lib/error-message.d.ts.map +1 -0
- package/dist/orders/index.d.ts +114 -0
- package/dist/orders/index.d.ts.map +1 -0
- package/dist/orders/index.js +168 -0
- package/dist/orders/index.js.map +1 -0
- package/dist/plugin/index.js +2 -1
- package/dist/plugin/runtime.d.ts.map +1 -1
- package/dist/registry/extension-points.d.ts.map +1 -1
- package/dist/{shell-translations.pt-br-ZSSVWG6H.js → shell-translations.pt-br-GFBWBZZJ.js} +28 -12
- package/dist/shell-translations.pt-br-GFBWBZZJ.js.map +1 -0
- package/dist/testing/index.js +5 -4
- package/dist/testing.d.ts.map +1 -1
- package/dist/types/auth.d.ts +12 -0
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/crud.d.ts +42 -0
- package/dist/types/crud.d.ts.map +1 -1
- package/dist/types/onboarding.d.ts +25 -0
- package/dist/types/onboarding.d.ts.map +1 -1
- package/dist/types/plugins.d.ts +43 -0
- package/dist/types/plugins.d.ts.map +1 -1
- package/dist/types/setup-guide.d.ts +120 -0
- package/dist/types/setup-guide.d.ts.map +1 -0
- package/package.json +12 -3
- package/dist/chunk-2VY5IJW7.js.map +0 -1
- package/dist/chunk-2ZAUJVYP.js.map +0 -1
- package/dist/chunk-AOIYRUTF.js.map +0 -1
- package/dist/chunk-BCI743RH.js.map +0 -1
- package/dist/chunk-KJUD6JA5.js.map +0 -1
- package/dist/chunk-OHH26S4N.js.map +0 -1
- 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,
|
|
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
|
|
299
|
-
|
|
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", "
|
|
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
|
|
370
|
-
let q = isPure ? coreClient().from(ac.table).select("*"
|
|
371
|
-
q =
|
|
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
|
|
449
|
+
const { data, error } = await q;
|
|
409
450
|
if (error) throw error;
|
|
410
|
-
const rows = (data ?? []).map((row) => mapRow(row));
|
|
411
|
-
|
|
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 = "
|
|
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:
|
|
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) =>
|
|
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-
|
|
1697
|
-
//# sourceMappingURL=chunk-
|
|
1770
|
+
//# sourceMappingURL=chunk-S3EEGDEK.js.map
|
|
1771
|
+
//# sourceMappingURL=chunk-S3EEGDEK.js.map
|