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