@wildwinter/toolkit 0.1.0 → 0.2.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,54 @@
1
+ # @wildwinter/toolkit
2
+
3
+ ## 0.2.1
4
+
5
+ ### Changed
6
+
7
+ - **A claim may leave out an enum's or flags' `values`, and a quality's `stages`** (`./extensions`): the host
8
+ field's declaration is then the vocabulary, and the claim adds its label, help and value help, which may
9
+ describe values the host does not declare. `effectiveClaim(claim, hostDecl)` merges the two. An extension's
10
+ own fields still list their values.
11
+
12
+ ## 0.2.0
13
+
14
+ ### Added
15
+
16
+ - **`@wildwinter/toolkit/extensions`**: the shared spec and merge for Patter extensions and Storylet
17
+ extensions ([extensions.md](extensions.md)). The manifest and its checks (`manifestProblems`), the value types
18
+ and their checks (`valueProblems`, `entryProblems`) on the kernel's `valueFits`, `qualityDeclarationProblems`
19
+ and `stagesEitherSide`, defaults (`defaultOf`, through `defaultFor`), what a bundle carries (`compiledTarget`,
20
+ `compileEntry`), stage renames (`renameStages`), claims on host fields (`resolvedClaims`, `claimClashes`), the
21
+ three-way merge engine (`mergeTree`) and its manifest-built strategies (`mergeExtensionData`), the
22
+ `unresolved` mark (`markUnresolved`, `unresolvedProblems`) and orphaned entries (`orphanedEntries`).
23
+ Hand-written conformance cases in `conformance/extensions/`. `@wildwinter/expr` and
24
+ `@wildwinter/scoperegistry` are optional peer dependencies, needed by this entry only.
25
+
26
+ ## 0.1.0
27
+
28
+ First release. Small shared pieces of the authoring tooling, each of which
29
+ arrived by being found duplicated across the two consuming product families
30
+ rather than by being anticipated.
31
+
32
+ Nothing here runs in a game, and nothing here is about the expression language:
33
+ that is `@wildwinter/expr`, and game-side sharing is the vendored source in
34
+ `expr/ports`, because C++, C# and GDScript cannot consume an npm package.
35
+
36
+ ### Added
37
+
38
+ - **`newId` / `slug`** (`ids.ts`): opaque, rejection-sampled base-36 ids, so
39
+ every alphabet character is equally likely. A plain `byte % 36` over-weights
40
+ the first four.
41
+ - **`fnv32`** (`hash.ts`): FNV-1a, for short stable handles and change
42
+ detection. Not cryptographic.
43
+ - **`parseSource`** (`source.ts`): JSON5 with a tolerated byte-order mark, which
44
+ JSON5 alone will not accept and Windows editors add.
45
+ - **`findMatcher`** (`find.ts`): the escaped, global find regex behind find and
46
+ replace, so a query of `a.b` means those three characters.
47
+ - **`@wildwinter/toolkit/archive`**: the zip entry guards (`isUnsafeEntry`,
48
+ `escapesTarget`) and the settings that make an archive byte-reproducible. A
49
+ separate entry point because it needs `node:path`, and the main entry stays
50
+ isomorphic so a browser bundle can take it.
51
+
52
+ The guards were correct in both families before this, which is the state
53
+ BEFORE a drift rather than evidence there will not be one: a subtle weakening
54
+ of one copy is a vulnerability nobody reads a diff for.
package/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  Small shared pieces of the **authoring tooling** behind
4
4
  [Storylet Studio](https://github.com/storylet-studio) and
5
5
  [Patter](https://patterkit.com): opaque ids, a stable hash, JSON5 source parsing,
6
- find/replace matching, and the archive guards.
6
+ find/replace matching, the archive guards, and (in `./extensions`) the spec and merge
7
+ for Patter extensions and Storylet extensions.
7
8
 
8
9
  **Nothing here runs in a game.** This is the editor/CLI/pipeline side. The
9
10
  runtime language lives in [`@wildwinter/expr`](../expr), and unlike that package
@@ -15,7 +16,21 @@ Each was found duplicated, almost but not exactly, across both families. That
15
16
  one careless edit away from being correct in one, and nobody reads the diff of a
16
17
  file they believe is a copy.
17
18
 
18
- ESM + CJS + types. `json5` is the only dependency.
19
+ ESM + CJS + types. `json5` is the only dependency of the main entry.
20
+
21
+ ## Extensions (`@wildwinter/toolkit/extensions`)
22
+
23
+ The one part with shared semantics: the manifest a Patter extension or a Storylet extension ships, the types its
24
+ values take, the checks both editors and both command lines run, and the type-driven three-way merge both formats
25
+ use for extension data, with the `unresolved` mark an unresolved merge leaves in a shard. The spec is
26
+ [extensions.md](extensions.md) and its contract the hand-written cases in `conformance/extensions/`.
27
+
28
+ It is built on the kernel's own value and ladder rules, so it needs `@wildwinter/expr` and
29
+ `@wildwinter/scoperegistry`: optional peer dependencies, which every host of it already has.
30
+
31
+ ```ts
32
+ import { manifestProblems, mergeExtensionData } from "@wildwinter/toolkit/extensions";
33
+ ```
19
34
 
20
35
  ```ts
21
36
  import { newId, slug, fnv32, parseSource, findMatcher } from "@wildwinter/toolkit";
@@ -0,0 +1,69 @@
1
+ {
2
+ "about": "Defaults, compilation and stage renames. `defaults`: defaultOf for a declaration. `compile`: compiledTarget (declarations as a bundle carries them: editor-only fields gone, every default resolved, label, help, valueHelp and group gone) and compileEntry (a node's values as a bundle carries them: editor-only and undeclared fields gone, renamed stages renamed, otherwise raw). `renames`: renameStages over an entry, with the renames it made.",
3
+ "defaults": [
4
+ { "name": "a given default wins", "decl": { "name": "a", "type": "number", "default": 4 }, "default": 4 },
5
+ { "name": "text", "decl": { "name": "a", "type": "text" }, "default": "" },
6
+ { "name": "multiline", "decl": { "name": "a", "type": "multiline" }, "default": "" },
7
+ { "name": "number", "decl": { "name": "a", "type": "number" }, "default": 0 },
8
+ { "name": "boolean", "decl": { "name": "a", "type": "boolean" }, "default": false },
9
+ { "name": "enum: the first value", "decl": { "name": "a", "type": "enum", "values": ["x", "y"] }, "default": "x" },
10
+ { "name": "an enum may default to none chosen", "decl": { "name": "a", "type": "enum", "values": ["x", "y"], "default": "" }, "default": "" },
11
+ { "name": "flags: none", "decl": { "name": "a", "type": "flags", "values": ["x"] }, "default": [] },
12
+ { "name": "quality: the first stage", "decl": { "name": "a", "type": "quality", "stages": ["p", "q"] }, "default": "p" },
13
+ { "name": "reference: none", "decl": { "name": "a", "type": "reference", "to": "card" }, "default": "" },
14
+ { "name": "record: each field's default", "decl": { "name": "a", "type": "record", "fields": [
15
+ { "name": "s", "type": "text" }, { "name": "n", "type": "number", "default": 2 }, { "name": "q", "type": "quality", "stages": ["p", "q"] }
16
+ ] }, "default": { "n": 2, "q": "p", "s": "" } },
17
+ { "name": "record: a given default wins whole", "decl": { "name": "a", "type": "record", "fields": [{ "name": "s", "type": "text" }], "default": { "s": "x" } }, "default": { "s": "x" } },
18
+ { "name": "map: empty", "decl": { "name": "a", "type": "map", "key": { "type": "text" }, "of": { "type": "number" } }, "default": {} },
19
+ { "name": "list: empty", "decl": { "name": "a", "type": "list", "of": { "type": "number" } }, "default": [] }
20
+ ],
21
+ "compile": [
22
+ {
23
+ "name": "a target as the bundle carries it",
24
+ "target": { "fields": [
25
+ { "name": "note", "type": "text", "label": "Note", "help": "Help.", "group": "G", "required": true },
26
+ { "name": "draft", "type": "multiline", "editorOnly": true },
27
+ { "name": "tone", "type": "enum", "values": ["calm", "tense"], "valueHelp": { "calm": "x" } },
28
+ { "name": "act", "type": "quality", "stages": ["setup", "turn"], "renamedStages": { "opening": "setup" } },
29
+ { "name": "cue", "type": "record", "fields": [{ "name": "gain", "type": "number", "min": 0, "help": "Loudness." }] }
30
+ ], "claims": [{ "setting": "moodField", "type": "text" }] },
31
+ "compiled": [
32
+ { "name": "note", "type": "text", "default": "", "required": true },
33
+ { "name": "tone", "type": "enum", "values": ["calm", "tense"], "default": "calm" },
34
+ { "name": "act", "type": "quality", "stages": ["setup", "turn"], "default": "setup" },
35
+ { "name": "cue", "type": "record", "fields": [{ "name": "gain", "type": "number", "min": 0, "default": 0 }], "default": { "gain": 0 } }
36
+ ],
37
+ "entry": { "note": "n", "draft": "only in the editor", "act": "opening", "stray": 1, "cue": { "gain": 2 } },
38
+ "compiledEntry": { "act": "setup", "cue": { "gain": 2 }, "note": "n" }
39
+ },
40
+ {
41
+ "name": "an entry with nothing compiled compiles to nothing",
42
+ "target": { "fields": [{ "name": "draft", "type": "text", "editorOnly": true }] },
43
+ "compiled": [],
44
+ "entry": { "draft": "x" },
45
+ "compiledEntry": null
46
+ }
47
+ ],
48
+ "renames": [
49
+ {
50
+ "name": "stages renamed wherever a quality is, at any depth",
51
+ "target": { "fields": [
52
+ { "name": "act", "type": "quality", "stages": ["setup", "turn"], "renamedStages": { "opening": "setup", "middle": "turn" } },
53
+ { "name": "beats", "type": "list", "id": "id", "of": { "type": "record", "fields": [
54
+ { "name": "id", "type": "text" },
55
+ { "name": "phase", "type": "quality", "stages": ["in", "out"], "renamedStages": { "enter": "in" } }
56
+ ] } },
57
+ { "name": "byTag", "type": "map", "key": { "type": "text" }, "of": { "type": "quality", "stages": ["lo", "hi"], "renamedStages": { "low": "lo" } } },
58
+ { "name": "tone", "type": "text" }
59
+ ] },
60
+ "entry": { "act": "middle", "beats": [{ "id": "b1", "phase": "enter" }, { "id": "b2", "phase": "out" }], "byTag": { "a": "low", "b": "hi" }, "tone": "opening" },
61
+ "renamed": { "act": "turn", "beats": [{ "id": "b1", "phase": "in" }, { "id": "b2", "phase": "out" }], "byTag": { "a": "lo", "b": "hi" }, "tone": "opening" },
62
+ "renames": [
63
+ { "path": "act", "from": "middle", "to": "turn" },
64
+ { "path": "beats[0].phase", "from": "enter", "to": "in" },
65
+ { "path": "byTag.a", "from": "low", "to": "lo" }
66
+ ]
67
+ }
68
+ ]
69
+ }