@burojs/context 0.2.0 → 0.4.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/dist/index.cjs CHANGED
@@ -1,373 +1,328 @@
1
- 'use strict';
2
-
3
- // src/atom.ts
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/atom.ts
4
3
  function createAtom(initial) {
5
- let value = initial;
6
- const subscribers = /* @__PURE__ */ new Set();
7
- return {
8
- get() {
9
- return value;
10
- },
11
- set(next) {
12
- if (Object.is(value, next)) return;
13
- value = next;
14
- for (const sub of subscribers) {
15
- sub();
16
- }
17
- },
18
- subscribe(fn) {
19
- subscribers.add(fn);
20
- return () => {
21
- subscribers.delete(fn);
22
- };
23
- }
24
- };
4
+ let value = initial;
5
+ const subscribers = new Set();
6
+ return {
7
+ get() {
8
+ return value;
9
+ },
10
+ set(next) {
11
+ if (Object.is(value, next)) return;
12
+ value = next;
13
+ for (const sub of subscribers) sub();
14
+ },
15
+ subscribe(fn) {
16
+ subscribers.add(fn);
17
+ return () => {
18
+ subscribers.delete(fn);
19
+ };
20
+ }
21
+ };
25
22
  }
26
-
27
- // src/tracked-deps.ts
23
+ //#endregion
24
+ //#region src/tracked-deps.ts
28
25
  function makeTracker(target, prefix, deps) {
29
- if (target === null || target === void 0) return target;
30
- if (typeof target !== "object") return target;
31
- return new Proxy(target, {
32
- get(obj, key) {
33
- if (typeof key === "symbol") {
34
- return Reflect.get(obj, key);
35
- }
36
- const path = prefix ? `${prefix}.${key}` : key;
37
- deps.add(path);
38
- const value = Reflect.get(obj, key);
39
- return makeTracker(value, path, deps);
40
- }
41
- });
26
+ if (target === null || target === void 0) return target;
27
+ if (typeof target !== "object") return target;
28
+ return new Proxy(target, { get(obj, key) {
29
+ if (typeof key === "symbol") return Reflect.get(obj, key);
30
+ const path = prefix ? `${prefix}.${key}` : key;
31
+ deps.add(path);
32
+ return makeTracker(Reflect.get(obj, key), path, deps);
33
+ } });
42
34
  }
43
35
  function trackReads(ctx, selector) {
44
- const deps = /* @__PURE__ */ new Set();
45
- const tracker = makeTracker(ctx, "", deps);
46
- const result = selector(tracker);
47
- return { result, deps };
36
+ const deps = new Set();
37
+ return {
38
+ result: selector(makeTracker(ctx, "", deps)),
39
+ deps
40
+ };
48
41
  }
49
-
50
- // src/store.ts
42
+ //#endregion
43
+ //#region src/store.ts
51
44
  function createStore(initial) {
52
- const root = createAtom({ ...initial });
53
- const subscribers = /* @__PURE__ */ new Set();
54
- let lastChangedKey = null;
55
- root.subscribe(() => {
56
- for (const sub of subscribers) {
57
- if (!sub.selector) {
58
- sub.fn();
59
- continue;
60
- }
61
- const { deps } = trackReads(root.get(), sub.selector);
62
- sub.lastDeps = deps;
63
- if (lastChangedKey && intersects(deps, lastChangedKey)) {
64
- sub.fn();
65
- }
66
- }
67
- });
68
- return {
69
- snapshot() {
70
- return root.get();
71
- },
72
- setSlice(key, value) {
73
- const prev = root.get();
74
- lastChangedKey = key;
75
- root.set({ ...prev, [key]: value });
76
- },
77
- subscribe(fn, selector) {
78
- const sub = { fn, selector };
79
- if (selector) {
80
- const { deps } = trackReads(root.get(), selector);
81
- sub.lastDeps = deps;
82
- }
83
- subscribers.add(sub);
84
- return () => {
85
- subscribers.delete(sub);
86
- };
87
- }
88
- };
45
+ const root = createAtom({ ...initial });
46
+ const subscribers = new Set();
47
+ let lastChangedKey = null;
48
+ root.subscribe(() => {
49
+ for (const sub of subscribers) {
50
+ if (!sub.selector) {
51
+ sub.fn();
52
+ continue;
53
+ }
54
+ const { deps } = trackReads(root.get(), sub.selector);
55
+ sub.lastDeps = deps;
56
+ if (lastChangedKey && intersects(deps, lastChangedKey)) sub.fn();
57
+ }
58
+ });
59
+ return {
60
+ snapshot() {
61
+ return root.get();
62
+ },
63
+ setSlice(key, value) {
64
+ const prev = root.get();
65
+ lastChangedKey = key;
66
+ root.set({
67
+ ...prev,
68
+ [key]: value
69
+ });
70
+ },
71
+ subscribe(fn, selector) {
72
+ const sub = {
73
+ fn,
74
+ selector
75
+ };
76
+ if (selector) {
77
+ const { deps } = trackReads(root.get(), selector);
78
+ sub.lastDeps = deps;
79
+ }
80
+ subscribers.add(sub);
81
+ return () => {
82
+ subscribers.delete(sub);
83
+ };
84
+ }
85
+ };
89
86
  }
90
87
  function intersects(deps, changedKey) {
91
- if (deps.has(changedKey)) return true;
92
- for (const dep of deps) {
93
- if (dep.startsWith(`${changedKey}.`)) return true;
94
- }
95
- return false;
88
+ if (deps.has(changedKey)) return true;
89
+ for (const dep of deps) if (dep.startsWith(`${changedKey}.`)) return true;
90
+ return false;
96
91
  }
97
-
98
- // src/dag.ts
92
+ //#endregion
93
+ //#region src/dag.ts
99
94
  function topoSort(nodes) {
100
- const byName = /* @__PURE__ */ new Map();
101
- for (const n of nodes) {
102
- byName.set(n.name, n);
103
- }
104
- for (const n of nodes) {
105
- for (const d of n.deps) {
106
- if (!byName.has(d)) {
107
- throw new Error(`Unknown dependency '${d}' referenced by '${n.name}'`);
108
- }
109
- }
110
- }
111
- const visited = /* @__PURE__ */ new Set();
112
- const visiting = /* @__PURE__ */ new Set();
113
- const result = [];
114
- function visit(name, path) {
115
- if (visited.has(name)) return;
116
- if (visiting.has(name)) {
117
- const cycleStart = path.indexOf(name);
118
- const cyclePath = path.slice(cycleStart).concat(name).join(" \u2192 ");
119
- throw new Error(`Cycle detected: ${cyclePath}`);
120
- }
121
- visiting.add(name);
122
- const node = byName.get(name);
123
- for (const dep of node.deps) {
124
- visit(dep, [...path, name]);
125
- }
126
- visiting.delete(name);
127
- visited.add(name);
128
- result.push(node);
129
- }
130
- for (const n of nodes) {
131
- visit(n.name, []);
132
- }
133
- return result;
95
+ const byName = new Map();
96
+ for (const n of nodes) byName.set(n.name, n);
97
+ for (const n of nodes) for (const d of n.deps) if (!byName.has(d)) throw new Error(`Unknown dependency '${d}' referenced by '${n.name}'`);
98
+ const visited = new Set();
99
+ const visiting = new Set();
100
+ const result = [];
101
+ function visit(name, path) {
102
+ if (visited.has(name)) return;
103
+ if (visiting.has(name)) {
104
+ const cycleStart = path.indexOf(name);
105
+ const cyclePath = path.slice(cycleStart).concat(name).join(" → ");
106
+ throw new Error(`Cycle detected: ${cyclePath}`);
107
+ }
108
+ visiting.add(name);
109
+ const node = byName.get(name);
110
+ for (const dep of node.deps) visit(dep, [...path, name]);
111
+ visiting.delete(name);
112
+ visited.add(name);
113
+ result.push(node);
114
+ }
115
+ for (const n of nodes) visit(n.name, []);
116
+ return result;
134
117
  }
135
-
136
- // src/slices/static.ts
118
+ //#endregion
119
+ //#region src/slices/static.ts
137
120
  function defineStaticSlice(name, config) {
138
- const valueFn = typeof config.value === "function" ? config.value : () => config.value;
139
- return {
140
- kind: "static",
141
- name,
142
- deps: [],
143
- value: valueFn
144
- };
121
+ return {
122
+ kind: "static",
123
+ name,
124
+ deps: [],
125
+ value: typeof config.value === "function" ? config.value : () => config.value
126
+ };
145
127
  }
146
-
147
- // src/slices/from-auth.ts
128
+ //#endregion
129
+ //#region src/slices/from-auth.ts
148
130
  function defineFromAuthSlice(name, config) {
149
- return {
150
- kind: "fromAuth",
151
- name,
152
- deps: [],
153
- extract: config.extract,
154
- invalidateOn: config.invalidateOn ?? ["login", "logout"]
155
- };
131
+ return {
132
+ kind: "fromAuth",
133
+ name,
134
+ deps: [],
135
+ extract: config.extract,
136
+ invalidateOn: config.invalidateOn ?? ["login", "logout"]
137
+ };
156
138
  }
157
-
158
- // src/slices/picker.ts
139
+ //#endregion
140
+ //#region src/slices/picker.ts
159
141
  function definePickerSlice(name, config) {
160
- return {
161
- kind: "picker",
162
- name,
163
- deps: config.dependsOn ?? [],
164
- resource: config.resource,
165
- filter: config.filter,
166
- resetWhen: config.resetWhen,
167
- persist: config.persist,
168
- required: config.required ?? false
169
- };
142
+ return {
143
+ kind: "picker",
144
+ name,
145
+ deps: config.dependsOn ?? [],
146
+ resource: config.resource,
147
+ filter: config.filter,
148
+ resetWhen: config.resetWhen,
149
+ persist: config.persist,
150
+ required: config.required ?? false
151
+ };
170
152
  }
171
-
172
- // src/slices/async.ts
153
+ //#endregion
154
+ //#region src/slices/async.ts
173
155
  function defineAsyncSlice(name, config) {
174
- return {
175
- kind: "async",
176
- name,
177
- deps: config.dependsOn ?? [],
178
- fetch: config.fetch,
179
- cacheKey: config.cacheKey,
180
- staleTime: config.staleTime ?? 0
181
- };
156
+ return {
157
+ kind: "async",
158
+ name,
159
+ deps: config.dependsOn ?? [],
160
+ fetch: config.fetch,
161
+ cacheKey: config.cacheKey,
162
+ staleTime: config.staleTime ?? 0
163
+ };
182
164
  }
183
-
184
- // src/define-context-slices.ts
185
- var builder = {
186
- static: (config) => defineStaticSlice("", config),
187
- fromAuth: (config) => defineFromAuthSlice("", config),
188
- picker: (config) => definePickerSlice("", config),
189
- async: (config) => defineAsyncSlice("", config)
165
+ //#endregion
166
+ //#region src/define-context-slices.ts
167
+ const builder = {
168
+ static: (config) => defineStaticSlice("", config),
169
+ fromAuth: (config) => defineFromAuthSlice("", config),
170
+ picker: (config) => definePickerSlice("", config),
171
+ async: (config) => defineAsyncSlice("", config)
190
172
  };
191
173
  function defineContextSlices(factory) {
192
- const result = factory(builder);
193
- for (const [key, slice] of Object.entries(result)) {
194
- slice.name = key;
195
- }
196
- return result;
174
+ const result = factory(builder);
175
+ for (const [key, slice] of Object.entries(result)) slice.name = key;
176
+ return result;
197
177
  }
198
-
199
- // src/resolver.ts
178
+ //#endregion
179
+ //#region src/resolver.ts
200
180
  function createResolver(slices) {
201
- const sliceList = Object.values(slices);
202
- let sortedSlices = [];
203
- let store;
204
- const pendingAsync = /* @__PURE__ */ new Set();
205
- const asyncCache = /* @__PURE__ */ new Map();
206
- function initial() {
207
- const init = {};
208
- for (const slice of sliceList) {
209
- if (slice.kind === "static") {
210
- init[slice.name] = slice.value();
211
- } else if (slice.kind === "picker") {
212
- init[slice.name] = null;
213
- } else {
214
- init[slice.name] = void 0;
215
- }
216
- }
217
- return init;
218
- }
219
- async function runAsyncFor(slice) {
220
- if (slice.kind !== "async") return;
221
- const asyncSlice = slice;
222
- const ctx = store.snapshot();
223
- const key = asyncSlice.cacheKey ? asyncSlice.cacheKey(ctx) : "__no_key__";
224
- const cached = asyncCache.get(asyncSlice.name);
225
- const now = Date.now();
226
- if (cached && cached.key === key && asyncSlice.staleTime > 0 && now - cached.fetchedAt < asyncSlice.staleTime) {
227
- store.setSlice(asyncSlice.name, cached.value);
228
- return;
229
- }
230
- try {
231
- const value = await asyncSlice.fetch(ctx);
232
- asyncCache.set(asyncSlice.name, { key, value, fetchedAt: Date.now() });
233
- store.setSlice(asyncSlice.name, value);
234
- } catch (err) {
235
- store.setSlice(asyncSlice.name, { __error: err });
236
- }
237
- }
238
- function invalidateDependents(changedKey) {
239
- for (const slice of sortedSlices) {
240
- if (slice.deps.includes(changedKey)) {
241
- if (slice.kind === "picker") {
242
- const picker = slice;
243
- if (picker.resetWhen?.includes(changedKey)) {
244
- store.setSlice(picker.name, null);
245
- }
246
- } else if (slice.kind === "async") {
247
- pendingAsync.add(slice.name);
248
- }
249
- }
250
- }
251
- }
252
- return {
253
- async boot() {
254
- sortedSlices = topoSort(sliceList);
255
- store = createStore(initial());
256
- for (const slice of sortedSlices) {
257
- if (slice.kind === "picker") {
258
- try {
259
- const target = slice.resource();
260
- const t = target;
261
- if (t && (t.scope !== void 0 || t.requireScope !== void 0)) {
262
- if (typeof console !== "undefined") {
263
- console.warn(
264
- `[@burojs/context] picker-slice '${slice.name}': resource has scope/requireScope set; these are ignored by picker-slice (scope-isolation contract).`
265
- );
266
- }
267
- }
268
- } catch {
269
- }
270
- }
271
- }
272
- for (const slice of sortedSlices) {
273
- if (slice.kind === "async") {
274
- const snap = store.snapshot();
275
- const depsReady = slice.deps.every((dep) => {
276
- const val = snap[dep];
277
- return val !== null && val !== void 0;
278
- });
279
- if (depsReady) {
280
- pendingAsync.add(slice.name);
281
- }
282
- }
283
- }
284
- await this.flushAsync();
285
- },
286
- snapshot() {
287
- return store.snapshot();
288
- },
289
- setSlice(key, value) {
290
- store.setSlice(key, value);
291
- invalidateDependents(key);
292
- },
293
- subscribe(fn, selector) {
294
- return store.subscribe(fn, selector);
295
- },
296
- feedAuth(rawAuthUser) {
297
- for (const slice of sortedSlices) {
298
- if (slice.kind === "fromAuth") {
299
- const authSlice = slice;
300
- store.setSlice(authSlice.name, authSlice.extract(rawAuthUser));
301
- invalidateDependents(authSlice.name);
302
- }
303
- }
304
- },
305
- async flushAsync() {
306
- const queue = Array.from(pendingAsync);
307
- pendingAsync.clear();
308
- for (const name of queue) {
309
- const slice = sortedSlices.find((s) => s.name === name);
310
- if (slice) {
311
- await runAsyncFor(slice);
312
- }
313
- }
314
- }
315
- };
181
+ const sliceList = Object.values(slices);
182
+ let sortedSlices = [];
183
+ let store;
184
+ const pendingAsync = new Set();
185
+ const asyncCache = new Map();
186
+ function initial() {
187
+ const init = {};
188
+ for (const slice of sliceList) if (slice.kind === "static") init[slice.name] = slice.value();
189
+ else if (slice.kind === "picker") init[slice.name] = null;
190
+ else init[slice.name] = void 0;
191
+ return init;
192
+ }
193
+ async function runAsyncFor(slice) {
194
+ if (slice.kind !== "async") return;
195
+ const asyncSlice = slice;
196
+ const ctx = store.snapshot();
197
+ const key = asyncSlice.cacheKey ? asyncSlice.cacheKey(ctx) : "__no_key__";
198
+ const cached = asyncCache.get(asyncSlice.name);
199
+ const now = Date.now();
200
+ if (cached && cached.key === key && asyncSlice.staleTime > 0 && now - cached.fetchedAt < asyncSlice.staleTime) {
201
+ store.setSlice(asyncSlice.name, cached.value);
202
+ return;
203
+ }
204
+ try {
205
+ const value = await asyncSlice.fetch(ctx);
206
+ asyncCache.set(asyncSlice.name, {
207
+ key,
208
+ value,
209
+ fetchedAt: Date.now()
210
+ });
211
+ store.setSlice(asyncSlice.name, value);
212
+ } catch (err) {
213
+ store.setSlice(asyncSlice.name, { __error: err });
214
+ }
215
+ }
216
+ function invalidateDependents(changedKey) {
217
+ for (const slice of sortedSlices) if (slice.deps.includes(changedKey)) {
218
+ if (slice.kind === "picker") {
219
+ const picker = slice;
220
+ if (picker.resetWhen?.includes(changedKey)) store.setSlice(picker.name, null);
221
+ } else if (slice.kind === "async") pendingAsync.add(slice.name);
222
+ }
223
+ }
224
+ return {
225
+ async boot() {
226
+ sortedSlices = topoSort(sliceList);
227
+ store = createStore(initial());
228
+ for (const slice of sortedSlices) if (slice.kind === "picker") try {
229
+ const t = slice.resource();
230
+ if (t && (t.scope !== void 0 || t.requireScope !== void 0)) {
231
+ if (typeof console !== "undefined") console.warn(`[@burojs/context] picker-slice '${slice.name}': resource has scope/requireScope set; these are ignored by picker-slice (scope-isolation contract).`);
232
+ }
233
+ } catch {}
234
+ for (const slice of sortedSlices) if (slice.kind === "async") {
235
+ const snap = store.snapshot();
236
+ if (slice.deps.every((dep) => {
237
+ const val = snap[dep];
238
+ return val !== null && val !== void 0;
239
+ })) pendingAsync.add(slice.name);
240
+ }
241
+ await this.flushAsync();
242
+ },
243
+ snapshot() {
244
+ return store.snapshot();
245
+ },
246
+ setSlice(key, value) {
247
+ store.setSlice(key, value);
248
+ invalidateDependents(key);
249
+ },
250
+ subscribe(fn, selector) {
251
+ return store.subscribe(fn, selector);
252
+ },
253
+ feedAuth(rawAuthUser) {
254
+ for (const slice of sortedSlices) if (slice.kind === "fromAuth") {
255
+ const authSlice = slice;
256
+ store.setSlice(authSlice.name, authSlice.extract(rawAuthUser));
257
+ invalidateDependents(authSlice.name);
258
+ }
259
+ },
260
+ async flushAsync() {
261
+ const queue = Array.from(pendingAsync);
262
+ pendingAsync.clear();
263
+ for (const name of queue) {
264
+ const slice = sortedSlices.find((s) => s.name === name);
265
+ if (slice) await runAsyncFor(slice);
266
+ }
267
+ }
268
+ };
316
269
  }
317
-
318
- // src/scope.ts
270
+ //#endregion
271
+ //#region src/scope.ts
319
272
  function mergeScope(base, patch) {
320
- if (base === null && patch === null) return null;
321
- if (base === null) return { ...patch };
322
- if (patch === null) return { ...base };
323
- return { ...base, ...patch };
273
+ if (base === null && patch === null) return null;
274
+ if (base === null) return { ...patch };
275
+ if (patch === null) return { ...base };
276
+ return {
277
+ ...base,
278
+ ...patch
279
+ };
324
280
  }
325
281
  function cascadeScope(chain, ctx) {
326
- let acc = null;
327
- for (const entry of chain) {
328
- const slice = entry.scope(ctx);
329
- acc = mergeScope(acc, slice);
330
- }
331
- return acc;
282
+ let acc = null;
283
+ for (const entry of chain) {
284
+ const slice = entry.scope(ctx);
285
+ acc = mergeScope(acc, slice);
286
+ }
287
+ return acc;
332
288
  }
333
289
  function checkRequireScope(scope, required) {
334
- const missing = [];
335
- for (const key of required) {
336
- if (scope === null || scope[key] === void 0) {
337
- missing.push(key);
338
- }
339
- }
340
- return missing.length === 0 ? { ok: true } : { ok: false, missing };
290
+ const missing = [];
291
+ for (const key of required) if (scope === null || scope[key] === void 0) missing.push(key);
292
+ return missing.length === 0 ? { ok: true } : {
293
+ ok: false,
294
+ missing
295
+ };
341
296
  }
342
-
343
- // src/permissions.ts
297
+ //#endregion
298
+ //#region src/permissions.ts
344
299
  function createPendingPermissions() {
345
- return {
346
- status: "loading",
347
- can: () => false,
348
- reason: () => "Permissions loading\u2026"
349
- };
300
+ return {
301
+ status: "loading",
302
+ can: () => false,
303
+ reason: () => "Permissions loading…"
304
+ };
350
305
  }
351
306
  function createErrorPermissions(error) {
352
- const message = error instanceof Error ? error.message : String(error);
353
- return {
354
- status: "error",
355
- can: () => false,
356
- reason: () => `Permissions error: ${message}`
357
- };
307
+ const message = error instanceof Error ? error.message : String(error);
308
+ return {
309
+ status: "error",
310
+ can: () => false,
311
+ reason: () => `Permissions error: ${message}`
312
+ };
358
313
  }
359
314
  function createReadyPermissions(config) {
360
- return {
361
- status: "ready",
362
- can: config.can,
363
- reason: config.reason ?? (() => void 0)
364
- };
315
+ return {
316
+ status: "ready",
317
+ can: config.can,
318
+ reason: config.reason ?? (() => void 0)
319
+ };
365
320
  }
366
-
367
- // src/index.ts
368
- var PACKAGE_NAME = "@burojs/context";
369
- var PACKAGE_VERSION = "0.0.0";
370
-
321
+ //#endregion
322
+ //#region src/index.ts
323
+ const PACKAGE_NAME = "@burojs/context";
324
+ const PACKAGE_VERSION = "0.0.0";
325
+ //#endregion
371
326
  exports.PACKAGE_NAME = PACKAGE_NAME;
372
327
  exports.PACKAGE_VERSION = PACKAGE_VERSION;
373
328
  exports.cascadeScope = cascadeScope;
@@ -386,5 +341,5 @@ exports.defineStaticSlice = defineStaticSlice;
386
341
  exports.mergeScope = mergeScope;
387
342
  exports.topoSort = topoSort;
388
343
  exports.trackReads = trackReads;
389
- //# sourceMappingURL=index.cjs.map
344
+
390
345
  //# sourceMappingURL=index.cjs.map