@patterkit/core 0.4.0 → 0.5.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/dist/index.cjs CHANGED
@@ -167,6 +167,12 @@ function validateProject(input) {
167
167
  const sceneGameIds = /* @__PURE__ */ new Map();
168
168
  const jumps = [];
169
169
  const castNames = new Set((project.cast ?? []).map((c) => c.name));
170
+ const qualifierIds = new Set((0, import_model.projectQualifiers)(project).map((q) => q.gameId));
171
+ const checkQualifier = (beat, what) => {
172
+ if (beat.kind === "line" && beat.qualifier !== void 0 && !qualifierIds.has(beat.qualifier)) {
173
+ issues.push({ code: "unknown-qualifier", message: `${what} '${beat.id}' qualifier '${beat.qualifier}' is not in the project's qualifiers`, id: beat.id });
174
+ }
175
+ };
170
176
  const checkTags = (tags, where, id) => {
171
177
  for (const t of tags ?? []) {
172
178
  if (typeof t !== "string" || t.length === 0 || /[\s,]/.test(t)) {
@@ -174,6 +180,7 @@ function validateProject(input) {
174
180
  }
175
181
  }
176
182
  };
183
+ const addressOf = (node) => (0, import_model2.effectiveGameId)({ gameId: node.gameId, name: typeof node.name === "string" ? node.name : "" });
177
184
  const seeId = (id, kind, where) => {
178
185
  if (typeof id !== "string" || id.length === 0) {
179
186
  issues.push({ code: "missing-id", message: `${kind} in ${where} has no id` });
@@ -198,7 +205,7 @@ function validateProject(input) {
198
205
  if (scene.gameId && !(0, import_model2.isValidGameId)(scene.gameId)) {
199
206
  issues.push({ code: "invalid-gameid", message: `${where} address '${scene.gameId}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)`, id: scene.id });
200
207
  }
201
- const sgid = (0, import_model2.effectiveGameId)(scene);
208
+ const sgid = addressOf(scene);
202
209
  if (sgid) {
203
210
  const prev = sceneGameIds.get(sgid);
204
211
  if (prev && prev !== scene.id) issues.push({ code: "duplicate-gameid", message: `${where} address '${sgid}' is already used by scene '${prev}'`, id: scene.id });
@@ -233,7 +240,7 @@ function validateProject(input) {
233
240
  if (block.gameId && !(0, import_model2.isValidGameId)(block.gameId)) {
234
241
  issues.push({ code: "invalid-gameid", message: `block '${block.id}' address '${block.gameId}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)`, id: block.id });
235
242
  }
236
- const bgid = (0, import_model2.effectiveGameId)(block);
243
+ const bgid = addressOf(block);
237
244
  if (bgid) {
238
245
  const prev = blockGameIds.get(bgid);
239
246
  if (prev && prev !== block.id) issues.push({ code: "duplicate-gameid", message: `block '${block.id}' address '${bgid}' is already used by block '${prev}' in ${where}`, id: block.id });
@@ -289,6 +296,7 @@ function validateProject(input) {
289
296
  if (p.kind === "line" && p.character && !castNames.has(p.character)) {
290
297
  issues.push({ code: "unknown-character", message: `prompt '${p.id}' speaker '${p.character}' is not in the project cast`, id: p.id });
291
298
  }
299
+ checkQualifier(p, "prompt");
292
300
  }
293
301
  }
294
302
  if (fallbacks > 1) {
@@ -318,6 +326,7 @@ function validateProject(input) {
318
326
  id: beat.id
319
327
  });
320
328
  }
329
+ checkQualifier(beat, "beat");
321
330
  }
322
331
  if (node.jump) jumps.push({ to: node.jump.to, from: node.id });
323
332
  });
@@ -390,6 +399,18 @@ function validateProjectFile(project, issues) {
390
399
  if (project.recordingStatuses) {
391
400
  checkLadderNames(project.recordingStatuses.map((s) => s.name), "recordingStatuses", issues);
392
401
  }
402
+ const seenQualifiers = /* @__PURE__ */ new Set();
403
+ for (const q of project.qualifiers ?? []) {
404
+ if (typeof q.name !== "string" || !q.name.trim()) {
405
+ issues.push({ code: "invalid-qualifier", message: `qualifier '${String(q.gameId)}' has no name` });
406
+ }
407
+ if (typeof q.gameId !== "string" || !(0, import_model2.isValidGameId)(q.gameId)) {
408
+ issues.push({ code: "invalid-qualifier", message: `qualifier '${String(q.name)}' address '${String(q.gameId)}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)` });
409
+ } else if (seenQualifiers.has(q.gameId)) {
410
+ issues.push({ code: "invalid-qualifier", message: `qualifier address '${q.gameId}' is used more than once` });
411
+ }
412
+ if (typeof q.gameId === "string") seenQualifiers.add(q.gameId);
413
+ }
393
414
  for (const [kind, fields] of Object.entries(project.gameDataFields ?? {})) {
394
415
  const seen = /* @__PURE__ */ new Set();
395
416
  for (const f of fields ?? []) {
package/dist/index.d.cts CHANGED
@@ -26,7 +26,7 @@ interface StringifyOptions {
26
26
  declare function canonicalStringify(value: unknown, opts?: StringifyOptions): string;
27
27
 
28
28
  interface ValidationIssue {
29
- code: "duplicate-id" | "missing-id" | "malformed-node" | "missing-name" | "empty-scene" | "empty-snippet" | "empty-container" | "dangling-jump" | "jump-into-non-addressable" | "invalid-gameid" | "duplicate-gameid" | "unknown-character" | "invalid-temporary" | "invalid-declaration" | "invalid-status-ladder" | "invalid-locales" | "invalid-captions" | "missing-prompt" | "invalid-prompt" | "multiple-fallbacks" | "choice-can-empty" | "invalid-status-value" | "unknown-doc-class" | "invalid-tag" | "invalid-gamedata-field" | "jump-cycle";
29
+ code: "duplicate-id" | "missing-id" | "malformed-node" | "missing-name" | "empty-scene" | "empty-snippet" | "empty-container" | "dangling-jump" | "jump-into-non-addressable" | "invalid-gameid" | "duplicate-gameid" | "unknown-character" | "unknown-qualifier" | "invalid-qualifier" | "invalid-temporary" | "invalid-declaration" | "invalid-status-ladder" | "invalid-locales" | "invalid-captions" | "missing-prompt" | "invalid-prompt" | "multiple-fallbacks" | "choice-can-empty" | "invalid-status-value" | "unknown-doc-class" | "invalid-tag" | "invalid-gamedata-field" | "jump-cycle";
30
30
  message: string;
31
31
  /** Id of the offending node/beat, where applicable. */
32
32
  id?: string;
package/dist/index.d.ts CHANGED
@@ -26,7 +26,7 @@ interface StringifyOptions {
26
26
  declare function canonicalStringify(value: unknown, opts?: StringifyOptions): string;
27
27
 
28
28
  interface ValidationIssue {
29
- code: "duplicate-id" | "missing-id" | "malformed-node" | "missing-name" | "empty-scene" | "empty-snippet" | "empty-container" | "dangling-jump" | "jump-into-non-addressable" | "invalid-gameid" | "duplicate-gameid" | "unknown-character" | "invalid-temporary" | "invalid-declaration" | "invalid-status-ladder" | "invalid-locales" | "invalid-captions" | "missing-prompt" | "invalid-prompt" | "multiple-fallbacks" | "choice-can-empty" | "invalid-status-value" | "unknown-doc-class" | "invalid-tag" | "invalid-gamedata-field" | "jump-cycle";
29
+ code: "duplicate-id" | "missing-id" | "malformed-node" | "missing-name" | "empty-scene" | "empty-snippet" | "empty-container" | "dangling-jump" | "jump-into-non-addressable" | "invalid-gameid" | "duplicate-gameid" | "unknown-character" | "unknown-qualifier" | "invalid-qualifier" | "invalid-temporary" | "invalid-declaration" | "invalid-status-ladder" | "invalid-locales" | "invalid-captions" | "missing-prompt" | "invalid-prompt" | "multiple-fallbacks" | "choice-can-empty" | "invalid-status-value" | "unknown-doc-class" | "invalid-tag" | "invalid-gamedata-field" | "jump-cycle";
30
30
  message: string;
31
31
  /** Id of the offending node/beat, where applicable. */
32
32
  id?: string;
package/dist/index.js CHANGED
@@ -55,7 +55,7 @@ ${indent}}`;
55
55
  }
56
56
 
57
57
  // src/validate.ts
58
- import { walkNodes, DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES, DEFAULT_DOCUMENTATION_CLASSES } from "@patterkit/model";
58
+ import { walkNodes, DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES, DEFAULT_DOCUMENTATION_CLASSES, projectQualifiers } from "@patterkit/model";
59
59
  import { isValidGameId, effectiveGameId } from "@patterkit/model";
60
60
  import { isValidPropertyName, propertyNameify, RESERVED_PROPERTY_NAMES } from "@patterkit/model";
61
61
  function choiceLoopsBack(choice, ...targets) {
@@ -129,6 +129,12 @@ function validateProject(input) {
129
129
  const sceneGameIds = /* @__PURE__ */ new Map();
130
130
  const jumps = [];
131
131
  const castNames = new Set((project.cast ?? []).map((c) => c.name));
132
+ const qualifierIds = new Set(projectQualifiers(project).map((q) => q.gameId));
133
+ const checkQualifier = (beat, what) => {
134
+ if (beat.kind === "line" && beat.qualifier !== void 0 && !qualifierIds.has(beat.qualifier)) {
135
+ issues.push({ code: "unknown-qualifier", message: `${what} '${beat.id}' qualifier '${beat.qualifier}' is not in the project's qualifiers`, id: beat.id });
136
+ }
137
+ };
132
138
  const checkTags = (tags, where, id) => {
133
139
  for (const t of tags ?? []) {
134
140
  if (typeof t !== "string" || t.length === 0 || /[\s,]/.test(t)) {
@@ -136,6 +142,7 @@ function validateProject(input) {
136
142
  }
137
143
  }
138
144
  };
145
+ const addressOf = (node) => effectiveGameId({ gameId: node.gameId, name: typeof node.name === "string" ? node.name : "" });
139
146
  const seeId = (id, kind, where) => {
140
147
  if (typeof id !== "string" || id.length === 0) {
141
148
  issues.push({ code: "missing-id", message: `${kind} in ${where} has no id` });
@@ -160,7 +167,7 @@ function validateProject(input) {
160
167
  if (scene.gameId && !isValidGameId(scene.gameId)) {
161
168
  issues.push({ code: "invalid-gameid", message: `${where} address '${scene.gameId}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)`, id: scene.id });
162
169
  }
163
- const sgid = effectiveGameId(scene);
170
+ const sgid = addressOf(scene);
164
171
  if (sgid) {
165
172
  const prev = sceneGameIds.get(sgid);
166
173
  if (prev && prev !== scene.id) issues.push({ code: "duplicate-gameid", message: `${where} address '${sgid}' is already used by scene '${prev}'`, id: scene.id });
@@ -195,7 +202,7 @@ function validateProject(input) {
195
202
  if (block.gameId && !isValidGameId(block.gameId)) {
196
203
  issues.push({ code: "invalid-gameid", message: `block '${block.id}' address '${block.gameId}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)`, id: block.id });
197
204
  }
198
- const bgid = effectiveGameId(block);
205
+ const bgid = addressOf(block);
199
206
  if (bgid) {
200
207
  const prev = blockGameIds.get(bgid);
201
208
  if (prev && prev !== block.id) issues.push({ code: "duplicate-gameid", message: `block '${block.id}' address '${bgid}' is already used by block '${prev}' in ${where}`, id: block.id });
@@ -251,6 +258,7 @@ function validateProject(input) {
251
258
  if (p.kind === "line" && p.character && !castNames.has(p.character)) {
252
259
  issues.push({ code: "unknown-character", message: `prompt '${p.id}' speaker '${p.character}' is not in the project cast`, id: p.id });
253
260
  }
261
+ checkQualifier(p, "prompt");
254
262
  }
255
263
  }
256
264
  if (fallbacks > 1) {
@@ -280,6 +288,7 @@ function validateProject(input) {
280
288
  id: beat.id
281
289
  });
282
290
  }
291
+ checkQualifier(beat, "beat");
283
292
  }
284
293
  if (node.jump) jumps.push({ to: node.jump.to, from: node.id });
285
294
  });
@@ -352,6 +361,18 @@ function validateProjectFile(project, issues) {
352
361
  if (project.recordingStatuses) {
353
362
  checkLadderNames(project.recordingStatuses.map((s) => s.name), "recordingStatuses", issues);
354
363
  }
364
+ const seenQualifiers = /* @__PURE__ */ new Set();
365
+ for (const q of project.qualifiers ?? []) {
366
+ if (typeof q.name !== "string" || !q.name.trim()) {
367
+ issues.push({ code: "invalid-qualifier", message: `qualifier '${String(q.gameId)}' has no name` });
368
+ }
369
+ if (typeof q.gameId !== "string" || !isValidGameId(q.gameId)) {
370
+ issues.push({ code: "invalid-qualifier", message: `qualifier '${String(q.name)}' address '${String(q.gameId)}' is invalid (lowercase letters, digits, hyphens; no leading/trailing hyphen)` });
371
+ } else if (seenQualifiers.has(q.gameId)) {
372
+ issues.push({ code: "invalid-qualifier", message: `qualifier address '${q.gameId}' is used more than once` });
373
+ }
374
+ if (typeof q.gameId === "string") seenQualifiers.add(q.gameId);
375
+ }
355
376
  for (const [kind, fields] of Object.entries(project.gameDataFields ?? {})) {
356
377
  const seen = /* @__PURE__ */ new Set();
357
378
  for (const f of fields ?? []) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/core",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Patter core: stable id generation, readable-handle computation, canonical (de)serialisation, and the structural validator.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "build": "tsup src/index.ts --format esm,cjs --dts --clean"
35
35
  },
36
36
  "dependencies": {
37
- "@patterkit/model": "0.10.0",
37
+ "@patterkit/model": "0.11.0",
38
38
  "@wildwinter/toolkit": "^0.1.0",
39
39
  "json5": "^2.2.3"
40
40
  }