wardens 0.5.1 → 0.6.0-rc.0

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});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(v){const W=Array.from(o).reverse(),S=(await Promise.allSettled(W.map(d=>a.destroy(d)))).filter(d=>d.status==="rejected");throw S.length?D(S.map(d=>d.reason),{cause:v}):v}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,125 @@
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) => {
82
+ n(this, i)[x.getId(e)] = t;
83
+ });
84
+ /** Retrieve a value from context, or a default if it is unset. */
85
+ f(this, "getContext", (e) => {
86
+ const t = x.getId(e);
87
+ return t in n(this, i) ? n(this, i)[t] : x.getDefaultValue(e);
61
88
  });
62
- y(this, i, e), y(this, u, r);
89
+ h(this, i, e), h(this, u, t), h(this, p, s);
63
90
  }
64
91
  }
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;
92
+ y = new WeakMap(), u = new WeakMap(), p = new WeakMap(), i = new WeakMap();
93
+ const k = async (r, e, ...t) => {
94
+ const s = { enforced: !1 }, o = /* @__PURE__ */ new Set(), a = new A(r, o, s);
95
+ let c;
69
96
  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))
97
+ c = await e(a, ...t);
98
+ } catch (b) {
99
+ const I = Array.from(o).reverse(), v = (await Promise.allSettled(
100
+ I.map((d) => a.destroy(d))
74
101
  )).filter(
75
102
  (d) => d.status === "rejected"
76
103
  );
77
- throw v.length ? b(
104
+ throw v.length ? E(
78
105
  v.map((d) => d.reason),
79
- { cause: m }
80
- ) : m;
106
+ { cause: b }
107
+ ) : b;
81
108
  }
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,
109
+ const P = c.value, { proxy: m, revoke: W } = B(P);
110
+ return j.add(m), C.set(m, {
111
+ curfew: s,
112
+ resource: c,
86
113
  children: o,
87
- revoke: k
88
- }), w;
89
- }, S = async (t) => {
90
- if (!g.has(t))
114
+ revoke: W
115
+ }), m;
116
+ }, D = async (r) => {
117
+ if (!j.has(r))
91
118
  throw new Error("Cannot destroy object. It is not a resource.");
92
- const e = p.get(t);
119
+ const e = C.get(r);
93
120
  if (e) {
94
- p.delete(t), e.revoke();
95
- let r = {
121
+ C.delete(r), e.revoke();
122
+ let t = {
96
123
  status: "fulfilled",
97
124
  value: void 0
98
125
  };
@@ -100,26 +127,28 @@ const R = async (t, ...e) => {
100
127
  try {
101
128
  await e.resource.destroy();
102
129
  } catch (c) {
103
- r = { status: "rejected", reason: c };
130
+ t = { status: "rejected", reason: c };
104
131
  }
105
132
  e.curfew.enforced = !0;
106
- const o = Array.from(e.children).reverse().map(S), s = await Promise.allSettled(o), n = [r].concat(s).filter(
133
+ const s = Array.from(e.children).reverse().map(D), o = await Promise.allSettled(s), a = [t].concat(o).filter(
107
134
  (c) => c.status === "rejected"
108
135
  );
109
- if (n.length)
110
- throw b(n.map((c) => c.reason));
136
+ if (a.length)
137
+ throw E(a.map((c) => c.reason));
111
138
  }
112
- }, b = (t, e) => t.length === 1 ? t[0] : new B(t, e);
113
- class B extends Error {
114
- constructor(e, r) {
139
+ }, E = (r, e) => r.length === 1 ? r[0] : new M(r, e);
140
+ class M extends Error {
141
+ constructor(e, t) {
115
142
  super(
116
143
  "Some resources could not be destroyed. See the `failures` property for details.",
117
- r
144
+ t
118
145
  ), this.failures = e;
119
146
  }
120
147
  }
148
+ const Y = async (r, ...e) => k(/* @__PURE__ */ Object.create(null), r, ...e);
121
149
  export {
122
- A as bindContext,
123
- R as create,
124
- S as destroy
150
+ T as bindContext,
151
+ Y as create,
152
+ U as createContext,
153
+ D as destroy
125
154
  };
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.0",
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,186 @@
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('passes context to child resources', async () => {
96
+ const SharedValue = createContext<null | string>(() => null);
97
+
98
+ const Child = async (ctx: ResourceControls) => ({
99
+ value: { content: ctx.getContext(SharedValue) },
100
+ });
101
+
102
+ const Parent = async (resource: ResourceControls) => {
103
+ resource.setContext(SharedValue, 'inherited');
104
+ return { value: await resource.create(Child) };
105
+ };
106
+
107
+ await expect(create(Parent)).resolves.toEqual({ content: 'inherited' });
108
+ });
109
+
110
+ it('can override context without affecting the parent', async () => {
111
+ const Message = createContext<null | string>(() => null);
112
+ const Child = async (resource: ResourceControls) => {
113
+ // This should *NOT* affect the parent context.
114
+ resource.setContext(Message, 'child context');
115
+
116
+ return {
117
+ value: { content: resource.getContext(Message) },
118
+ };
119
+ };
120
+
121
+ const Parent = async (resource: ResourceControls) => {
122
+ resource.setContext(Message, 'parent context');
123
+ const child = await resource.create(Child);
124
+
125
+ expect(resource.getContext(Message)).toBe('parent context');
126
+
127
+ return {
128
+ value: {
129
+ content: resource.getContext(Message),
130
+ child,
131
+ },
132
+ };
133
+ };
134
+
135
+ await expect(create(Parent)).resolves.toEqual({
136
+ content: 'parent context',
137
+ child: { content: 'child context' },
138
+ });
139
+ });
140
+
141
+ it('allows two siblings to have different context values', async () => {
142
+ const Message = createContext<null | string>(() => null);
143
+ const Child = async (resource: ResourceControls, msg: string) => {
144
+ resource.setContext(Message, msg);
145
+
146
+ return {
147
+ value: { getMessage: () => resource.getContext(Message) },
148
+ };
149
+ };
150
+
151
+ const Parent = async (resource: ResourceControls) => {
152
+ resource.setContext(Message, 'parent context');
153
+
154
+ return {
155
+ value: await Promise.all([
156
+ resource.create(Child, 'child context 1'),
157
+ resource.create(Child, 'child context 2'),
158
+ ]),
159
+ };
160
+ };
161
+
162
+ const [child1, child2] = await create(Parent);
163
+ expect(child1.getMessage()).toBe('child context 1');
164
+ expect(child2.getMessage()).toBe('child context 2');
165
+ });
166
+
167
+ it('provides a live view of the current value, not just a snapshot', async () => {
168
+ const Message = createContext(() => 'default');
169
+ const Child = async (resource: ResourceControls) => ({
170
+ value: { getMessage: () => resource.getContext(Message) },
171
+ });
172
+
173
+ const Parent = async (resource: ResourceControls) => ({
174
+ value: {
175
+ setMessage: (msg: string) => resource.setContext(Message, msg),
176
+ child: await resource.create(Child),
177
+ },
178
+ });
179
+
180
+ const { setMessage, child } = await create(Parent);
181
+ expect(child.getMessage()).toBe('default');
182
+
183
+ setMessage('updated');
184
+ expect(child.getMessage()).toBe('updated');
185
+ });
186
+ });
@@ -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