@patterkit/model 0.2.1 → 0.4.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
@@ -29,12 +29,16 @@ __export(index_exports, {
29
29
  PROJECT_LOCALE_SCENE: () => PROJECT_LOCALE_SCENE,
30
30
  RERECORD_STATUS: () => RERECORD_STATUS,
31
31
  RERECORD_STATUS_DECL: () => RERECORD_STATUS_DECL,
32
+ RESERVED_PROPERTY_NAMES: () => RESERVED_PROPERTY_NAMES,
32
33
  castStringKey: () => castStringKey,
33
34
  deriveRecordingFolders: () => deriveRecordingFolders,
34
35
  effectiveGameId: () => effectiveGameId,
35
36
  gameIdify: () => gameIdify,
37
+ isCaseOnlyPropertyName: () => isCaseOnlyPropertyName,
36
38
  isContentlessBeat: () => isContentlessBeat,
37
39
  isValidGameId: () => isValidGameId,
40
+ isValidPropertyName: () => isValidPropertyName,
41
+ propertyNameify: () => propertyNameify,
38
42
  walkNodes: () => walkNodes
39
43
  });
40
44
  module.exports = __toCommonJS(index_exports);
@@ -58,6 +62,22 @@ function gameIdify(text) {
58
62
  function isValidGameId(gameId) {
59
63
  return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(gameId);
60
64
  }
65
+ var RESERVED_PROPERTY_NAMES = ["true", "false", "and", "or", "not"];
66
+ function propertyNameify(text) {
67
+ const trimmed = text.trim();
68
+ const deliberateLeading = trimmed.startsWith("_");
69
+ let out = trimmed.toLowerCase().replace(/['\u2019]/g, "").replace(/[^a-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
70
+ if (out === "") return "";
71
+ if (deliberateLeading || /^[0-9]/.test(out)) out = `_${out}`;
72
+ if (RESERVED_PROPERTY_NAMES.includes(out)) out = `${out}_`;
73
+ return out;
74
+ }
75
+ function isValidPropertyName(name) {
76
+ return /^[a-z_][a-z0-9_]*$/.test(name) && !RESERVED_PROPERTY_NAMES.includes(name);
77
+ }
78
+ function isCaseOnlyPropertyName(name) {
79
+ return !isValidPropertyName(name) && isValidPropertyName(name.toLowerCase());
80
+ }
61
81
  function effectiveGameId(entity) {
62
82
  const g = entity.gameId?.trim();
63
83
  return g ? g : gameIdify(entity.name);
@@ -119,11 +139,15 @@ var DEFAULT_CAPTION_CHARACTER = "SFX";
119
139
  PROJECT_LOCALE_SCENE,
120
140
  RERECORD_STATUS,
121
141
  RERECORD_STATUS_DECL,
142
+ RESERVED_PROPERTY_NAMES,
122
143
  castStringKey,
123
144
  deriveRecordingFolders,
124
145
  effectiveGameId,
125
146
  gameIdify,
147
+ isCaseOnlyPropertyName,
126
148
  isContentlessBeat,
127
149
  isValidGameId,
150
+ isValidPropertyName,
151
+ propertyNameify,
128
152
  walkNodes
129
153
  });
package/dist/index.d.cts CHANGED
@@ -207,12 +207,27 @@ declare function isContentlessBeat(beat: Beat, hasText: boolean): boolean;
207
207
  declare function gameIdify(text: string): string;
208
208
  /** A valid authored game id: lowercase alphanumerics + hyphens, no leading / trailing hyphen. */
209
209
  declare function isValidGameId(gameId: string): boolean;
210
+ /** The words `@wildwinter/expr` lexes as keywords, so no property may be called one.
211
+ * `packages/model/test/property-name-grammar.test.ts` probes the real parser rather
212
+ * than trusting this copy. */
213
+ declare const RESERVED_PROPERTY_NAMES: readonly string[];
214
+ /** Coerce a label into a legal property name: lower case, apostrophes dropped, runs of
215
+ * anything else to a single underscore, no trailing underscore, an underscore in front
216
+ * of a leading digit, one behind a keyword. "" when nothing usable was left. */
217
+ declare function propertyNameify(text: string): string;
218
+ /** Is this a name an expression can actually reach? Lower case letters, digits and
219
+ * underscores, not starting with a digit, not a keyword. "" is not a name. */
220
+ declare function isValidPropertyName(name: string): boolean;
221
+ /** True when folding case ALONE would make it legal (`isNight`). The only violation a
222
+ * loader may repair without guessing at intent: every reference is folded already, so
223
+ * folding the declaration to match changes nothing observable. */
224
+ declare function isCaseOnlyPropertyName(name: string): boolean;
210
225
  /** The effective address: the explicit `gameId` if pinned, else derived from `name`. */
211
226
  declare function effectiveGameId(entity: {
212
227
  gameId?: string;
213
228
  name: string;
214
229
  }): string;
215
- type PropertyType = "boolean" | "number" | "string" | "flags" | "enum";
230
+ type PropertyType = "boolean" | "number" | "string" | "flags" | "enum" | "quality";
216
231
  interface PropertyDecl {
217
232
  name: string;
218
233
  type: PropertyType;
@@ -236,6 +251,11 @@ interface PropertyDecl {
236
251
  temporary?: boolean;
237
252
  /** For enum / flags. */
238
253
  values?: string[];
254
+ /** For quality: the ORDERED ladder of stage names (the shared engine's quality type, expr 0.4.0).
255
+ * Ordering operators compare by position in this list and `advance()` steps along it, so the order
256
+ * is the meaning - unlike `values`, whose order is presentation only. Default seeds to the first
257
+ * stage when no `default` is declared. */
258
+ stages?: string[];
239
259
  /** Free-text author note documenting what this property is for (authoring only; shown as a hint). */
240
260
  purpose?: string;
241
261
  }
@@ -250,6 +270,8 @@ interface HostScopeDecl {
250
270
  name: string;
251
271
  type: PropertyType;
252
272
  values?: string[];
273
+ /** For quality: the ordered stage ladder (see `PropertyDecl.stages`). */
274
+ stages?: string[];
253
275
  default?: ScalarValue;
254
276
  writable?: boolean;
255
277
  /** Free-text author note documenting the property (authoring only; shown as a hint). */
@@ -356,7 +378,7 @@ interface WritingStatusDecl {
356
378
  /** Threshold marker: this status and every later one classify as "ready to ship". */
357
379
  readyToShip?: boolean;
358
380
  /** Theme character-palette slot (0-11) for the per-line status badge / inspector swatch (Patterpad #196).
359
- * A slot (not a hex) so it adapts to light/dark + the reading palettes. Absent = no colour (neutral). */
381
+ * A slot (not a hex) so it adapts to light/dark + the colour themes. Absent = no colour (neutral). */
360
382
  colour?: number;
361
383
  }
362
384
  /**
@@ -607,6 +629,17 @@ interface CommentMessage {
607
629
  author: string;
608
630
  ts: string;
609
631
  body: string;
632
+ /** A TOMBSTONE: the words are gone, the turn in the conversation is not.
633
+ *
634
+ * Removing a reply outright would renumber the argument around it, so what is
635
+ * left records who spoke and when, and that they withdrew it. The `body` is
636
+ * EMPTIED rather than kept and hidden, because "deleted" has to mean gone from
637
+ * a file that lives in version control.
638
+ *
639
+ * This is the one message allowed an empty body: `saveSceneComments` prunes
640
+ * empty messages so a cancelled composer leaves nothing behind, and without
641
+ * this flag a tombstone would be pruned on the way to disk. */
642
+ deleted?: boolean;
610
643
  }
611
644
  /** A sub-text range a comment is pinned to, within its anchor node's say text (#148). Offsets are
612
645
  * character positions over the rendered (plain) source text - inline formatting is marks, not part of
@@ -826,4 +859,4 @@ declare const DEFAULT_CAPTION_DELIMITERS: CaptionDelimiters;
826
859
  * captions are off). Applies even to a project that pins no `closedCaptions`. */
827
860
  declare const DEFAULT_CAPTION_CHARACTER = "SFX";
828
861
 
829
- export { type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_RECORDING_STATUSES, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, type EditRecord, type Effect, type EstimatingConfig, type Expression, type FlowFile, type FlowNode, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, type LineBeat, type LocaleFile, PROJECT_LOCALE_SCENE, type ProjectDictionary, type ProjectFile, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, type RecordingFolder, type RecordingStatusDecl, type ScalarValue, type Scene, type Selector, type SelectorExhaust, type SelectorOrder, type SequenceOptions, type Snippet, type Suggestion, type TextBeat, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, gameIdify, isContentlessBeat, isValidGameId, walkNodes };
862
+ export { type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_RECORDING_STATUSES, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, type EditRecord, type Effect, type EstimatingConfig, type Expression, type FlowFile, type FlowNode, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, type LineBeat, type LocaleFile, PROJECT_LOCALE_SCENE, type ProjectDictionary, type ProjectFile, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, RESERVED_PROPERTY_NAMES, type RecordingFolder, type RecordingStatusDecl, type ScalarValue, type Scene, type Selector, type SelectorExhaust, type SelectorOrder, type SequenceOptions, type Snippet, type Suggestion, type TextBeat, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, gameIdify, isCaseOnlyPropertyName, isContentlessBeat, isValidGameId, isValidPropertyName, propertyNameify, walkNodes };
package/dist/index.d.ts CHANGED
@@ -207,12 +207,27 @@ declare function isContentlessBeat(beat: Beat, hasText: boolean): boolean;
207
207
  declare function gameIdify(text: string): string;
208
208
  /** A valid authored game id: lowercase alphanumerics + hyphens, no leading / trailing hyphen. */
209
209
  declare function isValidGameId(gameId: string): boolean;
210
+ /** The words `@wildwinter/expr` lexes as keywords, so no property may be called one.
211
+ * `packages/model/test/property-name-grammar.test.ts` probes the real parser rather
212
+ * than trusting this copy. */
213
+ declare const RESERVED_PROPERTY_NAMES: readonly string[];
214
+ /** Coerce a label into a legal property name: lower case, apostrophes dropped, runs of
215
+ * anything else to a single underscore, no trailing underscore, an underscore in front
216
+ * of a leading digit, one behind a keyword. "" when nothing usable was left. */
217
+ declare function propertyNameify(text: string): string;
218
+ /** Is this a name an expression can actually reach? Lower case letters, digits and
219
+ * underscores, not starting with a digit, not a keyword. "" is not a name. */
220
+ declare function isValidPropertyName(name: string): boolean;
221
+ /** True when folding case ALONE would make it legal (`isNight`). The only violation a
222
+ * loader may repair without guessing at intent: every reference is folded already, so
223
+ * folding the declaration to match changes nothing observable. */
224
+ declare function isCaseOnlyPropertyName(name: string): boolean;
210
225
  /** The effective address: the explicit `gameId` if pinned, else derived from `name`. */
211
226
  declare function effectiveGameId(entity: {
212
227
  gameId?: string;
213
228
  name: string;
214
229
  }): string;
215
- type PropertyType = "boolean" | "number" | "string" | "flags" | "enum";
230
+ type PropertyType = "boolean" | "number" | "string" | "flags" | "enum" | "quality";
216
231
  interface PropertyDecl {
217
232
  name: string;
218
233
  type: PropertyType;
@@ -236,6 +251,11 @@ interface PropertyDecl {
236
251
  temporary?: boolean;
237
252
  /** For enum / flags. */
238
253
  values?: string[];
254
+ /** For quality: the ORDERED ladder of stage names (the shared engine's quality type, expr 0.4.0).
255
+ * Ordering operators compare by position in this list and `advance()` steps along it, so the order
256
+ * is the meaning - unlike `values`, whose order is presentation only. Default seeds to the first
257
+ * stage when no `default` is declared. */
258
+ stages?: string[];
239
259
  /** Free-text author note documenting what this property is for (authoring only; shown as a hint). */
240
260
  purpose?: string;
241
261
  }
@@ -250,6 +270,8 @@ interface HostScopeDecl {
250
270
  name: string;
251
271
  type: PropertyType;
252
272
  values?: string[];
273
+ /** For quality: the ordered stage ladder (see `PropertyDecl.stages`). */
274
+ stages?: string[];
253
275
  default?: ScalarValue;
254
276
  writable?: boolean;
255
277
  /** Free-text author note documenting the property (authoring only; shown as a hint). */
@@ -356,7 +378,7 @@ interface WritingStatusDecl {
356
378
  /** Threshold marker: this status and every later one classify as "ready to ship". */
357
379
  readyToShip?: boolean;
358
380
  /** Theme character-palette slot (0-11) for the per-line status badge / inspector swatch (Patterpad #196).
359
- * A slot (not a hex) so it adapts to light/dark + the reading palettes. Absent = no colour (neutral). */
381
+ * A slot (not a hex) so it adapts to light/dark + the colour themes. Absent = no colour (neutral). */
360
382
  colour?: number;
361
383
  }
362
384
  /**
@@ -607,6 +629,17 @@ interface CommentMessage {
607
629
  author: string;
608
630
  ts: string;
609
631
  body: string;
632
+ /** A TOMBSTONE: the words are gone, the turn in the conversation is not.
633
+ *
634
+ * Removing a reply outright would renumber the argument around it, so what is
635
+ * left records who spoke and when, and that they withdrew it. The `body` is
636
+ * EMPTIED rather than kept and hidden, because "deleted" has to mean gone from
637
+ * a file that lives in version control.
638
+ *
639
+ * This is the one message allowed an empty body: `saveSceneComments` prunes
640
+ * empty messages so a cancelled composer leaves nothing behind, and without
641
+ * this flag a tombstone would be pruned on the way to disk. */
642
+ deleted?: boolean;
610
643
  }
611
644
  /** A sub-text range a comment is pinned to, within its anchor node's say text (#148). Offsets are
612
645
  * character positions over the rendered (plain) source text - inline formatting is marks, not part of
@@ -826,4 +859,4 @@ declare const DEFAULT_CAPTION_DELIMITERS: CaptionDelimiters;
826
859
  * captions are off). Applies even to a project that pins no `closedCaptions`. */
827
860
  declare const DEFAULT_CAPTION_CHARACTER = "SFX";
828
861
 
829
- export { type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_RECORDING_STATUSES, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, type EditRecord, type Effect, type EstimatingConfig, type Expression, type FlowFile, type FlowNode, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, type LineBeat, type LocaleFile, PROJECT_LOCALE_SCENE, type ProjectDictionary, type ProjectFile, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, type RecordingFolder, type RecordingStatusDecl, type ScalarValue, type Scene, type Selector, type SelectorExhaust, type SelectorOrder, type SequenceOptions, type Snippet, type Suggestion, type TextBeat, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, gameIdify, isContentlessBeat, isValidGameId, walkNodes };
862
+ export { type AuthoringFile, type Beat, type Block, type Bundle, type BundleCastMember, COMMON_GENDERS, type CaptionDelimiters, type CastMember, type Comment, type CommentMessage, type CommentRange, type CompiledBlock, type CompiledEffect, type CompiledGroup, type CompiledScene, type CompiledSnippet, type CoverageDriver, DEFAULT_CAPTION_CHARACTER, DEFAULT_CAPTION_DELIMITERS, DEFAULT_DOCUMENTATION_CLASSES, DEFAULT_RECORDING_STATUSES, DEFAULT_WRITING_STATUSES, type DocLine, type DocumentationClass, type EditRecord, type Effect, type EstimatingConfig, type Expression, type FlowFile, type FlowNode, type GameData, type GameDataField, type GameDataFieldType, type GameDataFields, type GameDataNodeKind, type GameEventBeat, type GrammaticalGender, type Group, type HostScopeDecl, type HostScopeRegistry, type HostScopeSpec, type Jump, type JumpTarget, type LineBeat, type LocaleFile, PROJECT_LOCALE_SCENE, type ProjectDictionary, type ProjectFile, type PromptBeat, type PropertyDecl, type PropertyType, RERECORD_STATUS, RERECORD_STATUS_DECL, RESERVED_PROPERTY_NAMES, type RecordingFolder, type RecordingStatusDecl, type ScalarValue, type Scene, type Selector, type SelectorExhaust, type SelectorOrder, type SequenceOptions, type Snippet, type Suggestion, type TextBeat, type VcsKind, type WritingStatusDecl, castStringKey, deriveRecordingFolders, effectiveGameId, gameIdify, isCaseOnlyPropertyName, isContentlessBeat, isValidGameId, isValidPropertyName, propertyNameify, walkNodes };
package/dist/index.js CHANGED
@@ -19,6 +19,22 @@ function gameIdify(text) {
19
19
  function isValidGameId(gameId) {
20
20
  return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(gameId);
21
21
  }
22
+ var RESERVED_PROPERTY_NAMES = ["true", "false", "and", "or", "not"];
23
+ function propertyNameify(text) {
24
+ const trimmed = text.trim();
25
+ const deliberateLeading = trimmed.startsWith("_");
26
+ let out = trimmed.toLowerCase().replace(/['\u2019]/g, "").replace(/[^a-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
27
+ if (out === "") return "";
28
+ if (deliberateLeading || /^[0-9]/.test(out)) out = `_${out}`;
29
+ if (RESERVED_PROPERTY_NAMES.includes(out)) out = `${out}_`;
30
+ return out;
31
+ }
32
+ function isValidPropertyName(name) {
33
+ return /^[a-z_][a-z0-9_]*$/.test(name) && !RESERVED_PROPERTY_NAMES.includes(name);
34
+ }
35
+ function isCaseOnlyPropertyName(name) {
36
+ return !isValidPropertyName(name) && isValidPropertyName(name.toLowerCase());
37
+ }
22
38
  function effectiveGameId(entity) {
23
39
  const g = entity.gameId?.trim();
24
40
  return g ? g : gameIdify(entity.name);
@@ -79,11 +95,15 @@ export {
79
95
  PROJECT_LOCALE_SCENE,
80
96
  RERECORD_STATUS,
81
97
  RERECORD_STATUS_DECL,
98
+ RESERVED_PROPERTY_NAMES,
82
99
  castStringKey,
83
100
  deriveRecordingFolders,
84
101
  effectiveGameId,
85
102
  gameIdify,
103
+ isCaseOnlyPropertyName,
86
104
  isContentlessBeat,
87
105
  isValidGameId,
106
+ isValidPropertyName,
107
+ propertyNameify,
88
108
  walkNodes
89
109
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/model",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Patter data-model types: source (scene/block/group/snippet/beat/jump), project, locale, authoring. The shape source-of-truth.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,6 +34,6 @@
34
34
  "build": "tsup src/index.ts --format esm,cjs --dts --clean"
35
35
  },
36
36
  "dependencies": {
37
- "@wildwinter/expr": "^0.3.0"
37
+ "@wildwinter/expr": "^0.4.0"
38
38
  }
39
39
  }