api_binding_ui 1.1.0 → 2.0.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 (78) hide show
  1. package/README.md +74 -394
  2. package/SKILL.md +49 -350
  3. package/dist/framework/contract.d.ts +268 -0
  4. package/dist/framework/engine.d.ts +88 -0
  5. package/dist/framework/expr.d.ts +34 -0
  6. package/dist/framework/loadCatalog.d.ts +6 -0
  7. package/dist/framework/names.d.ts +7 -0
  8. package/dist/framework/openapi.d.ts +7 -0
  9. package/dist/framework/runner.d.ts +33 -0
  10. package/dist/framework/schema.d.ts +22 -0
  11. package/dist/framework/store.d.ts +17 -0
  12. package/dist/framework/validate.d.ts +7 -0
  13. package/dist/index-e9twc25y.js +754 -0
  14. package/dist/index-e9twc25y.js.map +16 -0
  15. package/dist/index.d.ts +11 -29
  16. package/dist/index.js +345 -11932
  17. package/dist/index.js.map +14 -0
  18. package/dist/runtime/BindingProvider.d.ts +42 -0
  19. package/dist/runtime/BindingScreen.d.ts +23 -0
  20. package/dist/runtime/hooks.d.ts +22 -0
  21. package/dist/runtime/screenStore.d.ts +7 -0
  22. package/dist/studio/ApiPicker.d.ts +15 -0
  23. package/dist/studio/BindingStudio.d.ts +21 -0
  24. package/dist/studio/BindingWidget.d.ts +9 -0
  25. package/dist/studio/Collapse.d.ts +6 -0
  26. package/dist/studio/ComponentList.d.ts +10 -0
  27. package/dist/studio/FindAnything.d.ts +7 -0
  28. package/dist/studio/Inspector.d.ts +12 -0
  29. package/dist/studio/Navigator.d.ts +17 -0
  30. package/dist/studio/SettingsPanel.d.ts +15 -0
  31. package/dist/studio/Tip.d.ts +23 -0
  32. package/dist/studio/editors/ConsumeEditor.d.ts +4 -0
  33. package/dist/studio/editors/RequestInputs.d.ts +11 -0
  34. package/dist/studio/editors/ScopedEditor.d.ts +3 -0
  35. package/dist/studio/editors/SlotSection.d.ts +3 -0
  36. package/dist/studio/editors/SourceSelect.d.ts +10 -0
  37. package/dist/studio/editors/StepCard.d.ts +12 -0
  38. package/dist/studio/editors/TriggerEditor.d.ts +4 -0
  39. package/dist/studio/editors/types.d.ts +14 -0
  40. package/dist/studio/icons.d.ts +65 -0
  41. package/dist/studio/index.css +2021 -0
  42. package/dist/studio/index.d.ts +3 -0
  43. package/dist/studio/index.js +2806 -0
  44. package/dist/studio/index.js.map +33 -0
  45. package/dist/studio/useBindings.d.ts +20 -0
  46. package/dist/studio/useCatalog.d.ts +15 -0
  47. package/dist/studio/useGlide.d.ts +11 -0
  48. package/dist/studio/useSettings.d.ts +8 -0
  49. package/package.json +20 -14
  50. package/spec/FRAMEWORK.md +271 -0
  51. package/dist/api-binding-ui.css +0 -248
  52. package/dist/components/ApiBindingWorkspace.d.ts +0 -10
  53. package/dist/components/ApiMappings.d.ts +0 -21
  54. package/dist/components/BindApiButton.d.ts +0 -41
  55. package/dist/components/RequestPreview.d.ts +0 -7
  56. package/dist/components/SimulationDialog.d.ts +0 -8
  57. package/dist/components/ValidationDialog.d.ts +0 -8
  58. package/dist/components/WorkspaceContext.d.ts +0 -7
  59. package/dist/components/ui/AppGrid.d.ts +0 -27
  60. package/dist/components/ui/Badge.d.ts +0 -9
  61. package/dist/components/ui/Banner.d.ts +0 -8
  62. package/dist/components/ui/Button.d.ts +0 -31
  63. package/dist/components/ui/Card.d.ts +0 -14
  64. package/dist/components/ui/Dialog.d.ts +0 -61
  65. package/dist/components/ui/Icons.d.ts +0 -21
  66. package/dist/components/ui/Input.d.ts +0 -31
  67. package/dist/core/bindingContext.d.ts +0 -67
  68. package/dist/core/catalog.d.ts +0 -18
  69. package/dist/core/componentId.d.ts +0 -12
  70. package/dist/core/model.d.ts +0 -63
  71. package/dist/core/openapi.d.ts +0 -2
  72. package/dist/core/transport.d.ts +0 -33
  73. package/dist/core/validation.d.ts +0 -2
  74. package/dist/core/valueCoercion.d.ts +0 -17
  75. package/dist/hooks/useAppliedBinding.d.ts +0 -6
  76. package/dist/index.css +0 -248
  77. package/dist/style.css +0 -244
  78. package/dist/types.d.ts +0 -245
@@ -0,0 +1,754 @@
1
+ // src/framework/openapi.ts
2
+ var METHODS = ["get", "put", "post", "delete", "patch", "head", "options", "trace", "query"];
3
+ function hashString(s) {
4
+ let h = 2166136261;
5
+ for (let i = 0;i < s.length; i++) {
6
+ h ^= s.charCodeAt(i);
7
+ h = Math.imul(h, 16777619);
8
+ }
9
+ return (h >>> 0).toString(16).padStart(8, "0");
10
+ }
11
+ function pointerGet(doc, ref) {
12
+ if (!ref.startsWith("#/"))
13
+ return;
14
+ return ref.slice(2).split("/").map((k) => k.replace(/~1/g, "/").replace(/~0/g, "~")).reduce((o, k) => o == null ? undefined : o[k], doc);
15
+ }
16
+ function deref(doc, node, seen = []) {
17
+ if (Array.isArray(node))
18
+ return node.map((n) => deref(doc, n, seen));
19
+ if (!node || typeof node !== "object")
20
+ return node;
21
+ const n = node;
22
+ if (typeof n.$ref === "string") {
23
+ if (seen.includes(n.$ref) || seen.length > 24)
24
+ return { $ref: n.$ref };
25
+ const { $ref, ...rest } = n;
26
+ const target = deref(doc, pointerGet(doc, $ref), [...seen, $ref]);
27
+ return Object.keys(rest).length ? { ...target, ...deref(doc, rest, seen) } : target;
28
+ }
29
+ const out = {};
30
+ for (const [k, v] of Object.entries(n))
31
+ out[k] = deref(doc, v, seen);
32
+ return out;
33
+ }
34
+ function jsonContent(content) {
35
+ if (!content)
36
+ return;
37
+ const key = Object.keys(content).find((k) => /json/i.test(k)) ?? Object.keys(content)[0];
38
+ return key ? content[key]?.schema : undefined;
39
+ }
40
+ function intentOf(method, op) {
41
+ if (typeof op["x-binding-intent"] === "string")
42
+ return op["x-binding-intent"];
43
+ if (method === "get" || method === "head" || method === "query")
44
+ return "read";
45
+ if (["put", "patch", "delete"].includes(method))
46
+ return "write";
47
+ return;
48
+ }
49
+ function catalogFromOpenApi2(input, source = {}) {
50
+ const doc = input;
51
+ if (!doc || typeof doc !== "object" || !doc.paths)
52
+ throw new Error("This file is not an OpenAPI document: it has no paths.");
53
+ const operations = [];
54
+ const productOf = new Map;
55
+ for (const g of doc["x-tagGroups"] ?? [])
56
+ g.tags?.forEach((t) => {
57
+ if (g.name)
58
+ productOf.set(t, g.name);
59
+ });
60
+ for (const [path, rawItem] of Object.entries(doc.paths)) {
61
+ const item = deref(doc, rawItem);
62
+ for (const method of METHODS) {
63
+ const op = item?.[method];
64
+ if (!op || !op.operationId)
65
+ continue;
66
+ const params = [];
67
+ for (const p of [...item.parameters ?? [], ...op.parameters ?? []]) {
68
+ if (!p?.name || !p?.in)
69
+ continue;
70
+ const pointer = `/${p.in}/${p.name}`;
71
+ const at = params.findIndex((x) => x.pointer === pointer);
72
+ const entry = { pointer, name: p.name, required: !!p.required || p.in === "path", schema: p.schema ?? {}, description: p.description };
73
+ if (at >= 0)
74
+ params[at] = entry;
75
+ else
76
+ params.push(entry);
77
+ }
78
+ for (const m of path.matchAll(/\{([^}]+)\}/g)) {
79
+ if (!params.some((x) => x.pointer === `/path/${m[1]}`))
80
+ params.unshift({ pointer: `/path/${m[1]}`, name: m[1], required: true, schema: { type: "string" } });
81
+ }
82
+ const bodySchema = jsonContent(op.requestBody?.content);
83
+ const responses = Object.entries(op.responses ?? {}).map(([status, r]) => ({ status, schema: jsonContent(r?.content) }));
84
+ const extensions = {};
85
+ for (const [k, v] of Object.entries(op))
86
+ if (k.startsWith("x-"))
87
+ extensions[k] = v;
88
+ operations.push({
89
+ id: op.operationId,
90
+ method: method.toUpperCase(),
91
+ path,
92
+ summary: op.summary,
93
+ description: op.description,
94
+ tags: op.tags ?? [],
95
+ product: op.tags?.map((t) => productOf.get(t)).find(Boolean),
96
+ intent: intentOf(method, op),
97
+ parameters: params,
98
+ body: bodySchema ? { required: !!op.requestBody?.required, schema: bodySchema } : undefined,
99
+ responses,
100
+ extensions
101
+ });
102
+ }
103
+ }
104
+ const body = JSON.stringify(operations);
105
+ return {
106
+ catalogVersion: 1,
107
+ source: { title: doc.info?.title ?? "API", version: doc.info?.version ?? "", url: source.url },
108
+ hash: hashString(body),
109
+ operations,
110
+ schemas: {}
111
+ };
112
+ }
113
+
114
+ // src/framework/store.ts
115
+ function localStorageAdapter2(prefix = "abs.manifest.") {
116
+ return {
117
+ async load(screen) {
118
+ try {
119
+ const raw = globalThis.localStorage?.getItem(prefix + screen);
120
+ return raw ? JSON.parse(raw) : null;
121
+ } catch {
122
+ return null;
123
+ }
124
+ },
125
+ async save(m) {
126
+ if (!globalThis.localStorage)
127
+ throw new Error("Browser storage is not available.");
128
+ globalThis.localStorage.setItem(prefix + m.screen, JSON.stringify(m));
129
+ }
130
+ };
131
+ }
132
+ function memoryAdapter2(seed = []) {
133
+ const m = new Map(seed.map((x) => [x.screen, x]));
134
+ return { async load(s) {
135
+ return m.get(s) ?? null;
136
+ }, async save(x) {
137
+ m.set(x.screen, x);
138
+ } };
139
+ }
140
+ function toManifest2(fw, screen, bindings) {
141
+ const S = fw.registry.screens.find((s) => s.id === screen);
142
+ const kinds = {};
143
+ S?.components.forEach((c) => {
144
+ const d = fw.descriptors.find((x) => x.kind === c.kind);
145
+ if (d)
146
+ kinds[c.kind] = d.version;
147
+ });
148
+ return { manifestVersion: 1, screen, catalog: { hash: fw.catalog.hash }, kinds, bindings };
149
+ }
150
+ function markDirty(pending, screen) {
151
+ pending.set(screen, (pending.get(screen) ?? 0) + 1);
152
+ }
153
+ async function savePending(adapter, fw, idx, pending) {
154
+ const batch = [...pending];
155
+ const results = await Promise.allSettled(batch.map(([s]) => adapter.save(toManifest2(fw, s, idx[s] ?? []))));
156
+ const failed = [];
157
+ batch.forEach(([s, version], i) => {
158
+ if (results[i].status === "rejected")
159
+ failed.push(s);
160
+ else if (pending.get(s) === version)
161
+ pending.delete(s);
162
+ });
163
+ return failed;
164
+ }
165
+ async function loadAll(adapter, screens) {
166
+ const out = {};
167
+ await Promise.all(screens.map(async (s) => {
168
+ const m = await adapter.load(s);
169
+ if (m)
170
+ out[s] = m.bindings;
171
+ }));
172
+ return out;
173
+ }
174
+ function putBinding(idx, screen, b) {
175
+ const list = (idx[screen] ?? []).filter((x) => !(x.component === b.component && x.slot === b.slot));
176
+ return { ...idx, [screen]: [...list, b] };
177
+ }
178
+ function dropBinding(idx, screen, component, slot) {
179
+ return { ...idx, [screen]: (idx[screen] ?? []).filter((x) => !(x.component === component && x.slot === slot)) };
180
+ }
181
+ var BINDING_TYPES = new Set(["operation", "expression", "steps", "none"]);
182
+ function parseManifests(text) {
183
+ let data;
184
+ try {
185
+ data = JSON.parse(text);
186
+ } catch {
187
+ throw new Error("The file is not valid JSON.");
188
+ }
189
+ const list = Array.isArray(data) ? data : [data];
190
+ list.forEach((m, i) => {
191
+ const at = `Manifest ${i + 1}`;
192
+ if (!m || typeof m !== "object")
193
+ throw new Error(`${at} is not an object.`);
194
+ const r = m;
195
+ if (r.manifestVersion !== 1)
196
+ throw new Error(`${at}: manifestVersion must be 1.`);
197
+ if (typeof r.screen !== "string" || !r.screen)
198
+ throw new Error(`${at}: "screen" is missing.`);
199
+ if (!Array.isArray(r.bindings))
200
+ throw new Error(`${at} (${r.screen}): "bindings" must be a list.`);
201
+ r.bindings.forEach((b, j) => {
202
+ const x = b;
203
+ if (!x || typeof x.component !== "string" || typeof x.slot !== "string" || !BINDING_TYPES.has(x.type))
204
+ throw new Error(`${at} (${r.screen}), binding ${j + 1}: needs "component", "slot" and a known "type".`);
205
+ });
206
+ });
207
+ return list;
208
+ }
209
+
210
+ // src/framework/names.ts
211
+ var sing = (w) => w.length > 3 && w.endsWith("s") ? w.slice(0, -1) : w;
212
+ function synonymIndex(glossaries) {
213
+ const m = {};
214
+ glossaries.forEach((g) => g.synonyms.forEach((grp) => {
215
+ const g2 = grp.map((x) => sing(x.toLowerCase()));
216
+ g2.forEach((w) => {
217
+ m[w] = g2;
218
+ });
219
+ }));
220
+ return m;
221
+ }
222
+ function words(text) {
223
+ return text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1).map(sing);
224
+ }
225
+ function humanize(id) {
226
+ const s = id.replace(/^\$\.?/, "").replace(/\[\*\]/g, "").split(/[./]/).filter(Boolean).pop() ?? id;
227
+ const w = s.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
228
+ return w ? w[0].toUpperCase() + w.slice(1) : id;
229
+ }
230
+
231
+ // src/framework/schema.ts
232
+ var obj = (s) => s && typeof s === "object" ? s : {};
233
+ function flat(s) {
234
+ const o = obj(s);
235
+ const parts = [o, ...(o.allOf ?? []).map(flat)];
236
+ if (parts.length === 1 && !o.oneOf && !o.anyOf)
237
+ return o;
238
+ const props = {};
239
+ const required = [];
240
+ parts.forEach((p) => {
241
+ Object.assign(props, p.properties ?? {});
242
+ required.push(...p.required ?? []);
243
+ });
244
+ const alt = (o.oneOf ?? o.anyOf)?.map(flat).find((x) => x.type !== "null");
245
+ return { ...alt ?? {}, ...o, properties: Object.keys(props).length ? props : alt?.properties, required };
246
+ }
247
+ function typeName(s) {
248
+ const o = flat(s);
249
+ const t = Array.isArray(o.type) ? o.type.find((x) => x !== "null") : o.type;
250
+ if (t === "array")
251
+ return typeName(o.items) + "[]";
252
+ if (o.format === "date-time" || o.format === "date")
253
+ return "date-time";
254
+ if (t === "integer" || t === "number")
255
+ return "number";
256
+ return t ?? (o.properties ? "object" : "any");
257
+ }
258
+ var isObject = (s) => {
259
+ const o = flat(s);
260
+ return o.type === "object" || !!o.properties;
261
+ };
262
+ var propsOf = (s) => Object.entries(flat(s).properties ?? {}).map(([k, v]) => [k, typeName(v)]);
263
+ function leaves(schema, prefix, required, out, depth = 0) {
264
+ const s = flat(schema);
265
+ if (isObject(s)) {
266
+ const props = Object.entries(s.properties ?? {});
267
+ if (!props.length || depth > 3) {
268
+ out.push([prefix, "object", required]);
269
+ return;
270
+ }
271
+ const req = new Set(s.required ?? []);
272
+ props.forEach(([k, v]) => {
273
+ if (!flat(v).readOnly)
274
+ leaves(v, `${prefix}/${k}`, required && req.has(k), out, depth + 1);
275
+ });
276
+ } else
277
+ out.push([prefix, typeName(s), required]);
278
+ }
279
+ function listsIn(schema, path, out, depth = 0) {
280
+ const s = flat(schema);
281
+ if (s.type === "array") {
282
+ if (isObject(s.items))
283
+ out.push({ select: `${path}[*]`, fields: propsOf(s.items) });
284
+ return;
285
+ }
286
+ if (isObject(s) && depth < 4)
287
+ Object.entries(s.properties ?? {}).forEach(([k, v]) => listsIn(v, `${path}.${k}`, out, depth + 1));
288
+ }
289
+ var views = new WeakMap;
290
+ function opViews(catalog) {
291
+ let m = views.get(catalog);
292
+ if (m)
293
+ return m;
294
+ m = new Map;
295
+ for (const op of catalog.operations) {
296
+ const req = op.parameters.map((p) => [p.pointer, typeName(p.schema), p.required]);
297
+ if (op.body)
298
+ leaves(op.body.schema, "/body", op.body.required, req);
299
+ const ok = op.responses.find((r) => /^2/.test(r.status) && r.schema);
300
+ const lists = [];
301
+ if (ok)
302
+ listsIn(ok.schema, "$", lists);
303
+ const one = ok && !lists.length && isObject(ok.schema) ? propsOf(ok.schema) : null;
304
+ const scalars = ok ? propsOf(ok.schema).filter(([, t]) => t !== "object" && !t.endsWith("[]")).map(([k, t]) => [`$.${k}`, t]) : [];
305
+ m.set(op.id, { op, req, lists, one, scalars });
306
+ }
307
+ views.set(catalog, m);
308
+ return m;
309
+ }
310
+
311
+ // src/framework/engine.ts
312
+ function createEngine2(fw, manifests) {
313
+ const OPV = opViews(fw.catalog);
314
+ const OPS = [...OPV.values()];
315
+ const DESC = new Map(fw.descriptors.map((d) => [d.kind, d]));
316
+ const SCR = new Map(fw.registry.screens.map((s) => [s.id, s]));
317
+ const transforms = fw.transforms ?? [];
318
+ const providers = fw.providers ?? [];
319
+ const syn = synonymIndex(fw.glossaries ?? []);
320
+ const key = (s, c, sl) => `${s}|${c}|${sl}`;
321
+ const BIND = new Map;
322
+ for (const [s, list] of Object.entries(manifests))
323
+ for (const b of list) {
324
+ if (b.type === "operation" && !OPV.has(b.operation))
325
+ continue;
326
+ if (b.type === "steps" && b.steps.some((st) => !OPV.has(st.operation)))
327
+ continue;
328
+ BIND.set(key(s, b.component, b.slot), b);
329
+ }
330
+ const getB = (s, c, sl) => BIND.get(key(s, c, sl));
331
+ const screen = (id) => SCR.get(id);
332
+ const compOf = (s, id) => SCR.get(s)?.components.find((c) => c.bindId === id);
333
+ const descOf = (c) => DESC.get(c.kind);
334
+ const bindable = (d) => d ? d.slots.filter((x) => x.role !== "produce") : [];
335
+ const tokCache = new Map;
336
+ const tok = (text, expand) => {
337
+ const k = (expand ? "+" : "-") + text;
338
+ let out = tokCache.get(k);
339
+ if (out)
340
+ return out;
341
+ out = new Set;
342
+ for (const w of words(text)) {
343
+ out.add(w);
344
+ if (expand)
345
+ (syn[w] ?? []).forEach((x) => out.add(x));
346
+ }
347
+ tokCache.set(k, out);
348
+ return out;
349
+ };
350
+ function nameScore(target, source) {
351
+ const a = tok(target, false), b = tok(source, true);
352
+ if (!a.size || !b.size)
353
+ return 0;
354
+ let n = 0;
355
+ a.forEach((x) => {
356
+ if (b.has(x))
357
+ n++;
358
+ });
359
+ return n && n / a.size >= 0.5 ? 0.6 * n / a.size + 0.4 * n / b.size : 0;
360
+ }
361
+ const base = (t) => t.replace("[]", "");
362
+ function typeFit(target, source) {
363
+ if (target !== "any" && source !== "any" && target.endsWith("[]") !== source.endsWith("[]"))
364
+ return { ok: false };
365
+ const t = base(target), s = base(source);
366
+ if (t === "any" || s === "any" || t === s)
367
+ return { ok: true };
368
+ const tr = transforms.find((x) => typeName(x.input) === s && typeName(x.output) === t);
369
+ return tr ? { ok: true, transform: tr.name } : { ok: false };
370
+ }
371
+ function bindingFields(s, c, slotId) {
372
+ const b = getB(s, c, slotId);
373
+ if (!b || b.type !== "operation")
374
+ return null;
375
+ const v = OPV.get(b.operation);
376
+ if (!v)
377
+ return null;
378
+ if (b.select === "$" && v.one)
379
+ return v.one;
380
+ return v.lists.find((x) => x.select === b.select)?.fields ?? null;
381
+ }
382
+ function shapeOf(s, comp, slot) {
383
+ const sc = obj(slot.schema);
384
+ if (typeof sc.$ref === "string" && sc.$ref.startsWith("#slot/")) {
385
+ const via = sc.$ref.slice(6);
386
+ return { many: !!slot.many, fields: bindingFields(s, comp.bindId, via) ?? [], via };
387
+ }
388
+ if (flat(sc).properties)
389
+ return { many: !!slot.many, fields: propsOf(sc) };
390
+ return { many: !!slot.many, type: typeName(sc) };
391
+ }
392
+ function contextShape(ctx) {
393
+ if ("schema" in ctx.shape)
394
+ return { many: !!ctx.many, fields: propsOf(ctx.shape.schema) };
395
+ const f = ctx.shape.from, comp = compOf(f.screen, f.component), d = comp && descOf(comp), slot = d?.slots.find((x) => x.id === f.slot);
396
+ return { many: !!ctx.many, fields: (slot && comp ? shapeOf(f.screen, comp, slot).fields : null) ?? [] };
397
+ }
398
+ function scopeFields(s, comp, slot) {
399
+ if (!slot.scope || !comp.parent)
400
+ return null;
401
+ return bindingFields(s, comp.parent, slot.scope.parentSlot);
402
+ }
403
+ function manyContext(s) {
404
+ return screen(s).context.find((x) => x.many);
405
+ }
406
+ function sources(s, opts = {}) {
407
+ const out = [], S = screen(s);
408
+ opts.item?.forEach(([f, t]) => out.push({ expr: `$item.${f}`, label: humanize(f), name: f, type: t, group: "item" }));
409
+ S.components.forEach((c) => {
410
+ const d = descOf(c);
411
+ d?.slots.filter((x) => x.role === "produce").forEach((sl) => {
412
+ const sh = shapeOf(s, c, sl);
413
+ if (sh.fields?.length) {
414
+ if (sh.many)
415
+ out.push({ expr: `${c.bindId}.${sl.id}`, label: `${c.label} · ${sl.label}`, type: "list", group: "screen", fields: sh.fields });
416
+ sh.fields.forEach(([f, t]) => out.push({ expr: `${c.bindId}.${sl.id}${sh.many ? "[*]" : ""}.${f}`, label: `${c.label} · ${humanize(f)}`, name: f, type: sh.many ? t + "[]" : t, group: "screen" }));
417
+ } else if (sh.via) {
418
+ const via = d.slots.find((x) => x.id === sh.via);
419
+ out.push({ expr: `${c.bindId}.${sl.id}`, label: `${c.label} · ${sl.label}`, type: "pending", group: "screen", waitsFor: `${c.label} · ${via?.label ?? sh.via}` });
420
+ } else
421
+ out.push({ expr: `${c.bindId}.${sl.id}`, label: c.label + (sl.label && bindable(d).length + 1 < d.slots.length ? ` · ${sl.label}` : ""), name: c.label, type: sh.many ? "list" : sh.type ?? "any", group: "screen" });
422
+ });
423
+ });
424
+ S.context.forEach((ctx) => {
425
+ const sh = contextShape(ctx);
426
+ if (ctx.many)
427
+ out.push({ expr: `$context.${ctx.id}`, label: ctx.label, type: "list", group: "context", fields: sh.fields });
428
+ sh.fields.forEach(([f, t]) => out.push({ expr: `$context.${ctx.id}${ctx.many ? "[*]" : ""}.${f}`, label: `${ctx.label} · ${humanize(f)}`, name: f, type: ctx.many ? t + "[]" : t, group: "context" }));
429
+ });
430
+ providers.forEach((p) => propsOf(p.schema).forEach(([f, t]) => out.push({ expr: `$${p.id}.${f}`, label: `${p.label} · ${humanize(f)}`, name: `${p.label} ${f}`, type: t, group: "provider" })));
431
+ return out;
432
+ }
433
+ function rankSources(targetName, targetType, srcs, min = 0.65) {
434
+ const out = [];
435
+ for (const x of srcs) {
436
+ if (x.type === "list" || x.waitsFor)
437
+ continue;
438
+ const fit = typeFit(targetType, x.type);
439
+ if (!fit.ok)
440
+ continue;
441
+ let sc = Math.max(nameScore(targetName, x.name ?? x.label), nameScore(targetName, x.label) * 0.9);
442
+ if (x.group === "screen")
443
+ sc += 0.12;
444
+ else if (x.group === "item")
445
+ sc += 0.05;
446
+ if (fit.transform)
447
+ sc -= 0.05;
448
+ if (sc >= min)
449
+ out.push({ expr: x.expr + (fit.transform ? ` | ${fit.transform}()` : ""), score: sc, source: x });
450
+ }
451
+ return out.sort((a, b) => b.score - a.score);
452
+ }
453
+ function bestSource(targetName, targetType, srcs) {
454
+ return rankSources(targetName, targetType, srcs)[0] ?? null;
455
+ }
456
+ function qualified(ptr, op) {
457
+ const parts = ptr.split("/").filter(Boolean), name = parts[parts.length - 1];
458
+ if (words(name).length > 1)
459
+ return name;
460
+ if (parts[0] === "body")
461
+ return parts.length > 2 ? `${parts[parts.length - 2]} ${name}` : name;
462
+ const segs = op.path.split("/").filter((x) => x && !x.startsWith("{"));
463
+ return `${segs[segs.length - 1] ?? ""} ${name}`;
464
+ }
465
+ function proposeRequest(v, srcs) {
466
+ const req = {};
467
+ v.req.forEach(([ptr, t]) => {
468
+ const m = bestSource(qualified(ptr, v.op), t, srcs);
469
+ if (m)
470
+ req[ptr] = m.expr;
471
+ });
472
+ return req;
473
+ }
474
+ function proposeMap(slotFields, itemFields) {
475
+ const map = {};
476
+ const srcs = itemFields.map(([f, t]) => ({ expr: `$item.${f}`, name: f, label: f, type: t, group: "item" }));
477
+ slotFields.forEach(([p, t]) => {
478
+ const m = bestSource(p, t, srcs);
479
+ if (m)
480
+ map[p] = m.expr;
481
+ });
482
+ return map;
483
+ }
484
+ function triggerSources(s, item) {
485
+ const mc = manyContext(s);
486
+ return sources(s, { item: item ?? (mc ? contextShape(mc).fields : undefined) });
487
+ }
488
+ function rank(s, comp, slot) {
489
+ const S = screen(s);
490
+ const near = tok([comp.label, ...slot.hints?.keywords ?? []].join(" "), true);
491
+ const far = tok([S.label, ...S.path].join(" "), true);
492
+ const cover = (o) => {
493
+ const own = tok([o.id, ...o.tags].join(" "), false);
494
+ if (!own.size)
495
+ return { score: 0, near: false };
496
+ let a = 0, b = 0;
497
+ own.forEach((w) => {
498
+ if (near.has(w))
499
+ a++;
500
+ else if (far.has(w))
501
+ b++;
502
+ });
503
+ return { score: Math.min(1, (a + 0.5 * b) / own.size), near: a > 0 };
504
+ };
505
+ const srcs = slot.role === "trigger" ? triggerSources(s) : sources(s);
506
+ const own = srcs.filter((x) => x.group === "screen" && x.type !== "list").length || 1;
507
+ return OPS.map((v) => {
508
+ const reasons = [];
509
+ let tot = 0, w = 0;
510
+ const add = (weight, score, why) => {
511
+ tot += weight * score;
512
+ w += weight;
513
+ if (score >= 0.5 && why)
514
+ reasons.push(why);
515
+ };
516
+ const o = v.op;
517
+ const prop = proposeRequest(v, srcs);
518
+ if (slot.role === "consume") {
519
+ const sh = shapeOf(s, comp, slot);
520
+ const shape = sh.many ? v.lists.length ? 1 : 0 : sh.fields?.length ? v.one || v.lists.length ? 0.7 : 0 : v.scalars.some(([, t]) => typeFit(sh.type ?? "any", t).ok) || v.one ? 0.7 : 0;
521
+ add(0.3, shape, sh.many ? "Returns a list" : "Returns a matching value");
522
+ add(0.15, o.intent === "read" ? 1 : o.intent ? 0 : 0.5, o.intent === "read" ? "Reads data" : null);
523
+ } else {
524
+ add(0.2, v.req.length ? 0.8 : 0.2, null);
525
+ add(0.15, o.intent === "write" ? 1 : o.intent === "read" ? 0.1 : 0.5, o.intent === "write" ? "Saves data" : null);
526
+ const body = v.req.filter(([p]) => p.startsWith("/body/"));
527
+ const filled = Object.entries(prop).filter(([p, e]) => p.startsWith("/body/") && !e.startsWith("$")).length;
528
+ add(0.3, body.length ? Math.min(1, filled / Math.min(own, body.length)) : 0, filled ? `Uses ${filled} values from this screen` : null);
529
+ }
530
+ const required = v.req.filter((r) => r[2]);
531
+ if (required.length) {
532
+ const ok = required.filter(([p]) => prop[p]).length;
533
+ add(0.25, ok / required.length, ok === required.length ? "All required inputs available" : null);
534
+ } else
535
+ add(0.25, 1, null);
536
+ if (o.product)
537
+ add(0.1, o.product === S.path[0] ? 1 : 0, o.product === S.path[0] ? `From ${o.product}` : null);
538
+ const nm = cover(o);
539
+ add(0.35, nm.score, nm.score >= 0.5 ? nm.near ? `Name fits “${comp.label}”` : "Name fits this screen" : null);
540
+ return { view: v, score: w ? tot / w : 0, reasons };
541
+ }).sort((a, b) => b.score - a.score);
542
+ }
543
+ function slotState(s, c, sl) {
544
+ const b = getB(s, c, sl);
545
+ return !b ? "unbound" : b.state === "confirmed" ? "bound" : "suggested";
546
+ }
547
+ function compState(s, comp) {
548
+ const sl = bindable(descOf(comp));
549
+ if (!sl.length)
550
+ return "bound";
551
+ const st = sl.map((x) => slotState(s, comp.bindId, x.id));
552
+ if (st.includes("suggested"))
553
+ return "suggested";
554
+ if (st.every((x) => x === "bound"))
555
+ return "bound";
556
+ const must = sl.filter((x) => x.required);
557
+ if (must.length && must.every((x) => slotState(s, comp.bindId, x.id) === "bound") && st.includes("bound"))
558
+ return "bound";
559
+ return st.includes("bound") ? "partial" : "unbound";
560
+ }
561
+ function tally(screens) {
562
+ const t = { bound: 0, suggested: 0, unbound: 0, total: 0 };
563
+ screens.forEach((S) => S.components.forEach((c) => {
564
+ if (!bindable(descOf(c)).length)
565
+ return;
566
+ const st = compState(S.id, c);
567
+ t[st === "partial" ? "unbound" : st]++;
568
+ t.total++;
569
+ }));
570
+ return t;
571
+ }
572
+ const fieldsAt = (v, select) => select === "$" ? v.one ?? [] : v.lists.find((l) => l.select === select)?.fields ?? [];
573
+ function proposeConsume(s, comp, slot, opId, strict = false) {
574
+ const v = OPV.get(opId);
575
+ if (!v)
576
+ return null;
577
+ const shape = shapeOf(s, comp, slot);
578
+ const props = shape.fields && !shape.via ? shape.fields : null;
579
+ let select;
580
+ if (shape.many)
581
+ select = v.lists[0]?.select;
582
+ else if (props)
583
+ select = v.one ? "$" : v.lists[0]?.select;
584
+ else {
585
+ const scal = v.scalars.map(([path, t]) => ({ expr: path, label: humanize(path), name: path.slice(2), type: t, group: "item" }));
586
+ select = (rankSources(slot.label, shape.type ?? "any", scal)[0] ?? rankSources(`${comp.label} ${slot.label}`, shape.type ?? "any", scal)[0])?.expr.split(" | ")[0];
587
+ if (!select && !strict)
588
+ select = v.scalars.find(([, t]) => typeFit(shape.type ?? "any", t).ok)?.[0];
589
+ }
590
+ if (!select) {
591
+ if (strict)
592
+ return null;
593
+ select = "$";
594
+ }
595
+ return {
596
+ component: comp.bindId,
597
+ slot: slot.id,
598
+ type: "operation",
599
+ operation: opId,
600
+ select,
601
+ request: proposeRequest(v, sources(s)),
602
+ map: props ? proposeMap(props, fieldsAt(v, select)) : undefined,
603
+ load: "open",
604
+ origin: "suggested",
605
+ state: "draft"
606
+ };
607
+ }
608
+ function siblingFills(s, comp, slot, opId) {
609
+ return bindable(descOf(comp)).filter((x) => x.role === "consume" && !x.scope && x.id !== slot.id && !getB(s, comp.bindId, x.id)).map((x) => proposeConsume(s, comp, x, opId, true)).filter((b) => !!b);
610
+ }
611
+ return {
612
+ fw,
613
+ OPV,
614
+ OPS,
615
+ DESC,
616
+ SCR,
617
+ getB,
618
+ screen,
619
+ compOf,
620
+ descOf,
621
+ bindable,
622
+ typeFit,
623
+ shapeOf,
624
+ contextShape,
625
+ scopeFields,
626
+ sources,
627
+ triggerSources,
628
+ bestSource,
629
+ rankSources,
630
+ proposeRequest,
631
+ proposeMap,
632
+ rank,
633
+ slotState,
634
+ compState,
635
+ tally,
636
+ manyContext,
637
+ fieldsAt,
638
+ proposeConsume,
639
+ siblingFills
640
+ };
641
+ }
642
+
643
+ // src/framework/validate.ts
644
+ function validateRegistry2(fw) {
645
+ const kinds = new Set(fw.descriptors.map((d) => d.kind));
646
+ const issues = [];
647
+ const ids = new Set(fw.registry.screens.map((s) => s.id));
648
+ const screens = fw.registry.screens.map((S) => {
649
+ const seen = new Set;
650
+ const components = S.components.filter((c) => {
651
+ if (!kinds.has(c.kind)) {
652
+ issues.push({ severity: "error", screen: S.id, component: c.bindId, code: "registry.unknown-kind", message: `"${c.label}" uses ${c.kind}, which has no descriptor.` });
653
+ return false;
654
+ }
655
+ if (seen.has(c.bindId)) {
656
+ issues.push({ severity: "error", screen: S.id, component: c.bindId, code: "registry.duplicate-bind-id", message: `bindId "${c.bindId}" is used twice.` });
657
+ return false;
658
+ }
659
+ seen.add(c.bindId);
660
+ return true;
661
+ }).filter((c) => !c.parent || seen.has(c.parent) || (issues.push({ severity: "error", screen: S.id, component: c.bindId, code: "registry.missing-parent", message: `"${c.label}" names a parent that is not on the screen.` }), false));
662
+ const parent = S.parent && ids.has(S.parent) ? S.parent : undefined;
663
+ return { ...S, components, parent };
664
+ });
665
+ return { registry: { ...fw.registry, screens }, issues };
666
+ }
667
+ function validateBindings2(engine) {
668
+ const issues = [];
669
+ for (const S of engine.fw.registry.screens)
670
+ for (const c of S.components) {
671
+ const d = engine.descOf(c);
672
+ d?.slots.filter((sl) => sl.required && sl.role !== "produce").forEach((sl) => {
673
+ if (engine.slotState(S.id, c.bindId, sl.id) !== "bound")
674
+ issues.push({ severity: "warning", screen: S.id, component: c.bindId, slot: sl.id, code: "slot.required-unbound", message: `${c.label}: ${sl.label} is not connected.` });
675
+ });
676
+ }
677
+ return issues;
678
+ }
679
+
680
+ // src/runtime/BindingProvider.tsx
681
+ import * as React from "react";
682
+ import { jsx } from "react/jsx-runtime";
683
+ var RuntimeContext = React.createContext(null);
684
+ function latestShown(screens) {
685
+ let best;
686
+ for (const m of screens)
687
+ if (m.shown && (!best || m.at > best.at))
688
+ best = m;
689
+ return best?.screen;
690
+ }
691
+ function useBindingRuntime() {
692
+ const r = React.useContext(RuntimeContext);
693
+ if (!r)
694
+ throw new Error("Wrap the app in <BindingProvider> before using binding hooks.");
695
+ return r;
696
+ }
697
+ function BindingProvider2({ catalog, manifests, fetcher, transforms, providers, persistence, children }) {
698
+ const [overrides, setOverrides] = React.useState({});
699
+ const requested = React.useRef(new Set);
700
+ const mounted = React.useRef(new Map);
701
+ const clock = React.useRef(0);
702
+ const loadSaved = React.useCallback((screen) => {
703
+ if (!persistence || requested.current.has(screen))
704
+ return;
705
+ requested.current.add(screen);
706
+ persistence.load(screen).then((m) => {
707
+ if (m)
708
+ setOverrides((o) => ({ ...o, [screen]: m }));
709
+ }, () => requested.current.delete(screen));
710
+ }, [persistence]);
711
+ const reloadSaved = React.useCallback(() => {
712
+ requested.current.clear();
713
+ new Set([...mounted.current.values()].map((m) => m.screen)).forEach(loadSaved);
714
+ }, [loadSaved]);
715
+ const mountScreen = React.useCallback((screen, shown) => {
716
+ const key = Symbol(screen);
717
+ mounted.current.set(key, { screen, shown, at: ++clock.current });
718
+ return () => {
719
+ mounted.current.delete(key);
720
+ };
721
+ }, []);
722
+ const activeScreen = React.useCallback(() => latestShown(mounted.current.values()), []);
723
+ const runtime = React.useMemo(() => {
724
+ const index = new Map;
725
+ const byScreen = new Map(manifests.map((m) => [m.screen, m]));
726
+ Object.values(overrides).forEach((m) => byScreen.set(m.screen, m));
727
+ byScreen.forEach((m) => m.bindings.forEach((b) => {
728
+ if (b.state === "confirmed")
729
+ index.set(`${m.screen}|${b.component}|${b.slot}`, b);
730
+ }));
731
+ return {
732
+ operations: new Map(catalog.operations.map((o) => [o.id, o])),
733
+ binding: (s, c, sl) => index.get(`${s}|${c}|${sl}`),
734
+ fetcher,
735
+ transforms: Object.fromEntries((transforms ?? []).map((t) => [t.name, t.apply])),
736
+ providers: () => Object.fromEntries((providers ?? []).map((p) => [p.id, p.read()])),
737
+ inflight: new Map,
738
+ persistence,
739
+ loadSaved,
740
+ reloadSaved,
741
+ mountScreen,
742
+ activeScreen
743
+ };
744
+ }, [catalog, manifests, overrides, fetcher, transforms, providers, persistence, loadSaved, reloadSaved, mountScreen, activeScreen]);
745
+ return /* @__PURE__ */ jsx(RuntimeContext.Provider, {
746
+ value: runtime,
747
+ children
748
+ });
749
+ }
750
+
751
+ export { catalogFromOpenApi2, localStorageAdapter2, memoryAdapter2, toManifest2, markDirty, savePending, loadAll, putBinding, dropBinding, parseManifests, humanize, createEngine2, validateRegistry2, validateBindings2, useBindingRuntime, BindingProvider2 };
752
+
753
+ //# debugId=AEDD74AF9B26729564756E2164756E21
754
+ //# sourceMappingURL=index-e9twc25y.js.map