@rpgm-tools/neo-angband-mod-sdk 0.10.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 (53) hide show
  1. package/LICENSE.md +43 -0
  2. package/README.md +72 -0
  3. package/dist/capabilities.d.ts +116 -0
  4. package/dist/capabilities.d.ts.map +1 -0
  5. package/dist/capabilities.js +170 -0
  6. package/dist/capabilities.js.map +1 -0
  7. package/dist/compose.d.ts +71 -0
  8. package/dist/compose.d.ts.map +1 -0
  9. package/dist/compose.js +118 -0
  10. package/dist/compose.js.map +1 -0
  11. package/dist/conflicts.d.ts +78 -0
  12. package/dist/conflicts.d.ts.map +1 -0
  13. package/dist/conflicts.js +160 -0
  14. package/dist/conflicts.js.map +1 -0
  15. package/dist/index.d.ts +31 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +24 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/loader.d.ts +83 -0
  20. package/dist/loader.d.ts.map +1 -0
  21. package/dist/loader.js +314 -0
  22. package/dist/loader.js.map +1 -0
  23. package/dist/manifest.d.ts +261 -0
  24. package/dist/manifest.d.ts.map +1 -0
  25. package/dist/manifest.js +264 -0
  26. package/dist/manifest.js.map +1 -0
  27. package/dist/patch.d.ts +90 -0
  28. package/dist/patch.d.ts.map +1 -0
  29. package/dist/patch.js +195 -0
  30. package/dist/patch.js.map +1 -0
  31. package/dist/record-key.d.ts +99 -0
  32. package/dist/record-key.d.ts.map +1 -0
  33. package/dist/record-key.js +157 -0
  34. package/dist/record-key.js.map +1 -0
  35. package/dist/resolve.d.ts +42 -0
  36. package/dist/resolve.d.ts.map +1 -0
  37. package/dist/resolve.js +161 -0
  38. package/dist/resolve.js.map +1 -0
  39. package/dist/semver.d.ts +37 -0
  40. package/dist/semver.d.ts.map +1 -0
  41. package/dist/semver.js +212 -0
  42. package/dist/semver.js.map +1 -0
  43. package/package.json +58 -0
  44. package/src/capabilities.ts +205 -0
  45. package/src/compose.ts +186 -0
  46. package/src/conflicts.ts +242 -0
  47. package/src/index.ts +73 -0
  48. package/src/loader.ts +393 -0
  49. package/src/manifest.ts +523 -0
  50. package/src/patch.ts +257 -0
  51. package/src/record-key.ts +180 -0
  52. package/src/resolve.ts +175 -0
  53. package/src/semver.ts +231 -0
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Pack manifests: identity, versioning, and dependencies.
3
+ *
4
+ * Every pack - the base game included - carries a manifest. Load order,
5
+ * record composition, and savefile provenance all key off it.
6
+ */
7
+ /** Every shape, for validation and for iterating the facet vocabulary. */
8
+ export const PACK_SHAPES = ["content", "tiles", "plugin"];
9
+ /**
10
+ * WHAT A PACK CONTRIBUTES, as a SET rather than one exclusive kind.
11
+ *
12
+ * `shape` was exclusive, and the two halves of the loader gated on opposite
13
+ * values: code loaded only for `shape: "plugin"` (web/src/mod-code.ts) and
14
+ * records composed only for `shape: "content"` (web/src/pack.ts). So the folder
15
+ * layout the plugin documentation promises -
16
+ *
17
+ * my-mod/ manifest.json plugin.js monster.json tiles/orc.png
18
+ *
19
+ * - could never work: declaring "plugin" dropped monster.json from composition,
20
+ * and declaring "content" refused the code. Each half had tests and each half
21
+ * passed; nothing asserted the two together. A mod that adds a monster AND gives
22
+ * it behaviour is the ordinary case, not an exotic one.
23
+ *
24
+ * `facets` is that set. `shape` stays REQUIRED and remains the pack's primary
25
+ * kind - it is what the manager displays and what every existing manifest
26
+ * already carries - and when `facets` is present it must CONTAIN `shape`, so the
27
+ * two fields cannot contradict each other. A hybrid declares:
28
+ *
29
+ * { "shape": "content", "facets": ["content", "plugin"] }
30
+ *
31
+ * The consent property is unchanged and is why `facets` is a declaration rather
32
+ * than something inferred from the folder's contents: shipping plugin.js without
33
+ * naming the `plugin` facet is still a REFUSAL, because running code must be
34
+ * something a mod states rather than something a file listing implies.
35
+ */
36
+ export function packFacets(manifest) {
37
+ return new Set(manifest.facets ?? [manifest.shape]);
38
+ }
39
+ /** Whether a pack contributes `facet` (its shape, or any declared facet). */
40
+ export function hasFacet(manifest, facet) {
41
+ return packFacets(manifest).has(facet);
42
+ }
43
+ const ID_RE = /^[a-z][a-z0-9-]*$/;
44
+ const VERSION_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
45
+ export class ManifestError extends Error {
46
+ }
47
+ /** Validate a parsed manifest object; throws ManifestError. */
48
+ export function validateManifest(value) {
49
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
50
+ throw new ManifestError("manifest must be an object");
51
+ }
52
+ const m = value;
53
+ if (typeof m["id"] !== "string" || !ID_RE.test(m["id"])) {
54
+ throw new ManifestError(`manifest id must be lowercase kebab-case: ${String(m["id"])}`);
55
+ }
56
+ if (typeof m["name"] !== "string" || m["name"].length === 0) {
57
+ throw new ManifestError(`manifest ${m["id"]}: name is required`);
58
+ }
59
+ if (typeof m["version"] !== "string" || !VERSION_RE.test(m["version"])) {
60
+ throw new ManifestError(`manifest ${m["id"]}: version must be semver, got ${String(m["version"])}`);
61
+ }
62
+ if (!PACK_SHAPES.includes(m["shape"])) {
63
+ throw new ManifestError(`manifest ${m["id"]}: shape must be one of ${PACK_SHAPES.join(", ")}`);
64
+ }
65
+ const id = m["id"];
66
+ validateFacets(m["facets"], id, m["shape"]);
67
+ validateDepMap(m["dependencies"], id, "dependencies");
68
+ validateDepMap(m["optionalDependencies"], id, "optionalDependencies");
69
+ validateIdList(m["loadAfter"], id, "loadAfter");
70
+ validateIdList(m["loadBefore"], id, "loadBefore");
71
+ if (m["saveSchema"] !== undefined) {
72
+ const s = m["saveSchema"];
73
+ if (typeof s !== "number" || !Number.isInteger(s) || s < 0) {
74
+ throw new ManifestError(`manifest ${id}: saveSchema must be a non-negative integer`);
75
+ }
76
+ }
77
+ if (m["capabilities"] !== undefined) {
78
+ if (!Array.isArray(m["capabilities"]) ||
79
+ m["capabilities"].some((c) => typeof c !== "string")) {
80
+ throw new ManifestError(`manifest ${id}: capabilities must be strings`);
81
+ }
82
+ }
83
+ if (m["modApi"] !== undefined) {
84
+ const a = m["modApi"];
85
+ if (typeof a !== "number" || !Number.isInteger(a) || a < 1) {
86
+ throw new ManifestError(`manifest ${id}: modApi must be a positive integer (the mod-plugin ABI version)`);
87
+ }
88
+ }
89
+ if (m["nondeterministic"] !== undefined &&
90
+ typeof m["nondeterministic"] !== "boolean") {
91
+ throw new ManifestError(`manifest ${id}: nondeterministic must be a boolean`);
92
+ }
93
+ if (m["affectsGameplay"] !== undefined &&
94
+ typeof m["affectsGameplay"] !== "boolean") {
95
+ throw new ManifestError(`manifest ${id}: affectsGameplay must be a boolean`);
96
+ }
97
+ validateRules(m["rules"], id);
98
+ validateTilePacks(m["tilePacks"], id);
99
+ for (const key of [
100
+ "engine",
101
+ "repository",
102
+ "changelog",
103
+ "description",
104
+ "author",
105
+ "license",
106
+ ]) {
107
+ if (m[key] !== undefined && typeof m[key] !== "string") {
108
+ throw new ManifestError(`manifest ${id}: ${key} must be a string`);
109
+ }
110
+ }
111
+ return m;
112
+ }
113
+ /** Validate an optional id->constraint map field (dependencies-shaped). */
114
+ function validateDepMap(deps, id, field) {
115
+ if (deps === undefined)
116
+ return;
117
+ if (typeof deps !== "object" || deps === null || Array.isArray(deps)) {
118
+ throw new ManifestError(`manifest ${id}: ${field} must be a map`);
119
+ }
120
+ for (const [dep, constraint] of Object.entries(deps)) {
121
+ if (!ID_RE.test(dep)) {
122
+ throw new ManifestError(`manifest ${id}: bad ${field} id ${dep}`);
123
+ }
124
+ if (typeof constraint !== "string") {
125
+ throw new ManifestError(`manifest ${id}: ${field} ${dep} constraint must be a string`);
126
+ }
127
+ }
128
+ }
129
+ /** Validate the optional `rules` array (PackRule[]); throws ManifestError. */
130
+ function validateRules(value, id) {
131
+ if (value === undefined)
132
+ return;
133
+ if (!Array.isArray(value)) {
134
+ throw new ManifestError(`manifest ${id}: rules must be an array`);
135
+ }
136
+ const seen = new Set();
137
+ for (const entry of value) {
138
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
139
+ throw new ManifestError(`manifest ${id}: each rule must be an object`);
140
+ }
141
+ const r = entry;
142
+ if (typeof r["flag"] !== "string" || r["flag"].length === 0) {
143
+ throw new ManifestError(`manifest ${id}: rule flag must be a non-empty string`);
144
+ }
145
+ if (seen.has(r["flag"])) {
146
+ throw new ManifestError(`manifest ${id}: duplicate rule flag ${r["flag"]}`);
147
+ }
148
+ seen.add(r["flag"]);
149
+ if (typeof r["title"] !== "string" || r["title"].length === 0) {
150
+ throw new ManifestError(`manifest ${id}: rule ${r["flag"]} title must be a non-empty string`);
151
+ }
152
+ if (typeof r["description"] !== "string") {
153
+ throw new ManifestError(`manifest ${id}: rule ${r["flag"]} description must be a string`);
154
+ }
155
+ if (typeof r["default"] !== "boolean") {
156
+ throw new ManifestError(`manifest ${id}: rule ${r["flag"]} default must be a boolean`);
157
+ }
158
+ }
159
+ }
160
+ /** The pack renderers a tilePacks entry may name. */
161
+ const TILE_ENGINES = ["tilesheet", "linoleum"];
162
+ /**
163
+ * Validate the optional `tilePacks` array (PackTilePack[]); throws ManifestError.
164
+ *
165
+ * `path` is checked for being MOD-RELATIVE, and that check is the point rather than
166
+ * tidiness. It used to be a site-root-relative URL base, which only a bundled mod
167
+ * could ever get right; a manifest carrying the old form would resolve to
168
+ * `mods/<id>/mods/<id>/…` and 404 into ASCII with nothing said. An absolute path, a
169
+ * scheme, or a `..` escape is refused for the same reason a pack's code files are
170
+ * read by pack-relative path: the host decides where a mod's bytes live.
171
+ */
172
+ function validateTilePacks(value, id) {
173
+ if (value === undefined)
174
+ return;
175
+ if (!Array.isArray(value)) {
176
+ throw new ManifestError(`manifest ${id}: tilePacks must be an array`);
177
+ }
178
+ for (const entry of value) {
179
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
180
+ throw new ManifestError(`manifest ${id}: each tilePacks entry must be an object`);
181
+ }
182
+ const p = entry;
183
+ const graf = p["grafID"];
184
+ if (typeof graf !== "number" || !Number.isInteger(graf) || graf < 0) {
185
+ throw new ManifestError(`manifest ${id}: tilePacks grafID must be a non-negative integer, got ${String(graf)}`);
186
+ }
187
+ if (p["engine"] !== undefined && !TILE_ENGINES.includes(p["engine"])) {
188
+ throw new ManifestError(`manifest ${id}: tilePacks engine must be one of ${TILE_ENGINES.join(", ")}, got ${String(p["engine"])}`);
189
+ }
190
+ if (p["menuname"] !== undefined && typeof p["menuname"] !== "string") {
191
+ throw new ManifestError(`manifest ${id}: tilePacks menuname must be a string`);
192
+ }
193
+ if (p["path"] === undefined)
194
+ continue;
195
+ const path = p["path"];
196
+ if (typeof path !== "string") {
197
+ throw new ManifestError(`manifest ${id}: tilePacks path must be a string`);
198
+ }
199
+ if (/^([a-z][a-z0-9+.-]*:)?\//iu.test(path) || path.startsWith("\\")) {
200
+ throw new ManifestError(`manifest ${id}: tilePacks path "${path}" must be relative to the mod folder, not a site or absolute path`);
201
+ }
202
+ if (path.split(/[/\\]/u).includes("..")) {
203
+ throw new ManifestError(`manifest ${id}: tilePacks path "${path}" must stay inside the mod folder`);
204
+ }
205
+ }
206
+ }
207
+ /** Validate an optional array-of-pack-ids field (loadAfter/loadBefore). */
208
+ function validateIdList(value, id, field) {
209
+ if (value === undefined)
210
+ return;
211
+ if (!Array.isArray(value)) {
212
+ throw new ManifestError(`manifest ${id}: ${field} must be an array`);
213
+ }
214
+ for (const entry of value) {
215
+ if (typeof entry !== "string" || !ID_RE.test(entry)) {
216
+ throw new ManifestError(`manifest ${id}: bad ${field} id ${String(entry)}`);
217
+ }
218
+ }
219
+ }
220
+ /**
221
+ * Validate the optional `facets` list against the pack's `shape`.
222
+ *
223
+ * `shape` must appear in `facets`. Without that rule the two fields could
224
+ * disagree - `{shape: "content", facets: ["plugin"]}` - and every consumer would
225
+ * have to decide which one it trusted, which is how the exclusive `shape`
226
+ * produced a folder layout the documentation promised and the loader refused.
227
+ * One source of truth, checked once, at the edge.
228
+ */
229
+ function validateFacets(value, id, shape) {
230
+ if (value === undefined)
231
+ return;
232
+ if (!Array.isArray(value) || value.length === 0) {
233
+ throw new ManifestError(`manifest ${id}: facets must be a non-empty array of ${PACK_SHAPES.join(", ")}`);
234
+ }
235
+ const seen = new Set();
236
+ for (const entry of value) {
237
+ if (typeof entry !== "string" || !PACK_SHAPES.includes(entry)) {
238
+ throw new ManifestError(`manifest ${id}: facet must be one of ${PACK_SHAPES.join(", ")}, got ${String(entry)}`);
239
+ }
240
+ if (seen.has(entry)) {
241
+ throw new ManifestError(`manifest ${id}: facet "${entry}" is listed twice`);
242
+ }
243
+ seen.add(entry);
244
+ }
245
+ if (!seen.has(shape)) {
246
+ throw new ManifestError(`manifest ${id}: facets ${JSON.stringify(value)} must include its shape "${shape}"`);
247
+ }
248
+ }
249
+ /**
250
+ * Slug a record name into the id segment of a PackRef: lowercase, runs
251
+ * of non-alphanumerics collapse to single hyphens ("Farmer Maggot" ->
252
+ * "farmer-maggot"). Stable: this is a savefile-visible identity.
253
+ */
254
+ export function slugify(name) {
255
+ return name
256
+ .toLowerCase()
257
+ .replace(/[^a-z0-9]+/g, "-")
258
+ .replace(/^-+|-+$/g, "");
259
+ }
260
+ /** Build a namespaced record reference. */
261
+ export function packRef(packId, name) {
262
+ return `${packId}:${slugify(name)}`;
263
+ }
264
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Field-level patch composition (MOD_LIFECYCLE section 3, P7 phase 3).
3
+ *
4
+ * The coarse `patches` path in compose.ts deep-merges whole record bodies:
5
+ * simple, but two mods touching one record always look like they collide even
6
+ * when they change unrelated fields. The field-level model fixes that. A patch
7
+ * is an ordered list of field operations - `set`, `merge`, `addFlag`,
8
+ * `removeFlag`, `add`, `mul` - each addressing a dot-path into the record.
9
+ * Patches from different packs apply in load order; two packs that touch
10
+ * DIFFERENT fields compose with zero conflict, and only a genuine same-field
11
+ * collision is reported (then load order decides who wins, and the app says
12
+ * so). This is the finer lever the ratified design calls for, and it is the
13
+ * data the conflict report (phase 6) reads.
14
+ *
15
+ * Pure and deterministic: given a base record and an ordered patch list the
16
+ * output and the conflict set are fully determined.
17
+ */
18
+ import type { JsonRecord, JsonValue } from "./compose.js";
19
+ export declare class PatchError extends Error {
20
+ }
21
+ /** One field operation, addressing `path` (a dot-path into the record). */
22
+ export type FieldOp =
23
+ /** Replace the value at path outright. */
24
+ {
25
+ op: "set";
26
+ path: string;
27
+ value: JsonValue;
28
+ }
29
+ /** Deep-merge an object value into the object at path (compose.mergePatch). */
30
+ | {
31
+ op: "merge";
32
+ path: string;
33
+ value: JsonRecord;
34
+ }
35
+ /** Ensure `flag` is present in the string array at path (set union). */
36
+ | {
37
+ op: "addFlag";
38
+ path: string;
39
+ flag: string;
40
+ }
41
+ /** Remove `flag` from the string array at path, if present. */
42
+ | {
43
+ op: "removeFlag";
44
+ path: string;
45
+ flag: string;
46
+ }
47
+ /** Add a number to the numeric value at path (missing = 0). */
48
+ | {
49
+ op: "add";
50
+ path: string;
51
+ value: number;
52
+ }
53
+ /** Multiply the numeric value at path (missing = 0). */
54
+ | {
55
+ op: "mul";
56
+ path: string;
57
+ value: number;
58
+ };
59
+ /** An ordered list of field operations - one pack's patch of one record. */
60
+ export type FieldPatch = FieldOp[];
61
+ /** Apply one field patch to a record, returning a new record (pure). */
62
+ export declare function applyFieldPatch(record: JsonRecord, ops: FieldPatch): JsonRecord;
63
+ /** One same-field collision between two or more packs. */
64
+ export interface FieldConflict {
65
+ /** The dot-path both packs wrote. */
66
+ path: string;
67
+ /** The packs that wrote it, in load order (last one wins the value). */
68
+ owners: string[];
69
+ }
70
+ /** The result of composing several packs' patches over one base record. */
71
+ export interface ComposedPatch {
72
+ /** The merged record (all patches applied in load order). */
73
+ value: JsonRecord;
74
+ /** Same-field collisions, empty when every pack touched distinct fields. */
75
+ conflicts: FieldConflict[];
76
+ }
77
+ /**
78
+ * Compose several packs' field patches over a base record, applying them in
79
+ * the given (load) order and reporting same-field collisions. A field is a
80
+ * conflict when two or more distinct packs write it with an order-dependent op
81
+ * (set / merge / add / mul); pure flag ops (addFlag / removeFlag) compose as
82
+ * set operations and never conflict on their own.
83
+ */
84
+ export declare function composeFieldPatches(base: JsonRecord, patches: ReadonlyArray<{
85
+ owner: string;
86
+ ops: FieldPatch;
87
+ }>): ComposedPatch;
88
+ /** The set of dot-paths a patch writes (for external conflict analysis). */
89
+ export declare function touchedFields(ops: FieldPatch): Set<string>;
90
+ //# sourceMappingURL=patch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG1D,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC,2EAA2E;AAC3E,MAAM,MAAM,OAAO;AACjB,0CAA0C;AACxC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE;AAC/C,+EAA+E;GAC7E;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE;AAClD,wEAAwE;GACtE;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC/C,+DAA+D;GAC7D;IAAE,EAAE,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAClD,+DAA+D;GAC7D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC5C,wDAAwD;GACtD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAAG,OAAO,EAAE,CAAC;AA2FnC,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,GAAG,UAAU,CAI/E;AAgED,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,KAAK,EAAE,UAAU,CAAC;IAClB,4EAA4E;IAC5E,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,aAAa,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAAE,CAAC,GACzD,aAAa,CAwBf;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CAI1D"}
package/dist/patch.js ADDED
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Field-level patch composition (MOD_LIFECYCLE section 3, P7 phase 3).
3
+ *
4
+ * The coarse `patches` path in compose.ts deep-merges whole record bodies:
5
+ * simple, but two mods touching one record always look like they collide even
6
+ * when they change unrelated fields. The field-level model fixes that. A patch
7
+ * is an ordered list of field operations - `set`, `merge`, `addFlag`,
8
+ * `removeFlag`, `add`, `mul` - each addressing a dot-path into the record.
9
+ * Patches from different packs apply in load order; two packs that touch
10
+ * DIFFERENT fields compose with zero conflict, and only a genuine same-field
11
+ * collision is reported (then load order decides who wins, and the app says
12
+ * so). This is the finer lever the ratified design calls for, and it is the
13
+ * data the conflict report (phase 6) reads.
14
+ *
15
+ * Pure and deterministic: given a base record and an ordered patch list the
16
+ * output and the conflict set are fully determined.
17
+ */
18
+ import { mergePatch } from "./compose.js";
19
+ export class PatchError extends Error {
20
+ }
21
+ /** The flag ops compose as set operations; the rest are order-dependent. */
22
+ function isCommutative(op) {
23
+ return op === "addFlag" || op === "removeFlag";
24
+ }
25
+ /* ------------------------------------------------------------------ *
26
+ * Dot-path access.
27
+ *
28
+ * A path segment that is a run of digits indexes an ARRAY ("level-max.0.value").
29
+ * Array traversal is not a convenience: a great deal of upstream gamedata is
30
+ * label/value lists rather than objects - every section of constants.json, a
31
+ * store's owner list, a body's slot list, the visuals flicker table - so without
32
+ * it a fieldPatch into those files could not address anything. Worse, the first
33
+ * version of setPath treated an existing array as an unusable intermediate and
34
+ * REPLACED it with a fresh object, which turned `set level-max.0.value` into
35
+ * silent destruction of the whole list. Objects still win when the container is
36
+ * an object, so a literal "0" key is unaffected.
37
+ * ------------------------------------------------------------------ */
38
+ /** A path segment as an array index, or null when it is a plain object key. */
39
+ function arrayIndex(part) {
40
+ return /^(?:0|[1-9][0-9]*)$/.test(part) ? Number(part) : null;
41
+ }
42
+ /** One step down a path, through either an object key or an array index. */
43
+ function childOf(cur, part) {
44
+ if (Array.isArray(cur)) {
45
+ const at = arrayIndex(part);
46
+ return at === null ? undefined : cur[at];
47
+ }
48
+ if (typeof cur === "object" && cur !== null)
49
+ return cur[part];
50
+ return undefined;
51
+ }
52
+ function getPath(record, path) {
53
+ let cur = record;
54
+ for (const part of path.split(".")) {
55
+ cur = childOf(cur, part);
56
+ if (cur === undefined)
57
+ return undefined;
58
+ }
59
+ return cur;
60
+ }
61
+ /** Write one slot of an object or an array container. */
62
+ function assignAt(container, part, value) {
63
+ if (Array.isArray(container)) {
64
+ const at = arrayIndex(part);
65
+ if (at === null) {
66
+ throw new PatchError(`patch: "${part}" is not an array index, and the value at that point is an array`);
67
+ }
68
+ container[at] = value;
69
+ return;
70
+ }
71
+ container[part] = value;
72
+ }
73
+ /**
74
+ * Set a value at a dot-path, creating intermediate containers as needed. A
75
+ * created container is an array when the next segment is an index and an object
76
+ * otherwise, so `set a.0.b` on a record with no `a` builds `{a:[{b:...}]}`.
77
+ */
78
+ function setPath(record, path, value) {
79
+ const parts = path.split(".");
80
+ let cur = record;
81
+ for (let i = 0; i < parts.length - 1; i++) {
82
+ const part = parts[i];
83
+ const next = childOf(cur, part);
84
+ if (typeof next !== "object" || next === null) {
85
+ const fresh = arrayIndex(parts[i + 1]) === null ? {} : [];
86
+ assignAt(cur, part, fresh);
87
+ cur = fresh;
88
+ }
89
+ else {
90
+ cur = next;
91
+ }
92
+ }
93
+ assignAt(cur, parts[parts.length - 1], value);
94
+ }
95
+ /* ------------------------------------------------------------------ *
96
+ * Applying a single patch.
97
+ * ------------------------------------------------------------------ */
98
+ /** Apply one field patch to a record, returning a new record (pure). */
99
+ export function applyFieldPatch(record, ops) {
100
+ const out = structuredJsonClone(record);
101
+ for (const op of ops)
102
+ applyOp(out, op);
103
+ return out;
104
+ }
105
+ function applyOp(record, op) {
106
+ switch (op.op) {
107
+ case "set":
108
+ setPath(record, op.path, op.value);
109
+ return;
110
+ case "merge": {
111
+ const cur = getPath(record, op.path);
112
+ const base = typeof cur === "object" && cur !== null && !Array.isArray(cur)
113
+ ? cur
114
+ : {};
115
+ setPath(record, op.path, mergePatch(base, op.value));
116
+ return;
117
+ }
118
+ case "addFlag": {
119
+ const list = asFlagList(getPath(record, op.path), op.path);
120
+ if (!list.includes(op.flag))
121
+ list.push(op.flag);
122
+ setPath(record, op.path, list);
123
+ return;
124
+ }
125
+ case "removeFlag": {
126
+ const list = asFlagList(getPath(record, op.path), op.path);
127
+ setPath(record, op.path, list.filter((f) => f !== op.flag));
128
+ return;
129
+ }
130
+ case "add": {
131
+ const cur = getPath(record, op.path);
132
+ const n = typeof cur === "number" ? cur : 0;
133
+ setPath(record, op.path, n + op.value);
134
+ return;
135
+ }
136
+ case "mul": {
137
+ const cur = getPath(record, op.path);
138
+ const n = typeof cur === "number" ? cur : 0;
139
+ setPath(record, op.path, n * op.value);
140
+ return;
141
+ }
142
+ }
143
+ }
144
+ /** A flag field must be a string array (or absent, treated as empty). */
145
+ function asFlagList(value, path) {
146
+ if (value === undefined)
147
+ return [];
148
+ if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) {
149
+ throw new PatchError(`patch: field ${path} is not a flag list (string[])`);
150
+ }
151
+ return [...value];
152
+ }
153
+ /** A structural JSON clone (no Date.now/Math.random dependence). */
154
+ function structuredJsonClone(record) {
155
+ return JSON.parse(JSON.stringify(record));
156
+ }
157
+ /**
158
+ * Compose several packs' field patches over a base record, applying them in
159
+ * the given (load) order and reporting same-field collisions. A field is a
160
+ * conflict when two or more distinct packs write it with an order-dependent op
161
+ * (set / merge / add / mul); pure flag ops (addFlag / removeFlag) compose as
162
+ * set operations and never conflict on their own.
163
+ */
164
+ export function composeFieldPatches(base, patches) {
165
+ let value = structuredJsonClone(base);
166
+ /* path -> the owners who wrote it, and whether any write was order-dependent. */
167
+ const writers = new Map();
168
+ for (const { owner, ops } of patches) {
169
+ value = applyFieldPatch(value, ops);
170
+ for (const op of ops) {
171
+ const entry = writers.get(op.path) ?? { owners: [], ordered: false };
172
+ if (entry.owners[entry.owners.length - 1] !== owner) {
173
+ entry.owners.push(owner);
174
+ }
175
+ if (!isCommutative(op.op))
176
+ entry.ordered = true;
177
+ writers.set(op.path, entry);
178
+ }
179
+ }
180
+ const conflicts = [];
181
+ for (const [path, entry] of writers) {
182
+ if (entry.ordered && entry.owners.length > 1) {
183
+ conflicts.push({ path, owners: entry.owners });
184
+ }
185
+ }
186
+ return { value, conflicts };
187
+ }
188
+ /** The set of dot-paths a patch writes (for external conflict analysis). */
189
+ export function touchedFields(ops) {
190
+ const out = new Set();
191
+ for (const op of ops)
192
+ out.add(op.path);
193
+ return out;
194
+ }
195
+ //# sourceMappingURL=patch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch.js","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"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"}