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