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 +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
|
|