@fayz-ai/core 0.8.1 → 0.8.3

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 (123) hide show
  1. package/dist/access/denial.d.ts +21 -0
  2. package/dist/access/denial.d.ts.map +1 -0
  3. package/dist/access/engine.d.ts +63 -0
  4. package/dist/access/engine.d.ts.map +1 -0
  5. package/dist/access/index.cjs +60 -0
  6. package/dist/access/index.cjs.map +1 -0
  7. package/dist/access/index.d.ts +6 -0
  8. package/dist/access/index.d.ts.map +1 -0
  9. package/dist/access/index.js +51 -0
  10. package/dist/access/index.js.map +1 -0
  11. package/dist/access/limits.d.ts +10 -0
  12. package/dist/access/limits.d.ts.map +1 -0
  13. package/dist/address/index.d.ts +52 -0
  14. package/dist/address/index.d.ts.map +1 -0
  15. package/dist/{chunk-QR7U2URY.cjs → chunk-CATASHPK.cjs} +345 -4
  16. package/dist/chunk-CATASHPK.cjs.map +1 -0
  17. package/dist/{chunk-WKZVSKEU.js → chunk-CU64QI4A.js} +2 -2
  18. package/dist/chunk-CU64QI4A.js.map +1 -0
  19. package/dist/{chunk-KNIVT6XZ.js → chunk-DZALZ6Q6.js} +345 -4
  20. package/dist/chunk-DZALZ6Q6.js.map +1 -0
  21. package/dist/{chunk-54SRL7AJ.cjs → chunk-KSI2L3E4.cjs} +145 -86
  22. package/dist/chunk-KSI2L3E4.cjs.map +1 -0
  23. package/dist/{chunk-ZBIWYDAR.js → chunk-TYPVSWLG.js} +144 -87
  24. package/dist/chunk-TYPVSWLG.js.map +1 -0
  25. package/dist/{chunk-2NTHNWEH.cjs → chunk-UKU4RIWM.cjs} +2 -2
  26. package/dist/chunk-UKU4RIWM.cjs.map +1 -0
  27. package/dist/{chunk-CHGX6UYR.js → chunk-YH25Y4FW.js} +6 -3
  28. package/dist/chunk-YH25Y4FW.js.map +1 -0
  29. package/dist/{chunk-5X2VX3RQ.cjs → chunk-ZOPJB5FN.cjs} +6 -3
  30. package/dist/chunk-ZOPJB5FN.cjs.map +1 -0
  31. package/dist/data/archetype.d.ts.map +1 -1
  32. package/dist/data/count.d.ts +34 -0
  33. package/dist/data/count.d.ts.map +1 -0
  34. package/dist/data/index.cjs +18 -10
  35. package/dist/data/index.d.ts +2 -0
  36. package/dist/data/index.d.ts.map +1 -1
  37. package/dist/data/index.js +1 -1
  38. package/dist/data/refresh.d.ts +36 -0
  39. package/dist/data/refresh.d.ts.map +1 -0
  40. package/dist/data/resolve.d.ts.map +1 -1
  41. package/dist/data/supabase.d.ts.map +1 -1
  42. package/dist/data/types.d.ts +6 -0
  43. package/dist/data/types.d.ts.map +1 -1
  44. package/dist/entity/index.cjs +6 -6
  45. package/dist/entity/index.js +1 -1
  46. package/dist/entity/registry.d.ts +9 -0
  47. package/dist/entity/registry.d.ts.map +1 -1
  48. package/dist/i18n/index.cjs +10 -10
  49. package/dist/i18n/index.js +1 -1
  50. package/dist/i18n/shell-translations.d.ts.map +1 -1
  51. package/dist/index.cjs +1517 -180
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.ts +15 -5
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +1412 -110
  56. package/dist/index.js.map +1 -1
  57. package/dist/manifest/index.d.ts +109 -3
  58. package/dist/manifest/index.d.ts.map +1 -1
  59. package/dist/manifest/serialize.d.ts +14 -0
  60. package/dist/manifest/serialize.d.ts.map +1 -0
  61. package/dist/plugin/index.cjs +7 -7
  62. package/dist/plugin/index.js +1 -1
  63. package/dist/plugin/runtime.d.ts.map +1 -1
  64. package/dist/search/engine.d.ts +12 -0
  65. package/dist/search/engine.d.ts.map +1 -0
  66. package/dist/search/index.d.ts +5 -0
  67. package/dist/search/index.d.ts.map +1 -0
  68. package/dist/search/text.d.ts +37 -0
  69. package/dist/search/text.d.ts.map +1 -0
  70. package/dist/search/types.d.ts +72 -0
  71. package/dist/search/types.d.ts.map +1 -0
  72. package/dist/types/billing.d.ts +20 -0
  73. package/dist/types/billing.d.ts.map +1 -1
  74. package/dist/types/crud.d.ts +47 -1
  75. package/dist/types/crud.d.ts.map +1 -1
  76. package/dist/types/entitlements.d.ts +45 -0
  77. package/dist/types/entitlements.d.ts.map +1 -0
  78. package/dist/types/index.d.ts +4 -3
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/types/org.d.ts +28 -0
  81. package/dist/types/org.d.ts.map +1 -1
  82. package/dist/types/plugins.d.ts +88 -0
  83. package/dist/types/plugins.d.ts.map +1 -1
  84. package/package.json +14 -4
  85. package/src/access/denial.ts +33 -0
  86. package/src/access/engine.test.ts +48 -0
  87. package/src/access/engine.ts +121 -0
  88. package/src/access/index.ts +13 -0
  89. package/src/access/limits.ts +18 -0
  90. package/src/address/index.ts +188 -0
  91. package/src/data/archetype.ts +4 -2
  92. package/src/data/count.test.ts +152 -0
  93. package/src/data/count.ts +131 -0
  94. package/src/data/index.ts +2 -0
  95. package/src/data/refresh.ts +105 -0
  96. package/src/data/resolve.ts +7 -1
  97. package/src/data/supabase.ts +3 -1
  98. package/src/data/types.ts +6 -0
  99. package/src/entity/registry.ts +7 -0
  100. package/src/i18n/shell-translations.ts +345 -2
  101. package/src/index.ts +37 -4
  102. package/src/manifest/app-manifest.schema.json +618 -45
  103. package/src/manifest/index.ts +127 -3
  104. package/src/manifest/serialize.ts +52 -0
  105. package/src/plugin/runtime.ts +5 -1
  106. package/src/search/engine.ts +551 -0
  107. package/src/search/index.ts +25 -0
  108. package/src/search/text.ts +206 -0
  109. package/src/search/types.ts +75 -0
  110. package/src/types/billing.ts +22 -0
  111. package/src/types/crud.ts +48 -1
  112. package/src/types/entitlements.ts +45 -0
  113. package/src/types/index.ts +4 -3
  114. package/src/types/org.ts +29 -0
  115. package/src/types/plugins.ts +83 -0
  116. package/dist/chunk-2NTHNWEH.cjs.map +0 -1
  117. package/dist/chunk-54SRL7AJ.cjs.map +0 -1
  118. package/dist/chunk-5X2VX3RQ.cjs.map +0 -1
  119. package/dist/chunk-CHGX6UYR.js.map +0 -1
  120. package/dist/chunk-KNIVT6XZ.js.map +0 -1
  121. package/dist/chunk-QR7U2URY.cjs.map +0 -1
  122. package/dist/chunk-WKZVSKEU.js.map +0 -1
  123. package/dist/chunk-ZBIWYDAR.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  export { withAfterHooks } from './chunk-BBDNC3FP.js';
2
2
  export { PluginContractError, assertConnectorContract, assertPluginManifestContract } from './chunk-X5THHG6T.js';
3
- import { getSupabaseClientOptional } from './chunk-ZBIWYDAR.js';
4
- export { FAYZ_CLOUD_PUBLISHABLE_KEY, FAYZ_CLOUD_URL, clearGlobalCache, createArchetypeProvider, createCacheStore, createFayzApiProvider, createMockProvider, createSupabaseProvider, getActiveTenantId, getFayzCloudClient, getSupabaseClientOptional, globalCache, resolveDataProvider, setActiveTenantId, setGlobalSupabaseClient, stableKey, withCache } from './chunk-ZBIWYDAR.js';
5
- export { clearEntityRegistry, deriveEntityKey, getAllEntities, getEntityByKey, registerEntity } from './chunk-WKZVSKEU.js';
6
- import { getBlock, getScaffold } from './chunk-CHGX6UYR.js';
7
- export { PLUGIN_API_VERSION, PluginRuntimeProvider, Registry, blockRegistry, componentRegistry, definePlugin, getBlock, getComponent, getDashboardWidgets, getMetric, getPage, getPlugin, getPluginFactory, getScaffold, getWidgetsForZone, isCustomId, listBlocks, listMetrics, listPluginFactories, listPlugins, metricRegistry, pageRegistry, pluginFactoryRegistry, pluginRegistry, registerBlock, registerComponent, registerMetric, registerPage, registerPlugin, registerPluginFactory, registerScaffold, resolvePluginComponent, resolvePluginRuntime, scaffoldRegistry, useConnectorsForPlugin, usePluginRuntime, usePluginRuntimeOptional } from './chunk-CHGX6UYR.js';
8
- import { getCurrentLocale } from './chunk-KNIVT6XZ.js';
9
- export { I18nProvider, coreTranslations, getCurrentLocale, mergeTranslations, registerTranslations, setCurrentLocale, setLocaleStore, useI18nConfig, useTranslation } from './chunk-KNIVT6XZ.js';
3
+ import { globalCache, getActiveTenantId, getSupabaseClientOptional, resolveDataProvider } from './chunk-TYPVSWLG.js';
4
+ export { FAYZ_CLOUD_PUBLISHABLE_KEY, FAYZ_CLOUD_URL, clearGlobalCache, countByTenant, createArchetypeProvider, createCacheStore, createFayzApiProvider, createMockProvider, createSupabaseProvider, getActiveTenantId, getFayzCloudClient, getSupabaseClientOptional, globalCache, invalidateCount, resolveDataProvider, setActiveTenantId, setGlobalSupabaseClient, stableKey, withCache } from './chunk-TYPVSWLG.js';
5
+ export { clearEntityRegistry, deriveEntityKey, getAllEntities, getEntityByKey, registerEntity } from './chunk-CU64QI4A.js';
6
+ import { getBlock, getScaffold } from './chunk-YH25Y4FW.js';
7
+ export { PLUGIN_API_VERSION, PluginRuntimeProvider, Registry, blockRegistry, componentRegistry, definePlugin, getBlock, getComponent, getDashboardWidgets, getMetric, getPage, getPlugin, getPluginFactory, getScaffold, getWidgetsForZone, isCustomId, listBlocks, listMetrics, listPluginFactories, listPlugins, metricRegistry, pageRegistry, pluginFactoryRegistry, pluginRegistry, registerBlock, registerComponent, registerMetric, registerPage, registerPlugin, registerPluginFactory, registerScaffold, resolvePluginComponent, resolvePluginRuntime, scaffoldRegistry, useConnectorsForPlugin, usePluginRuntime, usePluginRuntimeOptional } from './chunk-YH25Y4FW.js';
8
+ import { getCurrentLocale } from './chunk-DZALZ6Q6.js';
9
+ export { I18nProvider, coreTranslations, getCurrentLocale, mergeTranslations, registerTranslations, setCurrentLocale, setLocaleStore, useI18nConfig, useTranslation } from './chunk-DZALZ6Q6.js';
10
10
  export { hashRouterAdapter, windowRouterAdapter } from './chunk-Y2C2FBQP.js';
11
11
  export { FayzRuntimeError, createFayzRuntimeClient } from './chunk-7RTUHSKY.js';
12
- import * as React2 from 'react';
12
+ import * as React3 from 'react';
13
13
  import { useEffect } from 'react';
14
14
  import { create } from 'zustand';
15
15
 
@@ -25,6 +25,97 @@ var WidgetZone = {
25
25
  SETTINGS_AFTER: "settings.after",
26
26
  FLOATING: "shell.floating"
27
27
  };
28
+ function createEventBus() {
29
+ const handlers = /* @__PURE__ */ new Map();
30
+ const bus = {
31
+ emit(event, payload) {
32
+ const set = handlers.get(event);
33
+ if (!set) return;
34
+ for (const handler of Array.from(set)) {
35
+ try {
36
+ handler(payload);
37
+ } catch (err) {
38
+ console.error(`[fayz] event handler for "${event}" threw:`, err);
39
+ }
40
+ }
41
+ },
42
+ on(event, handler) {
43
+ let set = handlers.get(event);
44
+ if (!set) {
45
+ set = /* @__PURE__ */ new Set();
46
+ handlers.set(event, set);
47
+ }
48
+ set.add(handler);
49
+ return () => bus.off(event, handler);
50
+ },
51
+ once(event, handler) {
52
+ const wrapped = (payload) => {
53
+ bus.off(event, wrapped);
54
+ handler(payload);
55
+ };
56
+ return bus.on(event, wrapped);
57
+ },
58
+ off(event, handler) {
59
+ handlers.get(event)?.delete(handler);
60
+ },
61
+ clear(event) {
62
+ if (event) handlers.delete(event);
63
+ else handlers.clear();
64
+ }
65
+ };
66
+ return bus;
67
+ }
68
+ var eventBus = createEventBus();
69
+ function useOnEvent(event, handler, deps = []) {
70
+ const ref = React3.useRef(handler);
71
+ ref.current = handler;
72
+ React3.useEffect(
73
+ () => eventBus.on(event, (payload) => ref.current(payload)),
74
+ // eslint-disable-next-line react-hooks/exhaustive-deps
75
+ [event, ...deps]
76
+ );
77
+ }
78
+
79
+ // src/data/refresh.ts
80
+ var DATA_CHANGED_EVENT = "data:changed";
81
+ function matchesDataChange(match, payload) {
82
+ const attributed = payload.table ?? payload.entityKey ?? payload.archetype;
83
+ if (!attributed) return true;
84
+ if (match.table && payload.table && match.table === payload.table) return true;
85
+ if (match.entityKey && payload.entityKey && match.entityKey === payload.entityKey) return true;
86
+ if (match.archetype && payload.archetype && match.archetype === payload.archetype) return true;
87
+ return !match.table && !match.entityKey && !match.archetype;
88
+ }
89
+ var pending = [];
90
+ var flushTimer = null;
91
+ function flush() {
92
+ const batch = pending;
93
+ pending = [];
94
+ flushTimer = null;
95
+ for (const payload of batch) eventBus.emit(DATA_CHANGED_EVENT, payload);
96
+ }
97
+ function emitDataChanged(payload = {}) {
98
+ if (payload.table) {
99
+ globalCache.invalidate(`${getActiveTenantId() ?? "_"}:${payload.table}`);
100
+ } else {
101
+ globalCache.clear();
102
+ }
103
+ pending.push({ op: "unknown", source: "agent", ...payload });
104
+ if (flushTimer) return;
105
+ flushTimer = setTimeout(flush, 120);
106
+ }
107
+ function useDataChanged(match, handler, deps = []) {
108
+ const handlerRef = React3.useRef(handler);
109
+ handlerRef.current = handler;
110
+ const { table, entityKey, archetype } = match;
111
+ React3.useEffect(
112
+ () => eventBus.on(DATA_CHANGED_EVENT, (payload) => {
113
+ if (matchesDataChange({ table, entityKey, archetype }, payload)) handlerRef.current(payload);
114
+ }),
115
+ // eslint-disable-next-line react-hooks/exhaustive-deps
116
+ [table, entityKey, archetype, ...deps]
117
+ );
118
+ }
28
119
 
29
120
  // src/plugin/createSafeDataProvider.ts
30
121
  function createSafeDataProvider(makeSupabase, makeMock) {
@@ -84,6 +175,92 @@ function maskDigitCount(mask) {
84
175
  for (const ch of mask) if (ch === "#") n++;
85
176
  return n;
86
177
  }
178
+
179
+ // src/address/index.ts
180
+ function normalizePostalCode(value) {
181
+ return (value || "").replace(/\D/g, "");
182
+ }
183
+ function formatPostalCode(value) {
184
+ const digits = normalizePostalCode(value).slice(0, 8);
185
+ return digits.length > 5 ? `${digits.slice(0, 5)}-${digits.slice(5)}` : digits;
186
+ }
187
+ function isValidPostalCode(value) {
188
+ return normalizePostalCode(value).length === 8;
189
+ }
190
+ var CACHE_KEY = "fayz.postal.cache.v1";
191
+ var memoryCache = /* @__PURE__ */ new Map();
192
+ function readCache(code) {
193
+ const hit = memoryCache.get(code);
194
+ if (hit) return hit;
195
+ try {
196
+ const raw = globalThis.localStorage?.getItem(CACHE_KEY);
197
+ if (!raw) return void 0;
198
+ const all = JSON.parse(raw);
199
+ const stored = all[code];
200
+ if (stored) memoryCache.set(code, stored);
201
+ return stored;
202
+ } catch {
203
+ return void 0;
204
+ }
205
+ }
206
+ function writeCache(code, address) {
207
+ memoryCache.set(code, address);
208
+ try {
209
+ const store = globalThis.localStorage;
210
+ if (!store) return;
211
+ const raw = store.getItem(CACHE_KEY);
212
+ const all = raw ? JSON.parse(raw) : {};
213
+ all[code] = address;
214
+ store.setItem(CACHE_KEY, JSON.stringify(all));
215
+ } catch {
216
+ }
217
+ }
218
+ function clearPostalCache() {
219
+ memoryCache.clear();
220
+ try {
221
+ globalThis.localStorage?.removeItem(CACHE_KEY);
222
+ } catch {
223
+ }
224
+ }
225
+ function createViaCepProvider(options) {
226
+ const fetcher = options?.fetcher ?? globalThis.fetch;
227
+ return {
228
+ async lookup(postalCode) {
229
+ const code = normalizePostalCode(postalCode);
230
+ if (code.length !== 8) return null;
231
+ const response = await fetcher(`https://viacep.com.br/ws/${code}/json/`);
232
+ if (!response.ok) throw new Error(`Consulta de CEP falhou (${response.status})`);
233
+ const data = await response.json();
234
+ if (data.erro === true || data.erro === "true" || !data.localidade) return null;
235
+ return {
236
+ postalCode: code,
237
+ street: data.logradouro ?? "",
238
+ district: data.bairro ?? "",
239
+ city: data.localidade,
240
+ state: (data.uf ?? "").toUpperCase(),
241
+ country: "BR",
242
+ cityCode: data.ibge || void 0
243
+ };
244
+ }
245
+ };
246
+ }
247
+ var _provider = null;
248
+ function setPostalLookupProvider(provider) {
249
+ _provider = provider;
250
+ }
251
+ function getPostalLookupProvider() {
252
+ if (!_provider) _provider = createViaCepProvider();
253
+ return _provider;
254
+ }
255
+ async function lookupPostalCode(postalCode) {
256
+ const code = normalizePostalCode(postalCode);
257
+ if (code.length !== 8) return null;
258
+ const cached = readCache(code);
259
+ if (cached) return cached;
260
+ const address = await getPostalLookupProvider().lookup(code);
261
+ if (address) writeCache(code, address);
262
+ return address;
263
+ }
87
264
  var MANAGED = "data-fayz-seo";
88
265
  function seoOrigin() {
89
266
  return typeof window !== "undefined" ? window.location.origin : "";
@@ -245,58 +422,560 @@ function downloadCSV(csv, filename) {
245
422
  function exportCSV(columns, rows, filename) {
246
423
  downloadCSV(buildCSV(columns, rows), filename);
247
424
  }
248
- function createEventBus() {
249
- const handlers = /* @__PURE__ */ new Map();
250
- const bus = {
251
- emit(event, payload) {
252
- const set = handlers.get(event);
253
- if (!set) return;
254
- for (const handler of Array.from(set)) {
255
- try {
256
- handler(payload);
257
- } catch (err) {
258
- console.error(`[fayz] event handler for "${event}" threw:`, err);
259
- }
425
+
426
+ // src/search/text.ts
427
+ var COMBINING_MARKS = /[\u0300-\u036f]/g;
428
+ function foldText(value) {
429
+ if (typeof value !== "string" || value === "") return "";
430
+ return value.normalize("NFD").replace(COMBINING_MARKS, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
431
+ }
432
+ function foldWithMap(value) {
433
+ const out = [];
434
+ const map = [];
435
+ let pendingSpace = false;
436
+ for (let i = 0; i < value.length; i++) {
437
+ const ch = value[i];
438
+ const folded = ch.normalize("NFD").replace(COMBINING_MARKS, "").toLowerCase();
439
+ for (const f of folded) {
440
+ const alnum = f >= "a" && f <= "z" || f >= "0" && f <= "9";
441
+ if (!alnum) {
442
+ pendingSpace = out.length > 0;
443
+ continue;
260
444
  }
261
- },
262
- on(event, handler) {
263
- let set = handlers.get(event);
264
- if (!set) {
265
- set = /* @__PURE__ */ new Set();
266
- handlers.set(event, set);
445
+ if (pendingSpace) {
446
+ out.push(" ");
447
+ map.push(i);
448
+ pendingSpace = false;
267
449
  }
268
- set.add(handler);
269
- return () => bus.off(event, handler);
270
- },
271
- once(event, handler) {
272
- const wrapped = (payload) => {
273
- bus.off(event, wrapped);
274
- handler(payload);
275
- };
276
- return bus.on(event, wrapped);
277
- },
278
- off(event, handler) {
279
- handlers.get(event)?.delete(handler);
280
- },
281
- clear(event) {
282
- if (event) handlers.delete(event);
283
- else handlers.clear();
450
+ out.push(f);
451
+ map.push(i);
284
452
  }
453
+ }
454
+ return { folded: out.join(""), map };
455
+ }
456
+ function digitsOf(value) {
457
+ return typeof value === "string" ? value.replace(/\D+/g, "") : "";
458
+ }
459
+ var MIN_DIGIT_QUERY = 4;
460
+ function normalizeQuery(raw) {
461
+ const folded = foldText(raw);
462
+ const tokens = folded ? folded.split(" ").filter(Boolean) : [];
463
+ const digits = raw.replace(/\D+/g, "");
464
+ let anchor = "";
465
+ for (const token of tokens) if (token.length > anchor.length) anchor = token;
466
+ return {
467
+ raw,
468
+ folded,
469
+ tokens,
470
+ digits: digits.length >= MIN_DIGIT_QUERY ? digits : "",
471
+ anchor
285
472
  };
286
- return bus;
287
473
  }
288
- var eventBus = createEventBus();
289
- function useOnEvent(event, handler, deps = []) {
290
- const ref = React2.useRef(handler);
291
- ref.current = handler;
292
- React2.useEffect(
293
- () => eventBus.on(event, (payload) => ref.current(payload)),
294
- // eslint-disable-next-line react-hooks/exhaustive-deps
295
- [event, ...deps]
296
- );
474
+ function bigrams(value) {
475
+ if (value.length < 2) return value ? [value] : [];
476
+ const out = [];
477
+ for (let i = 0; i < value.length - 1; i++) out.push(value.slice(i, i + 2));
478
+ return out;
479
+ }
480
+ function similarity(a, b) {
481
+ if (!a || !b) return 0;
482
+ if (a === b) return 1;
483
+ const left = bigrams(a);
484
+ const right = bigrams(b);
485
+ if (left.length === 0 || right.length === 0) return 0;
486
+ const pool = /* @__PURE__ */ new Map();
487
+ for (const g of left) pool.set(g, (pool.get(g) ?? 0) + 1);
488
+ let shared = 0;
489
+ for (const g of right) {
490
+ const count = pool.get(g);
491
+ if (count) {
492
+ shared++;
493
+ pool.set(g, count - 1);
494
+ }
495
+ }
496
+ return 2 * shared / (left.length + right.length);
497
+ }
498
+ function wordSimilarity(text, token) {
499
+ let best = similarity(text, token);
500
+ for (const word of text.split(" ")) {
501
+ if (!word) continue;
502
+ const s = similarity(word, token);
503
+ if (s > best) best = s;
504
+ }
505
+ return best;
506
+ }
507
+ var FUZZY_FLOOR = 0.62;
508
+ function scoreCandidate(query, candidate) {
509
+ const { folded, tokens } = query;
510
+ if (!folded || tokens.length === 0) return 0;
511
+ const title = candidate.title;
512
+ const haystack = candidate.haystack || title;
513
+ let phrase = 0;
514
+ if (title === folded) phrase = 1;
515
+ else if (title.startsWith(folded)) phrase = 0.94;
516
+ else if (title.includes(" " + folded)) phrase = 0.88;
517
+ else if (title.includes(folded)) phrase = 0.76;
518
+ else if (haystack.startsWith(folded) || haystack.includes(" " + folded)) phrase = 0.7;
519
+ else if (haystack.includes(folded)) phrase = 0.62;
520
+ let tokenTotal = 0;
521
+ let fuzzyUsed = false;
522
+ for (const token of tokens) {
523
+ let best = 0;
524
+ if (title.startsWith(token)) best = 0.95;
525
+ else if (title.includes(" " + token)) best = 0.85;
526
+ else if (title.includes(token)) best = 0.7;
527
+ else if (haystack.startsWith(token) || haystack.includes(" " + token)) best = 0.6;
528
+ else if (haystack.includes(token)) best = 0.5;
529
+ else {
530
+ const sim = wordSimilarity(title, token);
531
+ if (sim >= FUZZY_FLOOR) {
532
+ best = 0.45 * sim;
533
+ fuzzyUsed = true;
534
+ }
535
+ }
536
+ if (best === 0) return phrase > 0 ? phrase : 0;
537
+ tokenTotal += best;
538
+ }
539
+ let tokenScore = tokenTotal / tokens.length;
540
+ if (fuzzyUsed) tokenScore *= 0.8;
541
+ let score = Math.max(phrase, tokenScore);
542
+ if (query.digits && candidate.digits && candidate.digits.includes(query.digits)) {
543
+ score = Math.max(score, 0.72 + Math.min(0.2, query.digits.length / 50));
544
+ }
545
+ if (score === 0) return 0;
546
+ const brevity = 1 / (1 + Math.max(0, title.length - folded.length) / 40);
547
+ return score * 0.97 + brevity * 0.03;
548
+ }
549
+ function highlightRanges(text, query) {
550
+ if (!text || query.tokens.length === 0) return [];
551
+ const { folded, map } = foldWithMap(text);
552
+ if (!folded) return [];
553
+ const spans = [];
554
+ const needles = query.tokens.length > 1 ? [query.folded, ...query.tokens] : [query.folded];
555
+ for (const needle of needles) {
556
+ if (!needle) continue;
557
+ let from = 0;
558
+ for (; ; ) {
559
+ const at = folded.indexOf(needle, from);
560
+ if (at === -1) break;
561
+ const start = map[at];
562
+ const endChar = map[at + needle.length - 1];
563
+ if (start !== void 0 && endChar !== void 0) spans.push([start, endChar + 1]);
564
+ from = at + needle.length;
565
+ }
566
+ }
567
+ if (spans.length === 0) return [];
568
+ spans.sort((a, b) => a[0] - b[0]);
569
+ const merged = [spans[0]];
570
+ for (const span of spans.slice(1)) {
571
+ const last = merged[merged.length - 1];
572
+ if (span[0] <= last[1]) last[1] = Math.max(last[1], span[1]);
573
+ else merged.push(span);
574
+ }
575
+ return merged;
576
+ }
577
+
578
+ // src/search/engine.ts
579
+ var MIN_QUERY_LENGTH = 2;
580
+ var DEFAULT_LIMIT = 30;
581
+ var DEFAULT_PER_TARGET = 5;
582
+ var SCAN_CONCURRENCY = 16;
583
+ var TARGET_TIMEOUT_MS = 3e3;
584
+ var CACHE_TTL_MS = 3e4;
585
+ var CACHE_MAX_ENTRIES = 40;
586
+ var RPC_NAME = "fayz_global_search";
587
+ var indexAvailable = null;
588
+ function setSearchIndexAvailable(value) {
589
+ indexAvailable = value;
590
+ }
591
+ function isSearchIndexAvailable() {
592
+ return indexAvailable;
593
+ }
594
+ function camelOf(key) {
595
+ return key.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
596
+ }
597
+ function snakeOf(key) {
598
+ return key.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
599
+ }
600
+ function readField(row, key) {
601
+ if (key in row) return row[key];
602
+ const camel = camelOf(key);
603
+ if (camel in row) return row[camel];
604
+ const snake = snakeOf(key);
605
+ if (snake in row) return row[snake];
606
+ return void 0;
607
+ }
608
+ function asText(value) {
609
+ if (typeof value === "string") return value;
610
+ if (typeof value === "number") return String(value);
611
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string").join(" ");
612
+ return "";
613
+ }
614
+ function searchableKeys(entity) {
615
+ const declared = entity.data?.searchColumns ?? entity.fields.filter((f) => f.searchable).map((f) => f.key);
616
+ const keys = [...declared];
617
+ const display = entity.displayField ?? "name";
618
+ if (!keys.includes(display)) keys.unshift(display);
619
+ const subtitle = entity.subtitleField;
620
+ if (subtitle && !keys.includes(subtitle)) keys.push(subtitle);
621
+ return keys;
622
+ }
623
+ var DIGIT_FIELD_TYPES = /* @__PURE__ */ new Set(["phone", "number"]);
624
+ function digitKeys(entity) {
625
+ const keys = entity.fields.filter((f) => DIGIT_FIELD_TYPES.has(f.type) || /phone|document|cpf|cnpj|zip|postal|sku|code|reference/i.test(f.key)).map((f) => f.key);
626
+ for (const extra of ["phone", "document_number", "sku", "reference_number", "postal_code"]) {
627
+ if (!keys.includes(extra)) keys.push(extra);
628
+ }
629
+ return keys;
630
+ }
631
+ function titleOf(entity, row) {
632
+ const display = entity.displayField ?? "name";
633
+ const primary = asText(readField(row, display));
634
+ if (primary) return primary;
635
+ for (const field of entity.fields) {
636
+ if (field.type === "text" || field.type === "email") {
637
+ const value = asText(readField(row, field.key));
638
+ if (value) return value;
639
+ }
640
+ }
641
+ return asText(row.id) || "\u2014";
642
+ }
643
+ function subtitleOf(entity, row, title) {
644
+ const distinct = (value) => {
645
+ if (!value || foldText(value) === foldText(title)) return void 0;
646
+ return value.length > 90 ? value.slice(0, 89) + "\u2026" : value;
647
+ };
648
+ const declared = entity.subtitleField;
649
+ if (declared) {
650
+ const value = distinct(asText(readField(row, declared)));
651
+ if (value) return value;
652
+ }
653
+ for (const key of ["email", "phone", "sku", "reference_number", "document_number", "description"]) {
654
+ const value = distinct(asText(readField(row, key)));
655
+ if (value) return value;
656
+ }
657
+ return void 0;
658
+ }
659
+ function hitFromRow(target, row, query) {
660
+ const id = asText(row.id);
661
+ if (!id) return null;
662
+ const entity = target.entity;
663
+ const title = titleOf(entity, row);
664
+ const parts = [title];
665
+ for (const key of searchableKeys(entity)) {
666
+ const value = asText(readField(row, key));
667
+ if (value) parts.push(value);
668
+ }
669
+ let digits = "";
670
+ for (const key of digitKeys(entity)) {
671
+ const value = readField(row, key);
672
+ if (typeof value === "string" || typeof value === "number") digits += digitsOf(String(value));
673
+ }
674
+ const score = scoreCandidate(query, {
675
+ title: foldText(title),
676
+ haystack: foldText(parts.join(" ")),
677
+ digits
678
+ });
679
+ if (score === 0) return null;
680
+ return {
681
+ uid: `${target.key}:${id}`,
682
+ id,
683
+ key: target.key,
684
+ group: target.label,
685
+ icon: target.icon ?? entity.icon,
686
+ title,
687
+ subtitle: subtitleOf(entity, row, title),
688
+ score: score * (target.boost ?? 1),
689
+ archetype: entity.data?.archetype,
690
+ archetypeKind: entity.data?.archetypeKind,
691
+ table: entity.data?.table,
692
+ record: row
693
+ };
694
+ }
695
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
696
+ function recordIdentity(hit) {
697
+ if (UUID.test(hit.id)) return hit.id;
698
+ return `${hit.table ?? hit.key}#${hit.id}`;
699
+ }
700
+ function assemble(query, hits, targets, opts) {
701
+ const seen = /* @__PURE__ */ new Set();
702
+ const unique = [];
703
+ for (const hit of hits.slice().sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))) {
704
+ const identity = recordIdentity(hit);
705
+ if (seen.has(identity)) continue;
706
+ seen.add(identity);
707
+ unique.push(hit);
708
+ }
709
+ const byKey = /* @__PURE__ */ new Map();
710
+ const kept = [];
711
+ const capped = /* @__PURE__ */ new Set();
712
+ for (const hit of unique) {
713
+ const bucket = byKey.get(hit.key) ?? [];
714
+ if (bucket.length >= opts.perTarget) {
715
+ capped.add(hit.key);
716
+ continue;
717
+ }
718
+ bucket.push(hit);
719
+ byKey.set(hit.key, bucket);
720
+ kept.push(hit);
721
+ }
722
+ const limited = kept.slice(0, opts.limit);
723
+ const order = new Map(targets.map((t, i) => [t.key, i]));
724
+ const groups = [];
725
+ for (const hit of limited) {
726
+ let group = groups.find((g) => g.key === hit.key);
727
+ if (!group) {
728
+ group = { key: hit.key, label: hit.group, icon: hit.icon, hits: [], hasMore: capped.has(hit.key) };
729
+ groups.push(group);
730
+ }
731
+ group.hits.push(hit);
732
+ }
733
+ groups.sort((a, b) => (b.hits[0]?.score ?? 0) - (a.hits[0]?.score ?? 0) || (order.get(a.key) ?? 99) - (order.get(b.key) ?? 99));
734
+ return {
735
+ query,
736
+ hits: limited,
737
+ groups,
738
+ via: opts.via,
739
+ failed: opts.failed,
740
+ elapsedMs: opts.elapsedMs,
741
+ partial: opts.partial ?? false
742
+ };
743
+ }
744
+ var cache = [];
745
+ function scopeKey(targets) {
746
+ return `${getActiveTenantId() ?? "_"}|${targets.map((t) => t.key).sort().join(",")}`;
747
+ }
748
+ function cacheGet(scope, folded) {
749
+ const now = Date.now();
750
+ return cache.find((e) => e.scope === scope && e.folded === folded && now - e.at < CACHE_TTL_MS);
751
+ }
752
+ function cachePrefix(scope, folded) {
753
+ const now = Date.now();
754
+ let best;
755
+ for (const entry of cache) {
756
+ if (entry.scope !== scope || !entry.complete) continue;
757
+ if (now - entry.at >= CACHE_TTL_MS) continue;
758
+ if (!folded.startsWith(entry.folded)) continue;
759
+ if (!best || entry.folded.length > best.folded.length) best = entry;
760
+ }
761
+ return best;
762
+ }
763
+ function cacheSet(entry) {
764
+ const at = cache.findIndex((e) => e.scope === entry.scope && e.folded === entry.folded);
765
+ if (at >= 0) cache.splice(at, 1);
766
+ cache.unshift(entry);
767
+ if (cache.length > CACHE_MAX_ENTRIES) cache.length = CACHE_MAX_ENTRIES;
768
+ }
769
+ function clearSearchCache() {
770
+ cache.length = 0;
771
+ }
772
+ function isMissingFunction(error) {
773
+ if (!error) return false;
774
+ if (error.code === "PGRST202") return true;
775
+ const message = (error.message ?? "").toLowerCase();
776
+ return message.includes(RPC_NAME) && (message.includes("does not exist") || message.includes("not find"));
777
+ }
778
+ async function searchViaIndex(query, targets, perTarget, limit) {
779
+ const client = getSupabaseClientOptional();
780
+ const tenantId = getActiveTenantId();
781
+ if (!client || !tenantId) return { hits: [], ok: false };
782
+ const byKey = new Map(targets.map((t) => [t.key, t]));
783
+ const { data, error } = await client.rpc(RPC_NAME, {
784
+ p_query: query.raw,
785
+ p_tenant_id: tenantId,
786
+ p_entity_keys: targets.map((t) => t.key),
787
+ p_per_source: perTarget * 3,
788
+ p_limit: limit * 3
789
+ });
790
+ if (error) {
791
+ if (isMissingFunction(error)) {
792
+ indexAvailable = false;
793
+ return { hits: [], ok: false };
794
+ }
795
+ throw error;
796
+ }
797
+ indexAvailable = true;
798
+ const rows = Array.isArray(data) ? data : [];
799
+ const hits = [];
800
+ for (const row of rows) {
801
+ const target = byKey.get(row.entity_key);
802
+ if (!target || !row.record_id) continue;
803
+ const title = row.title ?? "";
804
+ const local = scoreCandidate(query, {
805
+ title: foldText(title),
806
+ haystack: foldText(`${title} ${row.subtitle ?? ""}`),
807
+ digits: digitsOf(row.subtitle ?? "") + digitsOf(title)
808
+ });
809
+ const score = local > 0 ? local : Math.min(0.55, row.score ?? 0.4);
810
+ hits.push({
811
+ uid: `${target.key}:${row.record_id}`,
812
+ id: row.record_id,
813
+ key: target.key,
814
+ group: target.label,
815
+ icon: target.icon ?? target.entity.icon,
816
+ title: title || "\u2014",
817
+ subtitle: row.subtitle ?? void 0,
818
+ score: score * (target.boost ?? 1),
819
+ archetype: target.entity.data?.archetype,
820
+ archetypeKind: target.entity.data?.archetypeKind,
821
+ table: target.entity.data?.table
822
+ });
823
+ }
824
+ return { hits, ok: true };
825
+ }
826
+ async function withTimeout(promise, ms) {
827
+ let timer;
828
+ try {
829
+ return await Promise.race([
830
+ promise,
831
+ new Promise((_, reject) => {
832
+ timer = setTimeout(() => reject(new Error("search target timed out")), ms);
833
+ })
834
+ ]);
835
+ } finally {
836
+ if (timer) clearTimeout(timer);
837
+ }
838
+ }
839
+ async function mapLimited(items, limit, fn) {
840
+ const results = new Array(items.length);
841
+ let cursor = 0;
842
+ const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
843
+ for (; ; ) {
844
+ const index = cursor++;
845
+ if (index >= items.length) return;
846
+ const item = items[index];
847
+ try {
848
+ results[index] = { ok: true, value: await fn(item) };
849
+ } catch {
850
+ results[index] = { ok: false, item };
851
+ }
852
+ }
853
+ });
854
+ await Promise.all(workers);
855
+ return results;
856
+ }
857
+ async function searchViaScan(query, targets, perTarget, onProgress) {
858
+ const multiWord = query.tokens.length > 1;
859
+ const phrase = query.folded;
860
+ const token = query.anchor.length >= MIN_QUERY_LENGTH ? query.anchor : phrase;
861
+ const pageSize = Math.min(perTarget * 3, 24);
862
+ const widePageSize = Math.min(perTarget * 6, 40);
863
+ const searchable = targets.filter((t) => searchableKeys(t.entity).length > 0);
864
+ const running = [];
865
+ const round = (search, size) => mapLimited(searchable, SCAN_CONCURRENCY, async (target) => {
866
+ const provider = resolveDataProvider(target.entity, target.mockData);
867
+ const result = await withTimeout(
868
+ provider.list({ search, pageSize: size, countMode: "none" }),
869
+ TARGET_TIMEOUT_MS
870
+ );
871
+ const rows = result.data;
872
+ const hits = rows.map((row) => hitFromRow(target, row, query)).filter((h) => h !== null);
873
+ if (hits.length && onProgress) {
874
+ running.push(...hits);
875
+ onProgress(running.slice());
876
+ }
877
+ return { target, hits };
878
+ });
879
+ const collect = (settled) => {
880
+ const hits = [];
881
+ const failed = [];
882
+ for (const outcome of settled) {
883
+ if (outcome.ok) hits.push(...outcome.value.hits);
884
+ else failed.push(outcome.item.key);
885
+ }
886
+ return { hits, failed };
887
+ };
888
+ const first = collect(await round(phrase, pageSize));
889
+ if (first.hits.length > 0 || !multiWord || token === phrase) return first;
890
+ const second = collect(await round(token, widePageSize));
891
+ return { hits: second.hits, failed: [.../* @__PURE__ */ new Set([...first.failed, ...second.failed])] };
892
+ }
893
+ async function searchEverything(rawQuery, options) {
894
+ const started = Date.now();
895
+ const query = normalizeQuery(rawQuery);
896
+ const targets = options.targets;
897
+ const limit = options.limit ?? DEFAULT_LIMIT;
898
+ const perTarget = options.perTarget ?? DEFAULT_PER_TARGET;
899
+ const empty = (via2) => assemble(rawQuery, [], targets, { via: via2, failed: [], elapsedMs: Date.now() - started, perTarget, limit });
900
+ if (query.folded.length < MIN_QUERY_LENGTH || targets.length === 0) return empty("cache");
901
+ const scope = scopeKey(targets);
902
+ const exact = cacheGet(scope, query.folded);
903
+ if (exact) {
904
+ return assemble(rawQuery, exact.hits, targets, {
905
+ via: "cache",
906
+ failed: [],
907
+ elapsedMs: Date.now() - started,
908
+ perTarget,
909
+ limit
910
+ });
911
+ }
912
+ if (options.onPartial) {
913
+ const prefix = cachePrefix(scope, query.folded);
914
+ if (prefix) {
915
+ const rescored = prefix.hits.map((hit) => {
916
+ const score = scoreCandidate(query, {
917
+ title: foldText(hit.title),
918
+ haystack: foldText(`${hit.title} ${hit.subtitle ?? ""}`),
919
+ digits: digitsOf(hit.subtitle ?? "")
920
+ });
921
+ return score > 0 ? { ...hit, score } : null;
922
+ }).filter((h) => h !== null);
923
+ options.onPartial(assemble(rawQuery, rescored, targets, {
924
+ via: "cache",
925
+ failed: [],
926
+ elapsedMs: Date.now() - started,
927
+ perTarget,
928
+ limit,
929
+ partial: true
930
+ }));
931
+ }
932
+ }
933
+ let hits = [];
934
+ let failed = [];
935
+ let via = "scan";
936
+ if (!options.forceScan && indexAvailable !== false) {
937
+ try {
938
+ const indexed = await searchViaIndex(query, targets, perTarget, limit);
939
+ if (indexed.ok) {
940
+ hits = indexed.hits;
941
+ via = "index";
942
+ }
943
+ } catch {
944
+ }
945
+ }
946
+ if (via !== "index") {
947
+ const emit = options.onPartial;
948
+ const scanned = await searchViaScan(query, targets, perTarget, emit ? (running) => emit(assemble(rawQuery, running, targets, {
949
+ via: "scan",
950
+ failed: [],
951
+ elapsedMs: Date.now() - started,
952
+ perTarget,
953
+ limit,
954
+ partial: true
955
+ })) : void 0);
956
+ hits = scanned.hits;
957
+ failed = scanned.failed;
958
+ }
959
+ if (options.signal?.aborted) return empty(via);
960
+ const result = assemble(rawQuery, hits, targets, {
961
+ via,
962
+ failed,
963
+ elapsedMs: Date.now() - started,
964
+ perTarget,
965
+ limit
966
+ });
967
+ cacheSet({
968
+ scope,
969
+ folded: query.folded,
970
+ at: Date.now(),
971
+ // Only an uncapped answer is a sound superset for longer queries.
972
+ complete: failed.length === 0 && !result.groups.some((g) => g.hasMore),
973
+ hits
974
+ });
975
+ return result;
297
976
  }
298
977
  function UnknownBlock({ type }) {
299
- return React2.createElement(
978
+ return React3.createElement(
300
979
  "div",
301
980
  {
302
981
  style: {
@@ -313,14 +992,14 @@ function UnknownBlock({ type }) {
313
992
  function BlockRenderer({ node }) {
314
993
  const Comp = getBlock(node.type);
315
994
  const childEls = node.children?.length ? node.children.map(
316
- (child, i) => React2.createElement(BlockRenderer, { key: child.id ?? `${child.type}-${i}`, node: child })
995
+ (child, i) => React3.createElement(BlockRenderer, { key: child.id ?? `${child.type}-${i}`, node: child })
317
996
  ) : null;
318
- if (!Comp) return React2.createElement(UnknownBlock, { type: node.type });
319
- return React2.createElement(Comp, { ...node.props ?? {}, node }, childEls);
997
+ if (!Comp) return React3.createElement(UnknownBlock, { type: node.type });
998
+ return React3.createElement(Comp, { ...node.props ?? {}, node }, childEls);
320
999
  }
321
1000
  function renderBlocks(nodes) {
322
1001
  return nodes.map(
323
- (node, i) => React2.createElement(BlockRenderer, { key: node.id ?? `${node.type}-${i}`, node })
1002
+ (node, i) => React3.createElement(BlockRenderer, { key: node.id ?? `${node.type}-${i}`, node })
324
1003
  );
325
1004
  }
326
1005
  function BlockChildren({ node }) {
@@ -329,7 +1008,7 @@ function BlockChildren({ node }) {
329
1008
  }
330
1009
 
331
1010
  // src/manifest/index.ts
332
- var CURRENT_MANIFEST_VERSION = 2;
1011
+ var CURRENT_MANIFEST_VERSION = 3;
333
1012
  var migrations = /* @__PURE__ */ new Map();
334
1013
  var supportedBackendProviders = ["supabase", "fayz-api", "fayz-shop", "mock", "custom"];
335
1014
  var supportedPageSections = /* @__PURE__ */ new Set(["main", "secondary", "settings"]);
@@ -343,7 +1022,10 @@ var allowedManifestKeys = /* @__PURE__ */ new Set([
343
1022
  "permissions",
344
1023
  "billing",
345
1024
  "entities",
346
- "surfaces"
1025
+ "surfaces",
1026
+ "limitDeclarations",
1027
+ "agent",
1028
+ "contractHash"
347
1029
  ]);
348
1030
  var allowedBackendKeys = /* @__PURE__ */ new Set(["provider", "projectRef", "url", "adapterId", "options"]);
349
1031
  var allowedSurfaceKeys = /* @__PURE__ */ new Set(["scaffold", "options", "plugins", "pages"]);
@@ -355,6 +1037,7 @@ var manifestIdPattern = /^[a-z0-9][a-z0-9-]*$/;
355
1037
  function registerManifestMigration(fromVersion, fn) {
356
1038
  migrations.set(fromVersion, fn);
357
1039
  }
1040
+ registerManifestMigration(2, (m) => m);
358
1041
  function migrateManifest(input) {
359
1042
  let m = { ...input };
360
1043
  let version = typeof m.manifestVersion === "number" ? m.manifestVersion : 1;
@@ -392,6 +1075,21 @@ function validateManifest(m) {
392
1075
  validateLooseObject(manifest.theme, "manifest.theme", problems);
393
1076
  validateLooseObject(manifest.permissions, "manifest.permissions", problems);
394
1077
  validateLooseObject(manifest.billing, "manifest.billing", problems);
1078
+ validateLooseObject(manifest.agent, "manifest.agent", problems);
1079
+ if (manifest.contractHash !== void 0 && !isNonEmptyString(manifest.contractHash)) {
1080
+ problems.push("manifest.contractHash must be a non-empty string");
1081
+ }
1082
+ if (manifest.limitDeclarations !== void 0) {
1083
+ if (!Array.isArray(manifest.limitDeclarations)) {
1084
+ problems.push("manifest.limitDeclarations must be an array");
1085
+ } else {
1086
+ manifest.limitDeclarations.forEach((decl, index) => {
1087
+ if (!isRecord(decl) || !isNonEmptyString(decl.key) || !isNonEmptyString(decl.table)) {
1088
+ problems.push(`manifest.limitDeclarations[${index}] must declare key and table`);
1089
+ }
1090
+ });
1091
+ }
1092
+ }
395
1093
  if (manifest.entities !== void 0) {
396
1094
  if (!Array.isArray(manifest.entities)) {
397
1095
  problems.push("manifest.entities must be an array");
@@ -552,7 +1250,7 @@ function validateBlockManifest(block, path, problems) {
552
1250
  function addUnsupportedKeys(value, allowedKeys, path, problems) {
553
1251
  for (const key of Object.keys(value)) {
554
1252
  if (!allowedKeys.has(key)) {
555
- problems.push(`${path}.${key} is not part of AppManifest v2`);
1253
+ problems.push(`${path}.${key} is not part of AppManifest v${CURRENT_MANIFEST_VERSION}`);
556
1254
  }
557
1255
  }
558
1256
  }
@@ -573,113 +1271,717 @@ function isNonEmptyString(value) {
573
1271
  return typeof value === "string" && value.trim().length > 0;
574
1272
  }
575
1273
 
1274
+ // src/manifest/serialize.ts
1275
+ function sortValue(value) {
1276
+ if (Array.isArray(value)) return value.map(sortValue);
1277
+ if (value !== null && typeof value === "object") {
1278
+ const record = value;
1279
+ const out = {};
1280
+ for (const key of Object.keys(record).sort()) {
1281
+ const v = record[key];
1282
+ if (v !== void 0) out[key] = sortValue(v);
1283
+ }
1284
+ return out;
1285
+ }
1286
+ return value;
1287
+ }
1288
+ function serializeManifest(manifest) {
1289
+ const { contractHash: _omit, ...rest } = manifest;
1290
+ return `${JSON.stringify(sortValue(rest), null, 2)}
1291
+ `;
1292
+ }
1293
+ async function computeContractHash(manifest) {
1294
+ const bytes = new TextEncoder().encode(serializeManifest(manifest));
1295
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
1296
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
1297
+ }
1298
+ async function serializeManifestWithHash(manifest) {
1299
+ const contractHash = await computeContractHash(manifest);
1300
+ const { contractHash: _omit, ...rest } = manifest;
1301
+ return `${JSON.stringify(sortValue({ ...rest, contractHash }), null, 2)}
1302
+ `;
1303
+ }
1304
+
576
1305
  // src/manifest/app-manifest.schema.json
577
1306
  var app_manifest_schema_default = {
578
1307
  $schema: "https://json-schema.org/draft/2020-12/schema",
579
- $id: "https://fayz.ai/schemas/app-manifest/v2.json",
1308
+ $id: "https://fayz.ai/schemas/app-manifest/v3.json",
580
1309
  title: "Fayz AppManifest",
581
1310
  description: "The canonical, JSON-serializable definition of a Fayz app. Code is referenced by registry id; never inlined.",
582
1311
  type: "object",
583
- required: ["manifestVersion", "id", "name", "surfaces"],
1312
+ required: [
1313
+ "manifestVersion",
1314
+ "id",
1315
+ "name",
1316
+ "surfaces"
1317
+ ],
584
1318
  additionalProperties: false,
585
1319
  properties: {
586
1320
  manifestVersion: {
587
1321
  type: "integer",
588
- const: 2,
1322
+ const: 3,
589
1323
  description: "Current AppManifest schema version. Older or newer versions require an explicit SDK migration before validation."
590
1324
  },
591
- id: { type: "string", minLength: 1, pattern: "^[a-z0-9][a-z0-9-]*$" },
592
- name: { type: "string", minLength: 1 },
1325
+ id: {
1326
+ type: "string",
1327
+ minLength: 1,
1328
+ pattern: "^[a-z0-9][a-z0-9-]*$"
1329
+ },
1330
+ name: {
1331
+ type: "string",
1332
+ minLength: 1
1333
+ },
593
1334
  backend: {
594
1335
  type: "object",
595
1336
  additionalProperties: false,
596
- required: ["provider"],
1337
+ required: [
1338
+ "provider"
1339
+ ],
597
1340
  properties: {
598
- provider: { enum: ["supabase", "fayz-api", "fayz-shop", "mock", "custom"] },
599
- projectRef: { type: "string", minLength: 1 },
600
- url: { type: "string", minLength: 1 },
601
- adapterId: { type: "string", minLength: 1 },
602
- options: { type: "object" }
1341
+ provider: {
1342
+ enum: [
1343
+ "supabase",
1344
+ "fayz-api",
1345
+ "fayz-shop",
1346
+ "mock",
1347
+ "custom"
1348
+ ]
1349
+ },
1350
+ projectRef: {
1351
+ type: "string",
1352
+ minLength: 1
1353
+ },
1354
+ url: {
1355
+ type: "string",
1356
+ minLength: 1
1357
+ },
1358
+ adapterId: {
1359
+ type: "string",
1360
+ minLength: 1
1361
+ },
1362
+ options: {
1363
+ type: "object"
1364
+ }
1365
+ }
1366
+ },
1367
+ locale: {
1368
+ type: "object"
1369
+ },
1370
+ theme: {
1371
+ type: "object"
1372
+ },
1373
+ permissions: {
1374
+ type: "object"
1375
+ },
1376
+ billing: {
1377
+ type: "object"
1378
+ },
1379
+ entities: {
1380
+ type: "array",
1381
+ items: {
1382
+ type: "object"
603
1383
  }
604
1384
  },
605
- locale: { type: "object" },
606
- theme: { type: "object" },
607
- permissions: { type: "object" },
608
- billing: { type: "object" },
609
- entities: { type: "array", items: { type: "object" } },
610
1385
  surfaces: {
611
1386
  type: "object",
612
1387
  minProperties: 1,
613
- additionalProperties: { $ref: "#/$defs/surface" }
1388
+ additionalProperties: {
1389
+ $ref: "#/$defs/surface"
1390
+ }
1391
+ },
1392
+ limitDeclarations: {
1393
+ type: "array",
1394
+ description: "Resolved 4-layer limit-declaration merge (core < entity-derived < plugin < app) so server runtimes count exactly what the client counts.",
1395
+ items: {
1396
+ type: "object",
1397
+ required: [
1398
+ "key",
1399
+ "table"
1400
+ ],
1401
+ additionalProperties: false,
1402
+ properties: {
1403
+ key: {
1404
+ type: "string",
1405
+ minLength: 1
1406
+ },
1407
+ label: {
1408
+ type: "string"
1409
+ },
1410
+ table: {
1411
+ type: "string",
1412
+ minLength: 1
1413
+ },
1414
+ kindFilter: {
1415
+ type: "string"
1416
+ },
1417
+ period: {
1418
+ enum: [
1419
+ "month",
1420
+ "total"
1421
+ ]
1422
+ }
1423
+ }
1424
+ }
1425
+ },
1426
+ agent: {
1427
+ $ref: "#/$defs/agentContract"
1428
+ },
1429
+ contractHash: {
1430
+ type: "string",
1431
+ minLength: 1,
1432
+ description: "sha256 of the canonical serialization (excluding this field)."
614
1433
  }
615
1434
  },
616
1435
  $defs: {
617
1436
  surface: {
618
1437
  type: "object",
619
- required: ["scaffold"],
1438
+ required: [
1439
+ "scaffold"
1440
+ ],
620
1441
  additionalProperties: false,
621
1442
  properties: {
622
- scaffold: { type: "string", minLength: 1 },
623
- options: { type: "object" },
624
- plugins: { type: "array", items: { $ref: "#/$defs/pluginRef" } },
625
- pages: { type: "array", items: { $ref: "#/$defs/page" } }
1443
+ scaffold: {
1444
+ type: "string",
1445
+ minLength: 1
1446
+ },
1447
+ options: {
1448
+ type: "object"
1449
+ },
1450
+ plugins: {
1451
+ type: "array",
1452
+ items: {
1453
+ $ref: "#/$defs/pluginRef"
1454
+ }
1455
+ },
1456
+ pages: {
1457
+ type: "array",
1458
+ items: {
1459
+ $ref: "#/$defs/page"
1460
+ }
1461
+ }
626
1462
  }
627
1463
  },
628
1464
  pluginRef: {
629
1465
  type: "object",
630
- required: ["id"],
1466
+ required: [
1467
+ "id"
1468
+ ],
631
1469
  additionalProperties: false,
632
1470
  properties: {
633
- id: { type: "string", minLength: 1 },
634
- config: { type: "object" },
635
- enabled: { type: "boolean" }
1471
+ id: {
1472
+ type: "string",
1473
+ minLength: 1
1474
+ },
1475
+ config: {
1476
+ type: "object"
1477
+ },
1478
+ enabled: {
1479
+ type: "boolean"
1480
+ }
636
1481
  }
637
1482
  },
638
1483
  page: {
639
1484
  type: "object",
640
- required: ["path"],
1485
+ required: [
1486
+ "path"
1487
+ ],
641
1488
  additionalProperties: false,
642
1489
  description: "Exactly one of blocks/entity/component must be set.",
643
1490
  properties: {
644
- path: { type: "string", minLength: 1 },
645
- label: { type: "string" },
646
- icon: { type: "string" },
647
- section: { enum: ["main", "secondary", "settings"] },
648
- blocks: { type: "array", items: { $ref: "#/$defs/block" } },
649
- entity: { type: "string", minLength: 1 },
650
- component: { type: "string", minLength: 1 },
1491
+ path: {
1492
+ type: "string",
1493
+ minLength: 1
1494
+ },
1495
+ label: {
1496
+ type: "string"
1497
+ },
1498
+ icon: {
1499
+ type: "string"
1500
+ },
1501
+ section: {
1502
+ enum: [
1503
+ "main",
1504
+ "secondary",
1505
+ "settings"
1506
+ ]
1507
+ },
1508
+ blocks: {
1509
+ type: "array",
1510
+ items: {
1511
+ $ref: "#/$defs/block"
1512
+ }
1513
+ },
1514
+ entity: {
1515
+ type: "string",
1516
+ minLength: 1
1517
+ },
1518
+ component: {
1519
+ type: "string",
1520
+ minLength: 1
1521
+ },
651
1522
  permission: {
652
1523
  type: "object",
653
- required: ["feature", "action"],
1524
+ required: [
1525
+ "feature",
1526
+ "action"
1527
+ ],
654
1528
  additionalProperties: false,
655
1529
  properties: {
656
- feature: { type: "string", minLength: 1 },
657
- action: { type: "string", minLength: 1 }
1530
+ feature: {
1531
+ type: "string",
1532
+ minLength: 1
1533
+ },
1534
+ action: {
1535
+ type: "string",
1536
+ minLength: 1
1537
+ }
658
1538
  }
659
1539
  }
660
1540
  },
661
1541
  oneOf: [
662
- { required: ["blocks"] },
663
- { required: ["entity"] },
664
- { required: ["component"] }
1542
+ {
1543
+ required: [
1544
+ "blocks"
1545
+ ]
1546
+ },
1547
+ {
1548
+ required: [
1549
+ "entity"
1550
+ ]
1551
+ },
1552
+ {
1553
+ required: [
1554
+ "component"
1555
+ ]
1556
+ }
665
1557
  ]
666
1558
  },
667
1559
  block: {
668
1560
  type: "object",
669
- required: ["type"],
1561
+ required: [
1562
+ "type"
1563
+ ],
1564
+ additionalProperties: false,
1565
+ properties: {
1566
+ type: {
1567
+ type: "string",
1568
+ minLength: 1
1569
+ },
1570
+ id: {
1571
+ type: "string"
1572
+ },
1573
+ props: {
1574
+ type: "object"
1575
+ },
1576
+ children: {
1577
+ type: "array",
1578
+ items: {
1579
+ $ref: "#/$defs/block"
1580
+ }
1581
+ }
1582
+ }
1583
+ },
1584
+ agentContract: {
1585
+ type: "object",
670
1586
  additionalProperties: false,
1587
+ description: "Everything a server-side agent runtime needs to operate the app without a browser. Derived from code at publish; synced to Fayz as the versioned AgentContract.",
671
1588
  properties: {
672
- type: { type: "string", minLength: 1 },
673
- id: { type: "string" },
674
- props: { type: "object" },
675
- children: { type: "array", items: { $ref: "#/$defs/block" } }
1589
+ persona: {
1590
+ type: "object",
1591
+ additionalProperties: false,
1592
+ properties: {
1593
+ name: {
1594
+ type: "string"
1595
+ },
1596
+ systemPrompt: {
1597
+ type: "string"
1598
+ },
1599
+ icon: {
1600
+ type: "string"
1601
+ }
1602
+ }
1603
+ },
1604
+ executionPlane: {
1605
+ enum: [
1606
+ "client",
1607
+ "server"
1608
+ ]
1609
+ },
1610
+ entities: {
1611
+ type: "array",
1612
+ items: {
1613
+ $ref: "#/$defs/entityContract"
1614
+ }
1615
+ },
1616
+ tools: {
1617
+ type: "array",
1618
+ items: {
1619
+ $ref: "#/$defs/aiToolContract"
1620
+ }
1621
+ },
1622
+ registries: {
1623
+ type: "array",
1624
+ items: {
1625
+ type: "object",
1626
+ required: [
1627
+ "pluginId",
1628
+ "id"
1629
+ ],
1630
+ additionalProperties: false,
1631
+ properties: {
1632
+ pluginId: {
1633
+ type: "string",
1634
+ minLength: 1
1635
+ },
1636
+ id: {
1637
+ type: "string",
1638
+ minLength: 1
1639
+ },
1640
+ entityKey: {
1641
+ type: "string"
1642
+ },
1643
+ readOnly: {
1644
+ type: "boolean"
1645
+ }
1646
+ }
1647
+ }
1648
+ },
1649
+ declaredFeatures: {
1650
+ type: "array",
1651
+ items: {
1652
+ type: "object"
1653
+ }
1654
+ },
1655
+ rpcs: {
1656
+ type: "array",
1657
+ items: {
1658
+ $ref: "#/$defs/rpcContract"
1659
+ }
1660
+ },
1661
+ domainKnowledge: {
1662
+ type: "object",
1663
+ additionalProperties: false,
1664
+ properties: {
1665
+ statuses: {
1666
+ type: "object"
1667
+ },
1668
+ scheduleDefaults: {
1669
+ type: "object"
1670
+ },
1671
+ businessRules: {
1672
+ type: "array",
1673
+ items: {
1674
+ type: "object",
1675
+ required: [
1676
+ "id",
1677
+ "description"
1678
+ ],
1679
+ additionalProperties: false,
1680
+ properties: {
1681
+ id: {
1682
+ type: "string",
1683
+ minLength: 1
1684
+ },
1685
+ description: {
1686
+ type: "string",
1687
+ minLength: 1
1688
+ }
1689
+ }
1690
+ }
1691
+ }
1692
+ }
1693
+ }
1694
+ }
1695
+ },
1696
+ entityContract: {
1697
+ type: "object",
1698
+ required: [
1699
+ "key",
1700
+ "label",
1701
+ "data",
1702
+ "fields"
1703
+ ],
1704
+ additionalProperties: false,
1705
+ properties: {
1706
+ key: {
1707
+ type: "string",
1708
+ minLength: 1
1709
+ },
1710
+ label: {
1711
+ type: "string",
1712
+ minLength: 1
1713
+ },
1714
+ labelPlural: {
1715
+ type: "string"
1716
+ },
1717
+ limitKey: {
1718
+ type: "string"
1719
+ },
1720
+ data: {
1721
+ type: "object",
1722
+ required: [
1723
+ "table"
1724
+ ],
1725
+ additionalProperties: false,
1726
+ properties: {
1727
+ table: {
1728
+ type: "string",
1729
+ minLength: 1
1730
+ },
1731
+ schema: {
1732
+ type: "string"
1733
+ },
1734
+ tenantScoped: {
1735
+ type: "boolean"
1736
+ },
1737
+ tenantIdColumn: {
1738
+ type: "string"
1739
+ },
1740
+ searchColumns: {
1741
+ type: "array",
1742
+ items: {
1743
+ type: "string"
1744
+ }
1745
+ },
1746
+ selectColumns: {
1747
+ type: "string"
1748
+ },
1749
+ columnMap: {
1750
+ type: "object"
1751
+ },
1752
+ archetype: {
1753
+ type: "string"
1754
+ },
1755
+ archetypeKind: {
1756
+ type: "string"
1757
+ },
1758
+ filters: {
1759
+ type: "object"
1760
+ }
1761
+ }
1762
+ },
1763
+ fields: {
1764
+ type: "array",
1765
+ items: {
1766
+ type: "object",
1767
+ required: [
1768
+ "key",
1769
+ "label",
1770
+ "type"
1771
+ ],
1772
+ additionalProperties: false,
1773
+ properties: {
1774
+ key: {
1775
+ type: "string",
1776
+ minLength: 1
1777
+ },
1778
+ label: {
1779
+ type: "string"
1780
+ },
1781
+ type: {
1782
+ type: "string",
1783
+ minLength: 1
1784
+ },
1785
+ required: {
1786
+ type: "boolean"
1787
+ },
1788
+ searchable: {
1789
+ type: "boolean"
1790
+ },
1791
+ hint: {
1792
+ type: "string"
1793
+ },
1794
+ options: {
1795
+ type: "array",
1796
+ items: {
1797
+ type: "string"
1798
+ }
1799
+ },
1800
+ relationTable: {
1801
+ type: "string"
1802
+ }
1803
+ }
1804
+ }
1805
+ },
1806
+ permission: {
1807
+ type: "object",
1808
+ required: [
1809
+ "feature",
1810
+ "action"
1811
+ ],
1812
+ additionalProperties: false,
1813
+ properties: {
1814
+ feature: {
1815
+ type: "string",
1816
+ minLength: 1
1817
+ },
1818
+ action: {
1819
+ type: "string",
1820
+ minLength: 1
1821
+ }
1822
+ }
1823
+ }
1824
+ }
1825
+ },
1826
+ aiToolContract: {
1827
+ type: "object",
1828
+ required: [
1829
+ "id",
1830
+ "name",
1831
+ "description",
1832
+ "mode"
1833
+ ],
1834
+ additionalProperties: false,
1835
+ properties: {
1836
+ id: {
1837
+ type: "string",
1838
+ minLength: 1
1839
+ },
1840
+ name: {
1841
+ type: "string",
1842
+ minLength: 1,
1843
+ pattern: "^[a-zA-Z0-9_-]{1,64}$"
1844
+ },
1845
+ description: {
1846
+ type: "string",
1847
+ minLength: 1
1848
+ },
1849
+ mode: {
1850
+ enum: [
1851
+ "read",
1852
+ "persist"
1853
+ ]
1854
+ },
1855
+ parameters: {
1856
+ type: "object"
1857
+ },
1858
+ permission: {
1859
+ type: "object",
1860
+ required: [
1861
+ "feature",
1862
+ "action"
1863
+ ],
1864
+ additionalProperties: false,
1865
+ properties: {
1866
+ feature: {
1867
+ type: "string",
1868
+ minLength: 1
1869
+ },
1870
+ action: {
1871
+ type: "string",
1872
+ minLength: 1
1873
+ }
1874
+ }
1875
+ },
1876
+ limitKey: {
1877
+ type: "string"
1878
+ },
1879
+ execution: {
1880
+ oneOf: [
1881
+ {
1882
+ type: "object",
1883
+ required: [
1884
+ "plane",
1885
+ "kind",
1886
+ "rpc"
1887
+ ],
1888
+ additionalProperties: false,
1889
+ properties: {
1890
+ plane: {
1891
+ const: "server"
1892
+ },
1893
+ kind: {
1894
+ const: "rpc"
1895
+ },
1896
+ rpc: {
1897
+ type: "string",
1898
+ minLength: 1
1899
+ }
1900
+ }
1901
+ },
1902
+ {
1903
+ type: "object",
1904
+ required: [
1905
+ "plane",
1906
+ "kind",
1907
+ "entity"
1908
+ ],
1909
+ additionalProperties: false,
1910
+ properties: {
1911
+ plane: {
1912
+ const: "server"
1913
+ },
1914
+ kind: {
1915
+ const: "entity_read"
1916
+ },
1917
+ entity: {
1918
+ type: "string",
1919
+ minLength: 1
1920
+ }
1921
+ }
1922
+ },
1923
+ {
1924
+ type: "object",
1925
+ required: [
1926
+ "plane"
1927
+ ],
1928
+ additionalProperties: false,
1929
+ properties: {
1930
+ plane: {
1931
+ const: "client"
1932
+ }
1933
+ }
1934
+ }
1935
+ ]
1936
+ },
1937
+ requiresConfirmation: {
1938
+ type: "boolean"
1939
+ },
1940
+ pluginId: {
1941
+ type: "string"
1942
+ },
1943
+ category: {
1944
+ type: "string"
1945
+ }
1946
+ }
1947
+ },
1948
+ rpcContract: {
1949
+ type: "object",
1950
+ required: [
1951
+ "name",
1952
+ "kind",
1953
+ "description"
1954
+ ],
1955
+ additionalProperties: false,
1956
+ properties: {
1957
+ name: {
1958
+ type: "string",
1959
+ minLength: 1,
1960
+ pattern: "^agent_[a-z0-9_]+$"
1961
+ },
1962
+ kind: {
1963
+ enum: [
1964
+ "read",
1965
+ "write"
1966
+ ]
1967
+ },
1968
+ description: {
1969
+ type: "string",
1970
+ minLength: 1
1971
+ },
1972
+ payloadSchema: {
1973
+ type: "object"
1974
+ },
1975
+ audits: {
1976
+ type: "boolean"
1977
+ }
676
1978
  }
677
1979
  }
678
1980
  }
679
1981
  };
680
- var ManifestContext = React2.createContext(null);
1982
+ var ManifestContext = React3.createContext(null);
681
1983
  function useManifest() {
682
- return React2.useContext(ManifestContext);
1984
+ return React3.useContext(ManifestContext);
683
1985
  }
684
1986
  function defineApp(manifest) {
685
1987
  const full = {
@@ -711,10 +2013,10 @@ function renderApp(input, opts = {}) {
711
2013
  `Scaffold "${surface.scaffold}" is not registered. Import the package that provides it (e.g. @fayz-ai/saas or @fayz-ai/shop) before calling renderApp.`
712
2014
  );
713
2015
  }
714
- return React2.createElement(
2016
+ return React3.createElement(
715
2017
  ManifestContext.Provider,
716
2018
  { value: manifest },
717
- React2.createElement(Scaffold, { manifest, surface: surfaceName })
2019
+ React3.createElement(Scaffold, { manifest, surface: surfaceName })
718
2020
  );
719
2021
  }
720
2022
  var MAX_ENTRIES = 20;
@@ -757,6 +2059,6 @@ function routeModule(path) {
757
2059
  return path.replace(/^\/+/, "").split("/")[0] ?? "";
758
2060
  }
759
2061
 
760
- export { BlockChildren, BlockRenderer, COUNTRIES, CURRENT_MANIFEST_VERSION, DEFAULT_COUNTRY, WidgetZone, absoluteUrl, app_manifest_schema_default as appManifestSchema, applySeo, breadcrumbJsonLd, buildCSV, createEventBus, createSafeDataProvider, defineApp, downloadCSV, eventBus, exportCSV, formatCurrency, formatDate, formatDateTime, getActiveLocale, getCountry, getDefaultCurrency, maskDigitCount, maskPhone, migrateManifest, navHistoryBack, organizationJsonLd, recordNavigation, registerManifestMigration, renderApp, renderBlocks, routeModule, seoOrigin, setDefaultCurrency, unmaskPhone, useManifest, useNavHistoryStore, useNavReferrer, useOnEvent, useSeo, validateManifest };
2062
+ export { BlockChildren, BlockRenderer, COUNTRIES, CURRENT_MANIFEST_VERSION, DATA_CHANGED_EVENT, DEFAULT_COUNTRY, MIN_QUERY_LENGTH, WidgetZone, absoluteUrl, app_manifest_schema_default as appManifestSchema, applySeo, breadcrumbJsonLd, buildCSV, clearPostalCache, clearSearchCache, computeContractHash, createEventBus, createSafeDataProvider, createViaCepProvider, defineApp, digitsOf, downloadCSV, emitDataChanged, eventBus, exportCSV, foldText, foldWithMap, formatCurrency, formatDate, formatDateTime, formatPostalCode, getActiveLocale, getCountry, getDefaultCurrency, getPostalLookupProvider, highlightRanges, isSearchIndexAvailable, isValidPostalCode, lookupPostalCode, maskDigitCount, maskPhone, matchesDataChange, migrateManifest, navHistoryBack, normalizePostalCode, normalizeQuery, organizationJsonLd, recordNavigation, registerManifestMigration, renderApp, renderBlocks, routeModule, scoreCandidate, searchEverything, seoOrigin, serializeManifest, serializeManifestWithHash, setDefaultCurrency, setPostalLookupProvider, setSearchIndexAvailable, similarity, unmaskPhone, useDataChanged, useManifest, useNavHistoryStore, useNavReferrer, useOnEvent, useSeo, validateManifest };
761
2063
  //# sourceMappingURL=index.js.map
762
2064
  //# sourceMappingURL=index.js.map