doxum 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/chunk-pbuEa-1d.js +13 -0
- package/dist/contract-DwNiKioc.d.ts +480 -0
- package/dist/contract-Otb5W6cQ.d.cts +480 -0
- package/dist/dependency-BdEMyquf.js +735 -0
- package/dist/dependency-BdEMyquf.js.map +1 -0
- package/dist/dependency-DLcCvNKq.cjs +1015 -0
- package/dist/dependency-DLcCvNKq.cjs.map +1 -0
- package/dist/index.cjs +2718 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +89 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +2689 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.cjs +15 -0
- package/dist/integration.cjs.map +1 -0
- package/dist/integration.d.cts +11 -0
- package/dist/integration.d.ts +11 -0
- package/dist/integration.js +14 -0
- package/dist/integration.js.map +1 -0
- package/dist/react.cjs +99 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +16 -0
- package/dist/react.d.ts +16 -0
- package/dist/react.js +96 -0
- package/dist/react.js.map +1 -0
- package/package.json +89 -0
- package/skills/doxum-runtime/SKILL.md +51 -0
- package/skills/doxum-runtime/agents/openai.yaml +4 -0
- package/skills/doxum-runtime/references/guide.en.md +295 -0
- package/skills/doxum-runtime/references/guide.zh-CN.md +240 -0
- package/skills/doxum-runtime/references/invariants.en.md +147 -0
- package/skills/doxum-runtime/references/invariants.zh-CN.md +97 -0
- package/skills/doxum-runtime/references/patterns.en.md +287 -0
- package/skills/doxum-runtime/references/patterns.zh-CN.md +240 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { i as readWith, t as createDependencyTracker } from "./dependency-BdEMyquf.js";
|
|
2
|
+
//#region core/src/integration.ts
|
|
3
|
+
const track = (runtime, selector) => {
|
|
4
|
+
const dependencies = createDependencyTracker();
|
|
5
|
+
const value = readWith(runtime, selector, dependencies);
|
|
6
|
+
return Object.freeze({
|
|
7
|
+
value,
|
|
8
|
+
targets: dependencies.snapshot()
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { track };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=integration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.js","names":[],"sources":["../core/src/integration.ts"],"sourcesContent":["import type { DocumentSchema, ImpactTarget } from './schema';\nimport type { DocumentReader } from './access/reader';\nimport type { DocumentRuntime } from './runtime/contract';\nimport { createDependencyTracker } from './access/dependency';\nimport { readWith } from './runtime/access';\n\nexport type TrackedSelection<TValue> = {\n readonly value: TValue;\n readonly targets: readonly ImpactTarget<unknown>[];\n};\n\n// Framework adapters receive one immutable result instead of coordinating a\n// mutable collector with the reader's scoped lifetime themselves.\nexport const track = <TSchema extends DocumentSchema, TValue>(\n runtime: DocumentRuntime<TSchema>,\n selector: (read: DocumentReader<TSchema>) => TValue\n): TrackedSelection<TValue> => {\n const dependencies = createDependencyTracker();\n const value = readWith(runtime, selector, dependencies);\n return Object.freeze({ value, targets: dependencies.snapshot() });\n};\n"],"mappings":";;AAaA,MAAa,SACX,SACA,aAC6B;CAC7B,MAAM,eAAe,yBAAyB;CAC9C,MAAM,QAAQ,SAAS,SAAS,UAAU,aAAa;AACvD,QAAO,OAAO,OAAO;EAAE;EAAO,SAAS,aAAa,UAAU;EAAE,CAAC"}
|
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_integration = require("./integration.cjs");
|
|
3
|
+
let doxum = require("doxum");
|
|
4
|
+
let react = require("react");
|
|
5
|
+
//#region react/src/hooks.ts
|
|
6
|
+
const objectIs = (previous, next) => Object.is(previous, next);
|
|
7
|
+
const sameTargets = (left, right) => {
|
|
8
|
+
if (left.length !== right.length) return false;
|
|
9
|
+
for (let index = 0; index < left.length; index += 1) if (!doxum.target.same(left[index], right[index])) return false;
|
|
10
|
+
return true;
|
|
11
|
+
};
|
|
12
|
+
function useDocumentSelector(runtime, selector, options) {
|
|
13
|
+
const equality = options?.isEqual ?? objectIs;
|
|
14
|
+
const cache = (0, react.useRef)(void 0);
|
|
15
|
+
const serverCache = (0, react.useRef)(void 0);
|
|
16
|
+
const read = (0, react.useCallback)(() => {
|
|
17
|
+
const selection = require_integration.track(runtime, selector);
|
|
18
|
+
const next = selection.value;
|
|
19
|
+
const previous = cache.current;
|
|
20
|
+
const targets = selection.targets;
|
|
21
|
+
if (previous && previous.runtime === runtime && equality(previous.value, next)) {
|
|
22
|
+
cache.current = {
|
|
23
|
+
runtime,
|
|
24
|
+
value: previous.value,
|
|
25
|
+
targets
|
|
26
|
+
};
|
|
27
|
+
return previous.value;
|
|
28
|
+
}
|
|
29
|
+
cache.current = {
|
|
30
|
+
runtime,
|
|
31
|
+
value: next,
|
|
32
|
+
targets
|
|
33
|
+
};
|
|
34
|
+
return next;
|
|
35
|
+
}, [
|
|
36
|
+
equality,
|
|
37
|
+
runtime,
|
|
38
|
+
selector
|
|
39
|
+
]);
|
|
40
|
+
const subscribe = (0, react.useCallback)((listener) => {
|
|
41
|
+
let targets = cache.current?.targets ?? [];
|
|
42
|
+
let unsubscribe = () => void 0;
|
|
43
|
+
const install = () => {
|
|
44
|
+
if (targets.length === 0) return () => void 0;
|
|
45
|
+
return runtime.subscribe(targets.length === 1 ? targets[0] : targets, onCommit);
|
|
46
|
+
};
|
|
47
|
+
const onCommit = () => {
|
|
48
|
+
const previous = cache.current;
|
|
49
|
+
const next = read();
|
|
50
|
+
const nextTargets = cache.current?.targets ?? [];
|
|
51
|
+
if (!sameTargets(targets, nextTargets)) {
|
|
52
|
+
unsubscribe();
|
|
53
|
+
targets = nextTargets;
|
|
54
|
+
unsubscribe = install();
|
|
55
|
+
}
|
|
56
|
+
if (previous && previous.value !== next) listener();
|
|
57
|
+
};
|
|
58
|
+
unsubscribe = install();
|
|
59
|
+
return () => unsubscribe();
|
|
60
|
+
}, [read, runtime]);
|
|
61
|
+
const server = options?.server;
|
|
62
|
+
return (0, react.useSyncExternalStore)(subscribe, read, (0, react.useCallback)(() => {
|
|
63
|
+
const previous = serverCache.current;
|
|
64
|
+
if (previous && previous.source === server) return previous.value;
|
|
65
|
+
const value = server ? server() : read();
|
|
66
|
+
serverCache.current = {
|
|
67
|
+
source: server,
|
|
68
|
+
value
|
|
69
|
+
};
|
|
70
|
+
return value;
|
|
71
|
+
}, [read, server]));
|
|
72
|
+
}
|
|
73
|
+
function useReadable(readable) {
|
|
74
|
+
const subscribe = (0, react.useCallback)((listener) => readable.subscribe(listener), [readable]);
|
|
75
|
+
const read = (0, react.useCallback)(() => readable.current(), [readable]);
|
|
76
|
+
return (0, react.useSyncExternalStore)(subscribe, read, read);
|
|
77
|
+
}
|
|
78
|
+
function useHistory(history) {
|
|
79
|
+
const subscribe = (0, react.useCallback)((listener) => history.subscribe(listener), [history]);
|
|
80
|
+
const read = (0, react.useCallback)(() => history.current(), [history]);
|
|
81
|
+
const state = (0, react.useSyncExternalStore)(subscribe, read, read);
|
|
82
|
+
const undo = (0, react.useCallback)(() => history.undo(), [history]);
|
|
83
|
+
const redo = (0, react.useCallback)(() => history.redo(), [history]);
|
|
84
|
+
return (0, react.useMemo)(() => ({
|
|
85
|
+
...state,
|
|
86
|
+
undo,
|
|
87
|
+
redo
|
|
88
|
+
}), [
|
|
89
|
+
redo,
|
|
90
|
+
state,
|
|
91
|
+
undo
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
exports.useDocumentSelector = useDocumentSelector;
|
|
96
|
+
exports.useHistory = useHistory;
|
|
97
|
+
exports.useReadable = useReadable;
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=react.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.cjs","names":["target","track"],"sources":["../react/src/hooks.ts"],"sourcesContent":["import type {\n DocumentRuntime,\n DocumentSchema,\n DocumentSelector,\n HistoryState,\n ImpactTarget,\n LocalHistory,\n OperationResult,\n Readable,\n} from 'doxum';\nimport { target } from 'doxum';\nimport { track } from 'doxum/integration';\nimport { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';\n\nexport type DocumentSelectorOptions<TResult> = {\n readonly isEqual?: (previous: TResult, next: TResult) => boolean;\n readonly server?: () => TResult;\n};\n\ntype SelectorCache<TSchema extends DocumentSchema, TResult> = {\n readonly runtime: DocumentRuntime<TSchema>;\n readonly value: TResult;\n readonly targets: readonly ImpactTarget<unknown>[];\n};\n\nconst objectIs = <T>(previous: T, next: T): boolean => Object.is(previous, next);\nconst sameTargets = (\n left: readonly ImpactTarget<unknown>[],\n right: readonly ImpactTarget<unknown>[]\n): boolean => {\n if (left.length !== right.length) return false;\n for (let index = 0; index < left.length; index += 1)\n if (!target.same(left[index], right[index])) return false;\n return true;\n};\n\nexport function useDocumentSelector<TSchema extends DocumentSchema, TResult>(\n runtime: DocumentRuntime<TSchema>,\n selector: DocumentSelector<TSchema, TResult>,\n options?: DocumentSelectorOptions<TResult>\n): TResult {\n const equality = options?.isEqual ?? objectIs;\n const cache = useRef<SelectorCache<TSchema, TResult> | undefined>(undefined);\n const serverCache = useRef<\n { readonly source: (() => TResult) | undefined; readonly value: TResult } | undefined\n >(undefined);\n\n const read = useCallback((): TResult => {\n const selection = track(runtime, selector);\n const next = selection.value;\n const previous = cache.current;\n\n // Keep the prior reference for semantic equality on the same runtime. This\n // is what makes inline selectors safe with useSyncExternalStore.\n const targets = selection.targets;\n if (previous && previous.runtime === runtime && equality(previous.value, next)) {\n cache.current = { runtime, value: previous.value, targets };\n return previous.value;\n }\n\n cache.current = { runtime, value: next, targets };\n return next;\n }, [equality, runtime, selector]);\n\n const subscribe = useCallback(\n (listener: () => void) => {\n let targets = cache.current?.targets ?? [];\n let unsubscribe: () => void = () => undefined;\n const install = () => {\n if (targets.length === 0) return () => undefined;\n return runtime.subscribe(\n targets.length === 1\n ? targets[0]\n : (targets as [ImpactTarget<unknown>, ...ImpactTarget<unknown>[]]),\n onCommit\n );\n };\n const onCommit = () => {\n const previous = cache.current;\n const next = read();\n const nextTargets = cache.current?.targets ?? [];\n if (!sameTargets(targets, nextTargets)) {\n unsubscribe();\n targets = nextTargets;\n unsubscribe = install();\n }\n if (previous && previous.value !== next) listener();\n };\n unsubscribe = install();\n return () => unsubscribe();\n },\n [read, runtime]\n );\n\n const server = options?.server;\n const readServer = useCallback((): TResult => {\n const previous = serverCache.current;\n if (previous && previous.source === server) return previous.value;\n const value = server ? server() : read();\n serverCache.current = { source: server, value };\n return value;\n }, [read, server]);\n\n return useSyncExternalStore(subscribe, read, readServer);\n}\n\nexport function useReadable<T>(readable: Readable<T>): T {\n const subscribe = useCallback((listener: () => void) => readable.subscribe(listener), [readable]);\n const read = useCallback(() => readable.current(), [readable]);\n return useSyncExternalStore(subscribe, read, read);\n}\n\nexport function useHistory<TCommit>(history: LocalHistory<TCommit>): HistoryState & {\n undo(): OperationResult<TCommit>;\n redo(): OperationResult<TCommit>;\n} {\n const subscribe = useCallback((listener: () => void) => history.subscribe(listener), [history]);\n const read = useCallback(() => history.current(), [history]);\n const state = useSyncExternalStore(subscribe, read, read);\n const undo = useCallback(() => history.undo(), [history]);\n const redo = useCallback(() => history.redo(), [history]);\n\n return useMemo(() => ({ ...state, undo, redo }), [redo, state, undo]);\n}\n"],"mappings":"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"}
|
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DocumentRuntime, DocumentSchema, DocumentSelector, HistoryState, LocalHistory, OperationResult, Readable } from "doxum";
|
|
2
|
+
|
|
3
|
+
//#region react/src/hooks.d.ts
|
|
4
|
+
type DocumentSelectorOptions<TResult> = {
|
|
5
|
+
readonly isEqual?: (previous: TResult, next: TResult) => boolean;
|
|
6
|
+
readonly server?: () => TResult;
|
|
7
|
+
};
|
|
8
|
+
declare function useDocumentSelector<TSchema extends DocumentSchema, TResult>(runtime: DocumentRuntime<TSchema>, selector: DocumentSelector<TSchema, TResult>, options?: DocumentSelectorOptions<TResult>): TResult;
|
|
9
|
+
declare function useReadable<T>(readable: Readable<T>): T;
|
|
10
|
+
declare function useHistory<TCommit>(history: LocalHistory<TCommit>): HistoryState & {
|
|
11
|
+
undo(): OperationResult<TCommit>;
|
|
12
|
+
redo(): OperationResult<TCommit>;
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { type DocumentSelectorOptions, useDocumentSelector, useHistory, useReadable };
|
|
16
|
+
//# sourceMappingURL=react.d.cts.map
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DocumentRuntime, DocumentSchema, DocumentSelector, HistoryState, LocalHistory, OperationResult, Readable } from "doxum";
|
|
2
|
+
|
|
3
|
+
//#region react/src/hooks.d.ts
|
|
4
|
+
type DocumentSelectorOptions<TResult> = {
|
|
5
|
+
readonly isEqual?: (previous: TResult, next: TResult) => boolean;
|
|
6
|
+
readonly server?: () => TResult;
|
|
7
|
+
};
|
|
8
|
+
declare function useDocumentSelector<TSchema extends DocumentSchema, TResult>(runtime: DocumentRuntime<TSchema>, selector: DocumentSelector<TSchema, TResult>, options?: DocumentSelectorOptions<TResult>): TResult;
|
|
9
|
+
declare function useReadable<T>(readable: Readable<T>): T;
|
|
10
|
+
declare function useHistory<TCommit>(history: LocalHistory<TCommit>): HistoryState & {
|
|
11
|
+
undo(): OperationResult<TCommit>;
|
|
12
|
+
redo(): OperationResult<TCommit>;
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
export { type DocumentSelectorOptions, useDocumentSelector, useHistory, useReadable };
|
|
16
|
+
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { track } from "./integration.js";
|
|
2
|
+
import { target } from "doxum";
|
|
3
|
+
import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
|
|
4
|
+
//#region react/src/hooks.ts
|
|
5
|
+
const objectIs = (previous, next) => Object.is(previous, next);
|
|
6
|
+
const sameTargets = (left, right) => {
|
|
7
|
+
if (left.length !== right.length) return false;
|
|
8
|
+
for (let index = 0; index < left.length; index += 1) if (!target.same(left[index], right[index])) return false;
|
|
9
|
+
return true;
|
|
10
|
+
};
|
|
11
|
+
function useDocumentSelector(runtime, selector, options) {
|
|
12
|
+
const equality = options?.isEqual ?? objectIs;
|
|
13
|
+
const cache = useRef(void 0);
|
|
14
|
+
const serverCache = useRef(void 0);
|
|
15
|
+
const read = useCallback(() => {
|
|
16
|
+
const selection = track(runtime, selector);
|
|
17
|
+
const next = selection.value;
|
|
18
|
+
const previous = cache.current;
|
|
19
|
+
const targets = selection.targets;
|
|
20
|
+
if (previous && previous.runtime === runtime && equality(previous.value, next)) {
|
|
21
|
+
cache.current = {
|
|
22
|
+
runtime,
|
|
23
|
+
value: previous.value,
|
|
24
|
+
targets
|
|
25
|
+
};
|
|
26
|
+
return previous.value;
|
|
27
|
+
}
|
|
28
|
+
cache.current = {
|
|
29
|
+
runtime,
|
|
30
|
+
value: next,
|
|
31
|
+
targets
|
|
32
|
+
};
|
|
33
|
+
return next;
|
|
34
|
+
}, [
|
|
35
|
+
equality,
|
|
36
|
+
runtime,
|
|
37
|
+
selector
|
|
38
|
+
]);
|
|
39
|
+
const subscribe = useCallback((listener) => {
|
|
40
|
+
let targets = cache.current?.targets ?? [];
|
|
41
|
+
let unsubscribe = () => void 0;
|
|
42
|
+
const install = () => {
|
|
43
|
+
if (targets.length === 0) return () => void 0;
|
|
44
|
+
return runtime.subscribe(targets.length === 1 ? targets[0] : targets, onCommit);
|
|
45
|
+
};
|
|
46
|
+
const onCommit = () => {
|
|
47
|
+
const previous = cache.current;
|
|
48
|
+
const next = read();
|
|
49
|
+
const nextTargets = cache.current?.targets ?? [];
|
|
50
|
+
if (!sameTargets(targets, nextTargets)) {
|
|
51
|
+
unsubscribe();
|
|
52
|
+
targets = nextTargets;
|
|
53
|
+
unsubscribe = install();
|
|
54
|
+
}
|
|
55
|
+
if (previous && previous.value !== next) listener();
|
|
56
|
+
};
|
|
57
|
+
unsubscribe = install();
|
|
58
|
+
return () => unsubscribe();
|
|
59
|
+
}, [read, runtime]);
|
|
60
|
+
const server = options?.server;
|
|
61
|
+
return useSyncExternalStore(subscribe, read, useCallback(() => {
|
|
62
|
+
const previous = serverCache.current;
|
|
63
|
+
if (previous && previous.source === server) return previous.value;
|
|
64
|
+
const value = server ? server() : read();
|
|
65
|
+
serverCache.current = {
|
|
66
|
+
source: server,
|
|
67
|
+
value
|
|
68
|
+
};
|
|
69
|
+
return value;
|
|
70
|
+
}, [read, server]));
|
|
71
|
+
}
|
|
72
|
+
function useReadable(readable) {
|
|
73
|
+
const subscribe = useCallback((listener) => readable.subscribe(listener), [readable]);
|
|
74
|
+
const read = useCallback(() => readable.current(), [readable]);
|
|
75
|
+
return useSyncExternalStore(subscribe, read, read);
|
|
76
|
+
}
|
|
77
|
+
function useHistory(history) {
|
|
78
|
+
const subscribe = useCallback((listener) => history.subscribe(listener), [history]);
|
|
79
|
+
const read = useCallback(() => history.current(), [history]);
|
|
80
|
+
const state = useSyncExternalStore(subscribe, read, read);
|
|
81
|
+
const undo = useCallback(() => history.undo(), [history]);
|
|
82
|
+
const redo = useCallback(() => history.redo(), [history]);
|
|
83
|
+
return useMemo(() => ({
|
|
84
|
+
...state,
|
|
85
|
+
undo,
|
|
86
|
+
redo
|
|
87
|
+
}), [
|
|
88
|
+
redo,
|
|
89
|
+
state,
|
|
90
|
+
undo
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { useDocumentSelector, useHistory, useReadable };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","names":[],"sources":["../react/src/hooks.ts"],"sourcesContent":["import type {\n DocumentRuntime,\n DocumentSchema,\n DocumentSelector,\n HistoryState,\n ImpactTarget,\n LocalHistory,\n OperationResult,\n Readable,\n} from 'doxum';\nimport { target } from 'doxum';\nimport { track } from 'doxum/integration';\nimport { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';\n\nexport type DocumentSelectorOptions<TResult> = {\n readonly isEqual?: (previous: TResult, next: TResult) => boolean;\n readonly server?: () => TResult;\n};\n\ntype SelectorCache<TSchema extends DocumentSchema, TResult> = {\n readonly runtime: DocumentRuntime<TSchema>;\n readonly value: TResult;\n readonly targets: readonly ImpactTarget<unknown>[];\n};\n\nconst objectIs = <T>(previous: T, next: T): boolean => Object.is(previous, next);\nconst sameTargets = (\n left: readonly ImpactTarget<unknown>[],\n right: readonly ImpactTarget<unknown>[]\n): boolean => {\n if (left.length !== right.length) return false;\n for (let index = 0; index < left.length; index += 1)\n if (!target.same(left[index], right[index])) return false;\n return true;\n};\n\nexport function useDocumentSelector<TSchema extends DocumentSchema, TResult>(\n runtime: DocumentRuntime<TSchema>,\n selector: DocumentSelector<TSchema, TResult>,\n options?: DocumentSelectorOptions<TResult>\n): TResult {\n const equality = options?.isEqual ?? objectIs;\n const cache = useRef<SelectorCache<TSchema, TResult> | undefined>(undefined);\n const serverCache = useRef<\n { readonly source: (() => TResult) | undefined; readonly value: TResult } | undefined\n >(undefined);\n\n const read = useCallback((): TResult => {\n const selection = track(runtime, selector);\n const next = selection.value;\n const previous = cache.current;\n\n // Keep the prior reference for semantic equality on the same runtime. This\n // is what makes inline selectors safe with useSyncExternalStore.\n const targets = selection.targets;\n if (previous && previous.runtime === runtime && equality(previous.value, next)) {\n cache.current = { runtime, value: previous.value, targets };\n return previous.value;\n }\n\n cache.current = { runtime, value: next, targets };\n return next;\n }, [equality, runtime, selector]);\n\n const subscribe = useCallback(\n (listener: () => void) => {\n let targets = cache.current?.targets ?? [];\n let unsubscribe: () => void = () => undefined;\n const install = () => {\n if (targets.length === 0) return () => undefined;\n return runtime.subscribe(\n targets.length === 1\n ? targets[0]\n : (targets as [ImpactTarget<unknown>, ...ImpactTarget<unknown>[]]),\n onCommit\n );\n };\n const onCommit = () => {\n const previous = cache.current;\n const next = read();\n const nextTargets = cache.current?.targets ?? [];\n if (!sameTargets(targets, nextTargets)) {\n unsubscribe();\n targets = nextTargets;\n unsubscribe = install();\n }\n if (previous && previous.value !== next) listener();\n };\n unsubscribe = install();\n return () => unsubscribe();\n },\n [read, runtime]\n );\n\n const server = options?.server;\n const readServer = useCallback((): TResult => {\n const previous = serverCache.current;\n if (previous && previous.source === server) return previous.value;\n const value = server ? server() : read();\n serverCache.current = { source: server, value };\n return value;\n }, [read, server]);\n\n return useSyncExternalStore(subscribe, read, readServer);\n}\n\nexport function useReadable<T>(readable: Readable<T>): T {\n const subscribe = useCallback((listener: () => void) => readable.subscribe(listener), [readable]);\n const read = useCallback(() => readable.current(), [readable]);\n return useSyncExternalStore(subscribe, read, read);\n}\n\nexport function useHistory<TCommit>(history: LocalHistory<TCommit>): HistoryState & {\n undo(): OperationResult<TCommit>;\n redo(): OperationResult<TCommit>;\n} {\n const subscribe = useCallback((listener: () => void) => history.subscribe(listener), [history]);\n const read = useCallback(() => history.current(), [history]);\n const state = useSyncExternalStore(subscribe, read, read);\n const undo = useCallback(() => history.undo(), [history]);\n const redo = useCallback(() => history.redo(), [history]);\n\n return useMemo(() => ({ ...state, undo, redo }), [redo, state, undo]);\n}\n"],"mappings":";;;;AAyBA,MAAM,YAAe,UAAa,SAAqB,OAAO,GAAG,UAAU,KAAK;AAChF,MAAM,eACJ,MACA,UACY;AACZ,KAAI,KAAK,WAAW,MAAM,OAAQ,QAAO;AACzC,MAAK,IAAI,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,EAChD,KAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,MAAM,OAAO,CAAE,QAAO;AACtD,QAAO;;AAGT,SAAgB,oBACd,SACA,UACA,SACS;CACT,MAAM,WAAW,SAAS,WAAW;CACrC,MAAM,QAAQ,OAAoD,KAAA,EAAU;CAC5E,MAAM,cAAc,OAElB,KAAA,EAAU;CAEZ,MAAM,OAAO,kBAA2B;EACtC,MAAM,YAAY,MAAM,SAAS,SAAS;EAC1C,MAAM,OAAO,UAAU;EACvB,MAAM,WAAW,MAAM;EAIvB,MAAM,UAAU,UAAU;AAC1B,MAAI,YAAY,SAAS,YAAY,WAAW,SAAS,SAAS,OAAO,KAAK,EAAE;AAC9E,SAAM,UAAU;IAAE;IAAS,OAAO,SAAS;IAAO;IAAS;AAC3D,UAAO,SAAS;;AAGlB,QAAM,UAAU;GAAE;GAAS,OAAO;GAAM;GAAS;AACjD,SAAO;IACN;EAAC;EAAU;EAAS;EAAS,CAAC;CAEjC,MAAM,YAAY,aACf,aAAyB;EACxB,IAAI,UAAU,MAAM,SAAS,WAAW,EAAE;EAC1C,IAAI,oBAAgC,KAAA;EACpC,MAAM,gBAAgB;AACpB,OAAI,QAAQ,WAAW,EAAG,cAAa,KAAA;AACvC,UAAO,QAAQ,UACb,QAAQ,WAAW,IACf,QAAQ,KACP,SACL,SACD;;EAEH,MAAM,iBAAiB;GACrB,MAAM,WAAW,MAAM;GACvB,MAAM,OAAO,MAAM;GACnB,MAAM,cAAc,MAAM,SAAS,WAAW,EAAE;AAChD,OAAI,CAAC,YAAY,SAAS,YAAY,EAAE;AACtC,iBAAa;AACb,cAAU;AACV,kBAAc,SAAS;;AAEzB,OAAI,YAAY,SAAS,UAAU,KAAM,WAAU;;AAErD,gBAAc,SAAS;AACvB,eAAa,aAAa;IAE5B,CAAC,MAAM,QAAQ,CAChB;CAED,MAAM,SAAS,SAAS;AASxB,QAAO,qBAAqB,WAAW,MARpB,kBAA2B;EAC5C,MAAM,WAAW,YAAY;AAC7B,MAAI,YAAY,SAAS,WAAW,OAAQ,QAAO,SAAS;EAC5D,MAAM,QAAQ,SAAS,QAAQ,GAAG,MAAM;AACxC,cAAY,UAAU;GAAE,QAAQ;GAAQ;GAAO;AAC/C,SAAO;IACN,CAAC,MAAM,OAAO,CAEsC,CAAC;;AAG1D,SAAgB,YAAe,UAA0B;CACvD,MAAM,YAAY,aAAa,aAAyB,SAAS,UAAU,SAAS,EAAE,CAAC,SAAS,CAAC;CACjG,MAAM,OAAO,kBAAkB,SAAS,SAAS,EAAE,CAAC,SAAS,CAAC;AAC9D,QAAO,qBAAqB,WAAW,MAAM,KAAK;;AAGpD,SAAgB,WAAoB,SAGlC;CACA,MAAM,YAAY,aAAa,aAAyB,QAAQ,UAAU,SAAS,EAAE,CAAC,QAAQ,CAAC;CAC/F,MAAM,OAAO,kBAAkB,QAAQ,SAAS,EAAE,CAAC,QAAQ,CAAC;CAC5D,MAAM,QAAQ,qBAAqB,WAAW,MAAM,KAAK;CACzD,MAAM,OAAO,kBAAkB,QAAQ,MAAM,EAAE,CAAC,QAAQ,CAAC;CACzD,MAAM,OAAO,kBAAkB,QAAQ,MAAM,EAAE,CAAC,QAAQ,CAAC;AAEzD,QAAO,eAAe;EAAE,GAAG;EAAO;EAAM;EAAM,GAAG;EAAC;EAAM;EAAO;EAAK,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "doxum",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Doxum is a typed runtime for complex mutable documents.",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"document",
|
|
10
|
+
"state",
|
|
11
|
+
"runtime",
|
|
12
|
+
"typescript"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/Isrowan/doxum.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/Isrowan/doxum#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/Isrowan/doxum/issues"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.cjs",
|
|
26
|
+
"module": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"require": "./dist/index.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./integration": {
|
|
35
|
+
"types": "./dist/integration.d.ts",
|
|
36
|
+
"import": "./dist/integration.js",
|
|
37
|
+
"require": "./dist/integration.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./react": {
|
|
40
|
+
"types": "./dist/react.d.ts",
|
|
41
|
+
"import": "./dist/react.js",
|
|
42
|
+
"require": "./dist/react.cjs"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"react": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"dist",
|
|
55
|
+
"skills"
|
|
56
|
+
],
|
|
57
|
+
"sideEffects": false,
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@eslint/js": "^9.35.0",
|
|
60
|
+
"eslint": "^9.35.0",
|
|
61
|
+
"prettier": "^3.6.2",
|
|
62
|
+
"rimraf": "^6.1.3",
|
|
63
|
+
"tsdown": "^0.21.1",
|
|
64
|
+
"tsx": "^4.21.0",
|
|
65
|
+
"typescript": "^5.9.3",
|
|
66
|
+
"typescript-eslint": "^8.43.0",
|
|
67
|
+
"vitest": "^4.0.16",
|
|
68
|
+
"@types/react": "^19.2.5",
|
|
69
|
+
"@types/react-dom": "^19.2.3",
|
|
70
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
71
|
+
"react": "19.2.4",
|
|
72
|
+
"react-dom": "19.2.4",
|
|
73
|
+
"react-test-renderer": "19.2.4"
|
|
74
|
+
},
|
|
75
|
+
"scripts": {
|
|
76
|
+
"build": "tsdown --config tsdown.config.ts",
|
|
77
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
78
|
+
"test": "vitest run --config vitest.config.ts",
|
|
79
|
+
"check": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run test",
|
|
80
|
+
"bench": "vitest bench --config vitest.bench.config.ts",
|
|
81
|
+
"profile": "node --expose-gc --import tsx core/bench/profile.ts",
|
|
82
|
+
"format": "prettier --write --ignore-unknown .",
|
|
83
|
+
"format:check": "prettier --check --ignore-unknown .",
|
|
84
|
+
"lint": "eslint .",
|
|
85
|
+
"clean": "pnpm -r exec rimraf dist",
|
|
86
|
+
"release": "node scripts/release.mjs",
|
|
87
|
+
"release:resume": "node scripts/release.mjs --resume"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doxum-runtime
|
|
3
|
+
description: 'Use doxum and doxum/react to design, build, or review Doxum document runtime code. Apply for schemas, mutations, operations, views, subscriptions, and React integration.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Doxum Runtime
|
|
7
|
+
|
|
8
|
+
Use this skill when a task creates, modifies, reviews, or explains Doxum code.
|
|
9
|
+
It applies to `doxum`, `doxum/react`, and application code that uses
|
|
10
|
+
Doxum's schema, runtime, transaction, operation, impact, projection, or React
|
|
11
|
+
APIs. Do not apply it to unrelated TypeScript or React work merely because the
|
|
12
|
+
application happens to contain a Doxum runtime.
|
|
13
|
+
|
|
14
|
+
## Read the right reference
|
|
15
|
+
|
|
16
|
+
- Start with [the English guide](references/guide.en.md) for English requests
|
|
17
|
+
or [中文指南](references/guide.zh-CN.md) for Chinese requests. They are
|
|
18
|
+
equivalent task-oriented introductions.
|
|
19
|
+
- Read [the English patterns](references/patterns.en.md) or
|
|
20
|
+
[中文模式参考](references/patterns.zh-CN.md) when implementing schema models,
|
|
21
|
+
transactions, replay, selectors, views, or React code.
|
|
22
|
+
- Read [the English invariants](references/invariants.en.md) or
|
|
23
|
+
[中文不变量](references/invariants.zh-CN.md) before changing mutation,
|
|
24
|
+
addressing, impact, notification, history, tree, or derived-view behavior.
|
|
25
|
+
|
|
26
|
+
## Required Doxum decisions
|
|
27
|
+
|
|
28
|
+
- Treat `createDocument` as the only write authority for canonical document
|
|
29
|
+
state. Application writes use `runtime.update`; replayed external operations
|
|
30
|
+
use `runtime.apply`.
|
|
31
|
+
- A transaction is synchronous and atomic. Do not retain a transaction reader
|
|
32
|
+
or writer after its callback returns, and do not introduce another writable
|
|
33
|
+
state cache.
|
|
34
|
+
- Use the runtime's schema selectors, anchors, addresses, and `target` APIs.
|
|
35
|
+
Do not add local path parsers, duplicate impact-target helpers, or manually
|
|
36
|
+
synchronize derived data.
|
|
37
|
+
- Handle expected mutation failure from the returned `rejected` result and its
|
|
38
|
+
`MutationIssue` values. Use `tx.report` or `tx.reject` with
|
|
39
|
+
`DocumentDiagnostic` for application validation. A thrown callback error
|
|
40
|
+
rolls back and is rethrown.
|
|
41
|
+
- A committed result with `observerErrors` is still committed. Do not retry its
|
|
42
|
+
write as if canonical state had been rolled back.
|
|
43
|
+
- Keep `core` framework-neutral. React code belongs behind `doxum/react` and
|
|
44
|
+
must not make `core` import UI concepts.
|
|
45
|
+
- Doxum does not define persistence, synchronization, authorization, or
|
|
46
|
+
conflict resolution. Keep those policies in the application boundary before
|
|
47
|
+
`apply` or `replace`.
|
|
48
|
+
|
|
49
|
+
When public behavior or lifecycle semantics change, update the README and
|
|
50
|
+
architecture guide and add focused tests for rollback, inverse history, impact
|
|
51
|
+
or subscription behavior, and performance-sensitive collection or tree paths.
|