wardens 0.5.1 → 0.6.0-rc.1

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ### Added
10
+
11
+ - New context API carries state down the tree without plumbing through arguments.
12
+
13
+ ### Changed
14
+
15
+ - Renamed public type `ResourceContext` to `ResourceControls`.
16
+
9
17
  ## [0.5.1] - 2023-08-12
10
18
 
11
19
  ### Fixed
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 }: ResourceControls,
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 I=Object.defineProperty;var R=(r,e,t)=>e in r?I(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var f=(r,e,t)=>(R(r,typeof e!="symbol"?e+"":e,t),t),j=(r,e,t)=>{if(!e.has(r))throw TypeError("Cannot "+t)};var n=(r,e,t)=>(j(r,e,"read from private field"),t?t.call(r):e.get(r)),l=(r,e,t)=>{if(e.has(r))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(r):e.set(r,t)},h=(r,e,t,s)=>(j(r,e,"write to private field"),s?s.call(r,t):e.set(r,t),t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function B(r){const e=new WeakMap;return new Proxy(r,{get(t,s){const o=Reflect.get(t,s,t);if(typeof o=="function"){if(e.has(o)===!1){const a=o.bind(t);Object.defineProperties(a,Object.getOwnPropertyDescriptors(o)),e.set(o,a)}return e.get(o)}return o},set(t,s,o){return Reflect.set(t,s,o,t)}})}const m=new WeakMap,P=new WeakSet;function M(r){return Proxy.revocable(r,{})}var g,w;class x{constructor(e){l(this,g,Symbol("Context ID"));l(this,w,void 0);h(this,w,e)}static getId(e){return n(e,g)}static getDefaultValue(e){var t;return n(t=e,w).call(t)}}g=new WeakMap,w=new WeakMap;const A=r=>new x(r);var y,u,p,i;class T{constructor(e,t,s){l(this,y,new WeakSet);l(this,u,void 0);l(this,p,void 0);l(this,i,void 0);f(this,"create",async(e,...t)=>{if(n(this,p).enforced)throw new Error("Cannot create new resources after teardown.");const s=Object.create(n(this,i)),o=await k(s,e,...t);return n(this,u).add(o),o});f(this,"destroy",async e=>{if(n(this,y).has(e))throw new Error("Resource already destroyed.");if(!n(this,u).has(e))throw new Error("You do not own this resource.");n(this,u).delete(e),n(this,y).add(e),await b(e)});f(this,"setContext",(e,t)=>(n(this,i)[x.getId(e)]=t,t));f(this,"getContext",e=>{const t=x.getId(e);return t in n(this,i)?n(this,i)[t]:x.getDefaultValue(e)});h(this,i,e),h(this,u,t),h(this,p,s)}}y=new WeakMap,u=new WeakMap,p=new WeakMap,i=new WeakMap;const k=async(r,e,...t)=>{const s={enforced:!1},o=new Set,a=new T(r,o,s);let c;try{c=await e(a,...t)}catch(S){const W=Array.from(o).reverse(),v=(await Promise.allSettled(W.map(d=>a.destroy(d)))).filter(d=>d.status==="rejected");throw v.length?D(v.map(d=>d.reason),{cause:S}):S}const E=c.value,{proxy:C,revoke:O}=M(E);return P.add(C),m.set(C,{curfew:s,resource:c,children:o,revoke:O}),C},b=async r=>{if(!P.has(r))throw new Error("Cannot destroy object. It is not a resource.");const e=m.get(r);if(e){m.delete(r),e.revoke();let t={status:"fulfilled",value:void 0};if(e.resource.destroy)try{await e.resource.destroy()}catch(c){t={status:"rejected",reason:c}}e.curfew.enforced=!0;const s=Array.from(e.children).reverse().map(b),o=await Promise.allSettled(s),a=[t].concat(o).filter(c=>c.status==="rejected");if(a.length)throw D(a.map(c=>c.reason))}},D=(r,e)=>r.length===1?r[0]:new V(r,e);class V extends Error{constructor(e,t){super("Some resources could not be destroyed. See the `failures` property for details.",t),this.failures=e}}const H=async(r,...e)=>k(Object.create(null),r,...e);exports.bindContext=B;exports.create=H;exports.createContext=A;exports.destroy=b;
package/dist/wardens.js CHANGED
@@ -1,98 +1,123 @@
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 O = Object.defineProperty;
2
+ var R = (r, e, t) => e in r ? O(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
3
+ var f = (r, e, t) => (R(r, typeof e != "symbol" ? e + "" : e, t), t), S = (r, e, t) => {
4
+ if (!e.has(r))
5
+ throw TypeError("Cannot " + t);
6
6
  };
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))
7
+ var n = (r, e, t) => (S(r, e, "read from private field"), t ? t.call(r) : e.get(r)), l = (r, e, t) => {
8
+ if (e.has(r))
9
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) {
10
+ e instanceof WeakSet ? e.add(r) : e.set(r, t);
11
+ }, h = (r, e, t, s) => (S(r, e, "write to private field"), s ? s.call(r, t) : e.set(r, t), t);
12
+ function T(r) {
13
13
  const e = /* @__PURE__ */ new WeakMap();
14
- return new Proxy(t, {
15
- get(r, o) {
16
- const s = Reflect.get(r, o, r);
17
- if (typeof s == "function") {
18
- if (e.has(s) === !1) {
19
- const n = s.bind(r);
14
+ return new Proxy(r, {
15
+ get(t, s) {
16
+ const o = Reflect.get(t, s, t);
17
+ if (typeof o == "function") {
18
+ if (e.has(o) === !1) {
19
+ const a = o.bind(t);
20
20
  Object.defineProperties(
21
- n,
22
- Object.getOwnPropertyDescriptors(s)
23
- ), e.set(s, n);
21
+ a,
22
+ Object.getOwnPropertyDescriptors(o)
23
+ ), e.set(o, a);
24
24
  }
25
- return e.get(s);
25
+ return e.get(o);
26
26
  }
27
- return s;
27
+ return o;
28
28
  },
29
- set(r, o, s) {
30
- return Reflect.set(r, o, s, r);
29
+ set(t, s, o) {
30
+ return Reflect.set(t, s, o, t);
31
31
  }
32
32
  });
33
33
  }
34
- const p = /* @__PURE__ */ new WeakMap(), g = /* @__PURE__ */ new WeakSet();
35
- function W(t) {
36
- return Proxy.revocable(t, {});
34
+ const C = /* @__PURE__ */ new WeakMap(), j = /* @__PURE__ */ new WeakSet();
35
+ function B(r) {
36
+ return Proxy.revocable(r, {});
37
37
  }
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);
38
+ var g, w;
39
+ class x {
40
+ constructor(e) {
41
+ l(this, g, Symbol("Context ID"));
42
+ l(this, w, void 0);
43
+ h(this, w, e);
44
+ }
45
+ static getId(e) {
46
+ return n(e, g);
47
+ }
48
+ static getDefaultValue(e) {
49
+ var t;
50
+ return n(t = e, w).call(t);
51
+ }
52
+ }
53
+ g = new WeakMap(), w = new WeakMap();
54
+ const U = (r) => new x(r);
55
+ var y, u, p, i;
56
+ class A {
57
+ constructor(e, t, s) {
58
+ l(this, y, /* @__PURE__ */ new WeakSet());
59
+ l(this, u, void 0);
60
+ l(this, p, void 0);
61
+ l(this, i, void 0);
44
62
  /** 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)
63
+ f(this, "create", async (e, ...t) => {
64
+ if (n(this, p).enforced)
47
65
  throw new Error("Cannot create new resources after teardown.");
48
- const o = await R(e, ...r);
49
- return a(this, i).add(o), o;
66
+ const s = Object.create(n(this, i)), o = await k(s, e, ...t);
67
+ return n(this, u).add(o), o;
50
68
  });
51
69
  /**
52
70
  * Tear down a resource. Happens automatically when resource owners are
53
71
  * deallocated.
54
72
  */
55
- h(this, "destroy", async (e) => {
56
- if (a(this, l).has(e))
73
+ f(this, "destroy", async (e) => {
74
+ if (n(this, y).has(e))
57
75
  throw new Error("Resource already destroyed.");
58
- if (!a(this, i).has(e))
76
+ if (!n(this, u).has(e))
59
77
  throw new Error("You do not own this resource.");
60
- a(this, i).delete(e), a(this, l).add(e), await S(e);
78
+ n(this, u).delete(e), n(this, y).add(e), await D(e);
79
+ });
80
+ /** Store a value in context. Anything down the chain can read it. */
81
+ f(this, "setContext", (e, t) => (n(this, i)[x.getId(e)] = t, t));
82
+ /** Retrieve a value from context, or a default if it is unset. */
83
+ f(this, "getContext", (e) => {
84
+ const t = x.getId(e);
85
+ return t in n(this, i) ? n(this, i)[t] : x.getDefaultValue(e);
61
86
  });
62
- y(this, i, e), y(this, u, r);
87
+ h(this, i, e), h(this, u, t), h(this, p, s);
63
88
  }
64
89
  }
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;
90
+ y = new WeakMap(), u = new WeakMap(), p = new WeakMap(), i = new WeakMap();
91
+ const k = async (r, e, ...t) => {
92
+ const s = { enforced: !1 }, o = /* @__PURE__ */ new Set(), a = new A(r, o, s);
93
+ let c;
69
94
  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))
95
+ c = await e(a, ...t);
96
+ } catch (b) {
97
+ const I = Array.from(o).reverse(), v = (await Promise.allSettled(
98
+ I.map((d) => a.destroy(d))
74
99
  )).filter(
75
100
  (d) => d.status === "rejected"
76
101
  );
77
- throw v.length ? b(
102
+ throw v.length ? E(
78
103
  v.map((d) => d.reason),
79
- { cause: m }
80
- ) : m;
104
+ { cause: b }
105
+ ) : b;
81
106
  }
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,
107
+ const P = c.value, { proxy: m, revoke: W } = B(P);
108
+ return j.add(m), C.set(m, {
109
+ curfew: s,
110
+ resource: c,
86
111
  children: o,
87
- revoke: k
88
- }), w;
89
- }, S = async (t) => {
90
- if (!g.has(t))
112
+ revoke: W
113
+ }), m;
114
+ }, D = async (r) => {
115
+ if (!j.has(r))
91
116
  throw new Error("Cannot destroy object. It is not a resource.");
92
- const e = p.get(t);
117
+ const e = C.get(r);
93
118
  if (e) {
94
- p.delete(t), e.revoke();
95
- let r = {
119
+ C.delete(r), e.revoke();
120
+ let t = {
96
121
  status: "fulfilled",
97
122
  value: void 0
98
123
  };
@@ -100,26 +125,28 @@ const R = async (t, ...e) => {
100
125
  try {
101
126
  await e.resource.destroy();
102
127
  } catch (c) {
103
- r = { status: "rejected", reason: c };
128
+ t = { status: "rejected", reason: c };
104
129
  }
105
130
  e.curfew.enforced = !0;
106
- const o = Array.from(e.children).reverse().map(S), s = await Promise.allSettled(o), n = [r].concat(s).filter(
131
+ const s = Array.from(e.children).reverse().map(D), o = await Promise.allSettled(s), a = [t].concat(o).filter(
107
132
  (c) => c.status === "rejected"
108
133
  );
109
- if (n.length)
110
- throw b(n.map((c) => c.reason));
134
+ if (a.length)
135
+ throw E(a.map((c) => c.reason));
111
136
  }
112
- }, b = (t, e) => t.length === 1 ? t[0] : new B(t, e);
113
- class B extends Error {
114
- constructor(e, r) {
137
+ }, E = (r, e) => r.length === 1 ? r[0] : new M(r, e);
138
+ class M extends Error {
139
+ constructor(e, t) {
115
140
  super(
116
141
  "Some resources could not be destroyed. See the `failures` property for details.",
117
- r
142
+ t
118
143
  ), this.failures = e;
119
144
  }
120
145
  }
146
+ const Y = async (r, ...e) => k(/* @__PURE__ */ Object.create(null), r, ...e);
121
147
  export {
122
- A as bindContext,
123
- R as create,
124
- S as destroy
148
+ T as bindContext,
149
+ Y as create,
150
+ U as createContext,
151
+ D as destroy
125
152
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wardens",
3
- "version": "0.5.1",
3
+ "version": "0.6.0-rc.1",
4
4
  "description": "A framework for resource management",
5
5
  "type": "module",
6
6
  "main": "./dist/wardens.cjs",
@@ -79,11 +79,11 @@
79
79
  }
80
80
  },
81
81
  "devDependencies": {
82
- "@typescript-eslint/eslint-plugin": "6.3.0",
83
- "@typescript-eslint/parser": "6.3.0",
82
+ "@typescript-eslint/eslint-plugin": "6.4.0",
83
+ "@typescript-eslint/parser": "6.4.0",
84
84
  "eslint": "8.47.0",
85
85
  "husky": "8.0.3",
86
- "lint-staged": "13.2.3",
86
+ "lint-staged": "14.0.0",
87
87
  "prettier": "2.8.8",
88
88
  "typescript": "5.1.6",
89
89
  "vite": "4.4.9",
@@ -0,0 +1,198 @@
1
+ import ResourceControls from '../resource-controls';
2
+ import { create } from '../';
3
+ import { createContext } from '../inherited-context';
4
+
5
+ describe('ResourceControls', () => {
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: ResourceControls) => {
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: ResourceControls) => {
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: ResourceControls) => {
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 }: ResourceControls) {
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: ResourceControls) {
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: ResourceControls) => {
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: ResourceControls) => {
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: ResourceControls) => ({
111
+ value: { content: ctx.getContext(SharedValue) },
112
+ });
113
+
114
+ const Parent = async (resource: ResourceControls) => {
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: ResourceControls) => {
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: ResourceControls) => {
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: ResourceControls, msg: string) => {
156
+ resource.setContext(Message, msg);
157
+
158
+ return {
159
+ value: { getMessage: () => resource.getContext(Message) },
160
+ };
161
+ };
162
+
163
+ const Parent = async (resource: ResourceControls) => {
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: ResourceControls) => ({
182
+ value: { getMessage: () => resource.getContext(Message) },
183
+ });
184
+
185
+ const Parent = async (resource: ResourceControls) => ({
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
+ });
@@ -1,13 +1,13 @@
1
- import type ResourceContext from '../resource-context';
2
- import { create, destroy } from '../allocation';
1
+ import type ResourceControls from '../resource-controls';
2
+ import { create, destroy } from '../root-lifecycle';
3
3
  import bindContext from '../bind-context';
4
4
 
5
- describe('allocation', () => {
5
+ describe('roots', () => {
6
6
  describe('create', () => {
7
7
  it('allocates the resource', async () => {
8
8
  const config = { test: 'init-args' };
9
9
  const Test = vi.fn(
10
- async (_resource: ResourceContext, config: { test: string }) => ({
10
+ async (_resource: ResourceControls, config: { test: string }) => ({
11
11
  value: config,
12
12
  }),
13
13
  );
@@ -22,7 +22,7 @@ describe('allocation', () => {
22
22
  const First = async () => ({ value: [], destroy: () => spy('1st') });
23
23
  const Second = async () => ({ value: [], destroy: () => spy('2nd') });
24
24
 
25
- const Parent = async (resource: ResourceContext) => {
25
+ const Parent = async (resource: ResourceControls) => {
26
26
  await resource.create(First);
27
27
  await resource.create(Second);
28
28
  throw new Error('Testing resource initialization errors');
@@ -42,7 +42,7 @@ describe('allocation', () => {
42
42
  },
43
43
  });
44
44
 
45
- const Parent = async (resource: ResourceContext) => {
45
+ const Parent = async (resource: ResourceControls) => {
46
46
  await resource.create(Child);
47
47
  await resource.create(Child);
48
48
  throw parentError;
@@ -89,7 +89,7 @@ describe('allocation', () => {
89
89
  it('automatically unmounts all children', async () => {
90
90
  const spy = vi.fn();
91
91
  const Child = async () => ({ value: [], destroy: spy });
92
- async function Parent(resource: ResourceContext) {
92
+ async function Parent(resource: ResourceControls) {
93
93
  await resource.create(Child);
94
94
  return { value: [] };
95
95
  }
@@ -109,7 +109,7 @@ describe('allocation', () => {
109
109
  },
110
110
  });
111
111
 
112
- const Parent = async (resource: ResourceContext) => {
112
+ const Parent = async (resource: ResourceControls) => {
113
113
  await resource.create(Child);
114
114
  return { value: [] };
115
115
  };
@@ -123,7 +123,7 @@ describe('allocation', () => {
123
123
  const First = async () => ({ value: [], destroy: () => spy('1st') });
124
124
  const Second = async () => ({ value: [], destroy: () => spy('2nd') });
125
125
 
126
- const Parent = async (resource: ResourceContext) => {
126
+ const Parent = async (resource: ResourceControls) => {
127
127
  await resource.create(First);
128
128
  await resource.create(Second);
129
129
  return { value: [] };
@@ -144,7 +144,7 @@ describe('allocation', () => {
144
144
  },
145
145
  });
146
146
 
147
- const Parent = async (resource: ResourceContext) => {
147
+ const Parent = async (resource: ResourceControls) => {
148
148
  await resource.create(Child);
149
149
  await resource.create(Child);
150
150
  return { value: [] };
@@ -158,7 +158,7 @@ describe('allocation', () => {
158
158
 
159
159
  it('ensures child resources outlive their consumers', async () => {
160
160
  const Child = async () => ({ value: [1] });
161
- const Parent = async (resource: ResourceContext) => {
161
+ const Parent = async (resource: ResourceControls) => {
162
162
  const child = await resource.create(Child);
163
163
  return {
164
164
  value: [],
@@ -176,7 +176,7 @@ describe('allocation', () => {
176
176
  it('destroys child resources even if the parent fails to close', async () => {
177
177
  const spy = vi.fn();
178
178
  const Child = async () => ({ value: [], destroy: spy });
179
- const Parent = async (resource: ResourceContext) => {
179
+ const Parent = async (resource: ResourceControls) => {
180
180
  await resource.create(Child);
181
181
  return {
182
182
  value: [],
@@ -202,7 +202,7 @@ describe('allocation', () => {
202
202
  },
203
203
  });
204
204
 
205
- const Parent = async (resource: ResourceContext) => {
205
+ const Parent = async (resource: ResourceControls) => {
206
206
  await resource.create(Child);
207
207
  return {
208
208
  value: [],
@@ -220,7 +220,7 @@ describe('allocation', () => {
220
220
 
221
221
  it('guards against creating new resources after teardown', async () => {
222
222
  const Child = async () => ({ value: [] });
223
- const Parent = async (resource: ResourceContext) => ({
223
+ const Parent = async (resource: ResourceControls) => ({
224
224
  value: bindContext(resource),
225
225
  });
226
226