wardens 0.6.0-rc.1 → 0.6.0-rc.3

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -12,7 +12,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
12
12
 
13
13
  ### Changed
14
14
 
15
- - Renamed public type `ResourceContext` to `ResourceControls`.
15
+ - Renamed public type `ResourceContext` to `ResourceScope`.
16
+ - `destroy(...)` is no longer allowed to destroy child resources, only roots.
17
+ - Added tree-shaking metadata. This is useful for bundle analysis tools.
16
18
 
17
19
  ## [0.5.1] - 2023-08-12
18
20
 
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 }: ResourceControls,
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 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;
1
+ "use strict";var R=Object.defineProperty;var B=(e,t,r)=>t in e?R(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var f=(e,t,r)=>(B(e,typeof t!="symbol"?t+"":t,r),r),j=(e,t,r)=>{if(!t.has(e))throw TypeError("Cannot "+r)};var s=(e,t,r)=>(j(e,t,"read from private field"),r?r.call(e):t.get(e)),d=(e,t,r)=>{if(t.has(e))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(e):t.set(e,r)},h=(e,t,r,o)=>(j(e,t,"write to private field"),o?o.call(e,r):t.set(e,r),r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function M(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,k=new WeakSet,E=new WeakSet;function A(e){return Proxy.revocable(e,{})}var C,w;class x{constructor(t){d(this,C,Symbol("Context ID"));d(this,w,void 0);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 T=e=>new x(e);var y,l,p,i;class V{constructor(t,r,o){d(this,y,new WeakSet);d(this,l,void 0);d(this,p,void 0);d(this,i,void 0);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 P(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 P=async(e,t,...r)=>{const o={enforced:!1},n=new Set,a=new V(e,n,o);let c;try{c=await t(a,...r)}catch(S){const O=Array.from(n).reverse(),v=(await Promise.allSettled(O.map(u=>a.destroy(u)))).filter(u=>u.status==="rejected");throw v.length?D(v.map(u=>u.reason),{cause:S}):S}const W=c.value,{proxy:b,revoke:I}=A(W);return k.add(b),g.set(b,{curfew:o,resource:c,children:n,revoke:I}),b},m=async e=>{if(!k.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 D(a.map(c=>c.reason))}},D=(e,t)=>e.length===1?e[0]:new H(e,t);class H extends Error{constructor(t,r){super("Some resources could not be destroyed. See the `failures` property for details.",r),this.failures=t}}const U=async(e,...t)=>{const o=await P(Object.create(null),e,...t);return E.add(o),o},Y=async e=>{if(g.has(e)&&!E.has(e))throw new Error("Cannot destroy child resource. It is owned by another scope.");return m(e)};exports.bindContext=M;exports.create=U;exports.createContext=T;exports.destroy=Y;
package/dist/wardens.js CHANGED
@@ -1,152 +1,161 @@
1
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);
2
+ var B = (e, t, r) => t in e ? O(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
3
+ var h = (e, t, r) => (B(e, typeof t != "symbol" ? t + "" : t, r), r), j = (e, t, r) => {
4
+ if (!t.has(e))
5
+ throw TypeError("Cannot " + r);
6
6
  };
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))
7
+ var n = (e, t, r) => (j(e, t, "read from private field"), r ? r.call(e) : t.get(e)), d = (e, t, r) => {
8
+ if (t.has(e))
9
9
  throw TypeError("Cannot add the same private member more than once");
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
- const e = /* @__PURE__ */ new WeakMap();
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);
10
+ t instanceof WeakSet ? t.add(e) : t.set(e, r);
11
+ }, f = (e, t, r, o) => (j(e, t, "write to private field"), o ? o.call(e, r) : t.set(e, r), r);
12
+ function U(e) {
13
+ const t = /* @__PURE__ */ new WeakMap();
14
+ return new Proxy(e, {
15
+ get(r, o) {
16
+ const s = Reflect.get(r, o, r);
17
+ if (typeof s == "function") {
18
+ if (t.has(s) === !1) {
19
+ const a = s.bind(r);
20
20
  Object.defineProperties(
21
21
  a,
22
- Object.getOwnPropertyDescriptors(o)
23
- ), e.set(o, a);
22
+ Object.getOwnPropertyDescriptors(s)
23
+ ), t.set(s, a);
24
24
  }
25
- return e.get(o);
25
+ return t.get(s);
26
26
  }
27
- return o;
27
+ return s;
28
28
  },
29
- set(t, s, o) {
30
- return Reflect.set(t, s, o, t);
29
+ set(r, o, s) {
30
+ return Reflect.set(r, o, s, r);
31
31
  }
32
32
  });
33
33
  }
34
- const C = /* @__PURE__ */ new WeakMap(), j = /* @__PURE__ */ new WeakSet();
35
- function B(r) {
36
- return Proxy.revocable(r, {});
34
+ const g = /* @__PURE__ */ new WeakMap(), k = /* @__PURE__ */ new WeakSet(), E = /* @__PURE__ */ new WeakSet();
35
+ function A(e) {
36
+ return Proxy.revocable(e, {});
37
37
  }
38
- var g, w;
38
+ var m, w;
39
39
  class x {
40
- constructor(e) {
41
- l(this, g, Symbol("Context ID"));
42
- l(this, w, void 0);
43
- h(this, w, e);
40
+ constructor(t) {
41
+ d(this, m, Symbol("Context ID"));
42
+ d(this, w, void 0);
43
+ f(this, w, t);
44
44
  }
45
- static getId(e) {
46
- return n(e, g);
45
+ static getId(t) {
46
+ return n(t, m);
47
47
  }
48
- static getDefaultValue(e) {
49
- var t;
50
- return n(t = e, w).call(t);
48
+ static getDefaultValue(t) {
49
+ var r;
50
+ return n(r = t, w).call(r);
51
51
  }
52
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);
53
+ m = new WeakMap(), w = new WeakMap();
54
+ const Y = (e) => new x(e);
55
+ var y, l, p, i;
56
+ class M {
57
+ constructor(t, r, o) {
58
+ d(this, y, /* @__PURE__ */ new WeakSet());
59
+ d(this, l, void 0);
60
+ d(this, p, void 0);
61
+ d(this, i, void 0);
62
62
  /** Provision an owned resource and make sure it doesn't outlive us. */
63
- f(this, "create", async (e, ...t) => {
63
+ h(this, "create", async (t, ...r) => {
64
64
  if (n(this, p).enforced)
65
65
  throw new Error("Cannot create new resources after teardown.");
66
- const s = Object.create(n(this, i)), o = await k(s, e, ...t);
67
- return n(this, u).add(o), o;
66
+ const o = Object.create(n(this, i)), s = await D(o, t, ...r);
67
+ return n(this, l).add(s), s;
68
68
  });
69
69
  /**
70
70
  * Tear down a resource. Happens automatically when resource owners are
71
71
  * deallocated.
72
72
  */
73
- f(this, "destroy", async (e) => {
74
- if (n(this, y).has(e))
73
+ h(this, "destroy", async (t) => {
74
+ if (n(this, y).has(t))
75
75
  throw new Error("Resource already destroyed.");
76
- if (!n(this, u).has(e))
76
+ if (!n(this, l).has(t))
77
77
  throw new Error("You do not own this resource.");
78
- n(this, u).delete(e), n(this, y).add(e), await D(e);
78
+ n(this, l).delete(t), n(this, y).add(t), await b(t);
79
79
  });
80
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));
81
+ h(this, "setContext", (t, r) => (n(this, i)[x.getId(t)] = r, r));
82
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);
83
+ h(this, "getContext", (t) => {
84
+ const r = x.getId(t);
85
+ return r in n(this, i) ? n(this, i)[r] : x.getDefaultValue(t);
86
86
  });
87
- h(this, i, e), h(this, u, t), h(this, p, s);
87
+ f(this, i, t), f(this, l, r), f(this, p, o);
88
88
  }
89
89
  }
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);
90
+ y = new WeakMap(), l = new WeakMap(), p = new WeakMap(), i = new WeakMap();
91
+ const D = async (e, t, ...r) => {
92
+ const o = { enforced: !1 }, s = /* @__PURE__ */ new Set(), a = new M(e, s, o);
93
93
  let c;
94
94
  try {
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))
95
+ c = await t(a, ...r);
96
+ } catch (S) {
97
+ const R = Array.from(s).reverse(), v = (await Promise.allSettled(
98
+ R.map((u) => a.destroy(u))
99
99
  )).filter(
100
- (d) => d.status === "rejected"
100
+ (u) => u.status === "rejected"
101
101
  );
102
- throw v.length ? E(
103
- v.map((d) => d.reason),
104
- { cause: b }
105
- ) : b;
102
+ throw v.length ? P(
103
+ v.map((u) => u.reason),
104
+ { cause: S }
105
+ ) : S;
106
106
  }
107
- const P = c.value, { proxy: m, revoke: W } = B(P);
108
- return j.add(m), C.set(m, {
109
- curfew: s,
107
+ const W = c.value, { proxy: C, revoke: I } = A(W);
108
+ return k.add(C), g.set(C, {
109
+ curfew: o,
110
110
  resource: c,
111
- children: o,
112
- revoke: W
113
- }), m;
114
- }, D = async (r) => {
115
- if (!j.has(r))
111
+ children: s,
112
+ revoke: I
113
+ }), C;
114
+ }, b = async (e) => {
115
+ if (!k.has(e))
116
116
  throw new Error("Cannot destroy object. It is not a resource.");
117
- const e = C.get(r);
118
- if (e) {
119
- C.delete(r), e.revoke();
120
- let t = {
117
+ const t = g.get(e);
118
+ if (t) {
119
+ g.delete(e), t.revoke();
120
+ let r = {
121
121
  status: "fulfilled",
122
122
  value: void 0
123
123
  };
124
- if (e.resource.destroy)
124
+ if (t.resource.destroy)
125
125
  try {
126
- await e.resource.destroy();
126
+ await t.resource.destroy();
127
127
  } catch (c) {
128
- t = { status: "rejected", reason: c };
128
+ r = { status: "rejected", reason: c };
129
129
  }
130
- e.curfew.enforced = !0;
131
- const s = Array.from(e.children).reverse().map(D), o = await Promise.allSettled(s), a = [t].concat(o).filter(
130
+ t.curfew.enforced = !0;
131
+ const o = Array.from(t.children).reverse().map(b), s = await Promise.allSettled(o), a = [r].concat(s).filter(
132
132
  (c) => c.status === "rejected"
133
133
  );
134
134
  if (a.length)
135
- throw E(a.map((c) => c.reason));
135
+ throw P(a.map((c) => c.reason));
136
136
  }
137
- }, E = (r, e) => r.length === 1 ? r[0] : new M(r, e);
138
- class M extends Error {
139
- constructor(e, t) {
137
+ }, P = (e, t) => e.length === 1 ? e[0] : new V(e, t);
138
+ class V extends Error {
139
+ constructor(t, r) {
140
140
  super(
141
141
  "Some resources could not be destroyed. See the `failures` property for details.",
142
- t
143
- ), this.failures = e;
142
+ r
143
+ ), this.failures = t;
144
144
  }
145
145
  }
146
- const Y = async (r, ...e) => k(/* @__PURE__ */ Object.create(null), r, ...e);
146
+ const q = async (e, ...t) => {
147
+ const o = await D(/* @__PURE__ */ Object.create(null), e, ...t);
148
+ return E.add(o), o;
149
+ }, z = async (e) => {
150
+ if (g.has(e) && !E.has(e))
151
+ throw new Error(
152
+ "Cannot destroy child resource. It is owned by another scope."
153
+ );
154
+ return b(e);
155
+ };
147
156
  export {
148
- T as bindContext,
149
- Y as create,
150
- U as createContext,
151
- D as destroy
157
+ U as bindContext,
158
+ q as create,
159
+ Y as createContext,
160
+ z as destroy
152
161
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wardens",
3
- "version": "0.6.0-rc.1",
3
+ "version": "0.6.0-rc.3",
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",
@@ -79,13 +80,13 @@
79
80
  }
80
81
  },
81
82
  "devDependencies": {
82
- "@typescript-eslint/eslint-plugin": "6.4.0",
83
- "@typescript-eslint/parser": "6.4.0",
84
- "eslint": "8.47.0",
83
+ "@typescript-eslint/eslint-plugin": "6.4.1",
84
+ "@typescript-eslint/parser": "6.4.1",
85
+ "eslint": "8.48.0",
85
86
  "husky": "8.0.3",
86
- "lint-staged": "14.0.0",
87
+ "lint-staged": "14.0.1",
87
88
  "prettier": "2.8.8",
88
- "typescript": "5.1.6",
89
+ "typescript": "5.2.2",
89
90
  "vite": "4.4.9",
90
91
  "vitest": "^0.34.0"
91
92
  }
@@ -1,8 +1,8 @@
1
- import ResourceControls from '../resource-controls';
1
+ import ResourceScope from '../resource-scope';
2
2
  import { create } from '../';
3
3
  import { createContext } from '../inherited-context';
4
4
 
5
- describe('ResourceControls', () => {
5
+ describe('ResourceScope', () => {
6
6
  async function Test() {
7
7
  return { value: {} };
8
8
  }
@@ -12,7 +12,7 @@ describe('ResourceControls', () => {
12
12
  value: { child: true },
13
13
  });
14
14
 
15
- const Parent = async (resource: ResourceControls) => {
15
+ const Parent = async (resource: ResourceScope) => {
16
16
  return { value: await resource.create(Child) };
17
17
  };
18
18
 
@@ -27,7 +27,7 @@ describe('ResourceControls', () => {
27
27
  destroy: spy,
28
28
  });
29
29
 
30
- const Parent = async (resource: ResourceControls) => {
30
+ const Parent = async (resource: ResourceScope) => {
31
31
  const child = await resource.create(Child);
32
32
  await resource.destroy(child);
33
33
 
@@ -43,7 +43,7 @@ describe('ResourceControls', () => {
43
43
  it('fails to destroy resources owned by someone else', async () => {
44
44
  const test = await create(Test);
45
45
 
46
- const Sneaky = async (resource: ResourceControls) => {
46
+ const Sneaky = async (resource: ResourceScope) => {
47
47
  await resource.destroy(test);
48
48
  return { value: {} };
49
49
  };
@@ -52,7 +52,7 @@ describe('ResourceControls', () => {
52
52
  });
53
53
 
54
54
  it('binds create/destroy handlers to the class instance', async () => {
55
- async function Allocator({ create, destroy }: ResourceControls) {
55
+ async function Allocator({ create, destroy }: ResourceScope) {
56
56
  const test = await create(Test);
57
57
  await destroy(test);
58
58
 
@@ -63,7 +63,7 @@ describe('ResourceControls', () => {
63
63
  });
64
64
 
65
65
  it('indicates if a resource was already destroyed', async () => {
66
- async function Allocator(resource: ResourceControls) {
66
+ async function Allocator(resource: ResourceScope) {
67
67
  const test = await resource.create(Test);
68
68
  await resource.destroy(test);
69
69
  await resource.destroy(test);
@@ -77,7 +77,7 @@ describe('ResourceControls', () => {
77
77
  it('can set and retrieve context', async () => {
78
78
  const SharedValue = createContext(() => 'none');
79
79
 
80
- const Test = async (resource: ResourceControls) => {
80
+ const Test = async (resource: ResourceScope) => {
81
81
  expect(resource.getContext(SharedValue)).toBe('none');
82
82
 
83
83
  resource.setContext(SharedValue, 'saved');
@@ -95,7 +95,7 @@ describe('ResourceControls', () => {
95
95
  it('returns the new context value', async () => {
96
96
  const SharedValue = createContext(() => 'none');
97
97
 
98
- const Test = async (resource: ResourceControls) => {
98
+ const Test = async (resource: ResourceScope) => {
99
99
  return {
100
100
  value: { value: resource.setContext(SharedValue, 'returned') },
101
101
  };
@@ -107,11 +107,11 @@ describe('ResourceControls', () => {
107
107
  it('passes context to child resources', async () => {
108
108
  const SharedValue = createContext<null | string>(() => null);
109
109
 
110
- const Child = async (ctx: ResourceControls) => ({
110
+ const Child = async (ctx: ResourceScope) => ({
111
111
  value: { content: ctx.getContext(SharedValue) },
112
112
  });
113
113
 
114
- const Parent = async (resource: ResourceControls) => {
114
+ const Parent = async (resource: ResourceScope) => {
115
115
  resource.setContext(SharedValue, 'inherited');
116
116
  return { value: await resource.create(Child) };
117
117
  };
@@ -121,7 +121,7 @@ describe('ResourceControls', () => {
121
121
 
122
122
  it('can override context without affecting the parent', async () => {
123
123
  const Message = createContext<null | string>(() => null);
124
- const Child = async (resource: ResourceControls) => {
124
+ const Child = async (resource: ResourceScope) => {
125
125
  // This should *NOT* affect the parent context.
126
126
  resource.setContext(Message, 'child context');
127
127
 
@@ -130,7 +130,7 @@ describe('ResourceControls', () => {
130
130
  };
131
131
  };
132
132
 
133
- const Parent = async (resource: ResourceControls) => {
133
+ const Parent = async (resource: ResourceScope) => {
134
134
  resource.setContext(Message, 'parent context');
135
135
  const child = await resource.create(Child);
136
136
 
@@ -152,7 +152,7 @@ describe('ResourceControls', () => {
152
152
 
153
153
  it('allows two siblings to have different context values', async () => {
154
154
  const Message = createContext<null | string>(() => null);
155
- const Child = async (resource: ResourceControls, msg: string) => {
155
+ const Child = async (resource: ResourceScope, msg: string) => {
156
156
  resource.setContext(Message, msg);
157
157
 
158
158
  return {
@@ -160,7 +160,7 @@ describe('ResourceControls', () => {
160
160
  };
161
161
  };
162
162
 
163
- const Parent = async (resource: ResourceControls) => {
163
+ const Parent = async (resource: ResourceScope) => {
164
164
  resource.setContext(Message, 'parent context');
165
165
 
166
166
  return {
@@ -178,11 +178,11 @@ describe('ResourceControls', () => {
178
178
 
179
179
  it('provides a live view of the current value, not just a snapshot', async () => {
180
180
  const Message = createContext(() => 'default');
181
- const Child = async (resource: ResourceControls) => ({
181
+ const Child = async (resource: ResourceScope) => ({
182
182
  value: { getMessage: () => resource.getContext(Message) },
183
183
  });
184
184
 
185
- const Parent = async (resource: ResourceControls) => ({
185
+ const Parent = async (resource: ResourceScope) => ({
186
186
  value: {
187
187
  setMessage: (msg: string) => resource.setContext(Message, msg),
188
188
  child: await resource.create(Child),
@@ -1,5 +1,5 @@
1
- import type ResourceControls from '../resource-controls';
2
- import { create, destroy } from '../root-lifecycle';
1
+ import type ResourceScope from '../resource-scope';
2
+ import { create, destroy } from '../';
3
3
  import bindContext from '../bind-context';
4
4
 
5
5
  describe('roots', () => {
@@ -7,7 +7,7 @@ describe('roots', () => {
7
7
  it('allocates the resource', async () => {
8
8
  const config = { test: 'init-args' };
9
9
  const Test = vi.fn(
10
- async (_resource: ResourceControls, config: { test: string }) => ({
10
+ async (_resource: ResourceScope, config: { test: string }) => ({
11
11
  value: config,
12
12
  }),
13
13
  );
@@ -22,7 +22,7 @@ describe('roots', () => {
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: ResourceControls) => {
25
+ const Parent = async (resource: ResourceScope) => {
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('roots', () => {
42
42
  },
43
43
  });
44
44
 
45
- const Parent = async (resource: ResourceControls) => {
45
+ const Parent = async (resource: ResourceScope) => {
46
46
  await resource.create(Child);
47
47
  await resource.create(Child);
48
48
  throw parentError;
@@ -89,7 +89,7 @@ describe('roots', () => {
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: ResourceControls) {
92
+ async function Parent(resource: ResourceScope) {
93
93
  await resource.create(Child);
94
94
  return { value: [] };
95
95
  }
@@ -109,7 +109,7 @@ describe('roots', () => {
109
109
  },
110
110
  });
111
111
 
112
- const Parent = async (resource: ResourceControls) => {
112
+ const Parent = async (resource: ResourceScope) => {
113
113
  await resource.create(Child);
114
114
  return { value: [] };
115
115
  };
@@ -123,7 +123,7 @@ describe('roots', () => {
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: ResourceControls) => {
126
+ const Parent = async (resource: ResourceScope) => {
127
127
  await resource.create(First);
128
128
  await resource.create(Second);
129
129
  return { value: [] };
@@ -144,7 +144,7 @@ describe('roots', () => {
144
144
  },
145
145
  });
146
146
 
147
- const Parent = async (resource: ResourceControls) => {
147
+ const Parent = async (resource: ResourceScope) => {
148
148
  await resource.create(Child);
149
149
  await resource.create(Child);
150
150
  return { value: [] };
@@ -158,7 +158,7 @@ describe('roots', () => {
158
158
 
159
159
  it('ensures child resources outlive their consumers', async () => {
160
160
  const Child = async () => ({ value: [1] });
161
- const Parent = async (resource: ResourceControls) => {
161
+ const Parent = async (resource: ResourceScope) => {
162
162
  const child = await resource.create(Child);
163
163
  return {
164
164
  value: [],
@@ -176,7 +176,7 @@ describe('roots', () => {
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: ResourceControls) => {
179
+ const Parent = async (resource: ResourceScope) => {
180
180
  await resource.create(Child);
181
181
  return {
182
182
  value: [],
@@ -202,7 +202,7 @@ describe('roots', () => {
202
202
  },
203
203
  });
204
204
 
205
- const Parent = async (resource: ResourceControls) => {
205
+ const Parent = async (resource: ResourceScope) => {
206
206
  await resource.create(Child);
207
207
  return {
208
208
  value: [],
@@ -220,7 +220,7 @@ describe('roots', () => {
220
220
 
221
221
  it('guards against creating new resources after teardown', async () => {
222
222
  const Child = async () => ({ value: [] });
223
- const Parent = async (resource: ResourceControls) => ({
223
+ const Parent = async (resource: ResourceScope) => ({
224
224
  value: bindContext(resource),
225
225
  });
226
226
 
@@ -232,5 +232,18 @@ describe('roots', () => {
232
232
  /cannot create.*after teardown/i,
233
233
  );
234
234
  });
235
+
236
+ it('refuses to destroy non-root resources', async () => {
237
+ const Child = async () => ({ value: [] });
238
+ const Parent = async ({ create }: ResourceScope) => ({
239
+ value: { child: await create(Child) },
240
+ });
241
+
242
+ const { child } = await create(Parent);
243
+
244
+ await expect(destroy(child)).rejects.toThrow(
245
+ /cannot destroy child resource/i,
246
+ );
247
+ });
235
248
  });
236
249
  });
@@ -1,5 +1,5 @@
1
- import type ResourceControls from '../resource-controls';
2
- import { create, destroy } from '../root-lifecycle';
1
+ import type ResourceScope from '../resource-scope';
2
+ import { create, destroy } from '../';
3
3
  import bindContext from '../bind-context';
4
4
 
5
5
  describe('roots', () => {
@@ -7,7 +7,7 @@ describe('roots', () => {
7
7
  it('allocates the resource', async () => {
8
8
  const config = { test: 'init-args' };
9
9
  const Test = vi.fn(
10
- async (_resource: ResourceControls, config: { test: string }) => ({
10
+ async (_resource: ResourceScope, config: { test: string }) => ({
11
11
  value: config,
12
12
  }),
13
13
  );
@@ -22,7 +22,7 @@ describe('roots', () => {
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: ResourceControls) => {
25
+ const Parent = async (resource: ResourceScope) => {
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('roots', () => {
42
42
  },
43
43
  });
44
44
 
45
- const Parent = async (resource: ResourceControls) => {
45
+ const Parent = async (resource: ResourceScope) => {
46
46
  await resource.create(Child);
47
47
  await resource.create(Child);
48
48
  throw parentError;
@@ -89,7 +89,7 @@ describe('roots', () => {
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: ResourceControls) {
92
+ async function Parent(resource: ResourceScope) {
93
93
  await resource.create(Child);
94
94
  return { value: [] };
95
95
  }
@@ -109,7 +109,7 @@ describe('roots', () => {
109
109
  },
110
110
  });
111
111
 
112
- const Parent = async (resource: ResourceControls) => {
112
+ const Parent = async (resource: ResourceScope) => {
113
113
  await resource.create(Child);
114
114
  return { value: [] };
115
115
  };
@@ -123,7 +123,7 @@ describe('roots', () => {
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: ResourceControls) => {
126
+ const Parent = async (resource: ResourceScope) => {
127
127
  await resource.create(First);
128
128
  await resource.create(Second);
129
129
  return { value: [] };
@@ -144,7 +144,7 @@ describe('roots', () => {
144
144
  },
145
145
  });
146
146
 
147
- const Parent = async (resource: ResourceControls) => {
147
+ const Parent = async (resource: ResourceScope) => {
148
148
  await resource.create(Child);
149
149
  await resource.create(Child);
150
150
  return { value: [] };
@@ -158,7 +158,7 @@ describe('roots', () => {
158
158
 
159
159
  it('ensures child resources outlive their consumers', async () => {
160
160
  const Child = async () => ({ value: [1] });
161
- const Parent = async (resource: ResourceControls) => {
161
+ const Parent = async (resource: ResourceScope) => {
162
162
  const child = await resource.create(Child);
163
163
  return {
164
164
  value: [],
@@ -176,7 +176,7 @@ describe('roots', () => {
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: ResourceControls) => {
179
+ const Parent = async (resource: ResourceScope) => {
180
180
  await resource.create(Child);
181
181
  return {
182
182
  value: [],
@@ -202,7 +202,7 @@ describe('roots', () => {
202
202
  },
203
203
  });
204
204
 
205
- const Parent = async (resource: ResourceControls) => {
205
+ const Parent = async (resource: ResourceScope) => {
206
206
  await resource.create(Child);
207
207
  return {
208
208
  value: [],
@@ -220,7 +220,7 @@ describe('roots', () => {
220
220
 
221
221
  it('guards against creating new resources after teardown', async () => {
222
222
  const Child = async () => ({ value: [] });
223
- const Parent = async (resource: ResourceControls) => ({
223
+ const Parent = async (resource: ResourceScope) => ({
224
224
  value: bindContext(resource),
225
225
  });
226
226
 
@@ -1,4 +1,4 @@
1
- import { create, ResourceControls, ResourceHandle } from '../';
1
+ import { create, ResourceScope, ResourceHandle } from '../';
2
2
 
3
3
  describe('Utility types', () => {
4
4
  describe('ResourceHandle', () => {
@@ -17,7 +17,7 @@ describe('Utility types', () => {
17
17
  });
18
18
 
19
19
  it('infers the type when the value comes from a parameter', async () => {
20
- async function Test(_ctx: ResourceControls, value: { count: number }) {
20
+ async function Test(_ctx: ResourceScope, value: { count: number }) {
21
21
  return {
22
22
  value,
23
23
  };
@@ -10,6 +10,12 @@ export const resources = new WeakMap<object, RevokableResource>();
10
10
  */
11
11
  export const constructed = new WeakSet<object>();
12
12
 
13
+ /**
14
+ * Indicates if an object is a root resource. This is used to prevent
15
+ * child resources from being destroyed by the root `destroy(...)` function.
16
+ */
17
+ export const roots = new WeakSet<object>();
18
+
13
19
  export interface RevokableResource {
14
20
  resource: Resource<object>;
15
21
 
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { default as bindContext } from './bind-context';
2
- export { create, destroy } from './root-lifecycle';
2
+ export { createRoot as create, destroyRoot as destroy } from './root-lifecycle';
3
3
  export { createContext } from './inherited-context';
4
4
  export type { Resource, ResourceHandle } from './utility-types';
5
- export type { default as ResourceControls } from './resource-controls';
5
+ export type { default as ResourceScope } from './resource-scope';
@@ -1,6 +1,6 @@
1
1
  import { resources, constructed } from './global-weakrefs';
2
2
  import wrap from './wrap-with-proxy';
3
- import ResourceControls from './resource-controls';
3
+ import ResourceScope from './resource-scope';
4
4
  import type { InheritedContext } from './inherited-context';
5
5
  import { ResourceFactory, ParametrizedResourceFactory } from './utility-types';
6
6
 
@@ -17,7 +17,7 @@ export const createWithContext = async <
17
17
  ): Promise<Controls> => {
18
18
  const curfew = { enforced: false };
19
19
  const children: Set<object> = new Set();
20
- const context = new ResourceControls(state, children, curfew);
20
+ const context = new ResourceScope(state, children, curfew);
21
21
  let resource: Awaited<ReturnType<typeof factory>>;
22
22
 
23
23
  try {
@@ -60,7 +60,7 @@ export const createWithContext = async <
60
60
 
61
61
  /**
62
62
  * Tear down the resource and all its children, permanently destroying the
63
- * reference.
63
+ * reference. This works on both root resources and child resources.
64
64
  */
65
65
  export const destroy = async (handle: object) => {
66
66
  if (!constructed.has(handle)) {
@@ -8,7 +8,7 @@ import { ResourceFactory, ParametrizedResourceFactory } from './utility-types';
8
8
  * provisioned. It allows them to provision other resources while keeping
9
9
  * track of ownership and lifetimes.
10
10
  */
11
- export default class ResourceControls {
11
+ export default class ResourceScope {
12
12
  #destroyed = new WeakSet<object>();
13
13
  #resources: Set<object>;
14
14
  #curfew: RevokableResource['curfew'];
@@ -1,9 +1,10 @@
1
- import { createWithContext } from './resource-lifecycle';
1
+ import { createWithContext, destroy } from './resource-lifecycle';
2
2
  import { ParametrizedResourceFactory } from './utility-types';
3
3
  import { ResourceFactory } from './utility-types';
4
+ import { resources, roots } from './global-weakrefs';
4
5
 
5
6
  /** Provision a resource and return its external API. */
6
- export const create = async <
7
+ export const createRoot = async <
7
8
  Controls extends object,
8
9
  Args extends Array<unknown>,
9
10
  >(
@@ -14,9 +15,22 @@ export const create = async <
14
15
  ...args: Args
15
16
  ): Promise<Controls> => {
16
17
  const rootContext = Object.create(null);
17
- return createWithContext(rootContext, factory, ...args);
18
+ const root = await createWithContext(rootContext, factory, ...args);
19
+ roots.add(root);
20
+
21
+ return root;
18
22
  };
19
23
 
20
- // Destroying a root resource is the same process as destroying a child
21
- // resource. No need to change the implementation.
22
- export { destroy } from './resource-lifecycle';
24
+ /**
25
+ * Tear down the resource and all its children, permanently destroying the
26
+ * reference. This cannot be used to destroy child resources, only roots.
27
+ */
28
+ export const destroyRoot = async (resource: object): Promise<void> => {
29
+ if (resources.has(resource) && !roots.has(resource)) {
30
+ throw new Error(
31
+ 'Cannot destroy child resource. It is owned by another scope.',
32
+ );
33
+ }
34
+
35
+ return destroy(resource);
36
+ };
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type ResourceControls from './resource-controls';
2
+ import type ResourceScope from './resource-scope';
3
3
 
4
4
  /**
5
5
  * Represents an arbitrary stateful resource that is asynchronously provisioned
@@ -7,14 +7,14 @@ import type ResourceControls from './resource-controls';
7
7
  * first tears down the children.
8
8
  */
9
9
  export interface ResourceFactory<Value extends object> {
10
- (resource: ResourceControls): Promise<Resource<Value>>;
10
+ (resource: ResourceScope): Promise<Resource<Value>>;
11
11
  }
12
12
 
13
13
  export interface ParametrizedResourceFactory<
14
14
  Value extends object,
15
15
  Args extends Array<unknown>,
16
16
  > {
17
- (resource: ResourceControls, ...args: Args): Promise<Resource<Value>>;
17
+ (resource: ResourceScope, ...args: Args): Promise<Resource<Value>>;
18
18
  }
19
19
 
20
20
  export interface Resource<Value extends object> {
@@ -1,236 +0,0 @@
1
- import type ResourceControls from '../resource-controls';
2
- import { create, destroy } from '../root-lifecycle';
3
- import bindContext from '../bind-context';
4
-
5
- describe('roots', () => {
6
- describe('create', () => {
7
- it('allocates the resource', async () => {
8
- const config = { test: 'init-args' };
9
- const Test = vi.fn(
10
- async (_resource: ResourceControls, config: { test: string }) => ({
11
- value: config,
12
- }),
13
- );
14
-
15
- await expect(create(Test, config)).resolves.toEqual(config);
16
- expect(Test).toHaveBeenCalledWith(expect.anything(), config);
17
- });
18
-
19
- describe('after initialization failure', () => {
20
- it('destroys child resources in reverse order', async () => {
21
- const spy = vi.fn();
22
- const First = async () => ({ value: [], destroy: () => spy('1st') });
23
- const Second = async () => ({ value: [], destroy: () => spy('2nd') });
24
-
25
- const Parent = async (resource: ResourceControls) => {
26
- await resource.create(First);
27
- await resource.create(Second);
28
- throw new Error('Testing resource initialization errors');
29
- };
30
-
31
- await expect(create(Parent)).rejects.toThrow();
32
- expect(spy.mock.calls).toEqual([['2nd'], ['1st']]);
33
- });
34
-
35
- it('continues even if the children cannot be destroyed', async () => {
36
- const parentError = new Error('Testing parent resource aborts');
37
- const childError = new Error('Testing child resource aborts');
38
- const Child = async () => ({
39
- value: [],
40
- destroy() {
41
- throw childError;
42
- },
43
- });
44
-
45
- const Parent = async (resource: ResourceControls) => {
46
- await resource.create(Child);
47
- await resource.create(Child);
48
- throw parentError;
49
- };
50
-
51
- await expect(create(Parent)).rejects.toMatchObject({
52
- cause: parentError,
53
- failures: [childError, childError],
54
- });
55
- });
56
- });
57
- });
58
-
59
- describe('destroy', () => {
60
- it('deallocates the resource', async () => {
61
- const spy = vi.fn<[], void>();
62
- const Test = async () => ({
63
- value: {},
64
- destroy: spy,
65
- });
66
-
67
- const test = await create(Test);
68
-
69
- expect(spy).not.toHaveBeenCalled();
70
- await expect(destroy(test)).resolves.not.toThrow();
71
- expect(spy).toHaveBeenCalled();
72
- });
73
-
74
- it('throws an error if the object is not a resource', async () => {
75
- await expect(destroy({})).rejects.toThrow(
76
- 'Cannot destroy object. It is not a resource.',
77
- );
78
- });
79
-
80
- it('survives if the resource is already deallocated', async () => {
81
- const Test = async () => ({ value: [] });
82
-
83
- const test = await create(Test);
84
- await destroy(test);
85
-
86
- await expect(destroy(test)).resolves.not.toThrow();
87
- });
88
-
89
- it('automatically unmounts all children', async () => {
90
- const spy = vi.fn();
91
- const Child = async () => ({ value: [], destroy: spy });
92
- async function Parent(resource: ResourceControls) {
93
- await resource.create(Child);
94
- return { value: [] };
95
- }
96
-
97
- const parent = await create(Parent);
98
- await destroy(parent);
99
-
100
- expect(spy).toHaveBeenCalled();
101
- });
102
-
103
- it('throws an error if any of the children fail to close', async () => {
104
- const error = new Error('Testing resource destruction errors');
105
- const Child = async () => ({
106
- value: [],
107
- destroy() {
108
- throw error;
109
- },
110
- });
111
-
112
- const Parent = async (resource: ResourceControls) => {
113
- await resource.create(Child);
114
- return { value: [] };
115
- };
116
-
117
- const parent = await create(Parent);
118
- await expect(destroy(parent)).rejects.toThrow(error);
119
- });
120
-
121
- it('destroys child resources in reverse order', async () => {
122
- const spy = vi.fn();
123
- const First = async () => ({ value: [], destroy: () => spy('1st') });
124
- const Second = async () => ({ value: [], destroy: () => spy('2nd') });
125
-
126
- const Parent = async (resource: ResourceControls) => {
127
- await resource.create(First);
128
- await resource.create(Second);
129
- return { value: [] };
130
- };
131
-
132
- const parent = await create(Parent);
133
- await destroy(parent);
134
-
135
- expect(spy.mock.calls).toEqual([['2nd'], ['1st']]);
136
- });
137
-
138
- it('reports if multiple resources could not be destroyed', async () => {
139
- const childError = new Error('Testing child resource aborts');
140
- const Child = async () => ({
141
- value: [],
142
- destroy() {
143
- throw childError;
144
- },
145
- });
146
-
147
- const Parent = async (resource: ResourceControls) => {
148
- await resource.create(Child);
149
- await resource.create(Child);
150
- return { value: [] };
151
- };
152
-
153
- const parent = await create(Parent);
154
- await expect(destroy(parent)).rejects.toMatchObject({
155
- failures: [childError, childError],
156
- });
157
- });
158
-
159
- it('ensures child resources outlive their consumers', async () => {
160
- const Child = async () => ({ value: [1] });
161
- const Parent = async (resource: ResourceControls) => {
162
- const child = await resource.create(Child);
163
- return {
164
- value: [],
165
- destroy() {
166
- // The `child` resource must still be usable here.
167
- expect(child).toHaveLength(1);
168
- },
169
- };
170
- };
171
-
172
- const parent = await create(Parent);
173
- await destroy(parent);
174
- });
175
-
176
- it('destroys child resources even if the parent fails to close', async () => {
177
- const spy = vi.fn();
178
- const Child = async () => ({ value: [], destroy: spy });
179
- const Parent = async (resource: ResourceControls) => {
180
- await resource.create(Child);
181
- return {
182
- value: [],
183
- destroy() {
184
- throw new Error('Testing parent teardown errors');
185
- },
186
- };
187
- };
188
-
189
- const parent = await create(Parent);
190
- await expect(destroy(parent)).rejects.toThrow();
191
- expect(spy).toHaveBeenCalled();
192
- });
193
-
194
- it('aggregates errors if the child resource fails to close', async () => {
195
- const parentError = new Error('Testing parent resource aborts');
196
- const childError = new Error('Testing child resource aborts');
197
-
198
- const Child = async () => ({
199
- value: [],
200
- destroy() {
201
- throw childError;
202
- },
203
- });
204
-
205
- const Parent = async (resource: ResourceControls) => {
206
- await resource.create(Child);
207
- return {
208
- value: [],
209
- destroy() {
210
- throw parentError;
211
- },
212
- };
213
- };
214
-
215
- const parent = await create(Parent);
216
- await expect(destroy(parent)).rejects.toMatchObject({
217
- failures: [parentError, childError],
218
- });
219
- });
220
-
221
- it('guards against creating new resources after teardown', async () => {
222
- const Child = async () => ({ value: [] });
223
- const Parent = async (resource: ResourceControls) => ({
224
- value: bindContext(resource),
225
- });
226
-
227
- const parent = await create(Parent);
228
- const { create: resourceCreate } = parent;
229
- await destroy(parent);
230
-
231
- await expect(resourceCreate(Child)).rejects.toThrow(
232
- /cannot create.*after teardown/i,
233
- );
234
- });
235
- });
236
- });