doxum 0.1.41 → 0.1.42

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,6 +1,6 @@
1
- import { A as ownProjections, C as snapshotProjectionValues, D as defineProcessor, E as snapshotCollectionView, M as projectionRef, N as profile, O as defineSource, S as compileProjectionDependencies, T as mapRead, _ as assertSynchronous, a as collectionChangeTouchesKey, b as ProjectionError, c as assertKeyedProjection, d as outputKind, f as createKeyRelation, g as assertScope, h as snapshotArray, i as collectionChangeIntersects, j as producerOf, k as isProjection, l as compileKeyedDependencies, m as sameArray, n as keyedAbsent, o as collectionHasStructuralChange, p as iterableMatchesArray, s as createCollectionChange, t as createKeyedTransform, u as createKeyedDependencyRuntime, v as createScheduler, w as collectionView, x as collectKeyedInvalidation, y as ProjectionDisposedError } from "./transform-CKJ1ivhz.js";
1
+ import { A as ownProjections, C as snapshotProjectionValues, D as defineProcessor, E as snapshotCollectionView, M as projectionRef, N as profile, O as defineSource, S as compileProjectionDependencies, T as mapRead, _ as assertSynchronous, a as collectionChangeTouchesKey, b as ProjectionError, c as assertKeyedProjection, d as outputKind, f as createKeyRelation, g as assertScope, h as snapshotArray, i as collectionChangeIntersects, j as producerOf, k as isProjection, l as compileKeyedDependencies, m as sameArray, n as keyedAbsent, o as collectionHasStructuralChange, p as iterableMatchesArray, s as createCollectionChange, t as createKeyedTransform, u as createKeyedDependencyRuntime, v as createScheduler, w as collectionView, x as collectKeyedInvalidation, y as ProjectionDisposedError } from "./transform-BvDYPUMm.js";
2
2
  import { n as isPlainObject, r as isRecord, t as installOwn } from "./record-B2yRhYsc.js";
3
- import { c as AddressIndex, d as invalidValue, f as children, g as validate, h as parent, i as contextOf, l as MutationRejected, m as is, o as decodeChanges, p as contains$1, r as bindContext, s as sealChanges, t as assertRuntimeWritable, u as fail } from "./driver-B7RR-VfX.js";
3
+ import { _ as validate, c as addListener, d as fail, f as invalidValue, g as parent, h as is, i as contextOf, l as AddressIndex, m as contains$1, o as decodeChanges, p as children, r as bindContext, s as sealChanges, t as assertRuntimeWritable, u as MutationRejected } from "./driver-B7Hu6D0u.js";
4
4
  //#region core/src/schema/model.ts
5
5
  const schemaNodeOf = (schema) => schema;
6
6
  const collectionEntryNode = (node) => node.kind === "map" || node.kind === "table" || node.kind === "list" ? node.value : void 0;
@@ -1622,10 +1622,7 @@ const createHistory = (input) => {
1622
1622
  revision: () => revision,
1623
1623
  subscribe: (listener) => {
1624
1624
  if (disposed) throw new DocumentDisposedError();
1625
- listeners.add(listener);
1626
- return () => {
1627
- listeners.delete(listener);
1628
- };
1625
+ return addListener(listeners, listener);
1629
1626
  },
1630
1627
  undo: () => {
1631
1628
  input.assertIdle();
@@ -2296,6 +2293,7 @@ const addRoot = (notification, listener) => {
2296
2293
  };
2297
2294
  notification.root.add(entry);
2298
2295
  return () => {
2296
+ if (!entry.active) return;
2299
2297
  entry.active = false;
2300
2298
  if (!notification.notifying) notification.root.delete(entry);
2301
2299
  else notification.cleanups.add(() => {
@@ -2669,7 +2667,9 @@ const select = (document, selector, equality = Object.is) => {
2669
2667
  };
2670
2668
  };
2671
2669
  const installSubscription = () => {
2672
- unsubscribeDocument?.();
2670
+ const stop = unsubscribeDocument;
2671
+ unsubscribeDocument = void 0;
2672
+ stop?.();
2673
2673
  unsubscribeDocument = dependencies.length > 0 ? context.notifications.subscribeTargets(dependencies, () => {
2674
2674
  const result = evaluate();
2675
2675
  if (result.rebound) installSubscription();
@@ -2689,15 +2689,22 @@ const select = (document, selector, equality = Object.is) => {
2689
2689
  },
2690
2690
  subscribe: (listener) => {
2691
2691
  ensureCurrent();
2692
- listeners.add(listener);
2693
- if (listeners.size === 1) installSubscription();
2694
- return () => {
2695
- listeners.delete(listener);
2692
+ const unsubscribe = addListener(listeners, listener, () => {
2696
2693
  if (!listeners.size) {
2697
- unsubscribeDocument?.();
2694
+ const stop = unsubscribeDocument;
2698
2695
  unsubscribeDocument = void 0;
2696
+ stop?.();
2699
2697
  }
2700
- };
2698
+ });
2699
+ try {
2700
+ if (listeners.size === 1) installSubscription();
2701
+ } catch (error) {
2702
+ try {
2703
+ unsubscribe();
2704
+ } catch {}
2705
+ throw error;
2706
+ }
2707
+ return unsubscribe;
2701
2708
  }
2702
2709
  });
2703
2710
  };
@@ -3687,6 +3694,33 @@ function createValueDerive(dependencies, compute, equality = Object.is) {
3687
3694
  }
3688
3695
  const derive = Object.assign(createValueDerive, { keyed: keyedDerive });
3689
3696
  //#endregion
3697
+ //#region core/src/projection/cleanup.ts
3698
+ /** Owner-local, cancellable LIFO callbacks. Lifecycle admission stays with the owner. */
3699
+ const createCleanupStack = () => {
3700
+ const entries = /* @__PURE__ */ new Set();
3701
+ return {
3702
+ add(callback) {
3703
+ const entry = () => {
3704
+ assertSynchronous(callback());
3705
+ };
3706
+ entries.add(entry);
3707
+ return () => {
3708
+ entries.delete(entry);
3709
+ };
3710
+ },
3711
+ drain(failures) {
3712
+ for (const entry of [...entries].reverse()) {
3713
+ if (!entries.delete(entry)) continue;
3714
+ try {
3715
+ entry();
3716
+ } catch (error) {
3717
+ failures.push(error);
3718
+ }
3719
+ }
3720
+ }
3721
+ };
3722
+ };
3723
+ //#endregion
3690
3724
  //#region core/src/projection/collection/changes.ts
3691
3725
  /** Net transitions for one consumer's unconsumed interval; bounded by touched keys. */
3692
3726
  const createCollectionChanges = () => {
@@ -4208,8 +4242,7 @@ const createCollectionOutput = (binding) => {
4208
4242
  },
4209
4243
  observe: (listener) => {
4210
4244
  binding.check();
4211
- observers.add(listener);
4212
- return () => observers.delete(listener);
4245
+ return addListener(observers, listener);
4213
4246
  },
4214
4247
  hasConsumers: () => consumers.size > 0,
4215
4248
  forEachConsumer: (run) => consumers.forEach((_pending, consumer) => run(consumer)),
@@ -4241,8 +4274,7 @@ const createCollectionOutput = (binding) => {
4241
4274
  },
4242
4275
  subscribe: (listener) => {
4243
4276
  binding.check();
4244
- listeners.add(listener);
4245
- return () => listeners.delete(listener);
4277
+ return addListener(listeners, listener);
4246
4278
  }
4247
4279
  };
4248
4280
  };
@@ -4357,8 +4389,7 @@ const createValueOutput = (binding) => {
4357
4389
  },
4358
4390
  observe: (listener) => {
4359
4391
  binding.check();
4360
- observers.add(listener);
4361
- return () => observers.delete(listener);
4392
+ return addListener(observers, listener);
4362
4393
  },
4363
4394
  hasConsumers: () => consumers.size > 0,
4364
4395
  forEachConsumer: (run) => consumers.forEach((_revision, consumer) => run(consumer)),
@@ -4387,8 +4418,7 @@ const createValueOutput = (binding) => {
4387
4418
  },
4388
4419
  subscribe: (listener) => {
4389
4420
  binding.check();
4390
- listeners.add(listener);
4391
- return () => listeners.delete(listener);
4421
+ return addListener(listeners, listener);
4392
4422
  }
4393
4423
  };
4394
4424
  };
@@ -4592,7 +4622,7 @@ const selectionAffects = (selection, change) => {
4592
4622
  const createDirectReadable = (source) => Object.freeze({
4593
4623
  current: source.current,
4594
4624
  revision: source.revision,
4595
- subscribe: (listener) => source.subscribe(() => listener())
4625
+ subscribe: source.subscribe
4596
4626
  });
4597
4627
  /** Consumer-side selector tracking; it never mutates processor dependencies. */
4598
4628
  const createSelectorReadable = (source, selector, equality = Object.is) => {
@@ -4696,15 +4726,22 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
4696
4726
  },
4697
4727
  subscribe: (listener) => {
4698
4728
  ensureCurrent();
4699
- listeners.add(listener);
4700
- installSource();
4701
- return () => {
4702
- listeners.delete(listener);
4703
- if (!listeners.size && unsubscribeSource) {
4704
- unsubscribeSource();
4729
+ const unsubscribe = addListener(listeners, listener, () => {
4730
+ if (!listeners.size) {
4731
+ const stop = unsubscribeSource;
4705
4732
  unsubscribeSource = void 0;
4733
+ stop?.();
4706
4734
  }
4707
- };
4735
+ });
4736
+ try {
4737
+ installSource();
4738
+ } catch (error) {
4739
+ try {
4740
+ unsubscribe();
4741
+ } catch {}
4742
+ throw error;
4743
+ }
4744
+ return unsubscribe;
4708
4745
  }
4709
4746
  });
4710
4747
  };
@@ -6002,7 +6039,7 @@ const createSourceRegistry = (scheduler) => {
6002
6039
  //#endregion
6003
6040
  //#region core/src/projection/readable/keyed.ts
6004
6041
  /** Runtime-owned keyed consumer family over one materialized collection output. */
6005
- const createProjectionItems = (source, checkOwner) => {
6042
+ const createProjectionItems = (source, checkOwner, checkSubscription) => {
6006
6043
  if (source.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
6007
6044
  let disposed = false;
6008
6045
  const active = /* @__PURE__ */ new Map();
@@ -6131,12 +6168,12 @@ const createProjectionItems = (source, checkOwner) => {
6131
6168
  subscribe: (listener) => {
6132
6169
  check();
6133
6170
  ensureObserved();
6134
- state.listeners.add(listener);
6135
- if (!state.tracked && !state.ended) registerWaiting(state);
6136
- return () => {
6137
- state.listeners.delete(listener);
6171
+ checkSubscription();
6172
+ const unsubscribe = addListener(state.listeners, listener, () => {
6138
6173
  if (!state.listeners.size && !state.tracked) unregisterWaiting(state);
6139
- };
6174
+ });
6175
+ if (!state.tracked && !state.ended) registerWaiting(state);
6176
+ return unsubscribe;
6140
6177
  }
6141
6178
  });
6142
6179
  return state;
@@ -6193,8 +6230,8 @@ const createProjectionItems = (source, checkOwner) => {
6193
6230
  },
6194
6231
  subscribe: (listener) => {
6195
6232
  check();
6196
- keyListeners.add(listener);
6197
- return () => keyListeners.delete(listener);
6233
+ checkSubscription();
6234
+ return addListener(keyListeners, listener);
6198
6235
  }
6199
6236
  });
6200
6237
  return {
@@ -6202,6 +6239,7 @@ const createProjectionItems = (source, checkOwner) => {
6202
6239
  keys,
6203
6240
  get: (key) => {
6204
6241
  check();
6242
+ checkSubscription();
6205
6243
  const current = source.current();
6206
6244
  if (source.revision() !== settledRevision) pendingKeys.add(key);
6207
6245
  const canonical = active.get(key);
@@ -6246,6 +6284,7 @@ const createProjectionRuntime = (options) => {
6246
6284
  const materialized = /* @__PURE__ */ new WeakMap();
6247
6285
  const publicViews = /* @__PURE__ */ new WeakMap();
6248
6286
  const scopes = /* @__PURE__ */ new Set();
6287
+ const cleanups = createCleanupStack();
6249
6288
  const rootItems = /* @__PURE__ */ new Map();
6250
6289
  const disposeItemControllers = (registry, failures) => {
6251
6290
  const controllers = [...registry.values()];
@@ -6275,7 +6314,7 @@ const createProjectionRuntime = (options) => {
6275
6314
  const owner = definition.owner;
6276
6315
  if (owner === void 0) return;
6277
6316
  if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
6278
- if (!requester?.active) throw new ProjectionDisposedError();
6317
+ if (requester?.status === "disposed") throw new ProjectionDisposedError();
6279
6318
  };
6280
6319
  const rememberScoped = (definition, requester) => {
6281
6320
  if (requester && definition.owner === requester) requester.materialized.push(definition);
@@ -6355,25 +6394,33 @@ const createProjectionRuntime = (options) => {
6355
6394
  assertOutput(output);
6356
6395
  return output.revision();
6357
6396
  },
6358
- subscribe: output.subscribe,
6359
- observe: output.observe
6397
+ subscribe: (listener) => {
6398
+ scheduler.assertCommand();
6399
+ return output.subscribe(listener);
6400
+ },
6401
+ observe: (listener) => {
6402
+ scheduler.assertCommand();
6403
+ return output.observe(listener);
6404
+ }
6360
6405
  });
6361
6406
  };
6362
6407
  const projectionItems = (projection, requester) => {
6408
+ scheduler.assertCommand();
6363
6409
  const output = resolveOutput(projection, requester);
6364
6410
  if (output.kind !== "collection") throw new TypeError("Projection items require a keyed collection projection.");
6365
6411
  const registry = requester?.items ?? rootItems;
6366
6412
  const existing = registry.get(output);
6367
6413
  if (existing) return existing.items;
6368
6414
  const controller = createProjectionItems(readableSource(projection, requester), () => {
6369
- if (requester && !requester.active) throw new ProjectionDisposedError();
6415
+ if (requester?.status === "disposed") throw new ProjectionDisposedError();
6370
6416
  scheduler.ensureCurrent(output);
6371
6417
  assertOutput(output);
6372
- });
6418
+ }, scheduler.assertCommand);
6373
6419
  registry.set(output, controller);
6374
6420
  return controller.items;
6375
6421
  };
6376
6422
  const makeReadable = (projection, selector, equality, requester) => {
6423
+ scheduler.assertCommand();
6377
6424
  const source = readableSource(projection, requester);
6378
6425
  return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
6379
6426
  };
@@ -6381,8 +6428,8 @@ const createProjectionRuntime = (options) => {
6381
6428
  return makeReadable(projection, selector, equality);
6382
6429
  }
6383
6430
  const inputAccess = (target, requester) => {
6384
- scheduler.assertIdle();
6385
- if (requester && !requester.active) throw new ProjectionDisposedError();
6431
+ scheduler.assertCommand();
6432
+ if (requester?.status === "disposed") throw new ProjectionDisposedError();
6386
6433
  const ref = projectionRef(target);
6387
6434
  if (ref.output !== 0 || ref.producer.kind !== "source") throw new TypeError("Projection is not an input.");
6388
6435
  if (ref.producer.source.kind !== "value-input" && ref.producer.source.kind !== "collection-input") throw new TypeError("Projection is not an input.");
@@ -6403,11 +6450,9 @@ const createProjectionRuntime = (options) => {
6403
6450
  updateInput(target, valueOrRun);
6404
6451
  }
6405
6452
  const batch = (run, options) => scheduler.batch(run, options);
6406
- const disposeScope = (state) => {
6407
- if (!state.active) return;
6408
- scheduler.assertIdle();
6409
- state.active = false;
6410
- const failures = [];
6453
+ const releaseScope = (state, failures) => {
6454
+ state.status = "disposed";
6455
+ scopes.delete(state);
6411
6456
  disposeScopeConsumers(state, failures);
6412
6457
  for (let index = state.materialized.length - 1; index >= 0; index--) {
6413
6458
  const definition = state.materialized[index];
@@ -6422,24 +6467,38 @@ const createProjectionRuntime = (options) => {
6422
6467
  }
6423
6468
  }
6424
6469
  state.materialized.length = 0;
6425
- scopes.delete(state);
6470
+ };
6471
+ const disposeScope = (state) => {
6472
+ if (state.status !== "active") return;
6473
+ scheduler.assertCommand();
6474
+ state.status = "closing";
6475
+ const failures = [];
6476
+ try {
6477
+ scheduler.cleanup(() => state.cleanups.drain(failures));
6478
+ } catch (error) {
6479
+ failures.push(error);
6480
+ } finally {
6481
+ scheduler.releaseOwned(() => releaseScope(state, failures));
6482
+ }
6426
6483
  throwCleanupFailures(failures, "Projection scope cleanup failed.");
6427
6484
  };
6428
6485
  const createScope = () => {
6429
- scheduler.assertIdle();
6486
+ scheduler.assertCommand();
6430
6487
  const state = {
6431
- active: true,
6488
+ status: "active",
6489
+ cleanups: createCleanupStack(),
6432
6490
  materialized: [],
6433
6491
  subscriptions: /* @__PURE__ */ new Set(),
6434
6492
  items: /* @__PURE__ */ new Map()
6435
6493
  };
6436
6494
  scopes.add(state);
6437
6495
  const assertActive = () => {
6438
- if (!state.active) throw new ProjectionDisposedError();
6496
+ if (state.status === "disposed") throw new ProjectionDisposedError();
6439
6497
  scheduler.assertActive();
6440
6498
  };
6441
6499
  function own(target) {
6442
6500
  assertActive();
6501
+ scheduler.assertCommand();
6443
6502
  const projections = [];
6444
6503
  const collect = (value) => {
6445
6504
  if (isProjection(value)) {
@@ -6509,6 +6568,11 @@ const createProjectionRuntime = (options) => {
6509
6568
  },
6510
6569
  update: scopeUpdate,
6511
6570
  batch: scopeBatch,
6571
+ onDispose: (callback) => {
6572
+ assertActive();
6573
+ scheduler.assertCommand();
6574
+ return state.cleanups.add(callback);
6575
+ },
6512
6576
  dispose: () => disposeScope(state)
6513
6577
  });
6514
6578
  };
@@ -6519,24 +6583,33 @@ const createProjectionRuntime = (options) => {
6519
6583
  update,
6520
6584
  batch,
6521
6585
  scope: createScope,
6586
+ onDispose: (callback) => {
6587
+ scheduler.assertCommand();
6588
+ return cleanups.add(callback);
6589
+ },
6522
6590
  dispose: () => {
6523
- if (!scheduler.active) return;
6524
- scheduler.assertIdle();
6591
+ if (!scheduler.beginClose()) return;
6525
6592
  const failures = [];
6526
- for (const scope of scopes) {
6527
- scope.active = false;
6528
- disposeScopeConsumers(scope, failures);
6529
- scope.materialized.length = 0;
6530
- }
6531
- scopes.clear();
6532
- disposeItemControllers(rootItems, failures);
6593
+ const owned = [...scopes].reverse();
6594
+ for (const scope of owned) scope.status = "closing";
6533
6595
  try {
6534
- sources.dispose();
6596
+ scheduler.cleanup(() => {
6597
+ for (const scope of owned) scope.cleanups.drain(failures);
6598
+ cleanups.drain(failures);
6599
+ });
6535
6600
  } catch (error) {
6536
6601
  failures.push(error);
6537
6602
  }
6538
6603
  try {
6539
- scheduler.dispose();
6604
+ scheduler.dispose(() => {
6605
+ for (const scope of owned) {
6606
+ scope.status = "disposed";
6607
+ scope.materialized.length = 0;
6608
+ disposeScopeConsumers(scope, failures);
6609
+ }
6610
+ scopes.clear();
6611
+ disposeItemControllers(rootItems, failures);
6612
+ }, sources.dispose);
6540
6613
  } catch (error) {
6541
6614
  failures.push(error);
6542
6615
  }
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_record = require("./record-ChYUi8PP.cjs");
3
- const require_driver = require("./driver-CN3dGxQI.cjs");
3
+ const require_driver = require("./driver-i4G4iVX0.cjs");
4
4
  //#region core/src/local-sync/error.ts
5
5
  var LocalSyncError = class extends Error {
6
6
  code;
@@ -467,10 +467,7 @@ const attachLocalSync = async (input) => {
467
467
  revision: () => stateRevision,
468
468
  subscribe: (listener) => {
469
469
  if (disposed) throw new LocalSyncError("disposed", "Local sync has been disposed.");
470
- stateListeners.add(listener);
471
- return () => {
472
- stateListeners.delete(listener);
473
- };
470
+ return require_driver.addListener(stateListeners, listener);
474
471
  }
475
472
  }),
476
473
  flush: async () => {
@@ -1,4 +1,4 @@
1
- import { W as ObjectSchema, g as Readable, s as DocumentRuntime } from "./contract-CNR6BS75.cjs";
1
+ import { W as ObjectSchema, g as Readable, s as DocumentRuntime } from "./contract-DovG1Yp-.cjs";
2
2
 
3
3
  //#region core/src/local-sync/json.d.ts
4
4
  type JsonPrimitive = null | boolean | number | string;
@@ -1,4 +1,4 @@
1
- import { W as ObjectSchema, g as Readable, s as DocumentRuntime } from "./contract-COgxO5sv.js";
1
+ import { W as ObjectSchema, g as Readable, s as DocumentRuntime } from "./contract-PqdXNaP8.js";
2
2
 
3
3
  //#region core/src/local-sync/json.d.ts
4
4
  type JsonPrimitive = null | boolean | number | string;
@@ -1,5 +1,5 @@
1
1
  import { n as isPlainObject, r as isRecord } from "./record-B2yRhYsc.js";
2
- import { a as changeCount, n as installRuntimeWriteDriver, o as decodeChanges } from "./driver-B7RR-VfX.js";
2
+ import { a as changeCount, c as addListener, n as installRuntimeWriteDriver, o as decodeChanges } from "./driver-B7Hu6D0u.js";
3
3
  //#region core/src/local-sync/error.ts
4
4
  var LocalSyncError = class extends Error {
5
5
  code;
@@ -466,10 +466,7 @@ const attachLocalSync = async (input) => {
466
466
  revision: () => stateRevision,
467
467
  subscribe: (listener) => {
468
468
  if (disposed) throw new LocalSyncError("disposed", "Local sync has been disposed.");
469
- stateListeners.add(listener);
470
- return () => {
471
- stateListeners.delete(listener);
472
- };
469
+ return addListener(stateListeners, listener);
473
470
  }
474
471
  }),
475
472
  flush: async () => {
package/dist/react.cjs CHANGED
@@ -1,9 +1,43 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- let doxum = require("doxum");
3
2
  let react = require("react");
4
- //#region react/src/hooks.ts
3
+ let doxum = require("doxum");
4
+ //#region react/src/projection.ts
5
5
  const ProjectionContext = (0, react.createContext)(void 0);
6
- const ProjectionProvider = ProjectionContext.Provider;
6
+ const ManagedProjectionProvider = ({ runtime, fallback, children }) => {
7
+ const generation = (0, react.useRef)(0);
8
+ const [owner, setOwner] = (0, react.useState)();
9
+ (0, react.useEffect)(() => {
10
+ const scope = runtime.scope();
11
+ setOwner({
12
+ runtime,
13
+ scope,
14
+ generation: ++generation.current
15
+ });
16
+ return () => scope.dispose();
17
+ }, [runtime]);
18
+ if (!owner || owner.runtime !== runtime) return fallback ?? null;
19
+ return (0, react.createElement)(ProjectionContext.Provider, {
20
+ value: owner.scope,
21
+ key: owner.generation
22
+ }, children);
23
+ };
24
+ /** runtime creates an owned scope after commit; value borrows an external owner. */
25
+ const ProjectionProvider = (props) => {
26
+ if (props.runtime !== void 0) {
27
+ if (props.value !== void 0) throw new TypeError("ProjectionProvider accepts runtime or value, not both.");
28
+ return (0, react.createElement)(ManagedProjectionProvider, props);
29
+ }
30
+ if (props.value === void 0) throw new TypeError("ProjectionProvider requires runtime or value.");
31
+ return (0, react.createElement)(ProjectionContext.Provider, { value: props.value }, props.children);
32
+ };
33
+ /** Reads a scope supplied by a managed Provider or an explicitly injected scope. */
34
+ const useProjectionScope = () => {
35
+ const owner = (0, react.useContext)(ProjectionContext);
36
+ if (!owner || !("own" in owner)) throw new Error("A ProjectionScope provider is required.");
37
+ return owner;
38
+ };
39
+ //#endregion
40
+ //#region react/src/hooks.ts
7
41
  function useProjection(projection, selector, equality = Object.is) {
8
42
  const owner = (0, react.useContext)(ProjectionContext);
9
43
  if (!owner) throw new Error("ProjectionRuntime is required.");
@@ -56,4 +90,5 @@ exports.useDocumentSelector = useDocumentSelector;
56
90
  exports.useHistory = useHistory;
57
91
  exports.useInput = useInput;
58
92
  exports.useProjection = useProjection;
93
+ exports.useProjectionScope = useProjectionScope;
59
94
  exports.useReadable = useReadable;
package/dist/react.d.cts CHANGED
@@ -1,8 +1,7 @@
1
- import * as _$react from "react";
2
1
  import { CollectionInput, CollectionInputDraft, DocumentSelector, HistoryState, Input, LocalHistory, ObjectSchema, OperationResult, Projection, ProjectionRuntime, ProjectionScope, Readable, ReadonlyDocument } from "doxum";
2
+ import { ReactElement, ReactNode } from "react";
3
3
 
4
4
  //#region react/src/hooks.d.ts
5
- declare const ProjectionProvider: _$react.Provider<ProjectionRuntime | ProjectionScope | undefined>;
6
5
  declare function useProjection<T>(projection: Projection<T>): T;
7
6
  declare function useProjection<T, R>(projection: Projection<T>, selector: (value: T) => R, equality?: (previous: R, next: R) => boolean): R;
8
7
  declare function useInput<T>(input: Input<T>): readonly [T, (value: T) => void];
@@ -14,4 +13,21 @@ declare function useHistory<TCommit>(history: LocalHistory<TCommit>): HistorySta
14
13
  redo(): OperationResult<TCommit>;
15
14
  };
16
15
  //#endregion
17
- export { ProjectionProvider, useDocumentSelector, useHistory, useInput, useProjection, useReadable };
16
+ //#region react/src/projection.d.ts
17
+ type ProjectionProviderProps = {
18
+ readonly children?: ReactNode;
19
+ } & ({
20
+ readonly runtime: ProjectionRuntime;
21
+ readonly fallback?: ReactNode;
22
+ readonly value?: never;
23
+ } | {
24
+ readonly value: ProjectionRuntime | ProjectionScope;
25
+ readonly runtime?: never;
26
+ readonly fallback?: never;
27
+ });
28
+ /** runtime creates an owned scope after commit; value borrows an external owner. */
29
+ declare const ProjectionProvider: (props: ProjectionProviderProps) => ReactElement;
30
+ /** Reads a scope supplied by a managed Provider or an explicitly injected scope. */
31
+ declare const useProjectionScope: () => ProjectionScope;
32
+ //#endregion
33
+ export { ProjectionProvider, type ProjectionProviderProps, useDocumentSelector, useHistory, useInput, useProjection, useProjectionScope, useReadable };
package/dist/react.d.ts CHANGED
@@ -1,8 +1,7 @@
1
+ import { ReactElement, ReactNode } from "react";
1
2
  import { CollectionInput, CollectionInputDraft, DocumentSelector, HistoryState, Input, LocalHistory, ObjectSchema, OperationResult, Projection, ProjectionRuntime, ProjectionScope, Readable, ReadonlyDocument } from "doxum";
2
- import * as _$react from "react";
3
3
 
4
4
  //#region react/src/hooks.d.ts
5
- declare const ProjectionProvider: _$react.Provider<ProjectionRuntime | ProjectionScope | undefined>;
6
5
  declare function useProjection<T>(projection: Projection<T>): T;
7
6
  declare function useProjection<T, R>(projection: Projection<T>, selector: (value: T) => R, equality?: (previous: R, next: R) => boolean): R;
8
7
  declare function useInput<T>(input: Input<T>): readonly [T, (value: T) => void];
@@ -14,4 +13,21 @@ declare function useHistory<TCommit>(history: LocalHistory<TCommit>): HistorySta
14
13
  redo(): OperationResult<TCommit>;
15
14
  };
16
15
  //#endregion
17
- export { ProjectionProvider, useDocumentSelector, useHistory, useInput, useProjection, useReadable };
16
+ //#region react/src/projection.d.ts
17
+ type ProjectionProviderProps = {
18
+ readonly children?: ReactNode;
19
+ } & ({
20
+ readonly runtime: ProjectionRuntime;
21
+ readonly fallback?: ReactNode;
22
+ readonly value?: never;
23
+ } | {
24
+ readonly value: ProjectionRuntime | ProjectionScope;
25
+ readonly runtime?: never;
26
+ readonly fallback?: never;
27
+ });
28
+ /** runtime creates an owned scope after commit; value borrows an external owner. */
29
+ declare const ProjectionProvider: (props: ProjectionProviderProps) => ReactElement;
30
+ /** Reads a scope supplied by a managed Provider or an explicitly injected scope. */
31
+ declare const useProjectionScope: () => ProjectionScope;
32
+ //#endregion
33
+ export { ProjectionProvider, type ProjectionProviderProps, useDocumentSelector, useHistory, useInput, useProjection, useProjectionScope, useReadable };
package/dist/react.js CHANGED
@@ -1,8 +1,42 @@
1
+ import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
1
2
  import { select } from "doxum";
2
- import { createContext, useCallback, useContext, useMemo, useSyncExternalStore } from "react";
3
- //#region react/src/hooks.ts
3
+ //#region react/src/projection.ts
4
4
  const ProjectionContext = createContext(void 0);
5
- const ProjectionProvider = ProjectionContext.Provider;
5
+ const ManagedProjectionProvider = ({ runtime, fallback, children }) => {
6
+ const generation = useRef(0);
7
+ const [owner, setOwner] = useState();
8
+ useEffect(() => {
9
+ const scope = runtime.scope();
10
+ setOwner({
11
+ runtime,
12
+ scope,
13
+ generation: ++generation.current
14
+ });
15
+ return () => scope.dispose();
16
+ }, [runtime]);
17
+ if (!owner || owner.runtime !== runtime) return fallback ?? null;
18
+ return createElement(ProjectionContext.Provider, {
19
+ value: owner.scope,
20
+ key: owner.generation
21
+ }, children);
22
+ };
23
+ /** runtime creates an owned scope after commit; value borrows an external owner. */
24
+ const ProjectionProvider = (props) => {
25
+ if (props.runtime !== void 0) {
26
+ if (props.value !== void 0) throw new TypeError("ProjectionProvider accepts runtime or value, not both.");
27
+ return createElement(ManagedProjectionProvider, props);
28
+ }
29
+ if (props.value === void 0) throw new TypeError("ProjectionProvider requires runtime or value.");
30
+ return createElement(ProjectionContext.Provider, { value: props.value }, props.children);
31
+ };
32
+ /** Reads a scope supplied by a managed Provider or an explicitly injected scope. */
33
+ const useProjectionScope = () => {
34
+ const owner = useContext(ProjectionContext);
35
+ if (!owner || !("own" in owner)) throw new Error("A ProjectionScope provider is required.");
36
+ return owner;
37
+ };
38
+ //#endregion
39
+ //#region react/src/hooks.ts
6
40
  function useProjection(projection, selector, equality = Object.is) {
7
41
  const owner = useContext(ProjectionContext);
8
42
  if (!owner) throw new Error("ProjectionRuntime is required.");
@@ -50,4 +84,4 @@ function useHistory(history) {
50
84
  ]);
51
85
  }
52
86
  //#endregion
53
- export { ProjectionProvider, useDocumentSelector, useHistory, useInput, useProjection, useReadable };
87
+ export { ProjectionProvider, useDocumentSelector, useHistory, useInput, useProjection, useProjectionScope, useReadable };