@plitzi/sdk-schema 0.33.2 → 0.34.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.
@@ -1,7 +1,7 @@
1
- import { isValidIdRef as e } from "./idRef.mjs";
1
+ import { isValidElementId as e } from "./elementId.mjs";
2
2
  //#region src/helpers/schemaValidator.ts
3
3
  var t = (t) => {
4
- let n = [], r = [], { flat: i, pages: a, pageFolders: o, variables: s } = t, c = (e) => !!i[e], l = (e) => i[e], u = () => !i || typeof i != "object" ? (n.push({
4
+ let n = [], r = [], { flat: i, pages: a, pageFolders: o, variables: s } = t, c = (e) => !!i[e], l = (e) => i[e], u = () => Object.values(i).filter((e) => e?.definition?.type === "layoutContainer" && !e.definition.parentId).map((e) => e.id), d = () => [...a, ...u()], f = () => !i || typeof i != "object" ? (n.push({
5
5
  code: "INVALID_FLAT",
6
6
  message: "Schema.flat must be a valid Record<string, Element>"
7
7
  }), !1) : Array.isArray(a) ? Array.isArray(o) ? Array.isArray(s) ? !0 : (n.push({
@@ -13,7 +13,7 @@ var t = (t) => {
13
13
  }), !1) : (n.push({
14
14
  code: "INVALID_PAGES",
15
15
  message: "Schema.pages must be an array of element IDs"
16
- }), !1), d = () => {
16
+ }), !1), p = () => {
17
17
  Object.entries(i).forEach(([e, t]) => {
18
18
  if (!t) {
19
19
  n.push({
@@ -84,7 +84,7 @@ var t = (t) => {
84
84
  details: { parentId: i.parentId }
85
85
  });
86
86
  });
87
- }, f = () => {
87
+ }, m = () => {
88
88
  Object.values(i).forEach((e) => {
89
89
  if (!e?.definition) return;
90
90
  let { id: t, definition: r } = e;
@@ -114,7 +114,7 @@ var t = (t) => {
114
114
  });
115
115
  }
116
116
  });
117
- }, p = () => {
117
+ }, h = () => {
118
118
  let e = /* @__PURE__ */ new Set(), t = /* @__PURE__ */ new Set(), r = (i) => {
119
119
  if (t.has(i)) return n.push({
120
120
  code: "CIRCULAR_REFERENCE",
@@ -141,7 +141,7 @@ var t = (t) => {
141
141
  Object.keys(i).forEach((t) => {
142
142
  e.has(t) || r(t);
143
143
  });
144
- }, m = () => {
144
+ }, g = () => {
145
145
  let e = /* @__PURE__ */ new Set(), t = 0;
146
146
  a.forEach((r, i) => {
147
147
  e.has(r) && n.push({
@@ -174,15 +174,15 @@ var t = (t) => {
174
174
  code: "MULTIPLE_DEFAULT_PAGES",
175
175
  message: `Found ${t} default pages, expected 1`
176
176
  });
177
- }, h = () => {
178
- a.forEach((e) => {
177
+ }, _ = () => {
178
+ d().forEach((e) => {
179
179
  let t = l(e);
180
180
  if (!t) return;
181
181
  let r = (t) => {
182
182
  let i = l(t);
183
183
  i?.definition && (i.definition.rootId !== e && n.push({
184
184
  code: "ROOT_ID_MISMATCH",
185
- message: `Element "${t}" has rootId "${i.definition.rootId}" but should be "${e}" (page's ID)`,
185
+ message: `Element "${t}" has rootId "${i.definition.rootId}" but should be "${e}" (its root's ID)`,
186
186
  elementId: t,
187
187
  details: {
188
188
  expectedRootId: e,
@@ -192,7 +192,7 @@ var t = (t) => {
192
192
  };
193
193
  t.definition.items && t.definition.items.forEach((e) => r(e));
194
194
  });
195
- }, g = () => {
195
+ }, v = () => {
196
196
  let e = /* @__PURE__ */ new Set();
197
197
  o.forEach((t, r) => {
198
198
  if (!t.id) {
@@ -213,32 +213,28 @@ var t = (t) => {
213
213
  details: { parentId: t.parentId }
214
214
  }));
215
215
  });
216
- }, _ = (e) => {
216
+ }, y = (e) => {
217
217
  let t = /* @__PURE__ */ new Set(), n = (e) => {
218
218
  if (t.has(e)) return;
219
219
  t.add(e);
220
220
  let r = l(e);
221
221
  r?.definition?.items && r.definition.items.forEach((e) => n(e));
222
- };
223
- if (!e) a.forEach((e) => {
222
+ }, a = (e) => {
224
223
  n(e);
225
- let t = l(e);
226
- t?.attributes?.layoutContainer && n(t.attributes.layoutContainer);
227
- });
228
- else if (e) {
229
- n(e);
230
- let t = l(e);
231
- t?.attributes?.layoutContainer && n(t.attributes.layoutContainer);
232
- }
224
+ let t = l(e)?.attributes;
225
+ t?.layout && n(t.layout), t?.layoutContainer && n(t.layoutContainer);
226
+ };
227
+ e ? a(e) : d().forEach(a);
228
+ let o = new Set(d());
233
229
  Object.keys(i).forEach((e) => {
234
230
  let n = l(e);
235
- n && !t.has(e) && n.definition.type !== "page" && r.push({
231
+ n && !t.has(e) && n.definition.type !== "page" && !o.has(e) && r.push({
236
232
  code: "ORPHANED_ELEMENT",
237
- message: `Element "${e}" is not reachable from any page`,
233
+ message: `Element "${e}" is not reachable from any page or layout`,
238
234
  elementId: e
239
235
  });
240
236
  });
241
- }, v = () => {
237
+ }, b = () => {
242
238
  let e = /* @__PURE__ */ new Set();
243
239
  s.forEach((t, i) => {
244
240
  if (!t.name) {
@@ -258,43 +254,113 @@ var t = (t) => {
258
254
  details: { variableName: t.name }
259
255
  });
260
256
  });
261
- }, y = () => {
262
- let t = /* @__PURE__ */ new Map();
257
+ }, x = () => {
258
+ Object.values(i).forEach((t) => {
259
+ t && (e(t.id) || n.push({
260
+ code: "INVALID_ELEMENT_ID",
261
+ message: `Element "${t.id}" has a name that must start with a letter, then letters, numbers, hyphens and underscores (no hyphen or underscore at the start, and no hyphen at the end)`,
262
+ elementId: t.id
263
+ }));
264
+ });
265
+ }, S = (e) => {
266
+ let t = /* @__PURE__ */ new Set(), r = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Map();
267
+ Object.values(i).forEach((n) => {
268
+ if (!n) return;
269
+ t.add(n.id);
270
+ let i = e?.[n.definition.type];
271
+ i && a.set(n.id, i), r.add(n.definition.type);
272
+ }), Object.values(i).forEach((i) => {
273
+ i && i.definition.bindings && Object.entries(i.definition.bindings).forEach(([o, s]) => {
274
+ (s ?? []).forEach((s) => {
275
+ let c = s.source.split(".")[0], l = c.indexOf("_");
276
+ if (l === -1) return;
277
+ let u = c.slice(0, l), d = c.slice(l + 1), f = `Element "${i.id}" binds ${o}.${s.to} to "${s.source}"`;
278
+ if (!t.has(d)) {
279
+ (e || r.has(u)) && n.push({
280
+ code: "UNRESOLVED_BINDING_SOURCE",
281
+ message: `${f}, but no element answers to the name "${d}"`,
282
+ elementId: i.id,
283
+ details: {
284
+ source: s.source,
285
+ elementId: d
286
+ }
287
+ });
288
+ return;
289
+ }
290
+ let p = a.get(d);
291
+ p && p !== u && n.push({
292
+ code: "MISMATCHED_BINDING_SOURCE",
293
+ message: `${f}, but "${d}" publishes its source as "${p}_${d}"`,
294
+ elementId: i.id,
295
+ details: {
296
+ source: s.source,
297
+ elementId: d,
298
+ expected: `${p}_${d}`
299
+ }
300
+ });
301
+ });
302
+ });
303
+ });
304
+ }, C = () => {
305
+ let t = new Set(Object.keys(i));
263
306
  Object.values(i).forEach((r) => {
264
- if (!r) return;
265
- let { idRef: i } = r;
266
- if (!i) {
267
- r.definition.interactions && Object.keys(r.definition.interactions).length > 0 && n.push({
268
- code: "INTERACTIONS_WITHOUT_ID_REF",
269
- message: `Element "${r.id}" has interactions but no idRef, so none of them can fire`,
307
+ let i = r?.definition.interactions;
308
+ if (!i) return;
309
+ let a = Object.keys(i);
310
+ Object.entries(i).forEach(([i, o]) => {
311
+ e(i) || n.push({
312
+ code: "INVALID_INTERACTION_ID",
313
+ message: `Interaction "${i}" on element "${r.id}" has a name that must start with a letter, then letters, numbers, hyphens and underscores — a later step reads it as {{ ${i}.field }}`,
314
+ elementId: r.id
315
+ }), o.id !== i && n.push({
316
+ code: "INTERACTION_ID_MISMATCH",
317
+ message: `Interaction id "${o.id}" on element "${r.id}" doesn't match its key "${i}"`,
318
+ elementId: r.id
319
+ }), ["beforeNode", "afterNode"].forEach((e) => {
320
+ let t = o[e];
321
+ t && !a.includes(t) && n.push({
322
+ code: "BROKEN_FLOW_LINK",
323
+ message: `Interaction "${o.id}" on element "${r.id}" names ${e} "${t}", which is not a node of this flow`,
324
+ elementId: r.id
325
+ });
326
+ }), o.flowId && !a.includes(o.flowId) && n.push({
327
+ code: "BROKEN_FLOW_ID",
328
+ message: `Interaction "${o.id}" on element "${r.id}" belongs to flow "${o.flowId}", which is not a node of this flow`,
329
+ elementId: r.id
330
+ }), o.type === "callback" && o.elementId && !t.has(o.elementId) && n.push({
331
+ code: "UNRESOLVED_INTERACTION_TARGET",
332
+ message: `Interaction "${o.id}" on element "${r.id}" runs against "${o.elementId}", but no element answers to that name`,
270
333
  elementId: r.id
271
334
  });
272
- return;
273
- }
274
- e(i) || n.push({
275
- code: "INVALID_ID_REF",
276
- message: `Element "${r.id}" has idRef "${i}", which must start with a letter, then letters, numbers, hyphens and underscores (no hyphen or underscore at the start, and no hyphen at the end)`,
277
- elementId: r.id
278
335
  });
279
- let a = t.get(i);
280
- if (a) {
281
- n.push({
282
- code: "DUPLICATE_ID_REF",
283
- message: `Elements "${a}" and "${r.id}" share the idRef "${i}"`,
284
- elementId: r.id,
285
- details: {
286
- idRef: i,
287
- otherElementId: a
336
+ });
337
+ }, w = () => {
338
+ Object.values(i).forEach((e) => {
339
+ if (!e || !e.definition.bindings) return;
340
+ let t = /* @__PURE__ */ new Set();
341
+ Object.entries(e.definition.bindings).forEach(([r, i]) => {
342
+ (i ?? []).forEach((i) => {
343
+ if (!i.id) {
344
+ n.push({
345
+ code: "MISSING_BINDING_ID",
346
+ message: `Element "${e.id}" has a ${r} binding to "${i.to}" with no id`,
347
+ elementId: e.id
348
+ });
349
+ return;
288
350
  }
351
+ t.has(i.id) && n.push({
352
+ code: "DUPLICATE_BINDING_ID",
353
+ message: `Element "${e.id}" has two bindings called "${i.id}"`,
354
+ elementId: e.id,
355
+ details: { bindingId: i.id }
356
+ }), t.add(i.id);
289
357
  });
290
- return;
291
- }
292
- t.set(i, r.id);
358
+ });
293
359
  });
294
360
  };
295
361
  return { validate: (e) => {
296
362
  let { baseElementId: t } = e ?? {};
297
- return u() ? (d(), f(), p(), m(), h(), g(), _(t), v(), y(), {
363
+ return f() ? (p(), m(), h(), g(), _(), v(), y(t), b(), x(), S(e?.sourceTypes), w(), C(), {
298
364
  valid: n.length === 0,
299
365
  errors: n,
300
366
  warnings: r
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plitzi/sdk-schema",
3
- "version": "0.33.2",
3
+ "version": "0.34.0",
4
4
  "license": "AGPL-3.0",
5
5
  "files": [
6
6
  "dist"
@@ -18,9 +18,9 @@
18
18
  "types": "./dist/helpers/FlatMap.d.ts",
19
19
  "import": "./dist/helpers/FlatMap.mjs"
20
20
  },
21
- "./helpers/idRef": {
22
- "types": "./dist/helpers/idRef.d.ts",
23
- "import": "./dist/helpers/idRef.mjs"
21
+ "./helpers/elementId": {
22
+ "types": "./dist/helpers/elementId.d.ts",
23
+ "import": "./dist/helpers/elementId.mjs"
24
24
  },
25
25
  "./helpers/schemaValidator": {
26
26
  "types": "./dist/helpers/schemaValidator.d.ts",
@@ -44,15 +44,15 @@
44
44
  "build:prod": "vite build && node ../sdk-shared/scripts/generate-exports.mjs"
45
45
  },
46
46
  "dependencies": {
47
- "@plitzi/plitzi-ui": "^1.6.20",
48
- "@plitzi/sdk-shared": "0.33.2",
49
- "@plitzi/sdk-style": "0.33.2",
47
+ "@plitzi/plitzi-ui": "^1.6.21",
48
+ "@plitzi/sdk-shared": "0.34.0",
49
+ "@plitzi/sdk-style": "0.34.0",
50
50
  "prop-types": "^15.8.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "eslint": "^9.39.5",
54
54
  "typescript": "^6.0.3",
55
- "vite": "^8.2.1",
56
- "vitest": "^4.1.10"
55
+ "vite": "^8.3.0",
56
+ "vitest": "^5.0.0"
57
57
  }
58
58
  }
@@ -1,34 +0,0 @@
1
- import { Element } from '@plitzi/sdk-shared';
2
- /** True when an idRef is well formed: it starts with a letter and then carries letters, numbers, hyphens and
3
- * underscores. Leading letter keeps a source `<type>_<idRef>` a valid twig identifier. A '.' would split the
4
- * `<type>_<idRef>.<field>` source path and an interaction target lookup — so it is not allowed. A source name
5
- * uses the first '_' as separator between `<type>` and `<idRef>` (element types are camelCase with no underscore),
6
- * so underscores inside the idRef are unambiguous. A hyphen is allowed too: a source doubles as a twig token, and
7
- * `processTwig` resolves a hyphenated `<type>_<idRef>` through `_context` subscript access. */
8
- export declare const isValidIdRef: (idRef: string) => boolean;
9
- /** Every idRef currently in use, so a new one can be checked or minted against it. */
10
- export declare const takenIdRefs: (flat: Record<Element["id"], Element>) => Set<string>;
11
- /** Why `idRef` cannot be used in this schema, phrased for a person, or null when it is free and well formed.
12
- * `ignoreElementId` exempts the element being edited, so re-saving an element its own ref is not a conflict. */
13
- export declare const idRefConflict: (flat: Record<Element["id"], Element>, idRef: string, ignoreElementId?: Element["id"]) => string | null;
14
- /** Whether every idRef a write brings into a schema is usable: well formed, free — an element already stored never
15
- * clashes with itself — and not repeated inside the incoming set, which `idRefConflict` cannot see on its own
16
- * because those elements are not in `flat` yet. */
17
- export declare const idRefsUsable: (flat: Record<Element["id"], Element>, elements: Element[]) => boolean;
18
- /** A unique, charset-safe idRef for a new element: `<type>-<n>`, incrementing until `isTaken` says it is free.
19
- * The hyphen sits between two alphanumeric runs, so it is valid (never leading or trailing) and readable. */
20
- export declare const makeIdRef: (type: string, isTaken: (candidate: string) => boolean) => string;
21
- /** Apply an idRef renaming (old → new) across a set of elements: the idRef itself plus every reference written
22
- * against it — a binding's data `source`, its transformer params and `when`, and an interaction's target
23
- * `elementId`, params and `when`. A ref absent from the map is left alone, so references leaving this set keep
24
- * pointing outward, which is what callers want.
25
- *
26
- * Ids are remapped by a blind string replace over the serialized tree (see `FlatMap.cloneElements`); that is safe
27
- * for a 24-char hex id but not for an idRef, which is short and human-readable — replacing "card-1" everywhere
28
- * would corrupt any label containing it. Hence this field-by-field pass: a plain `source` goes through
29
- * `remapSource`, embedded tokens (twig, transformer params, query-builder operands) through `remapTokensDeep`,
30
- * which only ever rewrites a full `<type>_<idRef>` token. */
31
- export declare const repointIdRefs: (elements: Record<Element["id"], Element>, mapRefs: Record<string, string>) => void;
32
- /** Give every element in a freshly cloned subtree a new idRef, so the copy wires to itself instead of to the
33
- * original it was cloned from. An element that had no idRef stays without one — a clone copies what is there. */
34
- export declare const remapClonedRefs: (elements: Record<Element["id"], Element>, isTaken: (ref: string) => boolean) => void;
@@ -1,72 +0,0 @@
1
- //#region src/helpers/idRef.ts
2
- var e = /^[A-Za-z](?:[A-Za-z0-9_-]*[A-Za-z0-9_])?$/, t = (t) => e.test(t), n = (e) => {
3
- let t = /* @__PURE__ */ new Set();
4
- for (let n of Object.values(e)) n.idRef && t.add(n.idRef);
5
- return t;
6
- }, r = (e, n, r) => t(n) ? Object.values(e).find((e) => e.idRef === n && e.id !== r) ? `"${n}" is already used by another element in this space` : null : `"${n}" is not a valid reference: start with a letter, then letters, numbers, hyphens and underscores`, i = (e, t) => {
7
- let n = /* @__PURE__ */ new Set();
8
- for (let i of t) if (i.idRef) {
9
- if (n.has(i.idRef) || r(e, i.idRef, i.id)) return !1;
10
- n.add(i.idRef);
11
- }
12
- return !0;
13
- }, a = (e, t) => {
14
- let n = e.replace(/[^A-Za-z0-9]/g, "") || "element", r = 1;
15
- for (; t(`${n}-${r}`);) r += 1;
16
- return `${n}-${r}`;
17
- }, o = (e, t) => {
18
- let n = e.indexOf("."), r = n === -1 ? e : e.slice(0, n), i = r.indexOf("_");
19
- if (i === -1) return e;
20
- let a = t[r.slice(i + 1)];
21
- return a ? `${r.slice(0, i + 1)}${a}${n === -1 ? "" : e.slice(n)}` : e;
22
- }, s = /([A-Za-z][A-Za-z0-9]*)_([A-Za-z][A-Za-z0-9_-]*)/g, c = (e, t) => e.replace(s, (e, n, r) => {
23
- let i = t[r];
24
- return i ? `${n}_${i}` : e;
25
- }), l = (e, t) => {
26
- if (Array.isArray(e)) {
27
- e.forEach((n, r) => {
28
- if (typeof n == "string") {
29
- let i = c(n, t);
30
- i !== n && (e[r] = i);
31
- return;
32
- }
33
- l(n, t);
34
- });
35
- return;
36
- }
37
- if (!e || typeof e != "object") return;
38
- let n = e;
39
- for (let [e, r] of Object.entries(n)) {
40
- if (typeof r == "string") {
41
- let i = c(r, t);
42
- i !== r && (n[e] = i);
43
- continue;
44
- }
45
- l(r, t);
46
- }
47
- }, u = (e, t) => {
48
- for (let n of Object.values(e)) {
49
- n.idRef && t[n.idRef] && (n.idRef = t[n.idRef]);
50
- for (let e of Object.values(n.definition.bindings ?? {})) for (let n of e) {
51
- let e = o(n.source, t);
52
- e !== n.source && (n.source = e), n.transformers && l(n.transformers, t), n.when && l(n.when, t);
53
- }
54
- for (let e of Object.values(n.definition.interactions ?? {})) {
55
- if (e.elementId) {
56
- let n = t[e.elementId];
57
- n && (e.elementId = n);
58
- }
59
- l(e.params, t), e.when && l(e.when, t);
60
- }
61
- }
62
- }, d = (e, t) => {
63
- let n = {}, r = /* @__PURE__ */ new Set();
64
- for (let i of Object.values(e)) {
65
- if (!i.idRef) continue;
66
- let e = a(i.definition.type, (e) => t(e) || r.has(e));
67
- r.add(e), n[i.idRef] = e;
68
- }
69
- u(e, n);
70
- };
71
- //#endregion
72
- export { r as idRefConflict, i as idRefsUsable, t as isValidIdRef, a as makeIdRef, d as remapClonedRefs, u as repointIdRefs, n as takenIdRefs };