wardens 0.5.0 → 0.5.1-2024-07-29.a37451d
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/README.md +1 -1
- package/dist/wardens.cjs +1 -1
- package/dist/wardens.js +106 -74
- package/package.json +18 -40
- package/src/__tests__/resource-scope.test.ts +198 -0
- package/src/__tests__/root-lifecycle.test.ts +249 -0
- package/src/__tests__/{allocation.test.ts → roots.test.ts} +15 -15
- package/src/__tests__/utility-types.test-d.ts +56 -0
- package/src/{state.ts → global-weakrefs.ts} +17 -11
- package/src/index.ts +4 -3
- package/src/inherited-context.ts +32 -0
- package/src/{allocation.ts → resource-lifecycle.ts} +16 -11
- package/src/{resource-context.ts → resource-scope.ts} +33 -8
- package/src/root-lifecycle.ts +36 -0
- package/src/{types.ts → utility-types.ts} +12 -6
- package/CHANGELOG.md +0 -86
- package/src/__tests__/resource-context.test.ts +0 -75
- package/src/__tests__/types.test-d.ts +0 -19
- /package/src/{proxy.ts → wrap-with-proxy.ts} +0 -0
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 }: 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
|
1
|
+
"use strict";var B=Object.defineProperty;var j=e=>{throw TypeError(e)};var M=(e,t,r)=>t in e?B(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var f=(e,t,r)=>M(e,typeof t!="symbol"?t+"":t,r),k=(e,t,r)=>t.has(e)||j("Cannot "+r);var s=(e,t,r)=>(k(e,t,"read from private field"),r?r.call(e):t.get(e)),d=(e,t,r)=>t.has(e)?j("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,r),h=(e,t,r,o)=>(k(e,t,"write to private field"),o?o.call(e,r):t.set(e,r),r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function A(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,E=new WeakSet,P=new WeakSet;function T(e){return Proxy.revocable(e,{})}var C,w;class x{constructor(t){d(this,C,Symbol("Context ID"));d(this,w);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 V=e=>new x(e);var y,l,p,i;class H{constructor(t,r,o){d(this,y,new WeakSet);d(this,l);d(this,p);d(this,i);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 D(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 D=async(e,t,...r)=>{const o={enforced:!1},n=new Set,a=new H(e,n,o);let c;try{c=await t(a,...r)}catch(S){const R=Array.from(n).reverse(),v=(await Promise.allSettled(R.map(u=>a.destroy(u)))).filter(u=>u.status==="rejected");throw v.length?W(v.map(u=>u.reason),{cause:S}):S}const I=c.value,{proxy:b,revoke:O}=T(I);return E.add(b),g.set(b,{curfew:o,resource:c,children:n,revoke:O}),b},m=async e=>{if(!E.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 W(a.map(c=>c.reason))}},W=(e,t)=>e.length===1?e[0]:new U(e,t);class U extends Error{constructor(t,r){super("Some resources could not be destroyed. See the `failures` property for details.",r),this.failures=t}}const Y=async(e,...t)=>{const o=await D(Object.create(null),e,...t);return P.add(o),o},q=async e=>{if(g.has(e)&&!P.has(e))throw new Error("Cannot destroy child resource. It is owned by another scope.");return m(e)};exports.bindContext=A;exports.create=Y;exports.createContext=V;exports.destroy=q;
|
package/dist/wardens.js
CHANGED
@@ -1,28 +1,24 @@
|
|
1
|
-
var
|
2
|
-
var j = (
|
3
|
-
|
4
|
-
if (!e.has(t))
|
5
|
-
throw TypeError("Cannot " + r);
|
1
|
+
var B = Object.defineProperty;
|
2
|
+
var j = (e) => {
|
3
|
+
throw TypeError(e);
|
6
4
|
};
|
7
|
-
var
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
const e = /* @__PURE__ */ new WeakMap();
|
14
|
-
return new Proxy(t, {
|
5
|
+
var A = (e, t, r) => t in e ? B(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
|
6
|
+
var h = (e, t, r) => A(e, typeof t != "symbol" ? t + "" : t, r), k = (e, t, r) => t.has(e) || j("Cannot " + r);
|
7
|
+
var n = (e, t, r) => (k(e, t, "read from private field"), r ? r.call(e) : t.get(e)), d = (e, t, r) => t.has(e) ? j("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(e) : t.set(e, r), f = (e, t, r, o) => (k(e, t, "write to private field"), o ? o.call(e, r) : t.set(e, r), r);
|
8
|
+
function Y(e) {
|
9
|
+
const t = /* @__PURE__ */ new WeakMap();
|
10
|
+
return new Proxy(e, {
|
15
11
|
get(r, o) {
|
16
12
|
const s = Reflect.get(r, o, r);
|
17
13
|
if (typeof s == "function") {
|
18
|
-
if (
|
19
|
-
const
|
14
|
+
if (t.has(s) === !1) {
|
15
|
+
const a = s.bind(r);
|
20
16
|
Object.defineProperties(
|
21
|
-
|
17
|
+
a,
|
22
18
|
Object.getOwnPropertyDescriptors(s)
|
23
|
-
),
|
19
|
+
), t.set(s, a);
|
24
20
|
}
|
25
|
-
return
|
21
|
+
return t.get(s);
|
26
22
|
}
|
27
23
|
return s;
|
28
24
|
},
|
@@ -31,95 +27,131 @@ function A(t) {
|
|
31
27
|
}
|
32
28
|
});
|
33
29
|
}
|
34
|
-
const
|
35
|
-
function
|
36
|
-
return Proxy.revocable(
|
30
|
+
const g = /* @__PURE__ */ new WeakMap(), E = /* @__PURE__ */ new WeakSet(), D = /* @__PURE__ */ new WeakSet();
|
31
|
+
function M(e) {
|
32
|
+
return Proxy.revocable(e, {});
|
37
33
|
}
|
38
|
-
var
|
39
|
-
class
|
40
|
-
constructor(
|
41
|
-
|
42
|
-
|
43
|
-
f(this,
|
34
|
+
var m, w;
|
35
|
+
class x {
|
36
|
+
constructor(t) {
|
37
|
+
d(this, m, Symbol("Context ID"));
|
38
|
+
d(this, w);
|
39
|
+
f(this, w, t);
|
40
|
+
}
|
41
|
+
static getId(t) {
|
42
|
+
return n(t, m);
|
43
|
+
}
|
44
|
+
static getDefaultValue(t) {
|
45
|
+
var r;
|
46
|
+
return n(r = t, w).call(r);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
m = new WeakMap(), w = new WeakMap();
|
50
|
+
const q = (e) => new x(e);
|
51
|
+
var y, l, p, i;
|
52
|
+
class V {
|
53
|
+
constructor(t, r, o) {
|
54
|
+
d(this, y, /* @__PURE__ */ new WeakSet());
|
55
|
+
d(this, l);
|
56
|
+
d(this, p);
|
57
|
+
d(this, i);
|
44
58
|
/** Provision an owned resource and make sure it doesn't outlive us. */
|
45
|
-
h(this, "create", async (
|
46
|
-
if (
|
59
|
+
h(this, "create", async (t, ...r) => {
|
60
|
+
if (n(this, p).enforced)
|
47
61
|
throw new Error("Cannot create new resources after teardown.");
|
48
|
-
const o = await
|
49
|
-
return
|
62
|
+
const o = Object.create(n(this, i)), s = await P(o, t, ...r);
|
63
|
+
return n(this, l).add(s), s;
|
50
64
|
});
|
51
65
|
/**
|
52
66
|
* Tear down a resource. Happens automatically when resource owners are
|
53
67
|
* deallocated.
|
54
68
|
*/
|
55
|
-
h(this, "destroy", async (
|
56
|
-
if (
|
69
|
+
h(this, "destroy", async (t) => {
|
70
|
+
if (n(this, y).has(t))
|
57
71
|
throw new Error("Resource already destroyed.");
|
58
|
-
if (!
|
72
|
+
if (!n(this, l).has(t))
|
59
73
|
throw new Error("You do not own this resource.");
|
60
|
-
|
74
|
+
n(this, l).delete(t), n(this, y).add(t), await b(t);
|
61
75
|
});
|
62
|
-
|
76
|
+
/** Store a value in context. Anything down the chain can read it. */
|
77
|
+
h(this, "setContext", (t, r) => (n(this, i)[x.getId(t)] = r, r));
|
78
|
+
/** Retrieve a value from context, or a default if it is unset. */
|
79
|
+
h(this, "getContext", (t) => {
|
80
|
+
const r = x.getId(t);
|
81
|
+
return r in n(this, i) ? n(this, i)[r] : x.getDefaultValue(t);
|
82
|
+
});
|
83
|
+
f(this, i, t), f(this, l, r), f(this, p, o);
|
63
84
|
}
|
64
85
|
}
|
65
|
-
l = new WeakMap(),
|
66
|
-
const
|
67
|
-
const
|
68
|
-
let
|
86
|
+
y = new WeakMap(), l = new WeakMap(), p = new WeakMap(), i = new WeakMap();
|
87
|
+
const P = async (e, t, ...r) => {
|
88
|
+
const o = { enforced: !1 }, s = /* @__PURE__ */ new Set(), a = new V(e, s, o);
|
89
|
+
let c;
|
69
90
|
try {
|
70
|
-
|
71
|
-
} catch (
|
72
|
-
const
|
73
|
-
|
91
|
+
c = await t(a, ...r);
|
92
|
+
} catch (S) {
|
93
|
+
const O = Array.from(s).reverse(), v = (await Promise.allSettled(
|
94
|
+
O.map((u) => a.destroy(u))
|
74
95
|
)).filter(
|
75
|
-
(
|
96
|
+
(u) => u.status === "rejected"
|
76
97
|
);
|
77
|
-
throw v.length ?
|
78
|
-
v.map((
|
79
|
-
{ cause:
|
80
|
-
) :
|
98
|
+
throw v.length ? W(
|
99
|
+
v.map((u) => u.reason),
|
100
|
+
{ cause: S }
|
101
|
+
) : S;
|
81
102
|
}
|
82
|
-
const
|
83
|
-
return
|
84
|
-
curfew:
|
85
|
-
resource:
|
86
|
-
children:
|
87
|
-
revoke:
|
88
|
-
}),
|
89
|
-
},
|
90
|
-
if (!
|
103
|
+
const I = c.value, { proxy: C, revoke: R } = M(I);
|
104
|
+
return E.add(C), g.set(C, {
|
105
|
+
curfew: o,
|
106
|
+
resource: c,
|
107
|
+
children: s,
|
108
|
+
revoke: R
|
109
|
+
}), C;
|
110
|
+
}, b = async (e) => {
|
111
|
+
if (!E.has(e))
|
91
112
|
throw new Error("Cannot destroy object. It is not a resource.");
|
92
|
-
const
|
93
|
-
if (
|
94
|
-
|
113
|
+
const t = g.get(e);
|
114
|
+
if (t) {
|
115
|
+
g.delete(e), t.revoke();
|
95
116
|
let r = {
|
96
117
|
status: "fulfilled",
|
97
118
|
value: void 0
|
98
119
|
};
|
99
|
-
if (
|
120
|
+
if (t.resource.destroy)
|
100
121
|
try {
|
101
|
-
await
|
122
|
+
await t.resource.destroy();
|
102
123
|
} catch (c) {
|
103
124
|
r = { status: "rejected", reason: c };
|
104
125
|
}
|
105
|
-
|
106
|
-
const o = Array.from(
|
126
|
+
t.curfew.enforced = !0;
|
127
|
+
const o = Array.from(t.children).reverse().map(b), s = await Promise.allSettled(o), a = [r].concat(s).filter(
|
107
128
|
(c) => c.status === "rejected"
|
108
129
|
);
|
109
|
-
if (
|
110
|
-
throw
|
130
|
+
if (a.length)
|
131
|
+
throw W(a.map((c) => c.reason));
|
111
132
|
}
|
112
|
-
},
|
113
|
-
class
|
114
|
-
constructor(
|
133
|
+
}, W = (e, t) => e.length === 1 ? e[0] : new H(e, t);
|
134
|
+
class H extends Error {
|
135
|
+
constructor(t, r) {
|
115
136
|
super(
|
116
137
|
"Some resources could not be destroyed. See the `failures` property for details.",
|
117
138
|
r
|
118
|
-
), this.failures =
|
139
|
+
), this.failures = t;
|
119
140
|
}
|
120
141
|
}
|
142
|
+
const z = async (e, ...t) => {
|
143
|
+
const o = await P(/* @__PURE__ */ Object.create(null), e, ...t);
|
144
|
+
return D.add(o), o;
|
145
|
+
}, F = async (e) => {
|
146
|
+
if (g.has(e) && !D.has(e))
|
147
|
+
throw new Error(
|
148
|
+
"Cannot destroy child resource. It is owned by another scope."
|
149
|
+
);
|
150
|
+
return b(e);
|
151
|
+
};
|
121
152
|
export {
|
122
|
-
|
123
|
-
|
124
|
-
|
153
|
+
Y as bindContext,
|
154
|
+
z as create,
|
155
|
+
q as createContext,
|
156
|
+
F as destroy
|
125
157
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "wardens",
|
3
|
-
"version": "0.5.
|
3
|
+
"version": "0.5.1-2024-07-29.a37451d",
|
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",
|
@@ -30,12 +31,13 @@
|
|
30
31
|
],
|
31
32
|
"scripts": {
|
32
33
|
"prepack": "tsc && vite build",
|
33
|
-
"test": "./
|
34
|
+
"test": "./scripts/run-tests",
|
34
35
|
"test:lint": "eslint src --color",
|
35
|
-
"test:unit": "vitest --color",
|
36
|
-
"test:types": "vitest typecheck --color",
|
36
|
+
"test:unit": "vitest --color --typecheck",
|
37
37
|
"test:fmt": "prettier --check src --color",
|
38
|
-
"
|
38
|
+
"print-pkg-version": "echo ${npm_package_version}",
|
39
|
+
"release:candidate": "./scripts/publish-rc",
|
40
|
+
"release:stable": "./scripts/publish-stable"
|
39
41
|
},
|
40
42
|
"husky": {
|
41
43
|
"hooks": {
|
@@ -52,41 +54,17 @@
|
|
52
54
|
"singleQuote": true,
|
53
55
|
"trailingComma": "all"
|
54
56
|
},
|
55
|
-
"eslintConfig": {
|
56
|
-
"parser": "@typescript-eslint/parser",
|
57
|
-
"parserOptions": {
|
58
|
-
"sourceType": "module"
|
59
|
-
},
|
60
|
-
"overrides": [
|
61
|
-
{
|
62
|
-
"files": [
|
63
|
-
"./**/__tests__/*.ts{x,}"
|
64
|
-
],
|
65
|
-
"rules": {
|
66
|
-
"@typescript-eslint/no-explicit-any": "off"
|
67
|
-
}
|
68
|
-
}
|
69
|
-
],
|
70
|
-
"extends": [
|
71
|
-
"eslint:recommended",
|
72
|
-
"plugin:@typescript-eslint/recommended"
|
73
|
-
],
|
74
|
-
"rules": {
|
75
|
-
"@typescript-eslint/explicit-module-boundary-types": "off",
|
76
|
-
"@typescript-eslint/no-non-null-assertion": "off",
|
77
|
-
"@typescript-eslint/no-unused-vars": "error",
|
78
|
-
"no-prototype-builtins": "off"
|
79
|
-
}
|
80
|
-
},
|
81
57
|
"devDependencies": {
|
82
|
-
"@
|
83
|
-
"@
|
84
|
-
"
|
85
|
-
"
|
86
|
-
"
|
87
|
-
"
|
88
|
-
"
|
89
|
-
"
|
90
|
-
"
|
58
|
+
"@eslint/js": "^9.7.0",
|
59
|
+
"@types/eslint__js": "^8.42.3",
|
60
|
+
"@types/node": "^22.0.0",
|
61
|
+
"eslint": "^9.7.0",
|
62
|
+
"husky": "^9.1.3",
|
63
|
+
"lint-staged": "^15.2.7",
|
64
|
+
"prettier": "^3.3.3",
|
65
|
+
"typescript": "^5.5.4",
|
66
|
+
"typescript-eslint": "^8.0.0-alpha.54",
|
67
|
+
"vite": "^5.3.5",
|
68
|
+
"vitest": "^2.0.0"
|
91
69
|
}
|
92
70
|
}
|
@@ -0,0 +1,198 @@
|
|
1
|
+
import ResourceScope from '../resource-scope';
|
2
|
+
import { create } from '../';
|
3
|
+
import { createContext } from '../inherited-context';
|
4
|
+
|
5
|
+
describe('ResourceScope', () => {
|
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: ResourceScope) => {
|
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: ResourceScope) => {
|
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: ResourceScope) => {
|
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 }: ResourceScope) {
|
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: ResourceScope) {
|
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: ResourceScope) => {
|
81
|
+
expect(resource.getContext(SharedValue)).toBe('none');
|
82
|
+
|
83
|
+
resource.setContext(SharedValue, 'saved');
|
84
|
+
expect(resource.getContext(SharedValue)).toBe('saved');
|
85
|
+
|
86
|
+
resource.setContext(SharedValue, 'updated');
|
87
|
+
expect(resource.getContext(SharedValue)).toBe('updated');
|
88
|
+
|
89
|
+
return { value: [] };
|
90
|
+
};
|
91
|
+
|
92
|
+
await expect(create(Test)).resolves.toEqual([]);
|
93
|
+
});
|
94
|
+
|
95
|
+
it('returns the new context value', async () => {
|
96
|
+
const SharedValue = createContext(() => 'none');
|
97
|
+
|
98
|
+
const Test = async (resource: ResourceScope) => {
|
99
|
+
return {
|
100
|
+
value: { value: resource.setContext(SharedValue, 'returned') },
|
101
|
+
};
|
102
|
+
};
|
103
|
+
|
104
|
+
await expect(create(Test)).resolves.toEqual({ value: 'returned' });
|
105
|
+
});
|
106
|
+
|
107
|
+
it('passes context to child resources', async () => {
|
108
|
+
const SharedValue = createContext<null | string>(() => null);
|
109
|
+
|
110
|
+
const Child = async (ctx: ResourceScope) => ({
|
111
|
+
value: { content: ctx.getContext(SharedValue) },
|
112
|
+
});
|
113
|
+
|
114
|
+
const Parent = async (resource: ResourceScope) => {
|
115
|
+
resource.setContext(SharedValue, 'inherited');
|
116
|
+
return { value: await resource.create(Child) };
|
117
|
+
};
|
118
|
+
|
119
|
+
await expect(create(Parent)).resolves.toEqual({ content: 'inherited' });
|
120
|
+
});
|
121
|
+
|
122
|
+
it('can override context without affecting the parent', async () => {
|
123
|
+
const Message = createContext<null | string>(() => null);
|
124
|
+
const Child = async (resource: ResourceScope) => {
|
125
|
+
// This should *NOT* affect the parent context.
|
126
|
+
resource.setContext(Message, 'child context');
|
127
|
+
|
128
|
+
return {
|
129
|
+
value: { content: resource.getContext(Message) },
|
130
|
+
};
|
131
|
+
};
|
132
|
+
|
133
|
+
const Parent = async (resource: ResourceScope) => {
|
134
|
+
resource.setContext(Message, 'parent context');
|
135
|
+
const child = await resource.create(Child);
|
136
|
+
|
137
|
+
expect(resource.getContext(Message)).toBe('parent context');
|
138
|
+
|
139
|
+
return {
|
140
|
+
value: {
|
141
|
+
content: resource.getContext(Message),
|
142
|
+
child,
|
143
|
+
},
|
144
|
+
};
|
145
|
+
};
|
146
|
+
|
147
|
+
await expect(create(Parent)).resolves.toEqual({
|
148
|
+
content: 'parent context',
|
149
|
+
child: { content: 'child context' },
|
150
|
+
});
|
151
|
+
});
|
152
|
+
|
153
|
+
it('allows two siblings to have different context values', async () => {
|
154
|
+
const Message = createContext<null | string>(() => null);
|
155
|
+
const Child = async (resource: ResourceScope, msg: string) => {
|
156
|
+
resource.setContext(Message, msg);
|
157
|
+
|
158
|
+
return {
|
159
|
+
value: { getMessage: () => resource.getContext(Message) },
|
160
|
+
};
|
161
|
+
};
|
162
|
+
|
163
|
+
const Parent = async (resource: ResourceScope) => {
|
164
|
+
resource.setContext(Message, 'parent context');
|
165
|
+
|
166
|
+
return {
|
167
|
+
value: await Promise.all([
|
168
|
+
resource.create(Child, 'child context 1'),
|
169
|
+
resource.create(Child, 'child context 2'),
|
170
|
+
]),
|
171
|
+
};
|
172
|
+
};
|
173
|
+
|
174
|
+
const [child1, child2] = await create(Parent);
|
175
|
+
expect(child1.getMessage()).toBe('child context 1');
|
176
|
+
expect(child2.getMessage()).toBe('child context 2');
|
177
|
+
});
|
178
|
+
|
179
|
+
it('provides a live view of the current value, not just a snapshot', async () => {
|
180
|
+
const Message = createContext(() => 'default');
|
181
|
+
const Child = async (resource: ResourceScope) => ({
|
182
|
+
value: { getMessage: () => resource.getContext(Message) },
|
183
|
+
});
|
184
|
+
|
185
|
+
const Parent = async (resource: ResourceScope) => ({
|
186
|
+
value: {
|
187
|
+
setMessage: (msg: string) => resource.setContext(Message, msg),
|
188
|
+
child: await resource.create(Child),
|
189
|
+
},
|
190
|
+
});
|
191
|
+
|
192
|
+
const { setMessage, child } = await create(Parent);
|
193
|
+
expect(child.getMessage()).toBe('default');
|
194
|
+
|
195
|
+
setMessage('updated');
|
196
|
+
expect(child.getMessage()).toBe('updated');
|
197
|
+
});
|
198
|
+
});
|