doxum 0.1.40 → 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/README.md +36 -4
- package/dist/advanced.cjs +1 -1
- package/dist/advanced.d.cts +1 -1
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.js +1 -1
- package/dist/{contract-CNR6BS75.d.cts → contract-DovG1Yp-.d.cts} +2 -1
- package/dist/{contract-COgxO5sv.d.ts → contract-PqdXNaP8.d.ts} +2 -1
- package/dist/{dependency-DzxXGYe1.d.ts → dependency-BYiwTiBj.d.ts} +1 -1
- package/dist/{dependency-Cbf3C8eJ.d.cts → dependency-D0bo_xe_.d.cts} +1 -1
- package/dist/{driver-B7RR-VfX.js → driver-B7Hu6D0u.js} +15 -1
- package/dist/{driver-CN3dGxQI.cjs → driver-i4G4iVX0.cjs} +20 -0
- package/dist/index.cjs +257 -114
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +257 -114
- package/dist/local-sync.cjs +2 -5
- package/dist/local-sync.d.cts +1 -1
- package/dist/local-sync.d.ts +1 -1
- package/dist/local-sync.js +2 -5
- package/dist/react.cjs +38 -3
- package/dist/react.d.cts +19 -3
- package/dist/react.d.ts +19 -3
- package/dist/react.js +38 -4
- package/dist/{transform-8xXf0OKB.cjs → transform-BPh55GWO.cjs} +82 -48
- package/dist/{transform--WCn6_v0.js → transform-BvDYPUMm.js} +82 -48
- package/package.json +1 -1
- package/skills/doxum-runtime/references/integrations.md +38 -7
- package/skills/doxum-runtime/references/invariants.md +23 -0
- package/skills/doxum-runtime/references/projections.md +22 -0
- package/skills/doxum-runtime/references/public-api.md +27 -8
package/dist/local-sync.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { W as ObjectSchema, g as Readable, s as DocumentRuntime } from "./contract-
|
|
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;
|
package/dist/local-sync.js
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
-
|
|
3
|
+
let doxum = require("doxum");
|
|
4
|
+
//#region react/src/projection.ts
|
|
5
5
|
const ProjectionContext = (0, react.createContext)(void 0);
|
|
6
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
//#region react/src/hooks.ts
|
|
3
|
+
//#region react/src/projection.ts
|
|
4
4
|
const ProjectionContext = createContext(void 0);
|
|
5
|
-
const
|
|
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 };
|
|
@@ -29,6 +29,8 @@ const profile = {
|
|
|
29
29
|
segmentCompared: () => {}
|
|
30
30
|
},
|
|
31
31
|
collectionIndex: {
|
|
32
|
+
batch: (sortedKeys) => {},
|
|
33
|
+
visit: () => {},
|
|
32
34
|
node: () => {},
|
|
33
35
|
build: (items) => {}
|
|
34
36
|
},
|
|
@@ -259,7 +261,8 @@ var ProjectionDisposedError = class extends Error {
|
|
|
259
261
|
//#endregion
|
|
260
262
|
//#region core/src/projection/graph/scheduler.ts
|
|
261
263
|
const createScheduler = (onError) => {
|
|
262
|
-
let
|
|
264
|
+
let lifecycle = "active";
|
|
265
|
+
let lockDepth = 0;
|
|
263
266
|
let depth = 0;
|
|
264
267
|
let batchSequence = 0;
|
|
265
268
|
let activeBatch;
|
|
@@ -277,13 +280,39 @@ const createScheduler = (onError) => {
|
|
|
277
280
|
let reportingFailures = [];
|
|
278
281
|
const guards = /* @__PURE__ */ new Set();
|
|
279
282
|
const assertActive = () => {
|
|
280
|
-
if (disposed) throw new ProjectionDisposedError();
|
|
283
|
+
if (lifecycle === "disposed" || phase === "release") throw new ProjectionDisposedError();
|
|
281
284
|
};
|
|
282
285
|
const assertIdle = () => {
|
|
283
286
|
assertActive();
|
|
284
|
-
if (phase !== "idle") throw new Error("Projection cannot be re-entered during input callbacks, processing or notification.");
|
|
287
|
+
if (phase !== "idle" && phase !== "cleanup") throw new Error("Projection cannot be re-entered during input callbacks, processing or notification.");
|
|
288
|
+
};
|
|
289
|
+
const assertCommand = () => {
|
|
290
|
+
assertActive();
|
|
291
|
+
if (lifecycle === "closing" || phase === "cleanup") throw new Error("Projection cleanup only permits reads and unsubscribe.");
|
|
292
|
+
assertIdle();
|
|
293
|
+
};
|
|
294
|
+
const lock = (value) => {
|
|
295
|
+
const previous = lockDepth;
|
|
296
|
+
lockDepth += value ? 1 : -1;
|
|
297
|
+
if (!previous || !lockDepth) guards.forEach((guard) => guard(value));
|
|
298
|
+
};
|
|
299
|
+
const beginClose = () => {
|
|
300
|
+
if (lifecycle !== "active") return false;
|
|
301
|
+
assertCommand();
|
|
302
|
+
lifecycle = "closing";
|
|
303
|
+
return true;
|
|
304
|
+
};
|
|
305
|
+
const withPhase = (next, run) => {
|
|
306
|
+
const previous = phase;
|
|
307
|
+
phase = next;
|
|
308
|
+
lock(true);
|
|
309
|
+
try {
|
|
310
|
+
return run();
|
|
311
|
+
} finally {
|
|
312
|
+
lock(false);
|
|
313
|
+
phase = previous;
|
|
314
|
+
}
|
|
285
315
|
};
|
|
286
|
-
const lock = (value) => guards.forEach((guard) => guard(value));
|
|
287
316
|
const markDirty = (producer) => {
|
|
288
317
|
const pending = [producer];
|
|
289
318
|
for (let index = 0; index < pending.length; index++) {
|
|
@@ -300,7 +329,7 @@ const createScheduler = (onError) => {
|
|
|
300
329
|
};
|
|
301
330
|
const errorFor = (producer, cause, kind = producer.kind === "source" ? "source" : "processor") => new ProjectionError(kind, cause);
|
|
302
331
|
const capture = (source) => {
|
|
303
|
-
if (disposed) return;
|
|
332
|
+
if (lifecycle === "disposed") return;
|
|
304
333
|
if (phase !== "idle") {
|
|
305
334
|
source.fault = errorFor(source, /* @__PURE__ */ new Error("Source changed while projection was running."), "source");
|
|
306
335
|
errors.push(source.fault);
|
|
@@ -420,18 +449,9 @@ const createScheduler = (onError) => {
|
|
|
420
449
|
}
|
|
421
450
|
}
|
|
422
451
|
};
|
|
423
|
-
const compute = (run) =>
|
|
424
|
-
phase = "compute";
|
|
425
|
-
lock(true);
|
|
426
|
-
try {
|
|
427
|
-
return run();
|
|
428
|
-
} finally {
|
|
429
|
-
lock(false);
|
|
430
|
-
phase = "idle";
|
|
431
|
-
}
|
|
432
|
-
};
|
|
452
|
+
const compute = (run) => withPhase("compute", run);
|
|
433
453
|
const settle = () => {
|
|
434
|
-
if (
|
|
454
|
+
if (lifecycle !== "active" || depth || phase !== "idle") return;
|
|
435
455
|
const cause = settleCause();
|
|
436
456
|
compute(() => {
|
|
437
457
|
for (const producer of dirty) advance(producer, cause);
|
|
@@ -447,7 +467,7 @@ const createScheduler = (onError) => {
|
|
|
447
467
|
}
|
|
448
468
|
};
|
|
449
469
|
const flush = () => {
|
|
450
|
-
if (
|
|
470
|
+
if (lifecycle !== "active" || depth || phase === "cleanup" || phase === "release") return [];
|
|
451
471
|
if (emissions.size || completed.size) profile.projection("flushes");
|
|
452
472
|
phase = "notify";
|
|
453
473
|
lock(true);
|
|
@@ -495,7 +515,7 @@ const createScheduler = (onError) => {
|
|
|
495
515
|
return result;
|
|
496
516
|
};
|
|
497
517
|
const batch = (callback, options) => {
|
|
498
|
-
|
|
518
|
+
assertCommand();
|
|
499
519
|
if (depth === 0) activeBatch = Object.freeze({
|
|
500
520
|
id: ++batchSequence,
|
|
501
521
|
...options?.cause === void 0 ? {} : { cause: options.cause }
|
|
@@ -522,7 +542,8 @@ const createScheduler = (onError) => {
|
|
|
522
542
|
}
|
|
523
543
|
};
|
|
524
544
|
const releaseProducer = (producer) => {
|
|
525
|
-
|
|
545
|
+
if (producer.disposed) return;
|
|
546
|
+
if (phase !== "release") assertIdle();
|
|
526
547
|
if (producer.outputs.some((output) => output.hasConsumers())) throw new Error("Projection producer is still used by another processor.");
|
|
527
548
|
producer.disposed = true;
|
|
528
549
|
dirty.delete(producer);
|
|
@@ -552,6 +573,16 @@ const createScheduler = (onError) => {
|
|
|
552
573
|
return {
|
|
553
574
|
assertActive,
|
|
554
575
|
assertIdle,
|
|
576
|
+
assertCommand,
|
|
577
|
+
beginClose,
|
|
578
|
+
cleanup(run) {
|
|
579
|
+
assertIdle();
|
|
580
|
+
return withPhase("cleanup", run);
|
|
581
|
+
},
|
|
582
|
+
releaseOwned(run) {
|
|
583
|
+
assertIdle();
|
|
584
|
+
return withPhase("release", run);
|
|
585
|
+
},
|
|
555
586
|
capture,
|
|
556
587
|
settle,
|
|
557
588
|
ensureCurrent,
|
|
@@ -560,15 +591,17 @@ const createScheduler = (onError) => {
|
|
|
560
591
|
registerGuard(guard) {
|
|
561
592
|
assertActive();
|
|
562
593
|
guards.add(guard);
|
|
594
|
+
if (lockDepth) guard(true);
|
|
563
595
|
let registered = true;
|
|
564
596
|
return () => {
|
|
565
597
|
if (!registered) return;
|
|
566
598
|
registered = false;
|
|
567
599
|
guards.delete(guard);
|
|
600
|
+
if (lockDepth) guard(false);
|
|
568
601
|
};
|
|
569
602
|
},
|
|
570
603
|
get active() {
|
|
571
|
-
return
|
|
604
|
+
return lifecycle !== "disposed" && phase !== "release";
|
|
572
605
|
},
|
|
573
606
|
addSource(source) {
|
|
574
607
|
assertIdle();
|
|
@@ -582,7 +615,7 @@ const createScheduler = (onError) => {
|
|
|
582
615
|
releaseProducer,
|
|
583
616
|
nextId: () => sequence++,
|
|
584
617
|
acceptInput(callback) {
|
|
585
|
-
|
|
618
|
+
assertCommand();
|
|
586
619
|
phase = "input";
|
|
587
620
|
lock(true);
|
|
588
621
|
try {
|
|
@@ -598,38 +631,39 @@ const createScheduler = (onError) => {
|
|
|
598
631
|
},
|
|
599
632
|
batch,
|
|
600
633
|
batchContext: () => activeBatch,
|
|
601
|
-
dispose() {
|
|
602
|
-
if (disposed) return;
|
|
603
|
-
|
|
604
|
-
disposed = true;
|
|
634
|
+
dispose(releaseConsumers, releaseSources) {
|
|
635
|
+
if (lifecycle === "disposed") return;
|
|
636
|
+
if (lifecycle === "active") beginClose();
|
|
605
637
|
const failures = [];
|
|
606
|
-
|
|
607
|
-
processor.disposed = true;
|
|
608
|
-
try {
|
|
609
|
-
processor.release();
|
|
610
|
-
} catch (error) {
|
|
611
|
-
failures.push(error);
|
|
612
|
-
}
|
|
613
|
-
});
|
|
614
|
-
sources.forEach((source) => {
|
|
615
|
-
source.disposed = true;
|
|
638
|
+
const attempt = (run) => {
|
|
616
639
|
try {
|
|
617
|
-
|
|
640
|
+
run();
|
|
618
641
|
} catch (error) {
|
|
619
642
|
failures.push(error);
|
|
620
643
|
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
644
|
+
};
|
|
645
|
+
try {
|
|
646
|
+
withPhase("release", () => {
|
|
647
|
+
attempt(releaseConsumers);
|
|
648
|
+
for (const processor of [...processors].reverse()) attempt(() => releaseProducer(processor));
|
|
649
|
+
for (const source of [...sources].reverse()) attempt(() => releaseProducer(source));
|
|
650
|
+
attempt(releaseSources);
|
|
651
|
+
});
|
|
652
|
+
} finally {
|
|
653
|
+
lifecycle = "disposed";
|
|
654
|
+
processors.clear();
|
|
655
|
+
sources.clear();
|
|
656
|
+
pendingSources.clear();
|
|
657
|
+
dirty.clear();
|
|
658
|
+
queued.clear();
|
|
659
|
+
completed.clear();
|
|
660
|
+
emissions.clear();
|
|
661
|
+
forced.clear();
|
|
662
|
+
guards.clear();
|
|
663
|
+
errors.length = 0;
|
|
664
|
+
reportingFailures = [];
|
|
665
|
+
}
|
|
666
|
+
if (failures.length === 1) throw failures[0];
|
|
633
667
|
if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
|
|
634
668
|
}
|
|
635
669
|
};
|
|
@@ -29,6 +29,8 @@ const profile = {
|
|
|
29
29
|
segmentCompared: () => {}
|
|
30
30
|
},
|
|
31
31
|
collectionIndex: {
|
|
32
|
+
batch: (sortedKeys) => {},
|
|
33
|
+
visit: () => {},
|
|
32
34
|
node: () => {},
|
|
33
35
|
build: (items) => {}
|
|
34
36
|
},
|
|
@@ -259,7 +261,8 @@ var ProjectionDisposedError = class extends Error {
|
|
|
259
261
|
//#endregion
|
|
260
262
|
//#region core/src/projection/graph/scheduler.ts
|
|
261
263
|
const createScheduler = (onError) => {
|
|
262
|
-
let
|
|
264
|
+
let lifecycle = "active";
|
|
265
|
+
let lockDepth = 0;
|
|
263
266
|
let depth = 0;
|
|
264
267
|
let batchSequence = 0;
|
|
265
268
|
let activeBatch;
|
|
@@ -277,13 +280,39 @@ const createScheduler = (onError) => {
|
|
|
277
280
|
let reportingFailures = [];
|
|
278
281
|
const guards = /* @__PURE__ */ new Set();
|
|
279
282
|
const assertActive = () => {
|
|
280
|
-
if (disposed) throw new ProjectionDisposedError();
|
|
283
|
+
if (lifecycle === "disposed" || phase === "release") throw new ProjectionDisposedError();
|
|
281
284
|
};
|
|
282
285
|
const assertIdle = () => {
|
|
283
286
|
assertActive();
|
|
284
|
-
if (phase !== "idle") throw new Error("Projection cannot be re-entered during input callbacks, processing or notification.");
|
|
287
|
+
if (phase !== "idle" && phase !== "cleanup") throw new Error("Projection cannot be re-entered during input callbacks, processing or notification.");
|
|
288
|
+
};
|
|
289
|
+
const assertCommand = () => {
|
|
290
|
+
assertActive();
|
|
291
|
+
if (lifecycle === "closing" || phase === "cleanup") throw new Error("Projection cleanup only permits reads and unsubscribe.");
|
|
292
|
+
assertIdle();
|
|
293
|
+
};
|
|
294
|
+
const lock = (value) => {
|
|
295
|
+
const previous = lockDepth;
|
|
296
|
+
lockDepth += value ? 1 : -1;
|
|
297
|
+
if (!previous || !lockDepth) guards.forEach((guard) => guard(value));
|
|
298
|
+
};
|
|
299
|
+
const beginClose = () => {
|
|
300
|
+
if (lifecycle !== "active") return false;
|
|
301
|
+
assertCommand();
|
|
302
|
+
lifecycle = "closing";
|
|
303
|
+
return true;
|
|
304
|
+
};
|
|
305
|
+
const withPhase = (next, run) => {
|
|
306
|
+
const previous = phase;
|
|
307
|
+
phase = next;
|
|
308
|
+
lock(true);
|
|
309
|
+
try {
|
|
310
|
+
return run();
|
|
311
|
+
} finally {
|
|
312
|
+
lock(false);
|
|
313
|
+
phase = previous;
|
|
314
|
+
}
|
|
285
315
|
};
|
|
286
|
-
const lock = (value) => guards.forEach((guard) => guard(value));
|
|
287
316
|
const markDirty = (producer) => {
|
|
288
317
|
const pending = [producer];
|
|
289
318
|
for (let index = 0; index < pending.length; index++) {
|
|
@@ -300,7 +329,7 @@ const createScheduler = (onError) => {
|
|
|
300
329
|
};
|
|
301
330
|
const errorFor = (producer, cause, kind = producer.kind === "source" ? "source" : "processor") => new ProjectionError(kind, cause);
|
|
302
331
|
const capture = (source) => {
|
|
303
|
-
if (disposed) return;
|
|
332
|
+
if (lifecycle === "disposed") return;
|
|
304
333
|
if (phase !== "idle") {
|
|
305
334
|
source.fault = errorFor(source, /* @__PURE__ */ new Error("Source changed while projection was running."), "source");
|
|
306
335
|
errors.push(source.fault);
|
|
@@ -420,18 +449,9 @@ const createScheduler = (onError) => {
|
|
|
420
449
|
}
|
|
421
450
|
}
|
|
422
451
|
};
|
|
423
|
-
const compute = (run) =>
|
|
424
|
-
phase = "compute";
|
|
425
|
-
lock(true);
|
|
426
|
-
try {
|
|
427
|
-
return run();
|
|
428
|
-
} finally {
|
|
429
|
-
lock(false);
|
|
430
|
-
phase = "idle";
|
|
431
|
-
}
|
|
432
|
-
};
|
|
452
|
+
const compute = (run) => withPhase("compute", run);
|
|
433
453
|
const settle = () => {
|
|
434
|
-
if (
|
|
454
|
+
if (lifecycle !== "active" || depth || phase !== "idle") return;
|
|
435
455
|
const cause = settleCause();
|
|
436
456
|
compute(() => {
|
|
437
457
|
for (const producer of dirty) advance(producer, cause);
|
|
@@ -447,7 +467,7 @@ const createScheduler = (onError) => {
|
|
|
447
467
|
}
|
|
448
468
|
};
|
|
449
469
|
const flush = () => {
|
|
450
|
-
if (
|
|
470
|
+
if (lifecycle !== "active" || depth || phase === "cleanup" || phase === "release") return [];
|
|
451
471
|
if (emissions.size || completed.size) profile.projection("flushes");
|
|
452
472
|
phase = "notify";
|
|
453
473
|
lock(true);
|
|
@@ -495,7 +515,7 @@ const createScheduler = (onError) => {
|
|
|
495
515
|
return result;
|
|
496
516
|
};
|
|
497
517
|
const batch = (callback, options) => {
|
|
498
|
-
|
|
518
|
+
assertCommand();
|
|
499
519
|
if (depth === 0) activeBatch = Object.freeze({
|
|
500
520
|
id: ++batchSequence,
|
|
501
521
|
...options?.cause === void 0 ? {} : { cause: options.cause }
|
|
@@ -522,7 +542,8 @@ const createScheduler = (onError) => {
|
|
|
522
542
|
}
|
|
523
543
|
};
|
|
524
544
|
const releaseProducer = (producer) => {
|
|
525
|
-
|
|
545
|
+
if (producer.disposed) return;
|
|
546
|
+
if (phase !== "release") assertIdle();
|
|
526
547
|
if (producer.outputs.some((output) => output.hasConsumers())) throw new Error("Projection producer is still used by another processor.");
|
|
527
548
|
producer.disposed = true;
|
|
528
549
|
dirty.delete(producer);
|
|
@@ -552,6 +573,16 @@ const createScheduler = (onError) => {
|
|
|
552
573
|
return {
|
|
553
574
|
assertActive,
|
|
554
575
|
assertIdle,
|
|
576
|
+
assertCommand,
|
|
577
|
+
beginClose,
|
|
578
|
+
cleanup(run) {
|
|
579
|
+
assertIdle();
|
|
580
|
+
return withPhase("cleanup", run);
|
|
581
|
+
},
|
|
582
|
+
releaseOwned(run) {
|
|
583
|
+
assertIdle();
|
|
584
|
+
return withPhase("release", run);
|
|
585
|
+
},
|
|
555
586
|
capture,
|
|
556
587
|
settle,
|
|
557
588
|
ensureCurrent,
|
|
@@ -560,15 +591,17 @@ const createScheduler = (onError) => {
|
|
|
560
591
|
registerGuard(guard) {
|
|
561
592
|
assertActive();
|
|
562
593
|
guards.add(guard);
|
|
594
|
+
if (lockDepth) guard(true);
|
|
563
595
|
let registered = true;
|
|
564
596
|
return () => {
|
|
565
597
|
if (!registered) return;
|
|
566
598
|
registered = false;
|
|
567
599
|
guards.delete(guard);
|
|
600
|
+
if (lockDepth) guard(false);
|
|
568
601
|
};
|
|
569
602
|
},
|
|
570
603
|
get active() {
|
|
571
|
-
return
|
|
604
|
+
return lifecycle !== "disposed" && phase !== "release";
|
|
572
605
|
},
|
|
573
606
|
addSource(source) {
|
|
574
607
|
assertIdle();
|
|
@@ -582,7 +615,7 @@ const createScheduler = (onError) => {
|
|
|
582
615
|
releaseProducer,
|
|
583
616
|
nextId: () => sequence++,
|
|
584
617
|
acceptInput(callback) {
|
|
585
|
-
|
|
618
|
+
assertCommand();
|
|
586
619
|
phase = "input";
|
|
587
620
|
lock(true);
|
|
588
621
|
try {
|
|
@@ -598,38 +631,39 @@ const createScheduler = (onError) => {
|
|
|
598
631
|
},
|
|
599
632
|
batch,
|
|
600
633
|
batchContext: () => activeBatch,
|
|
601
|
-
dispose() {
|
|
602
|
-
if (disposed) return;
|
|
603
|
-
|
|
604
|
-
disposed = true;
|
|
634
|
+
dispose(releaseConsumers, releaseSources) {
|
|
635
|
+
if (lifecycle === "disposed") return;
|
|
636
|
+
if (lifecycle === "active") beginClose();
|
|
605
637
|
const failures = [];
|
|
606
|
-
|
|
607
|
-
processor.disposed = true;
|
|
608
|
-
try {
|
|
609
|
-
processor.release();
|
|
610
|
-
} catch (error) {
|
|
611
|
-
failures.push(error);
|
|
612
|
-
}
|
|
613
|
-
});
|
|
614
|
-
sources.forEach((source) => {
|
|
615
|
-
source.disposed = true;
|
|
638
|
+
const attempt = (run) => {
|
|
616
639
|
try {
|
|
617
|
-
|
|
640
|
+
run();
|
|
618
641
|
} catch (error) {
|
|
619
642
|
failures.push(error);
|
|
620
643
|
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
644
|
+
};
|
|
645
|
+
try {
|
|
646
|
+
withPhase("release", () => {
|
|
647
|
+
attempt(releaseConsumers);
|
|
648
|
+
for (const processor of [...processors].reverse()) attempt(() => releaseProducer(processor));
|
|
649
|
+
for (const source of [...sources].reverse()) attempt(() => releaseProducer(source));
|
|
650
|
+
attempt(releaseSources);
|
|
651
|
+
});
|
|
652
|
+
} finally {
|
|
653
|
+
lifecycle = "disposed";
|
|
654
|
+
processors.clear();
|
|
655
|
+
sources.clear();
|
|
656
|
+
pendingSources.clear();
|
|
657
|
+
dirty.clear();
|
|
658
|
+
queued.clear();
|
|
659
|
+
completed.clear();
|
|
660
|
+
emissions.clear();
|
|
661
|
+
forced.clear();
|
|
662
|
+
guards.clear();
|
|
663
|
+
errors.length = 0;
|
|
664
|
+
reportingFailures = [];
|
|
665
|
+
}
|
|
666
|
+
if (failures.length === 1) throw failures[0];
|
|
633
667
|
if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
|
|
634
668
|
}
|
|
635
669
|
};
|