@fayz-ai/core 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +2 -2
  2. package/dist/access/index.js.map +1 -1
  3. package/dist/{chunk-L4TLMYGC.js → chunk-6RBW2Y5K.js} +3 -3
  4. package/dist/chunk-6RBW2Y5K.js.map +1 -0
  5. package/dist/chunk-732GZEUI.js +1592 -0
  6. package/dist/chunk-732GZEUI.js.map +1 -0
  7. package/dist/{chunk-X5THHG6T.js → chunk-CPMHCTX3.js} +3 -3
  8. package/dist/chunk-CPMHCTX3.js.map +1 -0
  9. package/dist/{chunk-V2MSAUHO.js → chunk-JO7Z2AOE.js} +2 -2
  10. package/dist/chunk-JO7Z2AOE.js.map +1 -0
  11. package/dist/{chunk-5RJVUC6J.js → chunk-M27JPX3K.js} +12 -3
  12. package/dist/chunk-M27JPX3K.js.map +1 -0
  13. package/dist/chunk-OXQGRJZT.js +125 -0
  14. package/dist/chunk-OXQGRJZT.js.map +1 -0
  15. package/dist/data/backend-env.d.ts +38 -0
  16. package/dist/data/backend-env.d.ts.map +1 -0
  17. package/dist/data/index.js +1 -1
  18. package/dist/entity/events.d.ts +14 -0
  19. package/dist/entity/events.d.ts.map +1 -0
  20. package/dist/events/envelope.d.ts +31 -0
  21. package/dist/events/envelope.d.ts.map +1 -0
  22. package/dist/events/index.d.ts +4 -0
  23. package/dist/events/index.d.ts.map +1 -1
  24. package/dist/events/log.d.ts +44 -0
  25. package/dist/events/log.d.ts.map +1 -0
  26. package/dist/events/types.d.ts +40 -0
  27. package/dist/events/types.d.ts.map +1 -0
  28. package/dist/i18n/index.d.ts +10 -0
  29. package/dist/i18n/index.d.ts.map +1 -1
  30. package/dist/i18n/index.js +1 -1
  31. package/dist/index.d.ts +27 -17
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +388 -52
  34. package/dist/index.js.map +1 -1
  35. package/dist/integrations/index.js +1 -1
  36. package/dist/{integrations/index.d.ts → integrations.d.ts} +2 -2
  37. package/dist/integrations.d.ts.map +1 -0
  38. package/dist/{address/index.d.ts → lib/address.d.ts} +1 -1
  39. package/dist/{address/index.d.ts.map → lib/address.d.ts.map} +1 -1
  40. package/dist/{phone/index.d.ts → lib/phone.d.ts} +1 -1
  41. package/dist/lib/phone.d.ts.map +1 -0
  42. package/dist/{seo/index.d.ts → lib/seo.d.ts} +18 -1
  43. package/dist/lib/seo.d.ts.map +1 -0
  44. package/dist/{tenant/index.d.ts → lib/tenant.d.ts} +1 -1
  45. package/dist/lib/tenant.d.ts.map +1 -0
  46. package/dist/manifest/index.d.ts +1 -1
  47. package/dist/manifest/index.d.ts.map +1 -1
  48. package/dist/messaging/index.d.ts +3 -0
  49. package/dist/messaging/index.d.ts.map +1 -0
  50. package/dist/messaging/registry.d.ts +35 -0
  51. package/dist/messaging/registry.d.ts.map +1 -0
  52. package/dist/messaging/types.d.ts +67 -0
  53. package/dist/messaging/types.d.ts.map +1 -0
  54. package/dist/plugin/index.js +2 -1
  55. package/dist/plugin/runtime.d.ts +17 -0
  56. package/dist/plugin/runtime.d.ts.map +1 -1
  57. package/dist/registry/block-catalog.d.ts +78 -0
  58. package/dist/registry/block-catalog.d.ts.map +1 -0
  59. package/dist/registry/block-constraints.d.ts +64 -0
  60. package/dist/registry/block-constraints.d.ts.map +1 -0
  61. package/dist/registry/block-schema.d.ts +69 -0
  62. package/dist/registry/block-schema.d.ts.map +1 -0
  63. package/dist/registry/blocks.d.ts +84 -0
  64. package/dist/registry/blocks.d.ts.map +1 -0
  65. package/dist/registry/entities.d.ts +153 -0
  66. package/dist/registry/entities.d.ts.map +1 -0
  67. package/dist/registry/extension-points.d.ts +117 -0
  68. package/dist/registry/extension-points.d.ts.map +1 -0
  69. package/dist/registry/handles.d.ts +41 -0
  70. package/dist/registry/handles.d.ts.map +1 -0
  71. package/dist/registry/index.d.ts +41 -41
  72. package/dist/registry/index.d.ts.map +1 -1
  73. package/dist/registry/loaders.d.ts +55 -0
  74. package/dist/registry/loaders.d.ts.map +1 -0
  75. package/dist/registry/registry-class.d.ts +157 -0
  76. package/dist/registry/registry-class.d.ts.map +1 -0
  77. package/dist/registry/schema-check.d.ts +14 -0
  78. package/dist/registry/schema-check.d.ts.map +1 -0
  79. package/dist/router/index.d.ts +78 -1
  80. package/dist/router/index.d.ts.map +1 -1
  81. package/dist/router/index.js +1 -1
  82. package/dist/testing/index.js +1 -1
  83. package/dist/{testing/index.d.ts → testing.d.ts} +3 -3
  84. package/dist/testing.d.ts.map +1 -0
  85. package/dist/types/analytics.d.ts +196 -0
  86. package/dist/types/analytics.d.ts.map +1 -0
  87. package/dist/types/crud.d.ts +22 -2
  88. package/dist/types/crud.d.ts.map +1 -1
  89. package/dist/types/plugins.d.ts +55 -1
  90. package/dist/types/plugins.d.ts.map +1 -1
  91. package/package.json +6 -18
  92. package/dist/blocks/index.d.ts +0 -25
  93. package/dist/blocks/index.d.ts.map +0 -1
  94. package/dist/chunk-5RJVUC6J.js.map +0 -1
  95. package/dist/chunk-L4TLMYGC.js.map +0 -1
  96. package/dist/chunk-V2MSAUHO.js.map +0 -1
  97. package/dist/chunk-X5THHG6T.js.map +0 -1
  98. package/dist/chunk-Y2C2FBQP.js +0 -35
  99. package/dist/chunk-Y2C2FBQP.js.map +0 -1
  100. package/dist/chunk-YH25Y4FW.js +0 -311
  101. package/dist/chunk-YH25Y4FW.js.map +0 -1
  102. package/dist/integrations/index.d.ts.map +0 -1
  103. package/dist/phone/index.d.ts.map +0 -1
  104. package/dist/seo/index.d.ts.map +0 -1
  105. package/dist/tenant/index.d.ts.map +0 -1
  106. package/dist/testing/index.d.ts.map +0 -1
  107. package/src/access/denial.ts +0 -33
  108. package/src/access/engine.test.ts +0 -48
  109. package/src/access/engine.ts +0 -136
  110. package/src/access/index.ts +0 -14
  111. package/src/access/limits.ts +0 -18
  112. package/src/address/index.ts +0 -188
  113. package/src/app/render.tsx +0 -69
  114. package/src/blocks/index.tsx +0 -68
  115. package/src/data/archetype.ts +0 -255
  116. package/src/data/bulk.ts +0 -35
  117. package/src/data/cached.ts +0 -55
  118. package/src/data/count.test.ts +0 -152
  119. package/src/data/count.ts +0 -131
  120. package/src/data/filters.test.ts +0 -70
  121. package/src/data/filters.ts +0 -90
  122. package/src/data/index.ts +0 -11
  123. package/src/data/mock.ts +0 -90
  124. package/src/data/platform-api.ts +0 -209
  125. package/src/data/refresh.ts +0 -115
  126. package/src/data/resolve.ts +0 -126
  127. package/src/data/supabase.ts +0 -230
  128. package/src/data/types.ts +0 -50
  129. package/src/entity/index.ts +0 -2
  130. package/src/entity/registry.ts +0 -49
  131. package/src/events/index.tsx +0 -82
  132. package/src/i18n/index.ts +0 -476
  133. package/src/i18n/shell-translations.en.ts +0 -875
  134. package/src/i18n/shell-translations.pt-br.ts +0 -873
  135. package/src/index.ts +0 -217
  136. package/src/integrations/index.ts +0 -237
  137. package/src/lib/cache.ts +0 -118
  138. package/src/lib/csv.ts +0 -54
  139. package/src/lib/format.ts +0 -58
  140. package/src/manifest/app-manifest.schema.json +0 -676
  141. package/src/manifest/index.ts +0 -518
  142. package/src/manifest/serialize.ts +0 -52
  143. package/src/phone/index.ts +0 -85
  144. package/src/plugin/createSafeDataProvider.ts +0 -30
  145. package/src/plugin/index.ts +0 -1
  146. package/src/plugin/runtime.ts +0 -331
  147. package/src/registry/index.ts +0 -163
  148. package/src/router/history.ts +0 -86
  149. package/src/router/index.ts +0 -29
  150. package/src/runtime/index.ts +0 -15
  151. package/src/runtime/oauth.ts +0 -219
  152. package/src/search/engine.ts +0 -559
  153. package/src/search/index.ts +0 -25
  154. package/src/search/text.ts +0 -206
  155. package/src/search/types.ts +0 -75
  156. package/src/seo/index.ts +0 -128
  157. package/src/tenant/index.ts +0 -38
  158. package/src/testing/index.ts +0 -116
  159. package/src/types/auth.ts +0 -41
  160. package/src/types/billing.ts +0 -39
  161. package/src/types/crud.ts +0 -184
  162. package/src/types/entities.ts +0 -142
  163. package/src/types/entitlements.ts +0 -45
  164. package/src/types/i18n.ts +0 -7
  165. package/src/types/index.ts +0 -11
  166. package/src/types/org.ts +0 -137
  167. package/src/types/payment.ts +0 -44
  168. package/src/types/permissions.ts +0 -32
  169. package/src/types/plugins.ts +0 -480
  170. package/src/types/theme.ts +0 -8
@@ -0,0 +1,1592 @@
1
+ import { getAllEntities } from './chunk-CU64QI4A.js';
2
+ import * as React from 'react';
3
+
4
+ // src/entity/events.ts
5
+ function fieldsFor(entity) {
6
+ return [
7
+ { name: "record_name", label: entity.label, type: "string", example: `${entity.label} Exemplo` },
8
+ { name: "record_id", label: "ID", type: "string", example: "8f14e45f-\u2026" },
9
+ { name: "entity_label", label: "Cadastro", type: "string", example: entity.label },
10
+ { name: "actor_name", label: "Quem fez", type: "string", example: "Jo\xE3o Pereira" },
11
+ { name: "business_name", label: "Seu neg\xF3cio", type: "string", example: "Seu Neg\xF3cio" }
12
+ ];
13
+ }
14
+ function entityEventKey(entityKey, action) {
15
+ return `registry.${entityKey}.${action}`;
16
+ }
17
+ var ACTION_LABEL = {
18
+ created: (l) => `${l} cadastrado`,
19
+ updated: (l) => `${l} atualizado`,
20
+ deleted: (l) => `${l} exclu\xEDdo`,
21
+ imported: (_l, plural) => `${plural} importados`
22
+ };
23
+ var ACTION_TRIGGER = {
24
+ created: (l) => `Quando um registro de ${l.toLowerCase()} \xE9 criado`,
25
+ updated: (l) => `Quando um registro de ${l.toLowerCase()} \xE9 alterado`,
26
+ deleted: (l) => `Quando um registro de ${l.toLowerCase()} \xE9 exclu\xEDdo`,
27
+ imported: (_l, plural) => `Quando uma planilha de ${plural.toLowerCase()} \xE9 importada`
28
+ };
29
+ var ACTION_ICON = {
30
+ created: "Plus",
31
+ updated: "Pencil",
32
+ deleted: "Trash2",
33
+ imported: "Upload"
34
+ };
35
+ function buildEntityDomainEvents(entities) {
36
+ const out = [];
37
+ for (const entity of entities) {
38
+ const fields = fieldsFor(entity);
39
+ const actions = ["created", "updated", "deleted", "imported"];
40
+ for (const action of actions) {
41
+ const plural = entity.labelPlural || entity.label;
42
+ out.push({
43
+ name: entityEventKey(entity.entityKey, action),
44
+ title: ACTION_LABEL[action](entity.label, plural),
45
+ description: `${plural} \xB7 ${ACTION_LABEL[action](entity.label, plural).toLowerCase()}`,
46
+ trigger: ACTION_TRIGGER[action](entity.label, plural),
47
+ icon: entity.icon ?? ACTION_ICON[action],
48
+ fields: action === "imported" ? [
49
+ { name: "record_count", label: "Quantidade", type: "string", example: "128" },
50
+ ...fields.filter((f) => f.name !== "record_name" && f.name !== "record_id")
51
+ ] : fields,
52
+ audit: true,
53
+ // Cadastro não pertence a plugin nenhum: o dono é o app. Agrupar sob
54
+ // "Cadastros" mantém a lista legível quando são trinta entidades.
55
+ pluginId: entity.pluginId ?? "registry",
56
+ pluginName: entity.pluginName ?? "Cadastros",
57
+ pluginIcon: entity.pluginIcon ?? entity.icon
58
+ });
59
+ }
60
+ }
61
+ return out;
62
+ }
63
+
64
+ // src/registry/block-schema.ts
65
+ var BLOCK_SETTING_TYPES = [
66
+ "text",
67
+ "richtext",
68
+ "number",
69
+ "boolean",
70
+ "color",
71
+ "image",
72
+ "url",
73
+ "select",
74
+ "product",
75
+ "category",
76
+ "collection",
77
+ "entity",
78
+ "list"
79
+ ];
80
+ var STRING_TYPES = {
81
+ text: true,
82
+ richtext: true,
83
+ color: true,
84
+ image: true,
85
+ url: true,
86
+ select: true,
87
+ product: true,
88
+ category: true,
89
+ collection: true,
90
+ entity: true
91
+ };
92
+ var FORMAT_BY_TYPE = {
93
+ url: "uri",
94
+ image: "uri",
95
+ color: "color"
96
+ };
97
+ function settingToJsonSchema(setting) {
98
+ const node = {
99
+ title: setting.label,
100
+ // The control survives the compile: a `product` setting is a string in JSON
101
+ // Schema terms, but an editor rendering it as a text input is a bug.
102
+ "x-fayz-control": setting.type
103
+ };
104
+ if (setting.help) node.description = setting.help;
105
+ if (setting.default !== void 0) node.default = setting.default;
106
+ const format = FORMAT_BY_TYPE[setting.type];
107
+ if (format) node.format = format;
108
+ if (setting.type === "number") {
109
+ node.type = "number";
110
+ if (setting.min !== void 0) node.minimum = setting.min;
111
+ if (setting.max !== void 0) node.maximum = setting.max;
112
+ return node;
113
+ }
114
+ if (setting.type === "boolean") {
115
+ node.type = "boolean";
116
+ return node;
117
+ }
118
+ if (setting.type === "list") {
119
+ node.type = "array";
120
+ node.items = setting.of?.length ? settingsToJsonSchema(setting.of) : (
121
+ // A list with no declared item shape accepts anything rather than
122
+ // nothing — an under-specified block should still render.
123
+ {}
124
+ );
125
+ return node;
126
+ }
127
+ if (STRING_TYPES[setting.type]) {
128
+ node.type = "string";
129
+ if (setting.type === "select" && setting.options?.length) {
130
+ node.enum = setting.options.map((option) => option.value);
131
+ node["x-fayz-enum-labels"] = setting.options.map((option) => option.label);
132
+ }
133
+ return node;
134
+ }
135
+ return node;
136
+ }
137
+ function settingsToJsonSchema(settings = []) {
138
+ const properties = {};
139
+ const required = [];
140
+ for (const setting of settings) {
141
+ properties[setting.id] = settingToJsonSchema(setting);
142
+ if (setting.required) required.push(setting.id);
143
+ }
144
+ const schema = { type: "object", properties };
145
+ if (required.length > 0) schema.required = required;
146
+ return schema;
147
+ }
148
+ function defaultPropsFromSettings(settings = []) {
149
+ const props = {};
150
+ for (const setting of settings) {
151
+ if (setting.default !== void 0) props[setting.id] = setting.default;
152
+ }
153
+ return props;
154
+ }
155
+
156
+ // src/registry/registry-class.ts
157
+ var Registry = class {
158
+ constructor(kind) {
159
+ this.kind = kind;
160
+ this.entries = /* @__PURE__ */ new Map();
161
+ }
162
+ register(id, value, meta = {}) {
163
+ const title = meta.title ?? meta.label;
164
+ this.entries.set(id, { id, value, meta: title === void 0 ? meta : { ...meta, title, label: title } });
165
+ }
166
+ get(id) {
167
+ return this.entries.get(id)?.value;
168
+ }
169
+ getEntry(id) {
170
+ return this.entries.get(id);
171
+ }
172
+ has(id) {
173
+ return this.entries.has(id);
174
+ }
175
+ list() {
176
+ return Array.from(this.entries.values());
177
+ }
178
+ ids() {
179
+ return Array.from(this.entries.keys());
180
+ }
181
+ /**
182
+ * Every entry reduced to the uniform meta envelope, sorted by id.
183
+ *
184
+ * Sorted for the same reason as `describeBlocks`: this feeds prompts,
185
+ * documents and generated catalogs, and registration order is an
186
+ * implementation detail that would make every diff noisy.
187
+ */
188
+ describe() {
189
+ return this.list().map(({ id, meta }) => ({
190
+ id,
191
+ kind: this.kind,
192
+ title: meta.title ?? meta.label,
193
+ description: meta.description,
194
+ schema: meta.propsSchema,
195
+ stability: meta.stability ?? "stable",
196
+ since: meta.since,
197
+ source: meta.source,
198
+ pluginId: meta.pluginId
199
+ })).sort((a, b) => a.id.localeCompare(b.id));
200
+ }
201
+ clear() {
202
+ this.entries.clear();
203
+ }
204
+ };
205
+ function isCustomId(id) {
206
+ return id.startsWith("custom:");
207
+ }
208
+
209
+ // src/registry/handles.ts
210
+ var HANDLE_NAME_RE = /^[a-z0-9][a-z0-9-]*(\.[A-Za-z0-9_][A-Za-z0-9_-]*)+$/;
211
+ var handleRegistry = new Registry("handle");
212
+ function formatValidSet() {
213
+ const ids = handleRegistry.ids().sort();
214
+ return ids.length ? `Registered handles: ${ids.join(", ")}` : "No handles are registered \u2014 call registerHandle(name, value) before referencing one.";
215
+ }
216
+ function registerHandle(name, value, meta = {}, options = {}) {
217
+ if (!HANDLE_NAME_RE.test(name)) {
218
+ throw new Error(
219
+ `Invalid handle name "${name}". Handles are namespaced: '<app-or-plugin-id>.<name>' (e.g. 'artorious.customCard'), matching ${String(HANDLE_NAME_RE)}.`
220
+ );
221
+ }
222
+ if (value === void 0) {
223
+ throw new Error(`registerHandle("${name}"): the value is undefined \u2014 a handle must name something.`);
224
+ }
225
+ const existing = handleRegistry.getEntry(name);
226
+ if (existing) {
227
+ if (Object.is(existing.value, value)) return;
228
+ if (!options.replace) {
229
+ throw new Error(
230
+ `Handle "${name}" is already registered with a different value. Pass { replace: true } if the replacement is deliberate, or pick another name. ${formatValidSet()}`
231
+ );
232
+ }
233
+ }
234
+ handleRegistry.register(name, value, meta);
235
+ }
236
+ function getHandle(name) {
237
+ return handleRegistry.get(name);
238
+ }
239
+ function hasHandle(name) {
240
+ return handleRegistry.has(name);
241
+ }
242
+ function resolveHandle(name) {
243
+ if (!handleRegistry.has(name)) {
244
+ throw new Error(`Unknown handle "${name}". ${formatValidSet()}`);
245
+ }
246
+ return handleRegistry.get(name);
247
+ }
248
+ function listHandles() {
249
+ return handleRegistry.ids().sort();
250
+ }
251
+ function handleNameFor(value) {
252
+ for (const entry of handleRegistry.list()) {
253
+ if (Object.is(entry.value, value)) return entry.id;
254
+ }
255
+ return void 0;
256
+ }
257
+ function clearHandles() {
258
+ handleRegistry.clear();
259
+ }
260
+
261
+ // src/registry/loaders.ts
262
+ var loaderRegistry = new Registry("loader");
263
+ function defineLoader(id, impl, meta = {}) {
264
+ const propsSchema = meta.settings ? settingsToJsonSchema(meta.settings) : void 0;
265
+ loaderRegistry.register(
266
+ id,
267
+ impl,
268
+ propsSchema ? { ...meta, propsSchema } : { ...meta }
269
+ );
270
+ }
271
+ function formatValidSet2() {
272
+ const ids = loaderRegistry.ids().sort();
273
+ return ids.length ? `Registered loaders: ${ids.join(", ")}` : "No loaders are registered \u2014 call defineLoader(id, impl, meta) before referencing one.";
274
+ }
275
+ function resolveLoader(id) {
276
+ const impl = loaderRegistry.get(id);
277
+ if (!impl) throw new Error(`Unknown loader "${id}". ${formatValidSet2()}`);
278
+ return impl;
279
+ }
280
+ function hasLoader(id) {
281
+ return loaderRegistry.has(id);
282
+ }
283
+ function listLoaders() {
284
+ return loaderRegistry.ids().sort();
285
+ }
286
+ function clearLoaders() {
287
+ loaderRegistry.clear();
288
+ }
289
+ function describeLoaders() {
290
+ return loaderRegistry.describe().map((base) => {
291
+ const meta = loaderRegistry.getEntry(base.id)?.meta;
292
+ return {
293
+ ...base,
294
+ paramsSchema: base.schema,
295
+ params: meta?.settings ?? [],
296
+ output: meta?.output
297
+ };
298
+ });
299
+ }
300
+ function isLoaderParamRef(value) {
301
+ return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.$param === "string" && Object.keys(value).length === 1;
302
+ }
303
+ function isLoaderPropRef(value) {
304
+ return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.$prop === "string" && Object.keys(value).length === 1;
305
+ }
306
+ function canonicalJson(value) {
307
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
308
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
309
+ const record = value;
310
+ const parts = Object.keys(record).sort().filter((key) => record[key] !== void 0).map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`);
311
+ return `{${parts.join(",")}}`;
312
+ }
313
+ function dataNeedKey(loader, params = {}) {
314
+ return `${loader} ${canonicalJson(params)}`;
315
+ }
316
+
317
+ // src/registry/entities.ts
318
+ var ENTITY_FIELD_TYPES = [
319
+ "text",
320
+ "richtext",
321
+ "number",
322
+ "boolean",
323
+ "json",
324
+ "image",
325
+ "url",
326
+ "ref",
327
+ "point",
328
+ "datetime"
329
+ ];
330
+ var GENERIC_ENTITY_TABLE = "plg_entity_records";
331
+ var ENTITY_KEY_RE = /^[a-z][a-z0-9_]*$/;
332
+ var FIELD_NAME_RE = /^[a-z][a-z0-9_]*$/;
333
+ var RESERVED_ENTITY_FIELDS = ["id", "slug", "entity", "tenant_id", "created_at", "updated_at"];
334
+ var entityDeclarationRegistry = new Registry("entity");
335
+ function fail(message, valid) {
336
+ throw new Error(valid?.length ? `${message} Valid: ${valid.join(", ")}.` : message);
337
+ }
338
+ function normalizeDeclaration(key, input) {
339
+ if (!ENTITY_KEY_RE.test(key)) {
340
+ fail(`defineEntity: "${key}" is not a valid entity key \u2014 keys are snake_case (${ENTITY_KEY_RE}).`);
341
+ }
342
+ const fieldNames = Object.keys(input.fields ?? {});
343
+ if (fieldNames.length === 0) {
344
+ fail(`defineEntity("${key}"): an entity declares at least one field.`);
345
+ }
346
+ for (const [name, field] of Object.entries(input.fields)) {
347
+ if (!FIELD_NAME_RE.test(name)) {
348
+ fail(`defineEntity("${key}"): "${name}" is not a valid field name \u2014 fields are snake_case (${FIELD_NAME_RE}).`);
349
+ }
350
+ if (RESERVED_ENTITY_FIELDS.includes(name)) {
351
+ fail(`defineEntity("${key}"): "${name}" is a structural record column and cannot be a declared field.`, RESERVED_ENTITY_FIELDS);
352
+ }
353
+ if (!ENTITY_FIELD_TYPES.includes(field?.type)) {
354
+ fail(`defineEntity("${key}"): field "${name}" has unknown type "${String(field?.type)}".`, ENTITY_FIELD_TYPES);
355
+ }
356
+ if (field.type === "ref" && !field.entity) {
357
+ fail(`defineEntity("${key}"): ref field "${name}" must name the entity it points to ({ type: 'ref', entity: '<key>' }).`);
358
+ }
359
+ }
360
+ const knows = (name, where) => {
361
+ if (!fieldNames.includes(name)) {
362
+ fail(`defineEntity("${key}"): ${where} references undeclared field "${name}".`, fieldNames);
363
+ }
364
+ };
365
+ for (const index of input.indexes ?? []) {
366
+ for (const name of index) knows(name, "an index");
367
+ }
368
+ if (input.titleField) knows(input.titleField, "titleField");
369
+ for (const name of input.displayFields ?? []) knows(name, "displayFields");
370
+ const titleField = input.titleField ?? fieldNames.find((name) => input.fields[name].type === "text");
371
+ const displayFields = input.displayFields ?? fieldNames.filter((name) => input.fields[name].type !== "json");
372
+ return {
373
+ key,
374
+ title: input.title ?? key,
375
+ description: input.description,
376
+ fields: input.fields,
377
+ scope: input.scope ?? "tenant",
378
+ access: { read: input.access?.read ?? "staff", write: "staff" },
379
+ indexes: input.indexes ?? [],
380
+ titleField,
381
+ displayFields,
382
+ storage: { mode: "generic", table: GENERIC_ENTITY_TABLE }
383
+ };
384
+ }
385
+ function defineEntity(key, input, meta = {}) {
386
+ const declaration = normalizeDeclaration(key, input);
387
+ entityDeclarationRegistry.register(key, declaration, {
388
+ title: declaration.title,
389
+ description: declaration.description,
390
+ propsSchema: entityRecordSchema(declaration),
391
+ source: meta.source ?? "app",
392
+ pluginId: meta.pluginId,
393
+ since: meta.since
394
+ });
395
+ return declaration;
396
+ }
397
+ function formatValidEntities() {
398
+ const ids = entityDeclarationRegistry.ids().sort();
399
+ return ids.length ? `Declared entities: ${ids.join(", ")}` : "No entities are declared \u2014 call defineEntity(key, declaration) first.";
400
+ }
401
+ function resolveEntityDeclaration(key) {
402
+ const declaration = entityDeclarationRegistry.get(key);
403
+ if (!declaration) throw new Error(`Unknown entity "${key}". ${formatValidEntities()}`);
404
+ return declaration;
405
+ }
406
+ function hasEntityDeclaration(key) {
407
+ return entityDeclarationRegistry.has(key);
408
+ }
409
+ function listEntityDeclarations() {
410
+ return entityDeclarationRegistry.ids().sort();
411
+ }
412
+ function clearEntityDeclarations() {
413
+ entityDeclarationRegistry.clear();
414
+ }
415
+ var FIELD_FORMAT = {
416
+ image: "uri",
417
+ url: "uri",
418
+ datetime: "date-time"
419
+ };
420
+ function fieldToJsonSchema(name, field) {
421
+ const node = {
422
+ title: field.label ?? name,
423
+ // The control survives the compile, exactly as in block settings: a `ref`
424
+ // is a string in JSON Schema terms, but an editor rendering it as a text
425
+ // input (instead of a record picker) is a bug.
426
+ "x-fayz-control": field.type
427
+ };
428
+ if (field.help) node.description = field.help;
429
+ if (field.default !== void 0) node.default = field.default;
430
+ const format = FIELD_FORMAT[field.type];
431
+ if (format) node.format = format;
432
+ switch (field.type) {
433
+ case "number":
434
+ node.type = "number";
435
+ return node;
436
+ case "boolean":
437
+ node.type = "boolean";
438
+ return node;
439
+ case "json":
440
+ return node;
441
+ case "point":
442
+ node.type = "object";
443
+ node.properties = { lat: { type: "number" }, lng: { type: "number" } };
444
+ node.required = ["lat", "lng"];
445
+ node.additionalProperties = false;
446
+ return node;
447
+ case "ref":
448
+ node.type = "string";
449
+ node["x-fayz-ref-entity"] = field.entity;
450
+ return node;
451
+ default:
452
+ node.type = "string";
453
+ return node;
454
+ }
455
+ }
456
+ function entityRecordSchema(declaration) {
457
+ const properties = {};
458
+ const required = [];
459
+ for (const [name, field] of Object.entries(declaration.fields)) {
460
+ properties[name] = fieldToJsonSchema(name, field);
461
+ if (field.required) required.push(name);
462
+ }
463
+ const schema = {
464
+ type: "object",
465
+ title: declaration.title,
466
+ properties,
467
+ additionalProperties: false
468
+ };
469
+ if (declaration.description) schema.description = declaration.description;
470
+ if (required.length > 0) schema.required = required;
471
+ return schema;
472
+ }
473
+ function typeOf(value) {
474
+ return Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
475
+ }
476
+ function validateEntityRecordData(declaration, data) {
477
+ const issues = [];
478
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
479
+ return [{ path: "", code: "invalid-value", message: `record data is an object, got ${typeOf(data)}` }];
480
+ }
481
+ const record = data;
482
+ const fieldNames = Object.keys(declaration.fields);
483
+ for (const [name, value] of Object.entries(record)) {
484
+ const field = declaration.fields[name];
485
+ if (!field) {
486
+ issues.push({
487
+ path: name,
488
+ code: "unknown-field",
489
+ message: `field "${name}" is not declared by this entity`,
490
+ valid: fieldNames
491
+ });
492
+ continue;
493
+ }
494
+ if (value === null || value === void 0) continue;
495
+ switch (field.type) {
496
+ case "number":
497
+ if (typeof value !== "number" || !Number.isFinite(value)) {
498
+ issues.push({ path: name, code: "invalid-value", message: `"${name}" expects a number, got ${typeOf(value)}` });
499
+ }
500
+ break;
501
+ case "boolean":
502
+ if (typeof value !== "boolean") {
503
+ issues.push({ path: name, code: "invalid-value", message: `"${name}" expects a boolean, got ${typeOf(value)}` });
504
+ }
505
+ break;
506
+ case "json":
507
+ break;
508
+ // any JSON value
509
+ case "point": {
510
+ const point = value;
511
+ if (typeOf(value) !== "object" || typeof point.lat !== "number" || typeof point.lng !== "number" || Object.keys(point).length !== 2) {
512
+ issues.push({ path: name, code: "invalid-value", message: `"${name}" expects {lat: number, lng: number}` });
513
+ }
514
+ break;
515
+ }
516
+ default:
517
+ if (typeof value !== "string") {
518
+ issues.push({ path: name, code: "invalid-value", message: `"${name}" expects a string, got ${typeOf(value)}` });
519
+ }
520
+ }
521
+ }
522
+ for (const [name, field] of Object.entries(declaration.fields)) {
523
+ if (field.required && (record[name] === void 0 || record[name] === null || record[name] === "")) {
524
+ issues.push({ path: name, code: "missing-field", message: `required field "${name}" is missing` });
525
+ }
526
+ }
527
+ return issues;
528
+ }
529
+ function describeEntities() {
530
+ return entityDeclarationRegistry.describe().map((base) => {
531
+ const declaration = entityDeclarationRegistry.get(base.id);
532
+ return {
533
+ ...base,
534
+ recordSchema: base.schema,
535
+ access: declaration.access,
536
+ scope: declaration.scope,
537
+ storage: declaration.storage,
538
+ indexes: declaration.indexes,
539
+ titleField: declaration.titleField,
540
+ displayFields: declaration.displayFields
541
+ };
542
+ });
543
+ }
544
+ var FIELD_TYPE_TO_INPUT = {
545
+ text: "text",
546
+ richtext: "markdown",
547
+ number: "number",
548
+ boolean: "boolean",
549
+ // JSON and point have no dedicated control yet; they degrade to text inputs
550
+ // with a hint, which is honest — the descriptor still names the real type in
551
+ // the record schema, so a richer control can adopt them without a migration.
552
+ json: "textarea",
553
+ point: "text",
554
+ image: "image",
555
+ url: "url",
556
+ ref: "relation",
557
+ datetime: "datetime"
558
+ };
559
+ function entityAdminForm(declaration) {
560
+ const fields = Object.entries(declaration.fields).map(([name, field]) => {
561
+ const def = {
562
+ key: name,
563
+ label: field.label ?? name,
564
+ type: FIELD_TYPE_TO_INPUT[field.type],
565
+ showInForm: true,
566
+ showInTable: declaration.displayFields.includes(name)
567
+ };
568
+ if (field.required) def.required = true;
569
+ if (field.help) def.hint = field.help;
570
+ if (field.default !== void 0) def.defaultValue = field.default;
571
+ if (field.type === "json") def.hint = field.help ?? "JSON value";
572
+ if (field.type === "point") def.hint = field.help ?? "lat,lng";
573
+ if (field.type === "ref" && field.entity) {
574
+ def.relation = {
575
+ table: declaration.storage.table,
576
+ valueField: "id",
577
+ labelField: "slug",
578
+ filter: { entity: field.entity }
579
+ };
580
+ }
581
+ return def;
582
+ });
583
+ return { entity: declaration.key, title: declaration.title, fields };
584
+ }
585
+ var SIDECAR_REFERENCES = ["product", "category"];
586
+ var sidecarDeclarationRegistry = new Registry("sidecar");
587
+ function defineSidecarFields(reference, fields, meta = {}) {
588
+ if (!SIDECAR_REFERENCES.includes(reference)) {
589
+ fail(`defineSidecarFields: "${reference}" is not a sidecar reference.`, SIDECAR_REFERENCES);
590
+ }
591
+ for (const [name, field] of Object.entries(fields)) {
592
+ if (!FIELD_NAME_RE.test(name)) {
593
+ fail(`defineSidecarFields("${reference}"): "${name}" is not a valid field name (${FIELD_NAME_RE}).`);
594
+ }
595
+ if (!ENTITY_FIELD_TYPES.includes(field?.type)) {
596
+ fail(`defineSidecarFields("${reference}"): field "${name}" has unknown type "${String(field?.type)}".`, ENTITY_FIELD_TYPES);
597
+ }
598
+ }
599
+ const declaration = { reference, fields };
600
+ sidecarDeclarationRegistry.register(reference, declaration, {
601
+ title: `Custom fields: ${reference}`,
602
+ propsSchema: entityRecordSchema({ title: `${reference} custom fields`, fields }),
603
+ source: meta.source ?? "app",
604
+ pluginId: meta.pluginId
605
+ });
606
+ return declaration;
607
+ }
608
+ function resolveSidecarDeclaration(reference) {
609
+ const declaration = sidecarDeclarationRegistry.get(reference);
610
+ if (!declaration) {
611
+ const declared = sidecarDeclarationRegistry.ids().sort();
612
+ throw new Error(
613
+ `No sidecar fields are declared for "${reference}". ${declared.length ? `Declared references: ${declared.join(", ")}` : "Call defineSidecarFields(reference, fields) first."}`
614
+ );
615
+ }
616
+ return declaration;
617
+ }
618
+ function hasSidecarDeclaration(reference) {
619
+ return sidecarDeclarationRegistry.has(reference);
620
+ }
621
+ function clearSidecarDeclarations() {
622
+ sidecarDeclarationRegistry.clear();
623
+ }
624
+
625
+ // src/registry/block-constraints.ts
626
+ function metaFor(type) {
627
+ return blockRegistry.getEntry(type)?.meta;
628
+ }
629
+ function placement(parentType) {
630
+ return parentType ? `under "${parentType}"` : "at the tree root";
631
+ }
632
+ function* walk(nodes, basePath, ancestors) {
633
+ for (let i = 0; i < nodes.length; i++) {
634
+ const node = nodes[i];
635
+ const path = `${basePath}[${i}]`;
636
+ yield { node, path, ancestors };
637
+ if (node.children?.length) {
638
+ yield* walk(node.children, `${path}.children`, [...ancestors, node.type]);
639
+ }
640
+ }
641
+ }
642
+ function checkNodePlacement(type, ancestors) {
643
+ const meta = metaFor(type);
644
+ if (!meta) return [];
645
+ const found = [];
646
+ const parentType = ancestors[ancestors.length - 1];
647
+ if (meta.parent?.length && (parentType === void 0 || !meta.parent.includes(parentType))) {
648
+ found.push({
649
+ path: "",
650
+ type,
651
+ rule: "constraint-parent",
652
+ detail: `block "${type}" may only be a direct child of one of [${meta.parent.join(", ")}]; it is placed ${placement(parentType)}`,
653
+ valid: [...meta.parent]
654
+ });
655
+ }
656
+ if (meta.ancestor?.length && !ancestors.some((a) => meta.ancestor.includes(a))) {
657
+ found.push({
658
+ path: "",
659
+ type,
660
+ rule: "constraint-ancestor",
661
+ detail: `block "${type}" must appear inside one of [${meta.ancestor.join(", ")}] (at any depth); it is placed ${placement(parentType)}`,
662
+ valid: [...meta.ancestor]
663
+ });
664
+ }
665
+ return found;
666
+ }
667
+ function checkBlockConstraints(nodes) {
668
+ const found = [];
669
+ const firstInstanceOf = /* @__PURE__ */ new Map();
670
+ for (const { node, path, ancestors } of walk(nodes, "", [])) {
671
+ for (const violation of checkNodePlacement(node.type, ancestors)) {
672
+ found.push({ ...violation, path });
673
+ }
674
+ const meta = metaFor(node.type);
675
+ if (meta?.allowMultiple === false) {
676
+ const first = firstInstanceOf.get(node.type);
677
+ if (first === void 0) {
678
+ firstInstanceOf.set(node.type, path);
679
+ } else {
680
+ found.push({
681
+ path,
682
+ type: node.type,
683
+ rule: "constraint-multiple",
684
+ detail: `block "${node.type}" declares allowMultiple: false and already appears at ${first || "(root)"} \u2014 remove one of the instances`,
685
+ valid: [first]
686
+ });
687
+ }
688
+ }
689
+ }
690
+ return found;
691
+ }
692
+ function checkBlockContext(nodes, provided = []) {
693
+ const findings = [];
694
+ for (const { node, path, ancestors } of walk(nodes, "", [])) {
695
+ const meta = metaFor(node.type);
696
+ if (!meta?.usesContext?.length) continue;
697
+ const available = new Set(provided);
698
+ for (const ancestorType of ancestors) {
699
+ for (const key of metaFor(ancestorType)?.providesContext ?? []) available.add(key);
700
+ }
701
+ const missing = meta.usesContext.filter((key) => !available.has(key));
702
+ if (missing.length > 0) findings.push({ path, type: node.type, missing });
703
+ }
704
+ return findings;
705
+ }
706
+ function buildRequiredChild(spec) {
707
+ const defaults = defaultPropsFromSettings(metaFor(spec.type)?.settings);
708
+ return {
709
+ type: spec.type,
710
+ id: spec.id ?? spec.type,
711
+ props: { ...defaults, ...spec.props }
712
+ };
713
+ }
714
+ function repairChildren(parentPath, parentMeta, children, repairs) {
715
+ let out = [...children];
716
+ if (parentMeta?.requiredChildren?.length) {
717
+ let cursor = 0;
718
+ for (const spec of parentMeta.requiredChildren) {
719
+ const at = out.findIndex((child) => child.type === spec.type);
720
+ if (at >= 0) {
721
+ cursor = at + 1;
722
+ continue;
723
+ }
724
+ const index = Math.min(cursor, out.length);
725
+ out.splice(index, 0, buildRequiredChild(spec));
726
+ repairs.push({ path: parentPath, inserted: spec.type, index });
727
+ cursor = index + 1;
728
+ }
729
+ }
730
+ out = out.map((child, i) => {
731
+ if (!child.children?.length && !metaFor(child.type)?.requiredChildren?.length) return child;
732
+ const childPath = parentPath ? `${parentPath}[${i}]` : `[${i}]`;
733
+ const repaired = repairChildren(`${childPath}.children`, metaFor(child.type), child.children ?? [], repairs);
734
+ return repaired === child.children ? child : { ...child, children: repaired };
735
+ });
736
+ return out;
737
+ }
738
+ function repairBlockTree(nodes) {
739
+ const repairs = [];
740
+ const repaired = nodes.map((node, i) => {
741
+ const meta = metaFor(node.type);
742
+ if (!node.children?.length && !meta?.requiredChildren?.length) return node;
743
+ const children = repairChildren(`[${i}].children`, meta, node.children ?? [], repairs);
744
+ if (children.length === (node.children?.length ?? 0) && children.every((c, j) => c === node.children?.[j])) {
745
+ return node;
746
+ }
747
+ return { ...node, children };
748
+ });
749
+ if (repairs.length === 0) return { nodes, repairs };
750
+ return { nodes: repaired, repairs };
751
+ }
752
+
753
+ // src/registry/schema-check.ts
754
+ function typeOf2(value) {
755
+ if (value === null) return "null";
756
+ if (Array.isArray(value)) return "array";
757
+ if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
758
+ return typeof value;
759
+ }
760
+ function typeMatches(declared, actual) {
761
+ if (declared === actual) return true;
762
+ return declared === "number" && actual === "integer";
763
+ }
764
+ function joinPath(base, key) {
765
+ return base === "" ? String(key) : `${base}.${key}`;
766
+ }
767
+ function checkAgainstSchema(schema, value, path = "") {
768
+ const issues = [];
769
+ const declaredType = schema.type;
770
+ if (declaredType !== void 0) {
771
+ const actual = typeOf2(value);
772
+ const accepted = Array.isArray(declaredType) ? declaredType.map(String) : [String(declaredType)];
773
+ if (!accepted.some((t) => typeMatches(t, actual))) {
774
+ issues.push({ path, message: `expected ${accepted.join(" | ")}, got ${actual}` });
775
+ return issues;
776
+ }
777
+ }
778
+ if (Array.isArray(schema.enum) && !schema.enum.some((allowed) => JSON.stringify(allowed) === JSON.stringify(value))) {
779
+ issues.push({
780
+ path,
781
+ message: `value ${JSON.stringify(value)} is not one of ${schema.enum.map((v) => JSON.stringify(v)).join(", ")}`
782
+ });
783
+ }
784
+ if (schema.const !== void 0 && JSON.stringify(schema.const) !== JSON.stringify(value)) {
785
+ issues.push({ path, message: `expected constant ${JSON.stringify(schema.const)}` });
786
+ }
787
+ if (typeof value === "string") {
788
+ if (typeof schema.minLength === "number" && value.length < schema.minLength) {
789
+ issues.push({ path, message: `string shorter than minLength ${schema.minLength}` });
790
+ }
791
+ if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
792
+ issues.push({ path, message: `string longer than maxLength ${schema.maxLength}` });
793
+ }
794
+ }
795
+ if (typeof value === "number") {
796
+ if (typeof schema.minimum === "number" && value < schema.minimum) {
797
+ issues.push({ path, message: `${value} is below minimum ${schema.minimum}` });
798
+ }
799
+ if (typeof schema.maximum === "number" && value > schema.maximum) {
800
+ issues.push({ path, message: `${value} is above maximum ${schema.maximum}` });
801
+ }
802
+ }
803
+ if (Array.isArray(value)) {
804
+ if (typeof schema.minItems === "number" && value.length < schema.minItems) {
805
+ issues.push({ path, message: `array shorter than minItems ${schema.minItems}` });
806
+ }
807
+ if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
808
+ issues.push({ path, message: `array longer than maxItems ${schema.maxItems}` });
809
+ }
810
+ if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
811
+ value.forEach((item, index) => {
812
+ issues.push(...checkAgainstSchema(schema.items, item, joinPath(path, index)));
813
+ });
814
+ }
815
+ }
816
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
817
+ const record = value;
818
+ const properties = schema.properties && typeof schema.properties === "object" ? schema.properties : void 0;
819
+ if (Array.isArray(schema.required)) {
820
+ for (const key of schema.required) {
821
+ if (typeof key === "string" && record[key] === void 0) {
822
+ issues.push({ path: joinPath(path, key), message: `required property "${key}" is missing` });
823
+ }
824
+ }
825
+ }
826
+ if (properties) {
827
+ for (const [key, propSchema] of Object.entries(properties)) {
828
+ if (record[key] !== void 0) {
829
+ issues.push(...checkAgainstSchema(propSchema, record[key], joinPath(path, key)));
830
+ }
831
+ }
832
+ if (schema.additionalProperties === false) {
833
+ const known = new Set(Object.keys(properties));
834
+ for (const key of Object.keys(record)) {
835
+ if (!known.has(key)) {
836
+ issues.push({
837
+ path: joinPath(path, key),
838
+ message: `unknown property "${key}" \u2014 declared properties: ${[...known].sort().join(", ")}`
839
+ });
840
+ }
841
+ }
842
+ }
843
+ }
844
+ }
845
+ return issues;
846
+ }
847
+ function formatSchemaIssues(issues) {
848
+ return issues.map((issue) => issue.path ? `${issue.path}: ${issue.message}` : issue.message).join("; ");
849
+ }
850
+
851
+ // src/registry/extension-points.ts
852
+ var handlersByPoint = /* @__PURE__ */ new Map();
853
+ var extensionPointRegistry = new Registry("extension-point");
854
+ var POINT_ID_PATTERN = /^[a-z][a-zA-Z0-9-]*\.[a-z][a-zA-Z0-9-]*$/;
855
+ function isProductionBuild() {
856
+ try {
857
+ return process.env.NODE_ENV === "production";
858
+ } catch {
859
+ return true;
860
+ }
861
+ }
862
+ function formatValidPoints() {
863
+ const ids = extensionPointRegistry.ids().sort();
864
+ return ids.length ? `Declared extension points: ${ids.join(", ")}` : "No extension points are declared \u2014 call defineExtensionPoint first.";
865
+ }
866
+ function defineExtensionPoint(declaration) {
867
+ if (!POINT_ID_PATTERN.test(declaration.id)) {
868
+ throw new Error(
869
+ `Extension point id "${declaration.id}" is not namespaced. Use "namespace.point" (e.g. "cart.lineAnnotation").`
870
+ );
871
+ }
872
+ const meta = {
873
+ description: declaration.description,
874
+ source: declaration.source,
875
+ since: declaration.since
876
+ };
877
+ extensionPointRegistry.register(declaration.id, declaration, meta);
878
+ if (!handlersByPoint.has(declaration.id)) handlersByPoint.set(declaration.id, []);
879
+ }
880
+ function resolvePoint(pointId) {
881
+ const spec = extensionPointRegistry.get(pointId);
882
+ if (!spec) throw new Error(`Unknown extension point "${pointId}". ${formatValidPoints()}`);
883
+ return spec;
884
+ }
885
+ function registerExtensionHandler(pointId, handlerId, handler, options = {}) {
886
+ const spec = resolvePoint(pointId);
887
+ const handlers = handlersByPoint.get(pointId);
888
+ const existingIndex = handlers.findIndex((h) => h.id === handlerId);
889
+ if ((spec.mode ?? "many") === "escape" && existingIndex < 0 && handlers.length > 0) {
890
+ throw new Error(
891
+ `Extension point "${pointId}" is an escape valve and is already held by handler "${handlers[0].id}"${handlers[0].pluginId ? ` (plugin "${handlers[0].pluginId}")` : ""}. An escape valve accepts exactly ONE registrant (I4) \u2014 unregister the holder first, or extend through a many-handler point instead.`
892
+ );
893
+ }
894
+ const entry = {
895
+ id: handlerId,
896
+ handler,
897
+ after: options.after ?? [],
898
+ description: options.description,
899
+ source: options.source,
900
+ pluginId: options.pluginId
901
+ };
902
+ if (existingIndex >= 0) handlers[existingIndex] = entry;
903
+ else handlers.push(entry);
904
+ }
905
+ function unregisterExtensionHandler(pointId, handlerId) {
906
+ const handlers = handlersByPoint.get(pointId);
907
+ if (!handlers) return;
908
+ const index = handlers.findIndex((h) => h.id === handlerId);
909
+ if (index >= 0) handlers.splice(index, 1);
910
+ }
911
+ function orderHandlers(pointId, handlers) {
912
+ const byId = new Map(handlers.map((h) => [h.id, h]));
913
+ for (const handler of handlers) {
914
+ for (const dep of handler.after) {
915
+ if (!byId.has(dep)) {
916
+ throw new Error(
917
+ `Extension handler "${handler.id}" at "${pointId}" declares after: ["${dep}"], but no handler "${dep}" is registered there. Registered handlers: ${handlers.map((h) => h.id).join(", ")}`
918
+ );
919
+ }
920
+ }
921
+ }
922
+ const ordered = [];
923
+ const state = /* @__PURE__ */ new Map();
924
+ function visit(handler, chain) {
925
+ const mark = state.get(handler.id);
926
+ if (mark === "done") return;
927
+ if (mark === "visiting") {
928
+ throw new Error(
929
+ `Extension handlers at "${pointId}" declare a dependency cycle: ${[...chain, handler.id].join(" \u2192 ")}`
930
+ );
931
+ }
932
+ state.set(handler.id, "visiting");
933
+ for (const dep of handler.after) visit(byId.get(dep), [...chain, handler.id]);
934
+ state.set(handler.id, "done");
935
+ ordered.push(handler);
936
+ }
937
+ for (const handler of handlers) visit(handler, []);
938
+ return ordered;
939
+ }
940
+ function schemaViolation(kind, pointId, handlerId, detail) {
941
+ const who = handlerId ? ` handler "${handlerId}"` : "";
942
+ return new Error(`Extension point "${pointId}"${who}: invalid ${kind} \u2014 ${detail}`);
943
+ }
944
+ async function runExtensionPoint(pointId, input) {
945
+ const spec = resolvePoint(pointId);
946
+ if ((spec.mode ?? "many") === "escape") {
947
+ throw new Error(
948
+ `Extension point "${pointId}" is an escape valve \u2014 call runEscapeValve, whose Output | null contract is the point (I4).`
949
+ );
950
+ }
951
+ const inputIssues = checkAgainstSchema(spec.input, input);
952
+ if (inputIssues.length > 0) {
953
+ const error = schemaViolation("input", pointId, null, formatSchemaIssues(inputIssues));
954
+ if (!isProductionBuild()) throw error;
955
+ console.error(`[extension-points] ${error.message}`);
956
+ return [];
957
+ }
958
+ const handlers = orderHandlers(pointId, handlersByPoint.get(pointId) ?? []);
959
+ const contributions = [];
960
+ for (const handler of handlers) {
961
+ let value;
962
+ try {
963
+ value = await handler.handler(input);
964
+ } catch (cause) {
965
+ const error = new Error(
966
+ `Extension point "${pointId}" handler "${handler.id}" threw: ${cause instanceof Error ? cause.message : String(cause)}`
967
+ );
968
+ if (!isProductionBuild()) throw error;
969
+ console.error(`[extension-points] ${error.message}`);
970
+ continue;
971
+ }
972
+ if (value === void 0 || value === null) continue;
973
+ const outputIssues = checkAgainstSchema(spec.output, value);
974
+ if (outputIssues.length > 0) {
975
+ const error = schemaViolation("output", pointId, handler.id, formatSchemaIssues(outputIssues));
976
+ if (!isProductionBuild()) throw error;
977
+ console.error(`[extension-points] ${error.message}`);
978
+ continue;
979
+ }
980
+ contributions.push({ handlerId: handler.id, value });
981
+ }
982
+ return contributions;
983
+ }
984
+ async function runEscapeValve(pointId, input) {
985
+ const spec = resolvePoint(pointId);
986
+ if ((spec.mode ?? "many") !== "escape") {
987
+ throw new Error(`Extension point "${pointId}" is not an escape valve \u2014 call runExtensionPoint.`);
988
+ }
989
+ const inputIssues = checkAgainstSchema(spec.input, input);
990
+ if (inputIssues.length > 0) {
991
+ const error = schemaViolation("input", pointId, null, formatSchemaIssues(inputIssues));
992
+ if (!isProductionBuild()) throw error;
993
+ console.error(`[extension-points] ${error.message}`);
994
+ return null;
995
+ }
996
+ const holder = (handlersByPoint.get(pointId) ?? [])[0];
997
+ if (!holder) return null;
998
+ let value;
999
+ try {
1000
+ value = await holder.handler(input);
1001
+ } catch (cause) {
1002
+ const error = new Error(
1003
+ `Escape valve "${pointId}" handler "${holder.id}" threw: ${cause instanceof Error ? cause.message : String(cause)}`
1004
+ );
1005
+ if (!isProductionBuild()) throw error;
1006
+ console.error(`[extension-points] ${error.message}`);
1007
+ return null;
1008
+ }
1009
+ if (value === void 0 || value === null) return null;
1010
+ const outputIssues = checkAgainstSchema(spec.output, value);
1011
+ if (outputIssues.length > 0) {
1012
+ const error = schemaViolation("output", pointId, holder.id, formatSchemaIssues(outputIssues));
1013
+ if (!isProductionBuild()) throw error;
1014
+ console.error(`[extension-points] ${error.message}`);
1015
+ return null;
1016
+ }
1017
+ return value;
1018
+ }
1019
+ function describeExtensionPoints() {
1020
+ return extensionPointRegistry.describe().map((base) => {
1021
+ const spec = extensionPointRegistry.get(base.id);
1022
+ return {
1023
+ ...base,
1024
+ mode: spec.mode ?? "many",
1025
+ inputSchema: spec.input,
1026
+ outputSchema: spec.output,
1027
+ handlers: (handlersByPoint.get(base.id) ?? []).map((h) => ({
1028
+ id: h.id,
1029
+ after: h.after,
1030
+ description: h.description,
1031
+ source: h.source,
1032
+ pluginId: h.pluginId
1033
+ }))
1034
+ };
1035
+ });
1036
+ }
1037
+ function hasExtensionPoint(pointId) {
1038
+ return extensionPointRegistry.has(pointId);
1039
+ }
1040
+ function listExtensionPoints() {
1041
+ return extensionPointRegistry.ids().sort();
1042
+ }
1043
+ function listExtensionHandlers(pointId) {
1044
+ resolvePoint(pointId);
1045
+ return (handlersByPoint.get(pointId) ?? []).map((h) => h.id);
1046
+ }
1047
+ function clearExtensionPoints() {
1048
+ extensionPointRegistry.clear();
1049
+ handlersByPoint.clear();
1050
+ }
1051
+ function extensionAfterHooks(pointId, methods) {
1052
+ resolvePoint(pointId);
1053
+ const map = {};
1054
+ for (const method of methods) {
1055
+ map[method] = async (args, result) => {
1056
+ await runExtensionPoint(pointId, { method, args, result });
1057
+ };
1058
+ }
1059
+ return map;
1060
+ }
1061
+
1062
+ // src/registry/index.ts
1063
+ var componentRegistry = new Registry("component");
1064
+ var blockRegistry = new Registry("block");
1065
+ var pageRegistry = new Registry("page");
1066
+ var scaffoldRegistry = new Registry("scaffold");
1067
+ var metricRegistry = new Registry("metric");
1068
+ var pluginRegistry = new Registry("plugin");
1069
+ var registerComponent = (id, c, meta) => componentRegistry.register(id, c, meta);
1070
+ var getComponent = (id) => componentRegistry.get(id);
1071
+ var registerBlock = (id, c, meta) => blockRegistry.register(id, c, meta);
1072
+ var getBlock = (id) => blockRegistry.get(id);
1073
+ var listBlocks = () => blockRegistry.list();
1074
+ var getBlockMeta = (id) => blockRegistry.getEntry(id)?.meta;
1075
+ var defineBlock = (id, component, meta = {}) => {
1076
+ const propsSchema = meta.settings ? settingsToJsonSchema(meta.settings) : void 0;
1077
+ blockRegistry.register(id, component, propsSchema ? { ...meta, propsSchema } : { ...meta });
1078
+ };
1079
+ var registerPage = (id, c, meta) => pageRegistry.register(id, c, meta);
1080
+ var getPage = (id) => pageRegistry.get(id);
1081
+ var registerScaffold = (id, c, meta) => scaffoldRegistry.register(id, c, meta);
1082
+ var getScaffold = (id) => scaffoldRegistry.get(id);
1083
+ var registerMetric = (def, meta) => metricRegistry.register(def.id, def, meta);
1084
+ var getMetric = (id) => metricRegistry.get(id);
1085
+ var listMetrics = () => metricRegistry.list();
1086
+ var registerPlugin = (id, manifest, meta) => pluginRegistry.register(id, manifest, meta);
1087
+ var getPlugin = (id) => pluginRegistry.get(id);
1088
+ var listPlugins = () => pluginRegistry.list();
1089
+ var pluginFactoryRegistry = new Registry("plugin-factory");
1090
+ var registerPluginFactory = (id, factory, meta) => pluginFactoryRegistry.register(id, factory, meta);
1091
+ var getPluginFactory = (id) => pluginFactoryRegistry.get(id);
1092
+ var listPluginFactories = () => pluginFactoryRegistry.list();
1093
+
1094
+ // src/registry/block-catalog.ts
1095
+ function describeEntry(entry) {
1096
+ const meta = entry.meta ?? {};
1097
+ const settings = meta.settings ?? [];
1098
+ return {
1099
+ id: entry.id,
1100
+ label: meta.label,
1101
+ description: meta.description,
1102
+ source: meta.source,
1103
+ pluginId: meta.pluginId,
1104
+ settings,
1105
+ presets: meta.presets ?? [],
1106
+ enabledOn: meta.enabledOn ?? [],
1107
+ stability: meta.stability ?? "stable",
1108
+ propsSchema: meta.propsSchema,
1109
+ defaults: defaultPropsFromSettings(settings),
1110
+ parent: meta.parent ?? [],
1111
+ ancestor: meta.ancestor ?? [],
1112
+ usesContext: meta.usesContext ?? [],
1113
+ providesContext: meta.providesContext ?? [],
1114
+ allowMultiple: meta.allowMultiple ?? true,
1115
+ requiredChildren: meta.requiredChildren ?? []
1116
+ };
1117
+ }
1118
+ function describeBlock(id) {
1119
+ const entry = blockRegistry.getEntry(id);
1120
+ return entry ? describeEntry(entry) : void 0;
1121
+ }
1122
+ function describeBlocks(options = {}) {
1123
+ const { enabledOn, source, includeExperimental = false } = options;
1124
+ return blockRegistry.list().map(describeEntry).filter((block) => {
1125
+ if (!includeExperimental && block.stability === "experimental") return false;
1126
+ if (source && !source.includes(block.source ?? "app")) return false;
1127
+ if (enabledOn && block.enabledOn.length > 0 && !block.enabledOn.includes(enabledOn)) {
1128
+ return false;
1129
+ }
1130
+ return true;
1131
+ }).sort((a, b) => a.id.localeCompare(b.id));
1132
+ }
1133
+ function isBlockEnabledOn(block, surface) {
1134
+ return block.enabledOn.length === 0 || block.enabledOn.includes(surface);
1135
+ }
1136
+ function walkSettings(settings, prefix = "") {
1137
+ const out = [];
1138
+ for (const setting of settings) {
1139
+ const path = prefix ? `${prefix}[].${setting.id}` : setting.id;
1140
+ out.push({ path, setting });
1141
+ if (setting.of?.length) out.push(...walkSettings(setting.of, path));
1142
+ }
1143
+ return out;
1144
+ }
1145
+ function auditSettings(blockId, settings) {
1146
+ const found = [];
1147
+ const err = (rule, detail) => found.push({ level: "error", rule, blockId, detail });
1148
+ const warn = (rule, detail) => found.push({ level: "warning", rule, blockId, detail });
1149
+ for (const { path, setting } of walkSettings(settings)) {
1150
+ if (!setting.id) {
1151
+ err("setting-missing-id", `a setting at "${path}" has no id \u2014 it cannot write to a prop`);
1152
+ }
1153
+ if (!setting.label) {
1154
+ warn("setting-missing-label", `setting "${path}" has no label \u2014 the editor renders a blank field`);
1155
+ }
1156
+ if (setting.type === "select" && !setting.options?.length) {
1157
+ err("select-without-options", `setting "${path}" is a select with no options`);
1158
+ }
1159
+ if (setting.type === "list" && !setting.of?.length) {
1160
+ warn(
1161
+ "list-without-item-shape",
1162
+ `setting "${path}" is a list with no \`of\` \u2014 an agent cannot generate items for it`
1163
+ );
1164
+ }
1165
+ if (setting.type !== "number" && (setting.min !== void 0 || setting.max !== void 0)) {
1166
+ warn("bounds-on-non-number", `setting "${path}" declares min/max but is not a number`);
1167
+ }
1168
+ if (setting.required && setting.default !== void 0) {
1169
+ warn(
1170
+ "required-with-default",
1171
+ `setting "${path}" is required but also has a default \u2014 the default always satisfies it`
1172
+ );
1173
+ }
1174
+ }
1175
+ const seen = /* @__PURE__ */ new Set();
1176
+ for (const setting of settings) {
1177
+ if (setting.id && seen.has(setting.id)) {
1178
+ err("setting-duplicate-id", `setting "${setting.id}" is declared more than once`);
1179
+ }
1180
+ seen.add(setting.id);
1181
+ }
1182
+ return found;
1183
+ }
1184
+ function auditPresets(blockId, settings, presets) {
1185
+ const found = [];
1186
+ const declared = new Set(settings.map((s) => s.id));
1187
+ const required = settings.filter((s) => s.required).map((s) => s.id);
1188
+ const names = /* @__PURE__ */ new Set();
1189
+ for (const preset of presets) {
1190
+ const label = preset.name || "(unnamed)";
1191
+ if (!preset.name) {
1192
+ found.push({
1193
+ level: "error",
1194
+ rule: "preset-missing-name",
1195
+ blockId,
1196
+ detail: "a preset has no name \u2014 the editor has nothing to show in the picker"
1197
+ });
1198
+ } else if (names.has(preset.name)) {
1199
+ found.push({
1200
+ level: "error",
1201
+ rule: "preset-duplicate-name",
1202
+ blockId,
1203
+ detail: `two presets are both named "${preset.name}"`
1204
+ });
1205
+ }
1206
+ names.add(preset.name);
1207
+ for (const key of Object.keys(preset.props ?? {})) {
1208
+ if (!declared.has(key)) {
1209
+ found.push({
1210
+ level: "error",
1211
+ rule: "preset-undeclared-prop",
1212
+ blockId,
1213
+ detail: `preset "${label}" sets "${key}", which is not a declared setting`
1214
+ });
1215
+ }
1216
+ }
1217
+ for (const key of required) {
1218
+ if (preset.props?.[key] === void 0) {
1219
+ found.push({
1220
+ level: "error",
1221
+ rule: "preset-missing-required",
1222
+ blockId,
1223
+ detail: `preset "${label}" omits required setting "${key}" \u2014 it inserts a broken block`
1224
+ });
1225
+ }
1226
+ }
1227
+ }
1228
+ return found;
1229
+ }
1230
+ function auditBlocks(options = {}) {
1231
+ const { source, requirePresets = false, requireEnabledOn = false } = options;
1232
+ const findings = [];
1233
+ for (const entry of blockRegistry.list()) {
1234
+ const meta = entry.meta ?? {};
1235
+ if (source && !source.includes(meta.source ?? "app")) continue;
1236
+ const blockId = entry.id;
1237
+ const settings = meta.settings;
1238
+ const presets = meta.presets ?? [];
1239
+ for (const [field, refs] of [
1240
+ ["parent", meta.parent],
1241
+ ["ancestor", meta.ancestor],
1242
+ ["requiredChildren", meta.requiredChildren?.map((child) => child.type)]
1243
+ ]) {
1244
+ for (const ref of refs ?? []) {
1245
+ if (!blockRegistry.has(ref)) {
1246
+ findings.push({
1247
+ level: "error",
1248
+ rule: "constraint-unknown-block",
1249
+ blockId,
1250
+ detail: `${field} references "${ref}", which is not a registered block`
1251
+ });
1252
+ }
1253
+ }
1254
+ }
1255
+ if (!settings) {
1256
+ if (meta.propsSchema) {
1257
+ findings.push({
1258
+ level: "warning",
1259
+ rule: "block-handwritten-schema",
1260
+ blockId,
1261
+ detail: "has a hand-written propsSchema \u2014 declare `settings` and let defineBlock compile it"
1262
+ });
1263
+ } else {
1264
+ findings.push({
1265
+ level: "warning",
1266
+ rule: "block-undescribed",
1267
+ blockId,
1268
+ detail: "declares no settings \u2014 an editor and an agent can only guess its props"
1269
+ });
1270
+ }
1271
+ continue;
1272
+ }
1273
+ findings.push(...auditSettings(blockId, settings));
1274
+ findings.push(...auditPresets(blockId, settings, presets));
1275
+ if (meta.enabledOn?.length === 0) {
1276
+ findings.push({
1277
+ level: "error",
1278
+ rule: "enabled-on-empty",
1279
+ blockId,
1280
+ detail: 'enabledOn is an empty array, so the block is placeable nowhere \u2014 omit it for "anywhere"'
1281
+ });
1282
+ }
1283
+ if (requirePresets && presets.length === 0 && settings.length > 0) {
1284
+ findings.push({
1285
+ level: "warning",
1286
+ rule: "block-no-preset",
1287
+ blockId,
1288
+ detail: "has settings but no preset \u2014 inserting it lands an empty skeleton"
1289
+ });
1290
+ }
1291
+ if (requireEnabledOn && meta.enabledOn === void 0) {
1292
+ findings.push({
1293
+ level: "warning",
1294
+ rule: "block-no-enabled-on",
1295
+ blockId,
1296
+ detail: "does not declare enabledOn, so it is offered on every surface"
1297
+ });
1298
+ }
1299
+ }
1300
+ return findings;
1301
+ }
1302
+ function summarizeBlockAudit(findings) {
1303
+ const errors = findings.filter((f) => f.level === "error").length;
1304
+ return { ok: errors === 0, errors, warnings: findings.length - errors };
1305
+ }
1306
+ var PLUGIN_API_VERSION = 1;
1307
+ function resolvePluginComponent(def) {
1308
+ if (def.component) return def.component;
1309
+ if (def.componentId) return getComponent(def.componentId);
1310
+ return void 0;
1311
+ }
1312
+ var EMPTY_CONTEXT = {
1313
+ tenant: null,
1314
+ user: null,
1315
+ currentPath: "/",
1316
+ matchedPath: "/",
1317
+ layout: "sidebar"
1318
+ };
1319
+ function slugify(input) {
1320
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1321
+ }
1322
+ function matchesRoutePattern(pattern, currentPath) {
1323
+ if (pattern === currentPath) return true;
1324
+ if (pattern.endsWith("/*")) {
1325
+ const base = pattern.slice(0, -2);
1326
+ return currentPath === base || currentPath.startsWith(`${base}/`);
1327
+ }
1328
+ if (pattern.endsWith("*")) return currentPath.startsWith(pattern.slice(0, -1));
1329
+ return false;
1330
+ }
1331
+ function isWidgetVisible(widget, context) {
1332
+ if (widget.permission && context.hasPermission && !context.hasPermission(widget.permission)) return false;
1333
+ const visibility = widget.visibility;
1334
+ if (!visibility) return true;
1335
+ if (visibility.layouts && !visibility.layouts.includes(context.layout)) return false;
1336
+ if (visibility.roles && context.user?.role && !visibility.roles.includes(context.user.role)) return false;
1337
+ if (visibility.roles && !context.user?.role) return false;
1338
+ if (visibility.plans && context.tenant?.plan && !visibility.plans.includes(context.tenant.plan)) return false;
1339
+ if (visibility.plans && !context.tenant?.plan) return false;
1340
+ if (visibility.routes && !visibility.routes.some((p) => matchesRoutePattern(p, context.currentPath))) return false;
1341
+ if (visibility.excludeRoutes?.some((p) => matchesRoutePattern(p, context.currentPath))) return false;
1342
+ if (visibility.permissions?.length) {
1343
+ if (!context.hasPermission) return false;
1344
+ if (!visibility.permissions.every((req) => context.hasPermission?.(req))) return false;
1345
+ }
1346
+ if (visibility.when && !visibility.when(context)) return false;
1347
+ return true;
1348
+ }
1349
+ function normalizeSettingsTabs(plugin) {
1350
+ return (plugin.settings ?? []).map((entry, index) => ({
1351
+ ...entry,
1352
+ pluginId: entry.pluginId ?? plugin.id,
1353
+ order: entry.order ?? index,
1354
+ id: entry.id ?? `${plugin.id}-${slugify(entry.label)}`
1355
+ }));
1356
+ }
1357
+ function createEmptyRuntime(context = EMPTY_CONTEXT) {
1358
+ return {
1359
+ context,
1360
+ plugins: [],
1361
+ activePlugins: [],
1362
+ routes: [],
1363
+ navigation: [],
1364
+ settingsTabs: [],
1365
+ widgets: [],
1366
+ dashboardWidgets: [],
1367
+ analyticsCards: [],
1368
+ events: [],
1369
+ capabilities: [],
1370
+ aiTools: [],
1371
+ registries: /* @__PURE__ */ new Map(),
1372
+ connectorsByHost: /* @__PURE__ */ new Map(),
1373
+ pluginFeatures: [],
1374
+ pluginLimits: [],
1375
+ issues: []
1376
+ };
1377
+ }
1378
+ function definePlugin(plugin) {
1379
+ return plugin;
1380
+ }
1381
+ function resolvePluginRuntime({
1382
+ plugins = [],
1383
+ tenantPlugins = [],
1384
+ hasTenantBindings,
1385
+ context
1386
+ }) {
1387
+ if (plugins.length === 0) return createEmptyRuntime(context);
1388
+ const issues = [];
1389
+ const normalizedPlugins = /* @__PURE__ */ new Map();
1390
+ for (const plugin of plugins) {
1391
+ if (normalizedPlugins.has(plugin.id)) {
1392
+ issues.push({ type: "duplicate_plugin", pluginId: plugin.id, message: `Plugin "${plugin.id}" registered more than once.` });
1393
+ continue;
1394
+ }
1395
+ if (plugin.apiVersion != null && plugin.apiVersion > PLUGIN_API_VERSION) {
1396
+ issues.push({
1397
+ type: "incompatible_api_version",
1398
+ pluginId: plugin.id,
1399
+ message: `Plugin "${plugin.id}" requires plugin API v${plugin.apiVersion}, but this runtime supports v${PLUGIN_API_VERSION}. Upgrade @fayz-ai/core.`
1400
+ });
1401
+ continue;
1402
+ }
1403
+ normalizedPlugins.set(plugin.id, plugin);
1404
+ }
1405
+ const bindings = /* @__PURE__ */ new Map();
1406
+ for (const b of tenantPlugins) bindings.set(b.pluginId, b);
1407
+ const bindingsAreManaged = hasTenantBindings ?? tenantPlugins.length > 0;
1408
+ const requested = /* @__PURE__ */ new Set();
1409
+ const activationReason = /* @__PURE__ */ new Map();
1410
+ for (const plugin of normalizedPlugins.values()) {
1411
+ const binding = bindings.get(plugin.id);
1412
+ if (binding?.status === "active") {
1413
+ requested.add(plugin.id);
1414
+ activationReason.set(plugin.id, "tenant");
1415
+ continue;
1416
+ }
1417
+ if (plugin.defaultEnabled === true || !bindingsAreManaged && plugin.defaultEnabled !== false) {
1418
+ requested.add(plugin.id);
1419
+ activationReason.set(plugin.id, "default");
1420
+ }
1421
+ }
1422
+ const orderedIds = [];
1423
+ const visiting = /* @__PURE__ */ new Set();
1424
+ const visited = /* @__PURE__ */ new Set();
1425
+ function visit(pluginId, ancestry = []) {
1426
+ if (visited.has(pluginId)) return;
1427
+ const plugin = normalizedPlugins.get(pluginId);
1428
+ if (!plugin) return;
1429
+ if (visiting.has(pluginId)) {
1430
+ issues.push({ type: "circular_dependency", pluginId, message: `Circular dependency: ${[...ancestry, pluginId].join(" -> ")}.` });
1431
+ return;
1432
+ }
1433
+ visiting.add(pluginId);
1434
+ for (const depId of plugin.dependencies ?? []) {
1435
+ if (!normalizedPlugins.has(depId)) {
1436
+ issues.push({ type: "missing_dependency", pluginId, dependencyId: depId, message: `Plugin "${plugin.id}" depends on missing plugin "${depId}".` });
1437
+ continue;
1438
+ }
1439
+ if (requested.has(pluginId) && !requested.has(depId)) {
1440
+ requested.add(depId);
1441
+ activationReason.set(depId, "dependency");
1442
+ }
1443
+ visit(depId, [...ancestry, pluginId]);
1444
+ }
1445
+ visiting.delete(pluginId);
1446
+ visited.add(pluginId);
1447
+ orderedIds.push(pluginId);
1448
+ }
1449
+ for (const id of normalizedPlugins.keys()) visit(id);
1450
+ const resolvedById = /* @__PURE__ */ new Map();
1451
+ for (const pluginId of orderedIds) {
1452
+ const plugin = normalizedPlugins.get(pluginId);
1453
+ if (!plugin) continue;
1454
+ const binding = bindings.get(plugin.id);
1455
+ const missing = (plugin.dependencies ?? []).filter((id) => !normalizedPlugins.has(id));
1456
+ const compatible = !plugin.verticalId || plugin.verticalId === context.tenant?.verticalId;
1457
+ if (!compatible) {
1458
+ issues.push({ type: "vertical_mismatch", pluginId: plugin.id, message: `Plugin "${plugin.id}" requires vertical "${plugin.verticalId}".` });
1459
+ }
1460
+ const reason = activationReason.get(plugin.id) ?? "inactive";
1461
+ const depsReady = (plugin.dependencies ?? []).every((id) => resolvedById.get(id)?.isActive);
1462
+ const explicitlyDisabled = binding?.status === "disabled" || binding?.status === "removed";
1463
+ const shouldActivate = reason === "dependency" ? true : requested.has(plugin.id) && !explicitlyDisabled;
1464
+ const isActive = shouldActivate && compatible && missing.length === 0 && depsReady;
1465
+ resolvedById.set(plugin.id, {
1466
+ ...plugin,
1467
+ status: isActive ? "active" : binding?.status ?? "disabled",
1468
+ isActive,
1469
+ activationReason: isActive ? reason : "inactive",
1470
+ config: binding?.config ?? {},
1471
+ missingDependencies: missing,
1472
+ settingsTabs: normalizeSettingsTabs(plugin),
1473
+ widgets: plugin.widgets ?? [],
1474
+ resolvedRegistries: plugin.registries ?? [],
1475
+ resolvedAITools: plugin.aiTools ?? []
1476
+ });
1477
+ }
1478
+ const resolvedPlugins = orderedIds.map((id) => resolvedById.get(id)).filter((p) => Boolean(p));
1479
+ const activePlugins = resolvedPlugins.filter((p) => p.isActive);
1480
+ const routes = [];
1481
+ const navigation = [];
1482
+ const settingsTabs = [];
1483
+ const widgets = [];
1484
+ const dashboardWidgets = [];
1485
+ const analyticsCards = [];
1486
+ const events = [];
1487
+ const capabilities = [];
1488
+ const aiTools = [];
1489
+ const registries = /* @__PURE__ */ new Map();
1490
+ const connectorsByHost = /* @__PURE__ */ new Map();
1491
+ const pluginFeatures = [];
1492
+ const pluginLimits = [];
1493
+ for (const plugin of activePlugins) {
1494
+ routes.push(...plugin.routes.map((r) => ({ ...r, plugin })));
1495
+ navigation.push(...plugin.navigation.map((entry, i) => ({
1496
+ ...entry,
1497
+ id: entry.id ?? `${plugin.id}:${entry.route}:${i}`,
1498
+ icon: entry.icon ?? plugin.icon
1499
+ })));
1500
+ settingsTabs.push(...plugin.settingsTabs);
1501
+ capabilities.push(...plugin.capabilities ?? []);
1502
+ events.push(...(plugin.events ?? []).map((e) => ({
1503
+ ...e,
1504
+ pluginId: plugin.id,
1505
+ pluginName: plugin.name,
1506
+ pluginIcon: plugin.icon
1507
+ })));
1508
+ aiTools.push(...plugin.resolvedAITools);
1509
+ if (plugin.resolvedRegistries.length > 0) registries.set(plugin.id, plugin.resolvedRegistries);
1510
+ for (const connector of plugin.connectors ?? []) {
1511
+ const list = connectorsByHost.get(connector.hostPluginId) ?? [];
1512
+ list.push(connector);
1513
+ connectorsByHost.set(connector.hostPluginId, list);
1514
+ }
1515
+ if (plugin.declaredFeatures) pluginFeatures.push(...plugin.declaredFeatures);
1516
+ if (plugin.declaredLimits) pluginLimits.push(...plugin.declaredLimits);
1517
+ widgets.push(...plugin.widgets.map((w, i) => ({
1518
+ ...w,
1519
+ order: w.order ?? i,
1520
+ config: { ...plugin.config, ...w.props ?? {} },
1521
+ plugin
1522
+ })));
1523
+ dashboardWidgets.push(...(plugin.dashboardWidgets ?? []).map((w, i) => ({
1524
+ ...w,
1525
+ domain: w.domain ?? plugin.id,
1526
+ order: w.defaultOrder ?? i,
1527
+ plugin
1528
+ })));
1529
+ analyticsCards.push(...(plugin.analyticsCards ?? []).map((c, i) => ({
1530
+ ...c,
1531
+ domain: c.domain ?? plugin.id,
1532
+ order: c.dashboard?.order ?? i,
1533
+ plugin
1534
+ })));
1535
+ }
1536
+ events.push(...buildEntityDomainEvents(getAllEntities()));
1537
+ navigation.sort((a, b) => {
1538
+ const order = { main: 0, secondary: 1, settings: 2 };
1539
+ return (order[a.section] ?? 99) - (order[b.section] ?? 99) || a.position - b.position;
1540
+ });
1541
+ settingsTabs.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
1542
+ widgets.sort((a, b) => a.zone !== b.zone ? a.zone.localeCompare(b.zone) : a.order - b.order);
1543
+ dashboardWidgets.sort((a, b) => a.order - b.order);
1544
+ analyticsCards.sort((a, b) => a.order - b.order);
1545
+ return { context, plugins: resolvedPlugins, activePlugins, routes, navigation, settingsTabs, widgets, dashboardWidgets, analyticsCards, capabilities, events, aiTools, issues, registries, connectorsByHost, pluginFeatures, pluginLimits };
1546
+ }
1547
+ function getWidgetsForZone(runtime, zone, contextOverrides) {
1548
+ const ctx = { ...runtime.context, ...contextOverrides };
1549
+ return runtime.widgets.filter((w) => w.zone === zone && isWidgetVisible(w, ctx));
1550
+ }
1551
+ function getAnalyticsCards(runtime, options = {}) {
1552
+ const ctx = { ...runtime.context, ...options.contextOverrides };
1553
+ const placedOnly = options.placedOnly || Boolean(options.surface);
1554
+ return runtime.analyticsCards.filter((c) => {
1555
+ if (placedOnly && !c.dashboard) return false;
1556
+ if (options.surface) {
1557
+ const surfaces = c.dashboard?.surfaces ?? ["analytics"];
1558
+ if (!surfaces.includes(options.surface)) return false;
1559
+ }
1560
+ if (options.domain && c.domain !== options.domain) return false;
1561
+ if (options.category && c.category !== options.category) return false;
1562
+ if (c.verticalId && ctx.tenant?.verticalId && c.verticalId !== ctx.tenant.verticalId) return false;
1563
+ if (c.permission && ctx.hasPermission && !ctx.hasPermission(c.permission)) return false;
1564
+ return true;
1565
+ });
1566
+ }
1567
+ function getDashboardWidgets(runtime, options) {
1568
+ const ctx = { ...runtime.context, ...options.contextOverrides };
1569
+ return runtime.dashboardWidgets.filter((w) => {
1570
+ const surfaces = w.surfaces ?? ["home", "plugin-home"];
1571
+ if (!surfaces.includes(options.surface)) return false;
1572
+ if (options.domain && w.domain !== options.domain) return false;
1573
+ if (w.verticalId && ctx.tenant?.verticalId && w.verticalId !== ctx.tenant.verticalId) return false;
1574
+ if (w.permission && ctx.hasPermission && !ctx.hasPermission(w.permission)) return false;
1575
+ return true;
1576
+ });
1577
+ }
1578
+ var PluginRuntimeContext = React.createContext(null);
1579
+ var PluginRuntimeProvider = PluginRuntimeContext.Provider;
1580
+ function usePluginRuntime() {
1581
+ return React.useContext(PluginRuntimeContext) ?? createEmptyRuntime();
1582
+ }
1583
+ function usePluginRuntimeOptional() {
1584
+ return React.useContext(PluginRuntimeContext);
1585
+ }
1586
+ function useConnectorsForPlugin(hostPluginId) {
1587
+ return usePluginRuntime().connectorsByHost.get(hostPluginId) ?? [];
1588
+ }
1589
+
1590
+ export { BLOCK_SETTING_TYPES, ENTITY_FIELD_TYPES, GENERIC_ENTITY_TABLE, PLUGIN_API_VERSION, PluginRuntimeProvider, RESERVED_ENTITY_FIELDS, Registry, SIDECAR_REFERENCES, auditBlocks, blockRegistry, buildEntityDomainEvents, checkAgainstSchema, checkBlockConstraints, checkBlockContext, checkNodePlacement, clearEntityDeclarations, clearExtensionPoints, clearHandles, clearLoaders, clearSidecarDeclarations, componentRegistry, dataNeedKey, defaultPropsFromSettings, defineBlock, defineEntity, defineExtensionPoint, defineLoader, definePlugin, defineSidecarFields, describeBlock, describeBlocks, describeEntities, describeExtensionPoints, describeLoaders, entityAdminForm, entityDeclarationRegistry, entityEventKey, entityRecordSchema, extensionAfterHooks, extensionPointRegistry, formatSchemaIssues, getAnalyticsCards, getBlock, getBlockMeta, getComponent, getDashboardWidgets, getHandle, getMetric, getPage, getPlugin, getPluginFactory, getScaffold, getWidgetsForZone, handleNameFor, handleRegistry, hasEntityDeclaration, hasExtensionPoint, hasHandle, hasLoader, hasSidecarDeclaration, isBlockEnabledOn, isCustomId, isLoaderParamRef, isLoaderPropRef, listBlocks, listEntityDeclarations, listExtensionHandlers, listExtensionPoints, listHandles, listLoaders, listMetrics, listPluginFactories, listPlugins, loaderRegistry, metricRegistry, pageRegistry, pluginFactoryRegistry, pluginRegistry, registerBlock, registerComponent, registerExtensionHandler, registerHandle, registerMetric, registerPage, registerPlugin, registerPluginFactory, registerScaffold, repairBlockTree, resolveEntityDeclaration, resolveHandle, resolveLoader, resolvePluginComponent, resolvePluginRuntime, resolveSidecarDeclaration, runEscapeValve, runExtensionPoint, scaffoldRegistry, settingsToJsonSchema, sidecarDeclarationRegistry, summarizeBlockAudit, unregisterExtensionHandler, useConnectorsForPlugin, usePluginRuntime, usePluginRuntimeOptional, validateEntityRecordData };
1591
+ //# sourceMappingURL=chunk-732GZEUI.js.map
1592
+ //# sourceMappingURL=chunk-732GZEUI.js.map