context 4.0.0 → 4.0.1
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 +46 -181
- package/dist/context.cjs +64 -0
- package/dist/context.cjs.map +1 -0
- package/dist/context.mjs +63 -0
- package/dist/context.mjs.map +1 -0
- package/package.json +24 -32
- package/src/__tests__/__snapshots__/cascade.test.ts.snap +23 -0
- package/src/__tests__/cascade.test.ts +342 -0
- package/src/__tests__/context.test.ts +104 -0
- package/src/context.ts +100 -0
- package/types/context.d.cts +27 -0
- package/types/context.d.cts.map +1 -0
- package/types/context.d.mts +27 -0
- package/types/context.d.mts.map +1 -0
- package/types/context.d.ts +14 -10
- package/vitest.config.ts +21 -0
- package/dist/cjs/context.development.js +0 -69
- package/dist/cjs/context.js +0 -7
- package/dist/cjs/context.production.js +0 -1
- package/dist/cjs/package.json +0 -1
- package/dist/es/context.development.js +0 -64
- package/dist/es/context.production.js +0 -1
- package/dist/es/package.json +0 -1
- package/dist/umd/context.development.js +0 -73
- package/dist/umd/context.production.js +0 -1
- package/tsconfig.json +0 -13
- package/types/context.d.ts.map +0 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createCascade, CtxCascadeApi } from '../context';
|
|
4
|
+
|
|
5
|
+
describe('Cascading Context', () => {
|
|
6
|
+
let ctx: CtxCascadeApi<any>;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
ctx = createCascade();
|
|
10
|
+
});
|
|
11
|
+
describe('createCascade', () => {
|
|
12
|
+
it('Should return a new context on each run', () => {
|
|
13
|
+
expect(createCascade()).not.toBe(createCascade());
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('Should return all methods', () => {
|
|
17
|
+
expect(createCascade()).toMatchSnapshot();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('context.run', () => {
|
|
22
|
+
it('Should create a new context instance', () => {
|
|
23
|
+
const top = ctx.use();
|
|
24
|
+
|
|
25
|
+
ctx.run({}, () => {
|
|
26
|
+
expect(ctx.use()).not.toBe(top);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('Should pass no arguments to the callback', () => {
|
|
31
|
+
ctx.run({}, (...args) => {
|
|
32
|
+
expect(args).toHaveLength(0);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('Adds provided `ctxref` properties to current context level', () => {
|
|
37
|
+
ctx.run(
|
|
38
|
+
{
|
|
39
|
+
id: 55,
|
|
40
|
+
user: 'boomsa',
|
|
41
|
+
},
|
|
42
|
+
() => {
|
|
43
|
+
expect(ctx.use().id).toBe(55);
|
|
44
|
+
expect(ctx.use().user).toBe('boomsa');
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('Returns undefined when property is not in context', () => {
|
|
50
|
+
ctx.run(
|
|
51
|
+
{
|
|
52
|
+
id: 55,
|
|
53
|
+
},
|
|
54
|
+
() => {
|
|
55
|
+
expect(ctx.use().id).toBe(55);
|
|
56
|
+
expect(ctx.use().user).toBeUndefined();
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('Should clear context after callback run', () => {
|
|
62
|
+
expect(ctx.use()).toBeUndefined();
|
|
63
|
+
ctx.run({ a: 1 }, () => {
|
|
64
|
+
expect(ctx.use()).toMatchSnapshot();
|
|
65
|
+
ctx.run({ b: 2 }, () => {
|
|
66
|
+
expect(ctx.use()).toMatchSnapshot();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
expect(ctx.use()).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('Context nesting', () => {
|
|
73
|
+
it('Should refer to closest defined value', () => {
|
|
74
|
+
ctx.run(
|
|
75
|
+
{
|
|
76
|
+
id: 99,
|
|
77
|
+
name: 'watermelonbunny',
|
|
78
|
+
},
|
|
79
|
+
() => {
|
|
80
|
+
expect(ctx.use().id).toBe(99);
|
|
81
|
+
expect(ctx.use().name).toBe('watermelonbunny');
|
|
82
|
+
|
|
83
|
+
ctx.run(
|
|
84
|
+
{
|
|
85
|
+
name: 'Emanuelle',
|
|
86
|
+
color: 'blue',
|
|
87
|
+
},
|
|
88
|
+
() => {
|
|
89
|
+
expect(ctx.use().id).toBe(99);
|
|
90
|
+
expect(ctx.use().name).toBe('Emanuelle');
|
|
91
|
+
expect(ctx.use().color).toBe('blue');
|
|
92
|
+
|
|
93
|
+
ctx.run({}, () => {
|
|
94
|
+
expect(ctx.use().id).toBe(99);
|
|
95
|
+
expect(ctx.use().name).toBe('Emanuelle');
|
|
96
|
+
expect(ctx.use().color).toBe('blue');
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('Should return previous context value after nested context run', () => {
|
|
105
|
+
ctx.run(
|
|
106
|
+
{
|
|
107
|
+
id: 99,
|
|
108
|
+
name: 'watermelonbunny',
|
|
109
|
+
},
|
|
110
|
+
() => {
|
|
111
|
+
ctx.run(
|
|
112
|
+
{
|
|
113
|
+
name: 'Emanuelle',
|
|
114
|
+
color: 'blue',
|
|
115
|
+
},
|
|
116
|
+
() => {
|
|
117
|
+
ctx.run({}, () => null);
|
|
118
|
+
expect(ctx.use().id).toBe(99);
|
|
119
|
+
expect(ctx.use().name).toBe('Emanuelle');
|
|
120
|
+
expect(ctx.use().color).toBe('blue');
|
|
121
|
+
expect(ctx.use()).toMatchInlineSnapshot(`
|
|
122
|
+
{
|
|
123
|
+
"color": "blue",
|
|
124
|
+
"id": 99,
|
|
125
|
+
"name": "Emanuelle",
|
|
126
|
+
}
|
|
127
|
+
`);
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
expect(ctx.use().id).toBe(99);
|
|
131
|
+
expect(ctx.use().name).toBe('watermelonbunny');
|
|
132
|
+
expect(ctx.use()).toMatchInlineSnapshot(`
|
|
133
|
+
{
|
|
134
|
+
"id": 99,
|
|
135
|
+
"name": "watermelonbunny",
|
|
136
|
+
}
|
|
137
|
+
`);
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('context.bind', () => {
|
|
145
|
+
it('Returns a function', () => {
|
|
146
|
+
expect(typeof ctx.bind({}, vi.fn())).toBe('function');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('Wraps the function with context', () => {
|
|
150
|
+
return new Promise<void>(done => {
|
|
151
|
+
const fn = () => {
|
|
152
|
+
expect(ctx.use()).toMatchInlineSnapshot(`
|
|
153
|
+
{
|
|
154
|
+
"value": 55,
|
|
155
|
+
}
|
|
156
|
+
`);
|
|
157
|
+
done(); // this makes sure the function actually runs
|
|
158
|
+
};
|
|
159
|
+
const bound = ctx.bind({ value: 55 }, fn);
|
|
160
|
+
bound();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('Passes runtime arguments to bound function', () => {
|
|
165
|
+
const fn = vi.fn();
|
|
166
|
+
const args = Array.from({ length: 100 }, (_, i) => `${i}`); // 1-100
|
|
167
|
+
ctx.bind({}, fn)(...args);
|
|
168
|
+
|
|
169
|
+
expect(fn).toHaveBeenCalledWith(...args);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('Maintains normal context behavior when runs within context.run', () => {
|
|
173
|
+
return new Promise<void>(done => {
|
|
174
|
+
const fn = () => {
|
|
175
|
+
expect(ctx.use()).toMatchObject({ value: 200, value2: 300 });
|
|
176
|
+
expect(ctx.use()).toMatchInlineSnapshot(`
|
|
177
|
+
{
|
|
178
|
+
"value": 200,
|
|
179
|
+
"value2": 300,
|
|
180
|
+
}
|
|
181
|
+
`);
|
|
182
|
+
done();
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const bound = ctx.bind({ value2: 300 }, fn);
|
|
186
|
+
ctx.run({ value: 200, value2: 200 }, bound);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('context.use', () => {
|
|
192
|
+
describe('When in an active context', () => {
|
|
193
|
+
it('Should return a cloned ctxRef object', () => {
|
|
194
|
+
const ctxRef = { a: 1, b: 2 };
|
|
195
|
+
|
|
196
|
+
ctx.run(ctxRef, () => {
|
|
197
|
+
expect(ctx.use()).toEqual(ctxRef);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('Should return a frozen context object', () => {
|
|
202
|
+
const ctxRef = { a: 1, b: 2 };
|
|
203
|
+
|
|
204
|
+
ctx.run(ctxRef, () => {
|
|
205
|
+
expect(Object.isFrozen(ctx.use())).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('When before running the context', () => {
|
|
210
|
+
it('Should return undefined', () => {
|
|
211
|
+
expect(ctx.use()).toBeUndefined();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('When after closing the context', () => {
|
|
216
|
+
it('Should return undefined', () => {
|
|
217
|
+
ctx.run({}, () => {});
|
|
218
|
+
expect(ctx.use()).toBeUndefined();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('context.useX', () => {
|
|
225
|
+
describe('When in an active context', () => {
|
|
226
|
+
it('Should return a cloned ctxRef object', () => {
|
|
227
|
+
const ctxRef = { a: 1, b: 2 };
|
|
228
|
+
|
|
229
|
+
ctx.run(ctxRef, () => {
|
|
230
|
+
expect(ctx.useX()).toEqual(ctxRef);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('Should return a frozen context object', () => {
|
|
235
|
+
const ctxRef = { a: 1, b: 2 };
|
|
236
|
+
|
|
237
|
+
ctx.run(ctxRef, () => {
|
|
238
|
+
expect(Object.isFrozen(ctx.useX())).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('When before running the context', () => {
|
|
243
|
+
it('Should throw error', () => {
|
|
244
|
+
expect(() => ctx.useX()).toThrow('Not inside of a running context.');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('Should allow a custom context message', () => {
|
|
248
|
+
expect(() => ctx.useX('Custom Failure Message')).toThrow(
|
|
249
|
+
'Custom Failure Message',
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('When after closing the context', () => {
|
|
255
|
+
beforeEach(() => {
|
|
256
|
+
ctx.run({}, () => {});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('Should return undefined', () => {
|
|
260
|
+
expect(() => ctx.useX()).toThrow('Not inside of a running context.');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('Should allow a custom context message', () => {
|
|
264
|
+
expect(() => ctx.useX('Custom Failure Message')).toThrow(
|
|
265
|
+
'Custom Failure Message',
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('init argument', () => {
|
|
273
|
+
it('Should run init function on every context.run', () => {
|
|
274
|
+
const init = vi.fn();
|
|
275
|
+
|
|
276
|
+
const ctx = createCascade(init);
|
|
277
|
+
|
|
278
|
+
expect(init).not.toHaveBeenCalled();
|
|
279
|
+
|
|
280
|
+
ctx.run({}, () => {
|
|
281
|
+
expect(init).toHaveBeenCalledTimes(1);
|
|
282
|
+
ctx.run({}, () => {
|
|
283
|
+
expect(init).toHaveBeenCalledTimes(2);
|
|
284
|
+
ctx.run({}, () => {
|
|
285
|
+
expect(init).toHaveBeenCalledTimes(3);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
expect(init).toHaveBeenCalledTimes(3);
|
|
290
|
+
|
|
291
|
+
ctx.run({}, () => {
|
|
292
|
+
expect(init).toHaveBeenCalledTimes(4);
|
|
293
|
+
});
|
|
294
|
+
expect(init).toHaveBeenCalledTimes(4);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('Should accept ctxRef as first argument', () => {
|
|
298
|
+
const init = vi.fn();
|
|
299
|
+
|
|
300
|
+
const ctx = createCascade(init);
|
|
301
|
+
const ref1 = { a: 1, b: 2 };
|
|
302
|
+
const ref2 = { a: 2, b: 3 };
|
|
303
|
+
|
|
304
|
+
ctx.run(ref1, () => {
|
|
305
|
+
ctx.run(ref2, () => null);
|
|
306
|
+
});
|
|
307
|
+
expect(init.mock.calls[0][0]).toBe(ref1);
|
|
308
|
+
expect(init.mock.calls[1][0]).toBe(ref2);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('Should accept parentContext as second argument', () => {
|
|
312
|
+
const init = vi.fn();
|
|
313
|
+
|
|
314
|
+
const ctx = createCascade(init);
|
|
315
|
+
let p1;
|
|
316
|
+
ctx.run({}, () => {
|
|
317
|
+
p1 = ctx.use();
|
|
318
|
+
ctx.run({}, () => null);
|
|
319
|
+
});
|
|
320
|
+
expect(init.mock.calls[0][1]).toBeUndefined();
|
|
321
|
+
expect(init.mock.calls[1][1]).toBe(p1);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('When not nullish, should use init value as ctxRef', () => {
|
|
325
|
+
const ctx = createCascade<{ override?: boolean; value?: string }>(() => ({
|
|
326
|
+
override: true,
|
|
327
|
+
}));
|
|
328
|
+
ctx.run({ value: 'x' }, () => {
|
|
329
|
+
expect(ctx.useX().override).toBe(true);
|
|
330
|
+
expect(ctx.useX().value).toBeUndefined();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('When nullish, should default to ctxRef', () => {
|
|
335
|
+
const ctx = createCascade(() => null);
|
|
336
|
+
|
|
337
|
+
ctx.run({ value: 'x' }, () => {
|
|
338
|
+
expect(ctx.useX().value).toBe('x');
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createContext, CtxApi } from '../context';
|
|
4
|
+
|
|
5
|
+
describe('Context', () => {
|
|
6
|
+
let ctx: CtxApi<any>;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
ctx = createContext();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('Exposed Methods', () => {
|
|
13
|
+
it('should have a use method', () => {
|
|
14
|
+
expect(ctx.use).toBeInstanceOf(Function);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should have a run method', () => {
|
|
18
|
+
expect(ctx.run).toBeInstanceOf(Function);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('use', () => {
|
|
23
|
+
describe('When not inside of an active context', () => {
|
|
24
|
+
describe('When a default value was not provided', () => {
|
|
25
|
+
it('should return undefined', () => {
|
|
26
|
+
expect(ctx.use()).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('When a default value was provided', () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
ctx = createContext('i am the default value!');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return the default value', () => {
|
|
36
|
+
expect(ctx.use()).toBe('i am the default value!');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('useX', () => {
|
|
43
|
+
describe('When not inside of an active context', () => {
|
|
44
|
+
it('Should throw an error', () => {
|
|
45
|
+
expect(() => {
|
|
46
|
+
ctx.useX();
|
|
47
|
+
}).toThrow('Not inside of a running context.');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('Should throw an error with a custom message when passed', () => {
|
|
51
|
+
expect(() => {
|
|
52
|
+
ctx.useX('i am the error message!');
|
|
53
|
+
}).toThrow('i am the error message!');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('When a default value was provided', () => {
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
ctx = createContext('i am the default value!');
|
|
59
|
+
});
|
|
60
|
+
it('Should disregard default value', () => {
|
|
61
|
+
expect(() => {
|
|
62
|
+
ctx.useX();
|
|
63
|
+
}).toThrow('Not inside of a running context.');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('run', () => {
|
|
70
|
+
describe('It should set the current context value to the passed value', () => {
|
|
71
|
+
it('should set the current context value to the passed value', () => {
|
|
72
|
+
const value = { some: 'object' };
|
|
73
|
+
ctx.run(value, () => {
|
|
74
|
+
expect(ctx.use()).toBe(value);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('When nesting run calls', () => {
|
|
80
|
+
it("sets each layer's context with its respective value", () => {
|
|
81
|
+
const value_a = { some: 'object' };
|
|
82
|
+
ctx.run(value_a, () => {
|
|
83
|
+
expect(ctx.use()).toBe(value_a);
|
|
84
|
+
const value_b = { another: 'obj' };
|
|
85
|
+
ctx.run(value_b, () => {
|
|
86
|
+
expect(ctx.use()).toBe(value_b);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('Restores the previous context value when exiting a context layer', () => {
|
|
92
|
+
const value_a = { some: 'object' };
|
|
93
|
+
ctx.run(value_a, () => {
|
|
94
|
+
const value_b = { another: 'obj' };
|
|
95
|
+
ctx.run(value_b, () => {
|
|
96
|
+
expect(ctx.use()).toBe(value_b);
|
|
97
|
+
});
|
|
98
|
+
expect(ctx.use()).toBe(value_a);
|
|
99
|
+
});
|
|
100
|
+
expect(ctx.use()).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { CB, Maybe } from 'vest-utils';
|
|
2
|
+
import {
|
|
3
|
+
assign,
|
|
4
|
+
defaultTo,
|
|
5
|
+
invariant,
|
|
6
|
+
dynamicValue,
|
|
7
|
+
Nullable,
|
|
8
|
+
} from 'vest-utils';
|
|
9
|
+
|
|
10
|
+
const USEX_DEFAULT_ERROR_MESSAGE = 'Not inside of a running context.';
|
|
11
|
+
const EMPTY_CONTEXT = Symbol();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Base context interface.
|
|
15
|
+
*/
|
|
16
|
+
export function createContext<T>(defaultContextValue?: T): CtxApi<T> {
|
|
17
|
+
let contextValue: T | symbol = EMPTY_CONTEXT;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
run,
|
|
21
|
+
use,
|
|
22
|
+
useX,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function use(): T {
|
|
26
|
+
return (isInsideContext() ? contextValue : defaultContextValue) as T;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function useX(errorMessage?: string): T {
|
|
30
|
+
invariant(
|
|
31
|
+
isInsideContext(),
|
|
32
|
+
defaultTo(errorMessage, USEX_DEFAULT_ERROR_MESSAGE),
|
|
33
|
+
);
|
|
34
|
+
return contextValue as T;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function run<R>(value: T, cb: () => R): R {
|
|
38
|
+
const parentContext = isInsideContext() ? use() : EMPTY_CONTEXT;
|
|
39
|
+
|
|
40
|
+
contextValue = value;
|
|
41
|
+
|
|
42
|
+
const res = cb();
|
|
43
|
+
|
|
44
|
+
contextValue = parentContext;
|
|
45
|
+
return res;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isInsideContext(): boolean {
|
|
49
|
+
return contextValue !== EMPTY_CONTEXT;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Cascading context - another implementation of context, that assumes the context value is an object.
|
|
55
|
+
* When nesting context runs, the the values of the current layer merges with the layers above it.
|
|
56
|
+
*/
|
|
57
|
+
export function createCascade<T extends Record<string, unknown>>(
|
|
58
|
+
init?: (value: Partial<T>, parentContext: Maybe<T>) => Nullable<T>,
|
|
59
|
+
): CtxCascadeApi<T> {
|
|
60
|
+
const ctx = createContext<T>();
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
bind,
|
|
64
|
+
run,
|
|
65
|
+
use: ctx.use,
|
|
66
|
+
useX: ctx.useX,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function run<R>(value: Partial<T>, fn: () => R): R {
|
|
70
|
+
const parentContext = ctx.use();
|
|
71
|
+
|
|
72
|
+
const initResult = dynamicValue(init, value, parentContext) ?? value;
|
|
73
|
+
|
|
74
|
+
const out = assign({}, parentContext ? parentContext : {}, initResult) as T;
|
|
75
|
+
|
|
76
|
+
return ctx.run(Object.freeze(out), fn) as R;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function bind<Fn extends CB>(value: Partial<T>, fn: Fn) {
|
|
80
|
+
return function (...runTimeArgs: Parameters<Fn>) {
|
|
81
|
+
return run<ReturnType<Fn>>(value, function () {
|
|
82
|
+
return fn(...runTimeArgs);
|
|
83
|
+
});
|
|
84
|
+
} as Fn;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type ContextConsumptionApi<T> = {
|
|
89
|
+
use: () => T;
|
|
90
|
+
useX: (errorMessage?: string) => T;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type CtxApi<T> = ContextConsumptionApi<T> & {
|
|
94
|
+
run: <R>(value: T, cb: () => R) => R;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type CtxCascadeApi<T> = ContextConsumptionApi<T> & {
|
|
98
|
+
run: <R>(value: Partial<T>, fn: () => R) => R;
|
|
99
|
+
bind: <Fn extends CB>(value: Partial<T>, fn: Fn) => Fn;
|
|
100
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CB, Maybe, Nullable } from "vest-utils";
|
|
2
|
+
|
|
3
|
+
//#region src/context.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base context interface.
|
|
7
|
+
*/
|
|
8
|
+
declare function createContext<T>(defaultContextValue?: T): CtxApi<T>;
|
|
9
|
+
/**
|
|
10
|
+
* Cascading context - another implementation of context, that assumes the context value is an object.
|
|
11
|
+
* When nesting context runs, the the values of the current layer merges with the layers above it.
|
|
12
|
+
*/
|
|
13
|
+
declare function createCascade<T extends Record<string, unknown>>(init?: (value: Partial<T>, parentContext: Maybe<T>) => Nullable<T>): CtxCascadeApi<T>;
|
|
14
|
+
type ContextConsumptionApi<T> = {
|
|
15
|
+
use: () => T;
|
|
16
|
+
useX: (errorMessage?: string) => T;
|
|
17
|
+
};
|
|
18
|
+
type CtxApi<T> = ContextConsumptionApi<T> & {
|
|
19
|
+
run: <R>(value: T, cb: () => R) => R;
|
|
20
|
+
};
|
|
21
|
+
type CtxCascadeApi<T> = ContextConsumptionApi<T> & {
|
|
22
|
+
run: <R>(value: Partial<T>, fn: () => R) => R;
|
|
23
|
+
bind: <Fn extends CB>(value: Partial<T>, fn: Fn) => Fn;
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { CtxApi, CtxCascadeApi, createCascade, createContext };
|
|
27
|
+
//# sourceMappingURL=context.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.cts","names":["CB","Maybe","Nullable","createContext","T","CtxApi","createCascade","Record","Partial","CtxCascadeApi","ContextConsumptionApi","R","Fn"],"sources":["../src/context.d.ts"],"sourcesContent":["import type { CB, Maybe } from 'vest-utils';\nimport { Nullable } from 'vest-utils';\n/**\n * Base context interface.\n */\nexport declare function createContext<T>(defaultContextValue?: T): CtxApi<T>;\n/**\n * Cascading context - another implementation of context, that assumes the context value is an object.\n * When nesting context runs, the the values of the current layer merges with the layers above it.\n */\nexport declare function createCascade<T extends Record<string, unknown>>(init?: (value: Partial<T>, parentContext: Maybe<T>) => Nullable<T>): CtxCascadeApi<T>;\ntype ContextConsumptionApi<T> = {\n use: () => T;\n useX: (errorMessage?: string) => T;\n};\nexport type CtxApi<T> = ContextConsumptionApi<T> & {\n run: <R>(value: T, cb: () => R) => R;\n};\nexport type CtxCascadeApi<T> = ContextConsumptionApi<T> & {\n run: <R>(value: Partial<T>, fn: () => R) => R;\n bind: <Fn extends CB>(value: Partial<T>, fn: Fn) => Fn;\n};\nexport {};\n"],"mappings":";;;;;AAKA;;AAA0EI,iBAAlDD,aAAkDC,CAAAA,CAAAA,CAAAA,CAAAA,mBAAAA,CAAAA,EAAXA,CAAWA,CAAAA,EAAPC,MAAOD,CAAAA,CAAAA,CAAAA;;;AAK1E;;AAAgGA,iBAAxEE,aAAwEF,CAAAA,UAAhDG,MAAgDH,CAAAA,MAAAA,EAAAA,OAAAA,CAAAA,CAAAA,CAAAA,IAAAA,CAAAA,EAAAA,CAAAA,KAAAA,EAARI,OAAQJ,CAAAA,CAAAA,CAAAA,EAAAA,aAAAA,EAAmBH,KAAnBG,CAAyBA,CAAzBA,CAAAA,EAAAA,GAAgCF,QAAhCE,CAAyCA,CAAzCA,CAAAA,CAAAA,EAA8CK,aAA9CL,CAA4DA,CAA5DA,CAAAA;KAC3FM,qBADmFF,CAAAA,CAAAA,CAAAA,GAAAA;EAAiCJ,GAAAA,EAAAA,GAAAA,GAE1GA,CAF0GA;EAANH,IAAAA,EAAAA,CAAAA,YAAAA,CAAAA,EAAAA,MAAAA,EAAAA,GAG9EG,CAH8EH;CAAsBG;AAATF,KAKpHG,MALoHH,CAAAA,CAAAA,CAAAA,GAKxGQ,qBALwGR,CAKlFE,CALkFF,CAAAA,GAAAA;EAA4BE,GAAAA,EAAAA,CAAAA,CAAAA,CAAAA,CAAAA,KAAAA,EAMxIA,CANwIA,EAAAA,EAAAA,EAAAA,GAAAA,GAM3HO,CAN2HP,EAAAA,GAMrHO,CANqHP;CAAdK;AAAa,KAQ/IA,aAR+I,CAAA,CAAA,CAAA,GAQ5HC,qBAR4H,CAQtGN,CARsG,CAAA,GAAA;EACtJM,GAAAA,EAAAA,CAAAA,CAAAA,CAAAA,CAAAA,KAAAA,EAQeF,OARfE,CAQuBN,CARF,CAAA,EAAA,EAAAA,EAAAA,GAAA,GAQgBO,CANLP,EAAC,GAMUO,CANV;EAE1BN,IAAAA,EAAAA,CAAAA,WAKUL,EALJI,CAAAA,CAAAA,KAAA,EAKeI,OALf,CAKuBJ,CALvB,CAAA,EAAA,EAAA,EAK+BQ,EAL/B,EAAA,GAKsCA,EALtC;CAA4BR"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CB, Maybe, Nullable } from "vest-utils";
|
|
2
|
+
|
|
3
|
+
//#region src/context.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base context interface.
|
|
7
|
+
*/
|
|
8
|
+
declare function createContext<T>(defaultContextValue?: T): CtxApi<T>;
|
|
9
|
+
/**
|
|
10
|
+
* Cascading context - another implementation of context, that assumes the context value is an object.
|
|
11
|
+
* When nesting context runs, the the values of the current layer merges with the layers above it.
|
|
12
|
+
*/
|
|
13
|
+
declare function createCascade<T extends Record<string, unknown>>(init?: (value: Partial<T>, parentContext: Maybe<T>) => Nullable<T>): CtxCascadeApi<T>;
|
|
14
|
+
type ContextConsumptionApi<T> = {
|
|
15
|
+
use: () => T;
|
|
16
|
+
useX: (errorMessage?: string) => T;
|
|
17
|
+
};
|
|
18
|
+
type CtxApi<T> = ContextConsumptionApi<T> & {
|
|
19
|
+
run: <R>(value: T, cb: () => R) => R;
|
|
20
|
+
};
|
|
21
|
+
type CtxCascadeApi<T> = ContextConsumptionApi<T> & {
|
|
22
|
+
run: <R>(value: Partial<T>, fn: () => R) => R;
|
|
23
|
+
bind: <Fn extends CB>(value: Partial<T>, fn: Fn) => Fn;
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { CtxApi, CtxCascadeApi, createCascade, createContext };
|
|
27
|
+
//# sourceMappingURL=context.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.mts","names":["CB","Maybe","Nullable","createContext","T","CtxApi","createCascade","Record","Partial","CtxCascadeApi","ContextConsumptionApi","R","Fn"],"sources":["../src/context.d.ts"],"sourcesContent":["import type { CB, Maybe } from 'vest-utils';\nimport { Nullable } from 'vest-utils';\n/**\n * Base context interface.\n */\nexport declare function createContext<T>(defaultContextValue?: T): CtxApi<T>;\n/**\n * Cascading context - another implementation of context, that assumes the context value is an object.\n * When nesting context runs, the the values of the current layer merges with the layers above it.\n */\nexport declare function createCascade<T extends Record<string, unknown>>(init?: (value: Partial<T>, parentContext: Maybe<T>) => Nullable<T>): CtxCascadeApi<T>;\ntype ContextConsumptionApi<T> = {\n use: () => T;\n useX: (errorMessage?: string) => T;\n};\nexport type CtxApi<T> = ContextConsumptionApi<T> & {\n run: <R>(value: T, cb: () => R) => R;\n};\nexport type CtxCascadeApi<T> = ContextConsumptionApi<T> & {\n run: <R>(value: Partial<T>, fn: () => R) => R;\n bind: <Fn extends CB>(value: Partial<T>, fn: Fn) => Fn;\n};\nexport {};\n"],"mappings":";;;;;AAKA;;AAA0EI,iBAAlDD,aAAkDC,CAAAA,CAAAA,CAAAA,CAAAA,mBAAAA,CAAAA,EAAXA,CAAWA,CAAAA,EAAPC,MAAOD,CAAAA,CAAAA,CAAAA;;;AAK1E;;AAAgGA,iBAAxEE,aAAwEF,CAAAA,UAAhDG,MAAgDH,CAAAA,MAAAA,EAAAA,OAAAA,CAAAA,CAAAA,CAAAA,IAAAA,CAAAA,EAAAA,CAAAA,KAAAA,EAARI,OAAQJ,CAAAA,CAAAA,CAAAA,EAAAA,aAAAA,EAAmBH,KAAnBG,CAAyBA,CAAzBA,CAAAA,EAAAA,GAAgCF,QAAhCE,CAAyCA,CAAzCA,CAAAA,CAAAA,EAA8CK,aAA9CL,CAA4DA,CAA5DA,CAAAA;KAC3FM,qBADmFF,CAAAA,CAAAA,CAAAA,GAAAA;EAAiCJ,GAAAA,EAAAA,GAAAA,GAE1GA,CAF0GA;EAANH,IAAAA,EAAAA,CAAAA,YAAAA,CAAAA,EAAAA,MAAAA,EAAAA,GAG9EG,CAH8EH;CAAsBG;AAATF,KAKpHG,MALoHH,CAAAA,CAAAA,CAAAA,GAKxGQ,qBALwGR,CAKlFE,CALkFF,CAAAA,GAAAA;EAA4BE,GAAAA,EAAAA,CAAAA,CAAAA,CAAAA,CAAAA,KAAAA,EAMxIA,CANwIA,EAAAA,EAAAA,EAAAA,GAAAA,GAM3HO,CAN2HP,EAAAA,GAMrHO,CANqHP;CAAdK;AAAa,KAQ/IA,aAR+I,CAAA,CAAA,CAAA,GAQ5HC,qBAR4H,CAQtGN,CARsG,CAAA,GAAA;EACtJM,GAAAA,EAAAA,CAAAA,CAAAA,CAAAA,CAAAA,KAAAA,EAQeF,OARfE,CAQuBN,CARF,CAAA,EAAA,EAAAA,EAAAA,GAAA,GAQgBO,CANLP,EAAC,GAMUO,CANV;EAE1BN,IAAAA,EAAAA,CAAAA,WAKUL,EALJI,CAAAA,CAAAA,KAAA,EAKeI,OALf,CAKuBJ,CALvB,CAAA,EAAA,EAAA,EAK+BQ,EAL/B,EAAA,GAKsCA,EALtC;CAA4BR"}
|
package/types/context.d.ts
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import { CB } from
|
|
1
|
+
import { CB, Maybe, Nullable } from "vest-utils";
|
|
2
|
+
|
|
3
|
+
//#region src/context.d.ts
|
|
4
|
+
|
|
2
5
|
/**
|
|
3
6
|
* Base context interface.
|
|
4
7
|
*/
|
|
5
|
-
declare function createContext<T
|
|
8
|
+
declare function createContext<T>(defaultContextValue?: T): CtxApi<T>;
|
|
6
9
|
/**
|
|
7
10
|
* Cascading context - another implementation of context, that assumes the context value is an object.
|
|
8
11
|
* When nesting context runs, the the values of the current layer merges with the layers above it.
|
|
9
12
|
*/
|
|
10
|
-
declare function createCascade<T extends Record<string, unknown>>(init?: (value: Partial<T>, parentContext: T
|
|
13
|
+
declare function createCascade<T extends Record<string, unknown>>(init?: (value: Partial<T>, parentContext: Maybe<T>) => Nullable<T>): CtxCascadeApi<T>;
|
|
11
14
|
type ContextConsumptionApi<T> = {
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
use: () => T;
|
|
16
|
+
useX: (errorMessage?: string) => T;
|
|
14
17
|
};
|
|
15
18
|
type CtxApi<T> = ContextConsumptionApi<T> & {
|
|
16
|
-
|
|
19
|
+
run: <R>(value: T, cb: () => R) => R;
|
|
17
20
|
};
|
|
18
21
|
type CtxCascadeApi<T> = ContextConsumptionApi<T> & {
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
run: <R>(value: Partial<T>, fn: () => R) => R;
|
|
23
|
+
bind: <Fn extends CB>(value: Partial<T>, fn: Fn) => Fn;
|
|
21
24
|
};
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
//#endregion
|
|
26
|
+
export { CtxApi, CtxCascadeApi, createCascade, createContext };
|
|
27
|
+
//# sourceMappingURL=context.d.cts.map
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path, { resolve } from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
|
|
4
|
+
import { defineConfig } from 'vitest/config';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
test: {
|
|
11
|
+
globals: true,
|
|
12
|
+
include: ['./**/__tests__/*.test.ts'],
|
|
13
|
+
setupFiles: [resolve(__dirname, '../../', 'vx/config/vitest')],
|
|
14
|
+
},
|
|
15
|
+
root: __dirname,
|
|
16
|
+
resolve: {
|
|
17
|
+
alias: {
|
|
18
|
+
context: resolve(__dirname, 'src/context.ts'),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|