@buildkitchen/context 0.0.0-stage → 0.0.8

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kwasi A. Mensah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,48 @@
1
- # Temporary Holding Version
1
+ # 📦 @buildkitchen/context
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Library for maintaining context across MaybePromises.
4
+
5
+ ## Installation
6
+
7
+ Install the package via your preferred package manager:
8
+
9
+ ```bash
10
+ npm install @buildkitchen/context
11
+ ```
12
+
13
+ Or using yarn/pnpm:
14
+
15
+ ```bash
16
+ yarn add @buildkitchen/context
17
+ pnpm add @buildkitchen/context
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```typescript
23
+ const context = Context.root("root");
24
+
25
+ const a = context.withChildContext((context) => {
26
+ // do some asynchrounous work.
27
+ });
28
+
29
+ const b = context.withChildContext((context) => {
30
+ // some where in this work we accidentally add a dependency that causes a cycle.
31
+ // We'll get an informative error instead of a hang.
32
+ a.addDependency(context);
33
+ });
34
+ ```
35
+
36
+ We use this most with `PromiseCache` classes to make sure cycles aren't introduced. Can be used similarly to `async_hooks.executionAsyncId` but does require some manual tracking.
37
+
38
+ ## API Reference
39
+
40
+ [Full documentation on Codeberg](https://codeberg.org/buildkitchen/context/src/branch/main/docs)
41
+
42
+ ## Contributing
43
+
44
+ We're not actively soliticing contributions but please let us know of any bugs/issues you find in this code and we'll try to resolve them in a timely manner.
45
+
46
+ ## License
47
+
48
+ Distributed under the MIT License. See `LICENSE` for more information.
package/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+ import { type ContextJSON } from "./src/common.js";
6
+ import { Context, type ContextProgressReporter } from "./src/context.js";
7
+ import { ReadonlyContext } from "./src/readonly_context.js";
8
+ export type { ContextProgressReporter };
9
+ export { type ContextJSON, ReadonlyContext, Context };
10
+ //# sourceMappingURL=index.d.ts.map
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { Context } from "./src/context.js";
2
+ import { ReadonlyContext } from "./src/readonly_context.js";
3
+ export { ReadonlyContext, Context }; //
4
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAgC,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,OAAO,EAAoB,eAAe,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE","sourcesContent":["/**\n * @module\n * @mergeModuleWith <project>\n */\nimport { type ContextJSON } from \"./src/common.js\";\nimport { Context, type ContextProgressReporter } from \"./src/context.js\";\nimport { ReadonlyContext } from \"./src/readonly_context.js\";\n\nexport type { ContextProgressReporter };\nexport { type ContextJSON, ReadonlyContext, Context }; //\n"]}
package/package.json CHANGED
@@ -1,6 +1,27 @@
1
1
  {
2
+ "description": "Library for maintaining context across MaybePromises",
3
+ "homepage": "https://codeberg.org/buildkitchen/context",
4
+ "bugs": {
5
+ "url": "https://codeberg.org/buildkitchen/context"
6
+ },
7
+ "license": "MIT",
8
+ "author": {
9
+ "name": "Kwasi Mensah",
10
+ "email": "kwasi.mensah@gmail.com"
11
+ },
12
+ "keywords": [
13
+ "asynccontext",
14
+ "maybepromise",
15
+ "promise"
16
+ ],
2
17
  "name": "@buildkitchen/context",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
18
+ "bin": {},
19
+ "private": false,
20
+ "version": "0.0.8",
21
+ "__buildkitchen": "Generated by buildkitchen",
22
+ "type": "module",
23
+ "bundledDependencies": [],
24
+ "dependencies": {
25
+ "@buildkitchen/validation": "^0.0.8"
26
+ }
6
27
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+ import { type JSONObject } from "@buildkitchen/validation";
6
+ export interface ContextJSON extends JSONObject {
7
+ readonly rootID: string;
8
+ readonly ids: ReadonlyArray<number>;
9
+ }
10
+ //# sourceMappingURL=common.d.ts.map
package/src/common.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["common.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @module\n * @mergeModuleWith <project>\n */\nimport { type JSONObject } from \"@buildkitchen/validation\";\n\nexport interface ContextJSON extends JSONObject {\n readonly rootID: string;\n readonly ids: ReadonlyArray<number>;\n} //\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+ import { type Unvalidated } from "@buildkitchen/validation";
6
+ import { type ContextJSON } from "./common.js";
7
+ export interface ContextProgressReporter {
8
+ newContext(): void;
9
+ newCompletedContext(): void;
10
+ }
11
+ export declare class Context {
12
+ readonly rootID: string;
13
+ private readonly ids;
14
+ private readonly progressReporter?;
15
+ private _hash;
16
+ private nextChildID;
17
+ private readonly dependencies;
18
+ private state;
19
+ static root(rootID: string, progressReporter?: ContextProgressReporter): Context;
20
+ private constructor();
21
+ cachedHash(): string;
22
+ isParentOf(other: Context): boolean;
23
+ private checkForCycles;
24
+ addDependency(context: Context): void;
25
+ withChildContext<T>(behavior: (context: Context) => PromiseLike<T>): PromiseLike<T>;
26
+ withChildContextSync<T>(behavior: (context: Context) => T): T;
27
+ static fromJSON(context: Unvalidated<ContextJSON>): Context;
28
+ withJSON<T>(behavior: (context: ContextJSON) => T): T;
29
+ toJSON(): ContextJSON;
30
+ private setFinished;
31
+ }
32
+ //# sourceMappingURL=context.d.ts.map
package/src/context.js ADDED
@@ -0,0 +1,167 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+ import { validateArray, validateNumber, validateString } from "@buildkitchen/validation";
6
+ var ContextState;
7
+ (function (ContextState) {
8
+ ContextState[ContextState["kInProgess"] = 0] = "kInProgess";
9
+ ContextState[ContextState["kFinished"] = 1] = "kFinished";
10
+ })(ContextState || (ContextState = {}));
11
+ export class Context {
12
+ static root(rootID, progressReporter) {
13
+ return new Context(rootID, [], progressReporter);
14
+ }
15
+ constructor(rootID, ids, progressReporter) {
16
+ this.rootID = rootID;
17
+ this.ids = ids;
18
+ this.progressReporter = progressReporter;
19
+ this.nextChildID = 0;
20
+ this.dependencies = new Map();
21
+ this.state = ContextState.kInProgess;
22
+ progressReporter?.newContext();
23
+ }
24
+ cachedHash() {
25
+ this._hash ?? (this._hash = `root: ${this.rootID} ids: [${this.ids.join(",")}]`);
26
+ return this._hash;
27
+ }
28
+ isParentOf(other) {
29
+ if (this.rootID !== other.rootID) {
30
+ return false;
31
+ }
32
+ if (this.ids.length >= other.ids.length) {
33
+ return false;
34
+ }
35
+ for (let i = 0; i < this.ids.length; ++i) {
36
+ if (this.ids[i] !== other.ids[i]) {
37
+ return false;
38
+ }
39
+ }
40
+ return true;
41
+ }
42
+ checkForCycles() {
43
+ const stack = new Array();
44
+ let CycleContextState;
45
+ (function (CycleContextState) {
46
+ CycleContextState[CycleContextState["kVisited"] = 0] = "kVisited";
47
+ CycleContextState[CycleContextState["kSafe"] = 1] = "kSafe";
48
+ })(CycleContextState || (CycleContextState = {}));
49
+ const contextStates = new Map();
50
+ stack.push({
51
+ context: this,
52
+ markAsSafe: () => {
53
+ // marking as safe doesn't do anything at the top level
54
+ }
55
+ });
56
+ while (true) {
57
+ const stackItem = stack.pop();
58
+ if (stackItem === undefined) {
59
+ return;
60
+ }
61
+ const { context, markAsSafe } = stackItem;
62
+ const state = contextStates.get(context.cachedHash());
63
+ if (state === CycleContextState.kSafe) {
64
+ markAsSafe();
65
+ continue;
66
+ }
67
+ if (state === CycleContextState.kVisited) {
68
+ //debugger;
69
+ throw new Error(`Cycle detected in context dependency graph`);
70
+ }
71
+ if (context.dependencies.size === 0) {
72
+ contextStates.set(context.cachedHash(), CycleContextState.kSafe);
73
+ markAsSafe();
74
+ continue;
75
+ }
76
+ contextStates.set(context.cachedHash(), CycleContextState.kVisited);
77
+ let depCounter = 1;
78
+ const resolvedDep = () => {
79
+ if (--depCounter === 0) {
80
+ contextStates.set(context.cachedHash(), CycleContextState.kSafe);
81
+ markAsSafe();
82
+ }
83
+ };
84
+ for (const dep of context.dependencies.values()) {
85
+ ++depCounter;
86
+ stack.push({
87
+ context: dep,
88
+ markAsSafe: resolvedDep
89
+ });
90
+ }
91
+ resolvedDep();
92
+ }
93
+ }
94
+ addDependency(context) {
95
+ if (!this.dependencies.has(context.cachedHash())) {
96
+ this.dependencies.set(context.cachedHash(), context);
97
+ context.checkForCycles();
98
+ }
99
+ }
100
+ withChildContext(behavior) {
101
+ if (this.state === ContextState.kFinished) {
102
+ return Promise.reject(new Error("Cannot add more children to a finished context"));
103
+ }
104
+ const childContext = new Context(this.rootID, [...this.ids, this.nextChildID++], this.progressReporter);
105
+ this.dependencies.set(childContext.cachedHash(), childContext);
106
+ try {
107
+ return behavior(childContext).then((value) => {
108
+ this.dependencies.delete(childContext.cachedHash());
109
+ childContext.setFinished();
110
+ return value;
111
+ }, (e) => {
112
+ this.dependencies.delete(childContext.cachedHash());
113
+ throw e;
114
+ });
115
+ }
116
+ catch (e) {
117
+ this.dependencies.delete(childContext.cachedHash());
118
+ return Promise.reject(e);
119
+ }
120
+ }
121
+ withChildContextSync(behavior) {
122
+ if (this.state === ContextState.kFinished) {
123
+ throw new Error("Cannot add more children to a finished context");
124
+ }
125
+ const childContext = new Context(this.rootID, [...this.ids, this.nextChildID++], this.progressReporter);
126
+ this.dependencies.set(childContext.cachedHash(), childContext);
127
+ const value = behavior(childContext);
128
+ this.dependencies.delete(childContext.cachedHash());
129
+ childContext.setFinished();
130
+ return value;
131
+ }
132
+ static fromJSON(context) {
133
+ return new Context(validateString(context.get("rootID")), validateArray(context.get("ids"), validateNumber));
134
+ }
135
+ // force you to create a new context in order to get JSON to make sure you don't JSON-infy a context shared by others.
136
+ withJSON(behavior) {
137
+ return this.withChildContextSync((context) => {
138
+ const contextJSON = {
139
+ rootID: context.rootID,
140
+ ids: [...context.ids]
141
+ };
142
+ return behavior(contextJSON);
143
+ });
144
+ }
145
+ toJSON() {
146
+ return {
147
+ rootID: this.rootID,
148
+ ids: [...this.ids]
149
+ };
150
+ }
151
+ setFinished() {
152
+ if (this.state === ContextState.kFinished) {
153
+ // Don't hard error. Promises allow for resolve/reject to both be called.
154
+ return;
155
+ }
156
+ //all of your dependencies should be finished too.
157
+ this.dependencies.forEach((dep) => {
158
+ if (dep.state !== ContextState.kFinished) {
159
+ throw new Error("Child context outlives its parent");
160
+ }
161
+ });
162
+ this.progressReporter?.newCompletedContext();
163
+ this.dependencies.clear();
164
+ this.state = ContextState.kFinished;
165
+ }
166
+ }
167
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @module\n * @mergeModuleWith <project>\n */\nimport {\n type Unvalidated,\n validateArray,\n validateNumber,\n validateString\n} from \"@buildkitchen/validation\";\nimport { type ContextJSON } from \"./common.js\";\n\nenum ContextState {\n kInProgess,\n kFinished\n}\n\nexport interface ContextProgressReporter {\n newContext(): void;\n newCompletedContext(): void;\n}\n\nexport class Context {\n private _hash: string | undefined;\n private nextChildID = 0;\n private readonly dependencies = new Map<string, Context>();\n private state = ContextState.kInProgess;\n\n static root(rootID: string, progressReporter?: ContextProgressReporter) {\n return new Context(rootID, [], progressReporter);\n }\n private constructor(\n public readonly rootID: string,\n private readonly ids: ReadonlyArray<number>,\n private readonly progressReporter?: ContextProgressReporter\n ) {\n progressReporter?.newContext();\n }\n\n cachedHash() {\n this._hash ??= `root: ${this.rootID} ids: [${this.ids.join(\",\")}]`;\n return this._hash;\n }\n\n isParentOf(other: Context) {\n if (this.rootID !== other.rootID) {\n return false;\n }\n if (this.ids.length >= other.ids.length) {\n return false;\n }\n\n for (let i = 0; i < this.ids.length; ++i) {\n if (this.ids[i] !== other.ids[i]) {\n return false;\n }\n }\n\n return true;\n }\n\n private checkForCycles() {\n const stack = new Array<{\n readonly context: Context;\n readonly markAsSafe: () => void;\n }>();\n\n enum CycleContextState {\n kVisited,\n kSafe\n }\n\n const contextStates = new Map<string, CycleContextState>();\n\n stack.push({\n context: this,\n markAsSafe: () => {\n // marking as safe doesn't do anything at the top level\n }\n });\n\n while (true) {\n const stackItem = stack.pop();\n if (stackItem === undefined) {\n return;\n }\n const { context, markAsSafe } = stackItem;\n\n const state = contextStates.get(context.cachedHash());\n if (state === CycleContextState.kSafe) {\n markAsSafe();\n continue;\n }\n\n if (state === CycleContextState.kVisited) {\n //debugger;\n throw new Error(`Cycle detected in context dependency graph`);\n }\n\n if (context.dependencies.size === 0) {\n contextStates.set(context.cachedHash(), CycleContextState.kSafe);\n markAsSafe();\n continue;\n }\n\n contextStates.set(context.cachedHash(), CycleContextState.kVisited);\n\n let depCounter = 1;\n const resolvedDep = () => {\n if (--depCounter === 0) {\n contextStates.set(context.cachedHash(), CycleContextState.kSafe);\n markAsSafe();\n }\n };\n\n for (const dep of context.dependencies.values()) {\n ++depCounter;\n stack.push({\n context: dep,\n markAsSafe: resolvedDep\n });\n }\n\n resolvedDep();\n }\n }\n\n addDependency(context: Context) {\n if (!this.dependencies.has(context.cachedHash())) {\n this.dependencies.set(context.cachedHash(), context);\n context.checkForCycles();\n }\n }\n\n withChildContext<T>(\n behavior: (context: Context) => PromiseLike<T>\n ): PromiseLike<T> {\n if (this.state === ContextState.kFinished) {\n return Promise.reject(\n new Error(\"Cannot add more children to a finished context\")\n );\n }\n const childContext = new Context(\n this.rootID,\n [...this.ids, this.nextChildID++],\n this.progressReporter\n );\n this.dependencies.set(childContext.cachedHash(), childContext);\n try {\n return behavior(childContext).then(\n (value) => {\n this.dependencies.delete(childContext.cachedHash());\n childContext.setFinished();\n return value;\n },\n (e: unknown) => {\n this.dependencies.delete(childContext.cachedHash());\n throw e;\n }\n );\n } catch (e) {\n this.dependencies.delete(childContext.cachedHash());\n return Promise.reject(e);\n }\n }\n\n withChildContextSync<T>(behavior: (context: Context) => T): T {\n if (this.state === ContextState.kFinished) {\n throw new Error(\"Cannot add more children to a finished context\");\n }\n const childContext = new Context(\n this.rootID,\n [...this.ids, this.nextChildID++],\n this.progressReporter\n );\n this.dependencies.set(childContext.cachedHash(), childContext);\n\n const value = behavior(childContext);\n this.dependencies.delete(childContext.cachedHash());\n childContext.setFinished();\n return value;\n }\n\n static fromJSON(context: Unvalidated<ContextJSON>) {\n return new Context(\n validateString(context.get(\"rootID\")),\n validateArray(context.get(\"ids\"), validateNumber)\n );\n }\n\n // force you to create a new context in order to get JSON to make sure you don't JSON-infy a context shared by others.\n withJSON<T>(behavior: (context: ContextJSON) => T) {\n return this.withChildContextSync((context) => {\n const contextJSON: ContextJSON = {\n rootID: context.rootID,\n ids: [...context.ids]\n };\n return behavior(contextJSON);\n });\n }\n\n toJSON(): ContextJSON {\n return {\n rootID: this.rootID,\n ids: [...this.ids]\n };\n }\n\n private setFinished() {\n if (this.state === ContextState.kFinished) {\n // Don't hard error. Promises allow for resolve/reject to both be called.\n return;\n }\n\n //all of your dependencies should be finished too.\n this.dependencies.forEach((dep) => {\n if (dep.state !== ContextState.kFinished) {\n throw new Error(\"Child context outlives its parent\");\n }\n });\n\n this.progressReporter?.newCompletedContext();\n this.dependencies.clear();\n this.state = ContextState.kFinished;\n }\n}\n"]}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+ import { type Unvalidated } from "@buildkitchen/validation";
6
+ import { type ContextJSON } from "./common.js";
7
+ export declare class ReadonlyContext {
8
+ readonly rootID: string;
9
+ private readonly ids;
10
+ private _cachedHash;
11
+ constructor(rootID: string, ids: ReadonlyArray<number>);
12
+ cachedHash(): string;
13
+ static hash(context: ContextJSON): string;
14
+ parent(): ReadonlyContext | undefined;
15
+ toJSON(): ContextJSON;
16
+ static fromJSON(json: Unvalidated<ContextJSON>): ReadonlyContext;
17
+ }
18
+ //# sourceMappingURL=readonly_context.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @module
3
+ * @mergeModuleWith <project>
4
+ */
5
+ import { validateArray, validateNumber, validateString } from "@buildkitchen/validation";
6
+ export class ReadonlyContext {
7
+ constructor(rootID, ids) {
8
+ this.rootID = rootID;
9
+ this.ids = ids;
10
+ }
11
+ cachedHash() {
12
+ this._cachedHash ?? (this._cachedHash = ReadonlyContext.hash(this.toJSON()));
13
+ return this._cachedHash;
14
+ }
15
+ static hash(context) {
16
+ return `root: ${context.rootID} ids: [${context.ids.join(",")}]`;
17
+ }
18
+ parent() {
19
+ if (this.ids.length === 0) {
20
+ return undefined;
21
+ }
22
+ return new ReadonlyContext(this.rootID, this.ids.slice(0, -1));
23
+ }
24
+ toJSON() {
25
+ return {
26
+ rootID: this.rootID,
27
+ ids: this.ids
28
+ };
29
+ }
30
+ static fromJSON(json) {
31
+ return new ReadonlyContext(validateString(json.get("rootID")), validateArray(json.get("ids"), validateNumber));
32
+ }
33
+ }
34
+ //# sourceMappingURL=readonly_context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readonly_context.js","sourceRoot":"","sources":["readonly_context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAEL,aAAa,EACb,cAAc,EACd,cAAc,EACf,MAAM,0BAA0B,CAAC;AAGlC,MAAM,OAAO,eAAe;IAG1B,YACkB,MAAc,EACb,GAA0B;QAD3B,WAAM,GAAN,MAAM,CAAQ;QACb,QAAG,GAAH,GAAG,CAAuB;IAC1C,CAAC;IAEJ,UAAU;QACR,IAAI,CAAC,WAAW,KAAhB,IAAI,CAAC,WAAW,GAAK,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAC;QACzD,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,OAAoB;QAC9B,OAAO,SAAS,OAAO,CAAC,MAAM,UAAU,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACnE,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,MAAM;QACJ,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,IAA8B;QAC5C,OAAO,IAAI,eAAe,CACxB,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAClC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC,CAC/C,CAAC;IACJ,CAAC;CACF","sourcesContent":["/**\n * @module\n * @mergeModuleWith <project>\n */\nimport {\n type Unvalidated,\n validateArray,\n validateNumber,\n validateString\n} from \"@buildkitchen/validation\";\nimport { type ContextJSON } from \"./common.js\";\n\nexport class ReadonlyContext {\n private _cachedHash: string | undefined;\n\n constructor(\n public readonly rootID: string,\n private readonly ids: ReadonlyArray<number>\n ) {}\n\n cachedHash() {\n this._cachedHash ??= ReadonlyContext.hash(this.toJSON());\n return this._cachedHash;\n }\n\n static hash(context: ContextJSON) {\n return `root: ${context.rootID} ids: [${context.ids.join(\",\")}]`;\n }\n\n parent(): ReadonlyContext | undefined {\n if (this.ids.length === 0) {\n return undefined;\n }\n\n return new ReadonlyContext(this.rootID, this.ids.slice(0, -1));\n }\n\n toJSON(): ContextJSON {\n return {\n rootID: this.rootID,\n ids: this.ids\n };\n }\n\n static fromJSON(json: Unvalidated<ContextJSON>) {\n return new ReadonlyContext(\n validateString(json.get(\"rootID\")),\n validateArray(json.get(\"ids\"), validateNumber)\n );\n }\n}\n"]}