wardens 0.5.0 → 0.5.1-2024-07-28.65ce2a7

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/README.md CHANGED
@@ -30,7 +30,7 @@ Now define a pool that creates and manages workers:
30
30
 
31
31
  ```typescript
32
32
  async function WorkerPool(
33
- { create }: ResourceContext,
33
+ { create }: ResourceScope,
34
34
  config: { poolSize: number },
35
35
  ) {
36
36
  const promises = Array(config.poolSize).fill(Worker).map(create);
package/dist/wardens.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var E=Object.defineProperty;var C=(t,e,r)=>e in t?E(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var h=(t,e,r)=>(C(t,typeof e!="symbol"?e+"":e,r),r),g=(t,e,r)=>{if(!e.has(t))throw TypeError("Cannot "+r)};var a=(t,e,r)=>(g(t,e,"read from private field"),r?r.call(t):e.get(t)),f=(t,e,r)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,r)},y=(t,e,r,o)=>(g(t,e,"write to private field"),o?o.call(t,r):e.set(t,r),r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function W(t){const e=new WeakMap;return new Proxy(t,{get(r,o){const s=Reflect.get(r,o,r);if(typeof s=="function"){if(e.has(s)===!1){const n=s.bind(r);Object.defineProperties(n,Object.getOwnPropertyDescriptors(s)),e.set(s,n)}return e.get(s)}return s},set(r,o,s){return Reflect.set(r,o,s,r)}})}const p=new WeakMap,S=new WeakSet;function O(t){return Proxy.revocable(t,{})}var l,i,u;class R{constructor(e,r){f(this,l,new WeakSet);f(this,i,void 0);f(this,u,void 0);h(this,"create",async(e,...r)=>{if(a(this,u).enforced)throw new Error("Cannot create new resources after teardown.");const o=await x(e,...r);return a(this,i).add(o),o});h(this,"destroy",async e=>{if(a(this,l).has(e))throw new Error("Resource already destroyed.");if(!a(this,i).has(e))throw new Error("You do not own this resource.");a(this,i).delete(e),a(this,l).add(e),await m(e)});y(this,i,e),y(this,u,r)}}l=new WeakMap,i=new WeakMap,u=new WeakMap;const x=async(t,...e)=>{const r={enforced:!1},o=new Set,s=new R(o,r);let n;try{n=await t(s,...e)}catch(v){const k=Array.from(o).reverse(),b=(await Promise.allSettled(k.map(d=>s.destroy(d)))).filter(d=>d.status==="rejected");throw b.length?P(b.map(d=>d.reason),{cause:v}):v}const c=n.value,{proxy:w,revoke:j}=O(c);return S.add(w),p.set(w,{curfew:r,resource:n,children:o,revoke:j}),w},m=async t=>{if(!S.has(t))throw new Error("Cannot destroy object. It is not a resource.");const e=p.get(t);if(e){p.delete(t),e.revoke();let r={status:"fulfilled",value:void 0};if(e.resource.destroy)try{await e.resource.destroy()}catch(c){r={status:"rejected",reason:c}}e.curfew.enforced=!0;const o=Array.from(e.children).reverse().map(m),s=await Promise.allSettled(o),n=[r].concat(s).filter(c=>c.status==="rejected");if(n.length)throw P(n.map(c=>c.reason))}},P=(t,e)=>t.length===1?t[0]:new B(t,e);class B extends Error{constructor(e,r){super("Some resources could not be destroyed. See the `failures` property for details.",r),this.failures=e}}exports.bindContext=W;exports.create=x;exports.destroy=m;
1
+ "use strict";var B=Object.defineProperty;var j=e=>{throw TypeError(e)};var M=(e,t,r)=>t in e?B(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var f=(e,t,r)=>M(e,typeof t!="symbol"?t+"":t,r),k=(e,t,r)=>t.has(e)||j("Cannot "+r);var s=(e,t,r)=>(k(e,t,"read from private field"),r?r.call(e):t.get(e)),d=(e,t,r)=>t.has(e)?j("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,r),h=(e,t,r,o)=>(k(e,t,"write to private field"),o?o.call(e,r):t.set(e,r),r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function A(e){const t=new WeakMap;return new Proxy(e,{get(r,o){const n=Reflect.get(r,o,r);if(typeof n=="function"){if(t.has(n)===!1){const a=n.bind(r);Object.defineProperties(a,Object.getOwnPropertyDescriptors(n)),t.set(n,a)}return t.get(n)}return n},set(r,o,n){return Reflect.set(r,o,n,r)}})}const g=new WeakMap,E=new WeakSet,P=new WeakSet;function T(e){return Proxy.revocable(e,{})}var C,w;class x{constructor(t){d(this,C,Symbol("Context ID"));d(this,w);h(this,w,t)}static getId(t){return s(t,C)}static getDefaultValue(t){var r;return s(r=t,w).call(r)}}C=new WeakMap,w=new WeakMap;const V=e=>new x(e);var y,l,p,i;class H{constructor(t,r,o){d(this,y,new WeakSet);d(this,l);d(this,p);d(this,i);f(this,"create",async(t,...r)=>{if(s(this,p).enforced)throw new Error("Cannot create new resources after teardown.");const o=Object.create(s(this,i)),n=await D(o,t,...r);return s(this,l).add(n),n});f(this,"destroy",async t=>{if(s(this,y).has(t))throw new Error("Resource already destroyed.");if(!s(this,l).has(t))throw new Error("You do not own this resource.");s(this,l).delete(t),s(this,y).add(t),await m(t)});f(this,"setContext",(t,r)=>(s(this,i)[x.getId(t)]=r,r));f(this,"getContext",t=>{const r=x.getId(t);return r in s(this,i)?s(this,i)[r]:x.getDefaultValue(t)});h(this,i,t),h(this,l,r),h(this,p,o)}}y=new WeakMap,l=new WeakMap,p=new WeakMap,i=new WeakMap;const D=async(e,t,...r)=>{const o={enforced:!1},n=new Set,a=new H(e,n,o);let c;try{c=await t(a,...r)}catch(S){const R=Array.from(n).reverse(),v=(await Promise.allSettled(R.map(u=>a.destroy(u)))).filter(u=>u.status==="rejected");throw v.length?W(v.map(u=>u.reason),{cause:S}):S}const I=c.value,{proxy:b,revoke:O}=T(I);return E.add(b),g.set(b,{curfew:o,resource:c,children:n,revoke:O}),b},m=async e=>{if(!E.has(e))throw new Error("Cannot destroy object. It is not a resource.");const t=g.get(e);if(t){g.delete(e),t.revoke();let r={status:"fulfilled",value:void 0};if(t.resource.destroy)try{await t.resource.destroy()}catch(c){r={status:"rejected",reason:c}}t.curfew.enforced=!0;const o=Array.from(t.children).reverse().map(m),n=await Promise.allSettled(o),a=[r].concat(n).filter(c=>c.status==="rejected");if(a.length)throw W(a.map(c=>c.reason))}},W=(e,t)=>e.length===1?e[0]:new U(e,t);class U extends Error{constructor(t,r){super("Some resources could not be destroyed. See the `failures` property for details.",r),this.failures=t}}const Y=async(e,...t)=>{const o=await D(Object.create(null),e,...t);return P.add(o),o},q=async e=>{if(g.has(e)&&!P.has(e))throw new Error("Cannot destroy child resource. It is owned by another scope.");return m(e)};exports.bindContext=A;exports.create=Y;exports.createContext=V;exports.destroy=q;
package/dist/wardens.js CHANGED
@@ -1,28 +1,24 @@
1
- var P = Object.defineProperty;
2
- var j = (t, e, r) => e in t ? P(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r;
3
- var h = (t, e, r) => (j(t, typeof e != "symbol" ? e + "" : e, r), r), x = (t, e, r) => {
4
- if (!e.has(t))
5
- throw TypeError("Cannot " + r);
1
+ var B = Object.defineProperty;
2
+ var j = (e) => {
3
+ throw TypeError(e);
6
4
  };
7
- var a = (t, e, r) => (x(t, e, "read from private field"), r ? r.call(t) : e.get(t)), f = (t, e, r) => {
8
- if (e.has(t))
9
- throw TypeError("Cannot add the same private member more than once");
10
- e instanceof WeakSet ? e.add(t) : e.set(t, r);
11
- }, y = (t, e, r, o) => (x(t, e, "write to private field"), o ? o.call(t, r) : e.set(t, r), r);
12
- function A(t) {
13
- const e = /* @__PURE__ */ new WeakMap();
14
- return new Proxy(t, {
5
+ var A = (e, t, r) => t in e ? B(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
6
+ var h = (e, t, r) => A(e, typeof t != "symbol" ? t + "" : t, r), k = (e, t, r) => t.has(e) || j("Cannot " + r);
7
+ var n = (e, t, r) => (k(e, t, "read from private field"), r ? r.call(e) : t.get(e)), d = (e, t, r) => t.has(e) ? j("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(e) : t.set(e, r), f = (e, t, r, o) => (k(e, t, "write to private field"), o ? o.call(e, r) : t.set(e, r), r);
8
+ function Y(e) {
9
+ const t = /* @__PURE__ */ new WeakMap();
10
+ return new Proxy(e, {
15
11
  get(r, o) {
16
12
  const s = Reflect.get(r, o, r);
17
13
  if (typeof s == "function") {
18
- if (e.has(s) === !1) {
19
- const n = s.bind(r);
14
+ if (t.has(s) === !1) {
15
+ const a = s.bind(r);
20
16
  Object.defineProperties(
21
- n,
17
+ a,
22
18
  Object.getOwnPropertyDescriptors(s)
23
- ), e.set(s, n);
19
+ ), t.set(s, a);
24
20
  }
25
- return e.get(s);
21
+ return t.get(s);
26
22
  }
27
23
  return s;
28
24
  },
@@ -31,95 +27,131 @@ function A(t) {
31
27
  }
32
28
  });
33
29
  }
34
- const p = /* @__PURE__ */ new WeakMap(), g = /* @__PURE__ */ new WeakSet();
35
- function W(t) {
36
- return Proxy.revocable(t, {});
30
+ const g = /* @__PURE__ */ new WeakMap(), E = /* @__PURE__ */ new WeakSet(), D = /* @__PURE__ */ new WeakSet();
31
+ function M(e) {
32
+ return Proxy.revocable(e, {});
37
33
  }
38
- var l, i, u;
39
- class C {
40
- constructor(e, r) {
41
- f(this, l, /* @__PURE__ */ new WeakSet());
42
- f(this, i, void 0);
43
- f(this, u, void 0);
34
+ var m, w;
35
+ class x {
36
+ constructor(t) {
37
+ d(this, m, Symbol("Context ID"));
38
+ d(this, w);
39
+ f(this, w, t);
40
+ }
41
+ static getId(t) {
42
+ return n(t, m);
43
+ }
44
+ static getDefaultValue(t) {
45
+ var r;
46
+ return n(r = t, w).call(r);
47
+ }
48
+ }
49
+ m = new WeakMap(), w = new WeakMap();
50
+ const q = (e) => new x(e);
51
+ var y, l, p, i;
52
+ class V {
53
+ constructor(t, r, o) {
54
+ d(this, y, /* @__PURE__ */ new WeakSet());
55
+ d(this, l);
56
+ d(this, p);
57
+ d(this, i);
44
58
  /** Provision an owned resource and make sure it doesn't outlive us. */
45
- h(this, "create", async (e, ...r) => {
46
- if (a(this, u).enforced)
59
+ h(this, "create", async (t, ...r) => {
60
+ if (n(this, p).enforced)
47
61
  throw new Error("Cannot create new resources after teardown.");
48
- const o = await R(e, ...r);
49
- return a(this, i).add(o), o;
62
+ const o = Object.create(n(this, i)), s = await P(o, t, ...r);
63
+ return n(this, l).add(s), s;
50
64
  });
51
65
  /**
52
66
  * Tear down a resource. Happens automatically when resource owners are
53
67
  * deallocated.
54
68
  */
55
- h(this, "destroy", async (e) => {
56
- if (a(this, l).has(e))
69
+ h(this, "destroy", async (t) => {
70
+ if (n(this, y).has(t))
57
71
  throw new Error("Resource already destroyed.");
58
- if (!a(this, i).has(e))
72
+ if (!n(this, l).has(t))
59
73
  throw new Error("You do not own this resource.");
60
- a(this, i).delete(e), a(this, l).add(e), await S(e);
74
+ n(this, l).delete(t), n(this, y).add(t), await b(t);
61
75
  });
62
- y(this, i, e), y(this, u, r);
76
+ /** Store a value in context. Anything down the chain can read it. */
77
+ h(this, "setContext", (t, r) => (n(this, i)[x.getId(t)] = r, r));
78
+ /** Retrieve a value from context, or a default if it is unset. */
79
+ h(this, "getContext", (t) => {
80
+ const r = x.getId(t);
81
+ return r in n(this, i) ? n(this, i)[r] : x.getDefaultValue(t);
82
+ });
83
+ f(this, i, t), f(this, l, r), f(this, p, o);
63
84
  }
64
85
  }
65
- l = new WeakMap(), i = new WeakMap(), u = new WeakMap();
66
- const R = async (t, ...e) => {
67
- const r = { enforced: !1 }, o = /* @__PURE__ */ new Set(), s = new C(o, r);
68
- let n;
86
+ y = new WeakMap(), l = new WeakMap(), p = new WeakMap(), i = new WeakMap();
87
+ const P = async (e, t, ...r) => {
88
+ const o = { enforced: !1 }, s = /* @__PURE__ */ new Set(), a = new V(e, s, o);
89
+ let c;
69
90
  try {
70
- n = await t(s, ...e);
71
- } catch (m) {
72
- const E = Array.from(o).reverse(), v = (await Promise.allSettled(
73
- E.map((d) => s.destroy(d))
91
+ c = await t(a, ...r);
92
+ } catch (S) {
93
+ const O = Array.from(s).reverse(), v = (await Promise.allSettled(
94
+ O.map((u) => a.destroy(u))
74
95
  )).filter(
75
- (d) => d.status === "rejected"
96
+ (u) => u.status === "rejected"
76
97
  );
77
- throw v.length ? b(
78
- v.map((d) => d.reason),
79
- { cause: m }
80
- ) : m;
98
+ throw v.length ? W(
99
+ v.map((u) => u.reason),
100
+ { cause: S }
101
+ ) : S;
81
102
  }
82
- const c = n.value, { proxy: w, revoke: k } = W(c);
83
- return g.add(w), p.set(w, {
84
- curfew: r,
85
- resource: n,
86
- children: o,
87
- revoke: k
88
- }), w;
89
- }, S = async (t) => {
90
- if (!g.has(t))
103
+ const I = c.value, { proxy: C, revoke: R } = M(I);
104
+ return E.add(C), g.set(C, {
105
+ curfew: o,
106
+ resource: c,
107
+ children: s,
108
+ revoke: R
109
+ }), C;
110
+ }, b = async (e) => {
111
+ if (!E.has(e))
91
112
  throw new Error("Cannot destroy object. It is not a resource.");
92
- const e = p.get(t);
93
- if (e) {
94
- p.delete(t), e.revoke();
113
+ const t = g.get(e);
114
+ if (t) {
115
+ g.delete(e), t.revoke();
95
116
  let r = {
96
117
  status: "fulfilled",
97
118
  value: void 0
98
119
  };
99
- if (e.resource.destroy)
120
+ if (t.resource.destroy)
100
121
  try {
101
- await e.resource.destroy();
122
+ await t.resource.destroy();
102
123
  } catch (c) {
103
124
  r = { status: "rejected", reason: c };
104
125
  }
105
- e.curfew.enforced = !0;
106
- const o = Array.from(e.children).reverse().map(S), s = await Promise.allSettled(o), n = [r].concat(s).filter(
126
+ t.curfew.enforced = !0;
127
+ const o = Array.from(t.children).reverse().map(b), s = await Promise.allSettled(o), a = [r].concat(s).filter(
107
128
  (c) => c.status === "rejected"
108
129
  );
109
- if (n.length)
110
- throw b(n.map((c) => c.reason));
130
+ if (a.length)
131
+ throw W(a.map((c) => c.reason));
111
132
  }
112
- }, b = (t, e) => t.length === 1 ? t[0] : new B(t, e);
113
- class B extends Error {
114
- constructor(e, r) {
133
+ }, W = (e, t) => e.length === 1 ? e[0] : new H(e, t);
134
+ class H extends Error {
135
+ constructor(t, r) {
115
136
  super(
116
137
  "Some resources could not be destroyed. See the `failures` property for details.",
117
138
  r
118
- ), this.failures = e;
139
+ ), this.failures = t;
119
140
  }
120
141
  }
142
+ const z = async (e, ...t) => {
143
+ const o = await P(/* @__PURE__ */ Object.create(null), e, ...t);
144
+ return D.add(o), o;
145
+ }, F = async (e) => {
146
+ if (g.has(e) && !D.has(e))
147
+ throw new Error(
148
+ "Cannot destroy child resource. It is owned by another scope."
149
+ );
150
+ return b(e);
151
+ };
121
152
  export {
122
- A as bindContext,
123
- R as create,
124
- S as destroy
153
+ Y as bindContext,
154
+ z as create,
155
+ q as createContext,
156
+ F as destroy
125
157
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wardens",
3
- "version": "0.5.0",
3
+ "version": "0.5.1-2024-07-28.65ce2a7",
4
4
  "description": "A framework for resource management",
5
5
  "type": "module",
6
6
  "main": "./dist/wardens.cjs",
@@ -9,6 +9,7 @@
9
9
  "repository": "git@github.com:PsychoLlama/wardens.git",
10
10
  "author": "Jesse Gibson <JesseTheGibson@gmail.com>",
11
11
  "license": "MIT",
12
+ "sideEffects": false,
12
13
  "exports": {
13
14
  ".": {
14
15
  "require": "./dist/wardens.cjs",
@@ -30,12 +31,13 @@
30
31
  ],
31
32
  "scripts": {
32
33
  "prepack": "tsc && vite build",
33
- "test": "./bin/run-tests",
34
+ "test": "./scripts/run-tests",
34
35
  "test:lint": "eslint src --color",
35
- "test:unit": "vitest --color",
36
- "test:types": "vitest typecheck --color",
36
+ "test:unit": "vitest --color --typecheck",
37
37
  "test:fmt": "prettier --check src --color",
38
- "dev": "vite build --watch"
38
+ "print-pkg-version": "echo ${npm_package_version}",
39
+ "release:candidate": "./scripts/publish-rc",
40
+ "release:stable": "./scripts/publish-stable"
39
41
  },
40
42
  "husky": {
41
43
  "hooks": {
@@ -52,41 +54,17 @@
52
54
  "singleQuote": true,
53
55
  "trailingComma": "all"
54
56
  },
55
- "eslintConfig": {
56
- "parser": "@typescript-eslint/parser",
57
- "parserOptions": {
58
- "sourceType": "module"
59
- },
60
- "overrides": [
61
- {
62
- "files": [
63
- "./**/__tests__/*.ts{x,}"
64
- ],
65
- "rules": {
66
- "@typescript-eslint/no-explicit-any": "off"
67
- }
68
- }
69
- ],
70
- "extends": [
71
- "eslint:recommended",
72
- "plugin:@typescript-eslint/recommended"
73
- ],
74
- "rules": {
75
- "@typescript-eslint/explicit-module-boundary-types": "off",
76
- "@typescript-eslint/no-non-null-assertion": "off",
77
- "@typescript-eslint/no-unused-vars": "error",
78
- "no-prototype-builtins": "off"
79
- }
80
- },
81
57
  "devDependencies": {
82
- "@typescript-eslint/eslint-plugin": "6.3.0",
83
- "@typescript-eslint/parser": "6.3.0",
84
- "eslint": "8.47.0",
85
- "husky": "8.0.3",
86
- "lint-staged": "13.2.3",
87
- "prettier": "2.8.8",
88
- "typescript": "5.1.6",
89
- "vite": "4.4.9",
90
- "vitest": "^0.34.0"
58
+ "@eslint/js": "^9.7.0",
59
+ "@types/eslint__js": "^8.42.3",
60
+ "@types/node": "^22.0.0",
61
+ "eslint": "^9.7.0",
62
+ "husky": "^9.1.3",
63
+ "lint-staged": "^15.2.7",
64
+ "prettier": "^3.3.3",
65
+ "typescript": "^5.5.4",
66
+ "typescript-eslint": "^8.0.0-alpha.54",
67
+ "vite": "^5.3.5",
68
+ "vitest": "^2.0.0"
91
69
  }
92
70
  }
@@ -0,0 +1,198 @@
1
+ import ResourceScope from '../resource-scope';
2
+ import { create } from '../';
3
+ import { createContext } from '../inherited-context';
4
+
5
+ describe('ResourceScope', () => {
6
+ async function Test() {
7
+ return { value: {} };
8
+ }
9
+
10
+ it('can spawn children of its own', async () => {
11
+ const Child = async () => ({
12
+ value: { child: true },
13
+ });
14
+
15
+ const Parent = async (resource: ResourceScope) => {
16
+ return { value: await resource.create(Child) };
17
+ };
18
+
19
+ await expect(create(Parent)).resolves.toEqual({ child: true });
20
+ });
21
+
22
+ it('can deallocate child resources on demand', async () => {
23
+ const spy = vi.fn();
24
+
25
+ const Child = async () => ({
26
+ value: { child: true },
27
+ destroy: spy,
28
+ });
29
+
30
+ const Parent = async (resource: ResourceScope) => {
31
+ const child = await resource.create(Child);
32
+ await resource.destroy(child);
33
+
34
+ return {
35
+ value: { parent: true },
36
+ };
37
+ };
38
+
39
+ await expect(create(Parent)).resolves.toEqual({ parent: true });
40
+ expect(spy).toHaveBeenCalled();
41
+ });
42
+
43
+ it('fails to destroy resources owned by someone else', async () => {
44
+ const test = await create(Test);
45
+
46
+ const Sneaky = async (resource: ResourceScope) => {
47
+ await resource.destroy(test);
48
+ return { value: {} };
49
+ };
50
+
51
+ await expect(create(Sneaky)).rejects.toThrow(/do not own/i);
52
+ });
53
+
54
+ it('binds create/destroy handlers to the class instance', async () => {
55
+ async function Allocator({ create, destroy }: ResourceScope) {
56
+ const test = await create(Test);
57
+ await destroy(test);
58
+
59
+ return { value: [] };
60
+ }
61
+
62
+ await expect(create(Allocator)).resolves.not.toThrow();
63
+ });
64
+
65
+ it('indicates if a resource was already destroyed', async () => {
66
+ async function Allocator(resource: ResourceScope) {
67
+ const test = await resource.create(Test);
68
+ await resource.destroy(test);
69
+ await resource.destroy(test);
70
+
71
+ return { value: [] };
72
+ }
73
+
74
+ await expect(create(Allocator)).rejects.toThrow(/already destroyed/i);
75
+ });
76
+
77
+ it('can set and retrieve context', async () => {
78
+ const SharedValue = createContext(() => 'none');
79
+
80
+ const Test = async (resource: ResourceScope) => {
81
+ expect(resource.getContext(SharedValue)).toBe('none');
82
+
83
+ resource.setContext(SharedValue, 'saved');
84
+ expect(resource.getContext(SharedValue)).toBe('saved');
85
+
86
+ resource.setContext(SharedValue, 'updated');
87
+ expect(resource.getContext(SharedValue)).toBe('updated');
88
+
89
+ return { value: [] };
90
+ };
91
+
92
+ await expect(create(Test)).resolves.toEqual([]);
93
+ });
94
+
95
+ it('returns the new context value', async () => {
96
+ const SharedValue = createContext(() => 'none');
97
+
98
+ const Test = async (resource: ResourceScope) => {
99
+ return {
100
+ value: { value: resource.setContext(SharedValue, 'returned') },
101
+ };
102
+ };
103
+
104
+ await expect(create(Test)).resolves.toEqual({ value: 'returned' });
105
+ });
106
+
107
+ it('passes context to child resources', async () => {
108
+ const SharedValue = createContext<null | string>(() => null);
109
+
110
+ const Child = async (ctx: ResourceScope) => ({
111
+ value: { content: ctx.getContext(SharedValue) },
112
+ });
113
+
114
+ const Parent = async (resource: ResourceScope) => {
115
+ resource.setContext(SharedValue, 'inherited');
116
+ return { value: await resource.create(Child) };
117
+ };
118
+
119
+ await expect(create(Parent)).resolves.toEqual({ content: 'inherited' });
120
+ });
121
+
122
+ it('can override context without affecting the parent', async () => {
123
+ const Message = createContext<null | string>(() => null);
124
+ const Child = async (resource: ResourceScope) => {
125
+ // This should *NOT* affect the parent context.
126
+ resource.setContext(Message, 'child context');
127
+
128
+ return {
129
+ value: { content: resource.getContext(Message) },
130
+ };
131
+ };
132
+
133
+ const Parent = async (resource: ResourceScope) => {
134
+ resource.setContext(Message, 'parent context');
135
+ const child = await resource.create(Child);
136
+
137
+ expect(resource.getContext(Message)).toBe('parent context');
138
+
139
+ return {
140
+ value: {
141
+ content: resource.getContext(Message),
142
+ child,
143
+ },
144
+ };
145
+ };
146
+
147
+ await expect(create(Parent)).resolves.toEqual({
148
+ content: 'parent context',
149
+ child: { content: 'child context' },
150
+ });
151
+ });
152
+
153
+ it('allows two siblings to have different context values', async () => {
154
+ const Message = createContext<null | string>(() => null);
155
+ const Child = async (resource: ResourceScope, msg: string) => {
156
+ resource.setContext(Message, msg);
157
+
158
+ return {
159
+ value: { getMessage: () => resource.getContext(Message) },
160
+ };
161
+ };
162
+
163
+ const Parent = async (resource: ResourceScope) => {
164
+ resource.setContext(Message, 'parent context');
165
+
166
+ return {
167
+ value: await Promise.all([
168
+ resource.create(Child, 'child context 1'),
169
+ resource.create(Child, 'child context 2'),
170
+ ]),
171
+ };
172
+ };
173
+
174
+ const [child1, child2] = await create(Parent);
175
+ expect(child1.getMessage()).toBe('child context 1');
176
+ expect(child2.getMessage()).toBe('child context 2');
177
+ });
178
+
179
+ it('provides a live view of the current value, not just a snapshot', async () => {
180
+ const Message = createContext(() => 'default');
181
+ const Child = async (resource: ResourceScope) => ({
182
+ value: { getMessage: () => resource.getContext(Message) },
183
+ });
184
+
185
+ const Parent = async (resource: ResourceScope) => ({
186
+ value: {
187
+ setMessage: (msg: string) => resource.setContext(Message, msg),
188
+ child: await resource.create(Child),
189
+ },
190
+ });
191
+
192
+ const { setMessage, child } = await create(Parent);
193
+ expect(child.getMessage()).toBe('default');
194
+
195
+ setMessage('updated');
196
+ expect(child.getMessage()).toBe('updated');
197
+ });
198
+ });