wardens 0.5.1 → 0.6.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/dist/wardens.cjs +1 -1
- package/dist/wardens.js +103 -74
- package/package.json +4 -4
- package/src/__tests__/resource-api.test.ts +186 -0
- package/src/__tests__/{allocation.test.ts → root-lifecycle.test.ts} +14 -14
- package/src/__tests__/root-lifecycles.test.ts +236 -0
- package/src/__tests__/roots.test.ts +236 -0
- package/src/__tests__/{types.test-d.ts → utility-types.test-d.ts} +2 -2
- package/src/{state.ts → global-weakrefs.ts} +11 -11
- package/src/index.ts +4 -3
- package/src/inherited-context.ts +32 -0
- package/src/{resource-context.ts → resource-controls.ts} +32 -8
- package/src/{allocation.ts → resource-lifecycle.ts} +11 -9
- package/src/root-lifecycle.ts +22 -0
- package/src/{types.ts → utility-types.ts} +3 -3
- package/src/__tests__/resource-context.test.ts +0 -75
- /package/src/{proxy.ts → wrap-with-proxy.ts} +0 -0
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 }:
|
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
|
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
|
2
|
-
var
|
3
|
-
var
|
4
|
-
if (!e.has(
|
5
|
-
throw TypeError("Cannot " +
|
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
|
8
|
-
if (e.has(
|
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(
|
11
|
-
},
|
12
|
-
function
|
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(
|
15
|
-
get(
|
16
|
-
const
|
17
|
-
if (typeof
|
18
|
-
if (e.has(
|
19
|
-
const
|
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
|
-
|
22
|
-
Object.getOwnPropertyDescriptors(
|
23
|
-
), e.set(
|
21
|
+
a,
|
22
|
+
Object.getOwnPropertyDescriptors(o)
|
23
|
+
), e.set(o, a);
|
24
24
|
}
|
25
|
-
return e.get(
|
25
|
+
return e.get(o);
|
26
26
|
}
|
27
|
-
return
|
27
|
+
return o;
|
28
28
|
},
|
29
|
-
set(
|
30
|
-
return Reflect.set(
|
29
|
+
set(t, s, o) {
|
30
|
+
return Reflect.set(t, s, o, t);
|
31
31
|
}
|
32
32
|
});
|
33
33
|
}
|
34
|
-
const
|
35
|
-
function
|
36
|
-
return Proxy.revocable(
|
34
|
+
const C = /* @__PURE__ */ new WeakMap(), j = /* @__PURE__ */ new WeakSet();
|
35
|
+
function B(r) {
|
36
|
+
return Proxy.revocable(r, {});
|
37
37
|
}
|
38
|
-
var
|
39
|
-
class
|
40
|
-
constructor(e
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
46
|
-
if (
|
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
|
49
|
-
return
|
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
|
-
|
56
|
-
if (
|
73
|
+
f(this, "destroy", async (e) => {
|
74
|
+
if (n(this, y).has(e))
|
57
75
|
throw new Error("Resource already destroyed.");
|
58
|
-
if (!
|
76
|
+
if (!n(this, u).has(e))
|
59
77
|
throw new Error("You do not own this resource.");
|
60
|
-
|
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
|
-
|
89
|
+
h(this, i, e), h(this, u, t), h(this, p, s);
|
63
90
|
}
|
64
91
|
}
|
65
|
-
|
66
|
-
const
|
67
|
-
const
|
68
|
-
let
|
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
|
-
|
71
|
-
} catch (
|
72
|
-
const
|
73
|
-
|
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 ?
|
104
|
+
throw v.length ? E(
|
78
105
|
v.map((d) => d.reason),
|
79
|
-
{ cause:
|
80
|
-
) :
|
106
|
+
{ cause: b }
|
107
|
+
) : b;
|
81
108
|
}
|
82
|
-
const
|
83
|
-
return
|
84
|
-
curfew:
|
85
|
-
resource:
|
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:
|
88
|
-
}),
|
89
|
-
},
|
90
|
-
if (!
|
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 =
|
119
|
+
const e = C.get(r);
|
93
120
|
if (e) {
|
94
|
-
|
95
|
-
let
|
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
|
-
|
130
|
+
t = { status: "rejected", reason: c };
|
104
131
|
}
|
105
132
|
e.curfew.enforced = !0;
|
106
|
-
const
|
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 (
|
110
|
-
throw
|
136
|
+
if (a.length)
|
137
|
+
throw E(a.map((c) => c.reason));
|
111
138
|
}
|
112
|
-
},
|
113
|
-
class
|
114
|
-
constructor(e,
|
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
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
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.
|
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.
|
83
|
-
"@typescript-eslint/parser": "6.
|
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": "
|
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
|
2
|
-
import { create, destroy } from '../
|
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('
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
223
|
+
const Parent = async (resource: ResourceControls) => ({
|
224
224
|
value: bindContext(resource),
|
225
225
|
});
|
226
226
|
|