@earendil-works/pi-durable 1.4.0 → 1.5.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/CHANGELOG.md +186 -0
- package/README.md +181 -84
- package/dist/codemode/codemode.d.ts +4 -4
- package/dist/codemode/codemode.d.ts.map +1 -1
- package/dist/codemode/codemode.js +9 -12
- package/dist/codemode/codemode.js.map +1 -1
- package/dist/codemode/models.js +4 -4
- package/dist/codemode/models.js.map +1 -1
- package/dist/env/index.d.ts +12 -10
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js.map +1 -1
- package/dist/env/node-watch.d.ts +3 -3
- package/dist/env/node-watch.d.ts.map +1 -1
- package/dist/env/node-watch.js +18 -16
- package/dist/env/node-watch.js.map +1 -1
- package/dist/env/node.d.ts +2 -2
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +1 -1
- package/dist/env/node.js.map +1 -1
- package/dist/errors.d.ts +18 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +26 -3
- package/dist/errors.js.map +1 -1
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +10 -2
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/compaction.d.ts.map +1 -1
- package/dist/harness/compaction.js +19 -2
- package/dist/harness/compaction.js.map +1 -1
- package/dist/harness/context.d.ts +23 -7
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +71 -25
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/define.d.ts +7 -3
- package/dist/harness/define.d.ts.map +1 -1
- package/dist/harness/define.js +4 -0
- package/dist/harness/define.js.map +1 -1
- package/dist/harness/generation.d.ts +38 -5
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +445 -96
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts +2 -0
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +87 -62
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +5 -1
- package/dist/harness/inbox.d.ts.map +1 -1
- package/dist/harness/inbox.js +4 -1
- package/dist/harness/inbox.js.map +1 -1
- package/dist/harness/json.d.ts +20 -4
- package/dist/harness/json.d.ts.map +1 -1
- package/dist/harness/json.js +23 -5
- package/dist/harness/json.js.map +1 -1
- package/dist/harness/live.d.ts +122 -16
- package/dist/harness/live.d.ts.map +1 -1
- package/dist/harness/live.js +93 -12
- package/dist/harness/live.js.map +1 -1
- package/dist/harness/partial-changes.d.ts +54 -0
- package/dist/harness/partial-changes.d.ts.map +1 -0
- package/dist/harness/partial-changes.js +162 -0
- package/dist/harness/partial-changes.js.map +1 -0
- package/dist/harness/registry-reader.d.ts +11 -0
- package/dist/harness/registry-reader.d.ts.map +1 -0
- package/dist/harness/registry-reader.js +25 -0
- package/dist/harness/registry-reader.js.map +1 -0
- package/dist/harness/scheduler.d.ts +46 -19
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +973 -376
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +17 -10
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +62 -25
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/task-graph.d.ts +12 -9
- package/dist/harness/task-graph.d.ts.map +1 -1
- package/dist/harness/task-graph.js +38 -28
- package/dist/harness/task-graph.js.map +1 -1
- package/dist/harness/telemetry.d.ts +11 -11
- package/dist/harness/telemetry.d.ts.map +1 -1
- package/dist/harness/telemetry.js +7 -7
- package/dist/harness/telemetry.js.map +1 -1
- package/dist/harness/tool.d.ts +2 -0
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +171 -55
- package/dist/harness/tool.js.map +1 -1
- package/dist/harness/transcript.d.ts +72 -0
- package/dist/harness/transcript.d.ts.map +1 -0
- package/dist/harness/transcript.js +264 -0
- package/dist/harness/transcript.js.map +1 -0
- package/dist/harness/types.d.ts +218 -59
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/util.d.ts +3 -1
- package/dist/harness/util.d.ts.map +1 -1
- package/dist/harness/util.js +9 -0
- package/dist/harness/util.js.map +1 -1
- package/dist/harness/view.d.ts +43 -22
- package/dist/harness/view.d.ts.map +1 -1
- package/dist/harness/view.js +130 -71
- package/dist/harness/view.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/search.d.ts +19 -3
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +107 -44
- package/dist/search.js.map +1 -1
- package/dist/session/cached-storage.d.ts +71 -0
- package/dist/session/cached-storage.d.ts.map +1 -0
- package/dist/session/cached-storage.js +269 -0
- package/dist/session/cached-storage.js.map +1 -0
- package/dist/session/conversation-tails.d.ts +33 -0
- package/dist/session/conversation-tails.d.ts.map +1 -0
- package/dist/session/conversation-tails.js +101 -0
- package/dist/session/conversation-tails.js.map +1 -0
- package/dist/session/keys.d.ts +68 -0
- package/dist/session/keys.d.ts.map +1 -0
- package/dist/session/keys.js +161 -0
- package/dist/session/keys.js.map +1 -0
- package/dist/session/session.d.ts +167 -26
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +1029 -171
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +81 -3
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +224 -37
- package/dist/session/transaction.js.map +1 -1
- package/dist/session/writer.d.ts +74 -0
- package/dist/session/writer.d.ts.map +1 -0
- package/dist/session/writer.js +294 -0
- package/dist/session/writer.js.map +1 -0
- package/dist/storage/indexeddb.d.ts +10 -2
- package/dist/storage/indexeddb.d.ts.map +1 -1
- package/dist/storage/indexeddb.js +91 -41
- package/dist/storage/indexeddb.js.map +1 -1
- package/dist/storage/jsonl/storage.d.ts +14 -2
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/jsonl/storage.js +88 -24
- package/dist/storage/jsonl/storage.js.map +1 -1
- package/dist/storage/memory.d.ts +7 -2
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +28 -2
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/storage.d.ts +14 -2
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +110 -36
- package/dist/storage/sqlite/storage.js.map +1 -1
- package/dist/storage/store/commit.d.ts +33 -0
- package/dist/storage/store/commit.d.ts.map +1 -0
- package/dist/storage/store/commit.js +286 -0
- package/dist/storage/store/commit.js.map +1 -0
- package/dist/storage/store/documents.d.ts +43 -0
- package/dist/storage/store/documents.d.ts.map +1 -0
- package/dist/storage/store/documents.js +102 -0
- package/dist/storage/store/documents.js.map +1 -0
- package/dist/storage/store/index.d.ts +2 -0
- package/dist/storage/store/index.d.ts.map +1 -0
- package/dist/storage/store/index.js +2 -0
- package/dist/storage/store/index.js.map +1 -0
- package/dist/storage/store/queries.d.ts +49 -0
- package/dist/storage/store/queries.d.ts.map +1 -0
- package/dist/storage/store/queries.js +93 -0
- package/dist/storage/store/queries.js.map +1 -0
- package/dist/storage/store/storage.d.ts +59 -0
- package/dist/storage/store/storage.d.ts.map +1 -0
- package/dist/storage/store/storage.js +438 -0
- package/dist/storage/store/storage.js.map +1 -0
- package/dist/storage/store/tables.d.ts +54 -0
- package/dist/storage/store/tables.d.ts.map +1 -0
- package/dist/storage/store/tables.js +108 -0
- package/dist/storage/store/tables.js.map +1 -0
- package/dist/testing/commit-writes.d.ts +5 -0
- package/dist/testing/commit-writes.d.ts.map +1 -0
- package/dist/testing/commit-writes.js +12 -0
- package/dist/testing/commit-writes.js.map +1 -0
- package/dist/testing/env-conformance.js +7 -7
- package/dist/testing/env-conformance.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/storage-benchmark.d.ts.map +1 -1
- package/dist/testing/storage-benchmark.js +18 -17
- package/dist/testing/storage-benchmark.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +354 -69
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/tool-search/tool-search.d.ts +6 -12
- package/dist/tool-search/tool-search.d.ts.map +1 -1
- package/dist/tool-search/tool-search.js +7 -7
- package/dist/tool-search/tool-search.js.map +1 -1
- package/dist/tools/bash.d.ts +12 -12
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +16 -15
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +7 -7
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +9 -9
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/index.d.ts +32 -32
- package/dist/tools/read.d.ts +6 -6
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +19 -8
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +4 -4
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +5 -5
- package/dist/tools/write.js.map +1 -1
- package/dist/types.d.ts +223 -77
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +19 -12
- package/dist/harness/events.d.ts +0 -186
- package/dist/harness/events.d.ts.map +0 -1
- package/dist/harness/events.js +0 -373
- package/dist/harness/events.js.map +0 -1
- package/dist/session/observation.d.ts +0 -42
- package/dist/session/observation.d.ts.map +0 -1
- package/dist/session/observation.js +0 -281
- package/dist/session/observation.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Context, type Draft, type JsonValue } from "@earendil-works/chord";
|
|
2
|
-
import { type Tracker } from "@earendil-works/chord/delta";
|
|
2
|
+
import { type Op, type PendingOps, type Tracker } from "@earendil-works/chord/delta";
|
|
3
3
|
import { type AnyDocDefinition } from "../documents.ts";
|
|
4
|
-
import type { ConversationDocFamilyToken, ConversationDocToken, ConversationId, ConversationOwnership, ConversationQuery, ConversationRecord, Cursor, DocumentAddress, DocumentCommitChange, DocumentId, DocumentRecord, Entry, EntryDraft, EntryId, EntryQuery, EntryRecord, JsonObject, Seq, SessionDocFamilyToken, SessionDocToken, Storage, StorageWrite, SubmissionCreate, SubmissionId, SubmissionRecord, SubmissionSettlement, Task, TaskDocFamilyToken, TaskDocToken, TaskId, TaskOptions, TaskQuery, TaskRecord, Tx, TypedEntry, TypedEntryDraft } from "../types.ts";
|
|
4
|
+
import type { ConversationDocFamilyToken, ConversationDocToken, ConversationId, ConversationOwnership, ConversationQuery, ConversationRecord, Cursor, DocumentAddress, DocumentCommitChange, DocumentContent, DocumentId, DocumentRecord, Entry, EntryDraft, EntryId, EntryQuery, EntryRecord, JsonObject, Seq, SessionDocFamilyToken, SessionDocToken, Storage, StorageWrite, SubmissionCreate, SubmissionId, SubmissionRecord, SubmissionSettlement, Task, TaskDocFamilyToken, TaskDocToken, TaskId, TaskOptions, TaskQuery, TaskRecord, Tx, TypedEntry, TypedEntryDraft } from "../types.ts";
|
|
5
5
|
type AnyTaskRecord = TaskRecord<JsonValue, JsonValue, JsonValue>;
|
|
6
6
|
/** One committed document incarnation owned by the Session tracker cache. */
|
|
7
7
|
export type LoadedDocument = {
|
|
@@ -15,9 +15,29 @@ export type LoadedDocument = {
|
|
|
15
15
|
deltasSinceBase: number;
|
|
16
16
|
readonly tracker: Tracker<JsonObject>;
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* The changes of a cached incarnation that deferred commits adopted in memory and the Session has not stored yet
|
|
20
|
+
* (`CommitPersistence`), folded. The Session owns it; a commit that stores them takes them.
|
|
21
|
+
*/
|
|
22
|
+
export type HeldDocument = {
|
|
23
|
+
readonly loaded: LoadedDocument;
|
|
24
|
+
readonly pending: PendingOps;
|
|
25
|
+
/** Set when a held commit selected a base (`checkpointWhen`): their store writes the whole value. */
|
|
26
|
+
base: boolean;
|
|
27
|
+
};
|
|
28
|
+
/** What adoption publishes: to the Session's own observers, and the stored changes to `observeCommits()`. */
|
|
29
|
+
export type AdoptedDocuments = {
|
|
30
|
+
/** This commit's own changes, as memory adopted them. */
|
|
31
|
+
readonly observed: readonly DocumentCommitChange[];
|
|
32
|
+
/** What Storage got: `observed`, unless the commit also stored held changes, which precede its own. */
|
|
33
|
+
readonly stored: readonly DocumentCommitChange[];
|
|
34
|
+
};
|
|
18
35
|
/** Session services used by a transaction while it holds the mutation line. */
|
|
19
36
|
export interface TransactionHost {
|
|
37
|
+
/** Reads wait while their key has an unpublished writer (`GatedStorage`). */
|
|
20
38
|
readonly storage: Storage;
|
|
39
|
+
/** Wait while `key` has an unpublished writer; `undefined` when it has none. */
|
|
40
|
+
touch(key: string): Promise<void> | undefined;
|
|
21
41
|
/** Wall clock for task lifecycle times. */
|
|
22
42
|
now(): number;
|
|
23
43
|
/** Return the cached current incarnation without loading. */
|
|
@@ -30,6 +50,16 @@ export interface TransactionHost {
|
|
|
30
50
|
evict(addressId: string, recordId: DocumentId): void;
|
|
31
51
|
/** Stage writes that belong to every newly created or forked conversation, in its creating transaction. */
|
|
32
52
|
conversationCreated(tx: Transaction, record: ConversationRecord): Promise<void>;
|
|
53
|
+
/** The newest entry ID visible from a conversation, known from the Session's commits. */
|
|
54
|
+
latestEntryId(conversationId: ConversationId, context: Context): Promise<EntryId | undefined>;
|
|
55
|
+
/**
|
|
56
|
+
* The current documents of a live task when the Session knows all of them (it created the task), else `undefined`.
|
|
57
|
+
*/
|
|
58
|
+
taskDocuments(taskId: TaskId): Iterable<DocumentRecord> | undefined;
|
|
59
|
+
/** The newest visible head marker of a conversation, known from the Session's commits. */
|
|
60
|
+
latestHeadMarker(conversationId: ConversationId, context: Context): Promise<(EntryRecord & {
|
|
61
|
+
readonly head: EntryId;
|
|
62
|
+
}) | undefined>;
|
|
33
63
|
}
|
|
34
64
|
/** Internal run creation resolves submission carriers against staged records before reading storage. */
|
|
35
65
|
export type TaskCreationOptions = TaskOptions & {
|
|
@@ -50,6 +80,15 @@ export type TransactionScope = {
|
|
|
50
80
|
export declare class Transaction implements Tx {
|
|
51
81
|
#private;
|
|
52
82
|
constructor(host: TransactionHost, context: Context, scope?: TransactionScope);
|
|
83
|
+
/** Internal: the context of the commit this transaction belongs to. */
|
|
84
|
+
get context(): Context;
|
|
85
|
+
/** Internal: whether a table write was staged, after which table reads throw `ReadAfterWrite`. */
|
|
86
|
+
get hasTableWrite(): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Internal: whether `tx` is a transaction of a commit of the Session behind `host`. A brand check, so a proxy or
|
|
89
|
+
* another wrapper of a transaction is not one.
|
|
90
|
+
*/
|
|
91
|
+
static ownedBy(tx: Tx, host: TransactionHost): tx is Transaction;
|
|
53
92
|
conversation(id: ConversationId): Promise<ConversationRecord | undefined>;
|
|
54
93
|
entry(id: EntryId): Promise<EntryRecord | undefined>;
|
|
55
94
|
entry<D extends JsonValue>(token: Entry<D>, id: EntryId): Promise<TypedEntry<D> | undefined>;
|
|
@@ -59,6 +98,7 @@ export declare class Transaction implements Tx {
|
|
|
59
98
|
latestHeadMarker(conversationId: ConversationId): Promise<(EntryRecord & {
|
|
60
99
|
readonly head: EntryId;
|
|
61
100
|
}) | undefined>;
|
|
101
|
+
latestEntryId(conversationId: ConversationId): Promise<EntryId | undefined>;
|
|
62
102
|
scanTasks(query: TaskQuery, limit: number, cursor?: Cursor): Promise<import("../types.ts").Page<TaskRecord<JsonValue, JsonValue, JsonValue>, Readonly<Record<string, JsonValue>>>>;
|
|
63
103
|
/** Internal: committed submission record. */
|
|
64
104
|
submission(id: SubmissionId): Promise<SubmissionRecord | undefined>;
|
|
@@ -90,6 +130,8 @@ export declare class Transaction implements Tx {
|
|
|
90
130
|
setTask(value: AnyTaskRecord): void;
|
|
91
131
|
/** Internal: candidate records of the tasks this transaction created or replaced so far. */
|
|
92
132
|
stagedTasks(): AnyTaskRecord[];
|
|
133
|
+
/** Internal: IDs of the tasks this transaction creates. */
|
|
134
|
+
createdTaskIds(): TaskId[];
|
|
93
135
|
/** Internal: conversations this transaction created or forked so far. */
|
|
94
136
|
stagedConversations(): ConversationRecord[];
|
|
95
137
|
doc<T extends JsonObject>(token: SessionDocToken<T>): Promise<Draft<T>>;
|
|
@@ -113,8 +155,44 @@ export declare class Transaction implements Tx {
|
|
|
113
155
|
settleSuccess(): Promise<readonly StorageWrite[]>;
|
|
114
156
|
/** Abort every prepared change after Storage failure or when no write is required. */
|
|
115
157
|
discard(): void;
|
|
158
|
+
/**
|
|
159
|
+
* Internal: what a deferred commit holds in memory instead of storing (`CommitPersistence`), each changed incarnation
|
|
160
|
+
* with its operations and whether they selected a base; `undefined` when the commit must be stored. It may be held
|
|
161
|
+
* only when every write changes an existing `latest`, `initial`-fork conversation document at its stored version.
|
|
162
|
+
*/
|
|
163
|
+
holdable(): {
|
|
164
|
+
readonly loaded: LoadedDocument;
|
|
165
|
+
readonly ops: readonly Op[];
|
|
166
|
+
readonly base: boolean;
|
|
167
|
+
}[] | undefined;
|
|
168
|
+
/**
|
|
169
|
+
* Internal: adopt a held commit (`holdable()`) in memory only. Trackers advance by pointer swap; stored versions and
|
|
170
|
+
* delta counts stay, as nothing was stored. Returns its publication for the Session's own observers.
|
|
171
|
+
*/
|
|
172
|
+
adoptHeld(): DocumentCommitChange[];
|
|
173
|
+
/**
|
|
174
|
+
* Internal: store the held changes of every incarnation this commit reads or writes in front of its own, so a stored
|
|
175
|
+
* document never misses an operation its later ones build on. Called right before Storage admission, after every
|
|
176
|
+
* fallible step, because it empties the holds: a failure earlier keeps them. Returns the holds it took.
|
|
177
|
+
*/
|
|
178
|
+
takeHeld<H extends HeldDocument>(held: (id: DocumentId) => H | undefined): H[];
|
|
179
|
+
/**
|
|
180
|
+
* Internal: the keys this commit writes, once settled (and after `takeHeld()`), for the Session's unpublished write
|
|
181
|
+
* set: its table records, and the address and incarnation of every document it creates, changes, or retires, with
|
|
182
|
+
* the scope of every incarnation it creates or retires.
|
|
183
|
+
*/
|
|
184
|
+
writeKeys(): string[];
|
|
116
185
|
/** Adopt every prepared change by pointer swap after Storage success and describe the publication. */
|
|
117
|
-
adopt(seq: Seq):
|
|
186
|
+
adopt(seq: Seq): AdoptedDocuments;
|
|
118
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Take a hold's changes after `ops`, a commit's own on top of them, as one stored content for an incarnation now at
|
|
190
|
+
* `value`, and the operations a reader of the stored state applies to reach it: the folded ones, or a root replacement
|
|
191
|
+
* after the queue overflowed, which stores a base, as does a hold whose commits selected one.
|
|
192
|
+
*/
|
|
193
|
+
export declare function storeHeld(hold: HeldDocument, ops: readonly Op[], value: JsonObject, version: number): {
|
|
194
|
+
readonly content: DocumentContent;
|
|
195
|
+
readonly ops: readonly Op[];
|
|
196
|
+
};
|
|
119
197
|
export {};
|
|
120
198
|
//# sourceMappingURL=transaction.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../../src/session/transaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAY,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE3F,OAAO,
|
|
1
|
+
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../../src/session/transaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,9 +5,10 @@ import { addressId, checkRecordScope, checkRecordVersion, documentCreate, materi
|
|
|
5
5
|
import { ReadAfterWrite } from "../errors.js";
|
|
6
6
|
import { ROOT_CONVERSATION_ID } from "../types.js";
|
|
7
7
|
import { prepareForkDocumentCopies } from "./forks.js";
|
|
8
|
+
import { documentKey, incarnationKey, scopeKey, tableKeys } from "./keys.js";
|
|
8
9
|
/**
|
|
9
|
-
* Complete record after applying one change. Placement turns a queued input `placed` and a queued write `done`;
|
|
10
|
-
* placed input can be answered. A settled record stays.
|
|
10
|
+
* Complete record after applying one change. Placement turns a queued input `placed` and a queued write `done`; a
|
|
11
|
+
* continue is never queued, so never placed. Only a placed input or continue can be answered. A settled record stays.
|
|
11
12
|
*/
|
|
12
13
|
function applySubmissionChange(current, change) {
|
|
13
14
|
if (current.status === "done" || current.status === "unanswered")
|
|
@@ -19,7 +20,7 @@ function applySubmissionChange(current, change) {
|
|
|
19
20
|
return { ...current, status, entry: change.entry };
|
|
20
21
|
}
|
|
21
22
|
if (change.status === "done" && current.status !== "placed") {
|
|
22
|
-
throw new Error(`Submission ${current.id} is not a placed input`);
|
|
23
|
+
throw new Error(`Submission ${current.id} is not a placed input or continue`);
|
|
23
24
|
}
|
|
24
25
|
// Queued and placed records carry no answer, reason, or detail; an unanswered input keeps its entry.
|
|
25
26
|
return { ...current, ...change };
|
|
@@ -62,6 +63,21 @@ export class Transaction {
|
|
|
62
63
|
this.#context = context;
|
|
63
64
|
this.#scope = scope;
|
|
64
65
|
}
|
|
66
|
+
/** Internal: the context of the commit this transaction belongs to. */
|
|
67
|
+
get context() {
|
|
68
|
+
return this.#context;
|
|
69
|
+
}
|
|
70
|
+
/** Internal: whether a table write was staged, after which table reads throw `ReadAfterWrite`. */
|
|
71
|
+
get hasTableWrite() {
|
|
72
|
+
return this.#hasTableWrite;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Internal: whether `tx` is a transaction of a commit of the Session behind `host`. A brand check, so a proxy or
|
|
76
|
+
* another wrapper of a transaction is not one.
|
|
77
|
+
*/
|
|
78
|
+
static ownedBy(tx, host) {
|
|
79
|
+
return #host in tx && tx.#host === host;
|
|
80
|
+
}
|
|
65
81
|
// ─── Table reads ────────────────────────────────────────────────────────
|
|
66
82
|
conversation(id) {
|
|
67
83
|
return this.#read("conversation", () => this.#host.storage.conversation(id, this.#context));
|
|
@@ -84,7 +100,10 @@ export class Transaction {
|
|
|
84
100
|
return this.#read("scanEntries", () => this.#host.storage.scanEntries(query, limit, cursor, this.#context));
|
|
85
101
|
}
|
|
86
102
|
latestHeadMarker(conversationId) {
|
|
87
|
-
return this.#read("latestHeadMarker", () => this.#host.
|
|
103
|
+
return this.#read("latestHeadMarker", () => this.#host.latestHeadMarker(conversationId, this.#context));
|
|
104
|
+
}
|
|
105
|
+
latestEntryId(conversationId) {
|
|
106
|
+
return this.#read("latestEntryId", () => this.#host.latestEntryId(conversationId, this.#context));
|
|
88
107
|
}
|
|
89
108
|
scanTasks(query, limit, cursor) {
|
|
90
109
|
return this.#read("scanTasks", () => this.#host.storage.scanTasks(query, limit, cursor, this.#context));
|
|
@@ -284,6 +303,14 @@ export class Transaction {
|
|
|
284
303
|
records.push(task.write.record);
|
|
285
304
|
return records;
|
|
286
305
|
}
|
|
306
|
+
/** Internal: IDs of the tasks this transaction creates. */
|
|
307
|
+
createdTaskIds() {
|
|
308
|
+
const ids = [];
|
|
309
|
+
for (const [id, task] of this.#tasksById)
|
|
310
|
+
if (task.write?.kind === "create")
|
|
311
|
+
ids.push(id);
|
|
312
|
+
return ids;
|
|
313
|
+
}
|
|
287
314
|
/** Internal: conversations this transaction created or forked so far. */
|
|
288
315
|
stagedConversations() {
|
|
289
316
|
const records = [];
|
|
@@ -414,6 +441,9 @@ export class Transaction {
|
|
|
414
441
|
return entry.change.state;
|
|
415
442
|
}
|
|
416
443
|
async #findRetirement(entry) {
|
|
444
|
+
const gate = this.#host.touch(documentKey(entry.addressId));
|
|
445
|
+
if (gate !== undefined)
|
|
446
|
+
await gate;
|
|
417
447
|
const record = this.#host.cached(entry.addressId)?.record ??
|
|
418
448
|
(await this.#host.storage.findDocument(entry.address, "current", this.#context));
|
|
419
449
|
this.#assertOpen();
|
|
@@ -462,9 +492,126 @@ export class Transaction {
|
|
|
462
492
|
discard() {
|
|
463
493
|
this.#abortChanges();
|
|
464
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Internal: what a deferred commit holds in memory instead of storing (`CommitPersistence`), each changed incarnation
|
|
497
|
+
* with its operations and whether they selected a base; `undefined` when the commit must be stored. It may be held
|
|
498
|
+
* only when every write changes an existing `latest`, `initial`-fork conversation document at its stored version.
|
|
499
|
+
*/
|
|
500
|
+
holdable() {
|
|
501
|
+
if (this.#writes.some((write) => write.type !== "document.change"))
|
|
502
|
+
return undefined;
|
|
503
|
+
const held = [];
|
|
504
|
+
for (const plan of this.#plans) {
|
|
505
|
+
const { content, change } = plan;
|
|
506
|
+
if (content === undefined && !plan.retire)
|
|
507
|
+
continue;
|
|
508
|
+
if (plan.retire || change?.loaded === undefined || content?.type !== "document.change")
|
|
509
|
+
return undefined;
|
|
510
|
+
const { loaded, prepared, version } = change;
|
|
511
|
+
const record = loaded.record;
|
|
512
|
+
if (record.scope.kind !== "conversation" || record.history !== "latest" || record.fork !== "initial") {
|
|
513
|
+
return undefined;
|
|
514
|
+
}
|
|
515
|
+
// A migration stores a base at once.
|
|
516
|
+
if (loaded.storedVersion !== version)
|
|
517
|
+
return undefined;
|
|
518
|
+
held.push({ loaded, ops: prepared.ops, base: content.content.kind === "base" });
|
|
519
|
+
}
|
|
520
|
+
return held;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Internal: adopt a held commit (`holdable()`) in memory only. Trackers advance by pointer swap; stored versions and
|
|
524
|
+
* delta counts stay, as nothing was stored. Returns its publication for the Session's own observers.
|
|
525
|
+
*/
|
|
526
|
+
adoptHeld() {
|
|
527
|
+
const publications = [];
|
|
528
|
+
for (const plan of this.#plans) {
|
|
529
|
+
// Every plan of a held commit changes or reads a loaded incarnation (`holdable()`).
|
|
530
|
+
const { tracker, prepared, version, loaded } = plan.change;
|
|
531
|
+
if (prepared.ops.length === 0) {
|
|
532
|
+
prepared.abort();
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
tracker.adopt(prepared);
|
|
536
|
+
const record = loaded.record;
|
|
537
|
+
const conversationId = plan.conversationId;
|
|
538
|
+
publications.push({
|
|
539
|
+
type: "document",
|
|
540
|
+
kind: "change",
|
|
541
|
+
record,
|
|
542
|
+
conversationId,
|
|
543
|
+
version,
|
|
544
|
+
value: prepared.value,
|
|
545
|
+
ops: prepared.ops,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
return publications;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Internal: store the held changes of every incarnation this commit reads or writes in front of its own, so a stored
|
|
552
|
+
* document never misses an operation its later ones build on. Called right before Storage admission, after every
|
|
553
|
+
* fallible step, because it empties the holds: a failure earlier keeps them. Returns the holds it took.
|
|
554
|
+
*/
|
|
555
|
+
takeHeld(held) {
|
|
556
|
+
const taken = [];
|
|
557
|
+
for (const plan of this.#plans) {
|
|
558
|
+
// A new incarnation holds nothing yet.
|
|
559
|
+
if (!("createdAt" in plan.record))
|
|
560
|
+
continue;
|
|
561
|
+
const hold = held(plan.record.id);
|
|
562
|
+
if (hold === undefined)
|
|
563
|
+
continue;
|
|
564
|
+
taken.push(hold);
|
|
565
|
+
const own = plan.content?.type === "document.change" ? plan.content : undefined;
|
|
566
|
+
const change = plan.change;
|
|
567
|
+
const value = change?.prepared.value ?? hold.loaded.tracker.value;
|
|
568
|
+
const stored = storeHeld(hold, change?.prepared.ops ?? EMPTY_OPERATIONS, value, hold.loaded.valueVersion);
|
|
569
|
+
// A base this commit selected or must write covers what was held.
|
|
570
|
+
const content = own?.content.kind === "base" ? own.content : stored.content;
|
|
571
|
+
const write = { type: "document.change", id: plan.record.id, content };
|
|
572
|
+
if (own !== undefined)
|
|
573
|
+
this.#writes[this.#writes.indexOf(own)] = write;
|
|
574
|
+
else
|
|
575
|
+
this.#insertBeforeRetirement(write);
|
|
576
|
+
if (!plan.retire)
|
|
577
|
+
plan.held = { ops: stored.ops, observed: own !== undefined };
|
|
578
|
+
plan.content = write;
|
|
579
|
+
// Held documents are conversation documents; one this commit only read has no owner resolved yet.
|
|
580
|
+
if (plan.record.scope.kind === "conversation")
|
|
581
|
+
plan.conversationId = plan.record.scope.conversationId;
|
|
582
|
+
}
|
|
583
|
+
return taken;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Internal: the keys this commit writes, once settled (and after `takeHeld()`), for the Session's unpublished write
|
|
587
|
+
* set: its table records, and the address and incarnation of every document it creates, changes, or retires, with
|
|
588
|
+
* the scope of every incarnation it creates or retires.
|
|
589
|
+
*/
|
|
590
|
+
writeKeys() {
|
|
591
|
+
const keys = [];
|
|
592
|
+
tableKeys(this.#writes, keys);
|
|
593
|
+
for (const plan of this.#plans) {
|
|
594
|
+
if (plan.content === undefined && !plan.retire)
|
|
595
|
+
continue;
|
|
596
|
+
keys.push(documentKey(plan.addressId), incarnationKey(plan.record.id));
|
|
597
|
+
if (plan.retire || !("createdAt" in plan.record))
|
|
598
|
+
keys.push(scopeKey(plan.record.scope));
|
|
599
|
+
}
|
|
600
|
+
return keys;
|
|
601
|
+
}
|
|
602
|
+
/** Stage a content write for an incarnation, before its retirement when it retires. */
|
|
603
|
+
#insertBeforeRetirement(write) {
|
|
604
|
+
const at = this.#writes.findIndex((candidate) => candidate.type === "document.retire" && candidate.id === write.id);
|
|
605
|
+
if (at < 0)
|
|
606
|
+
this.#writes.push(write);
|
|
607
|
+
else
|
|
608
|
+
this.#writes.splice(at, 0, write);
|
|
609
|
+
}
|
|
465
610
|
/** Adopt every prepared change by pointer swap after Storage success and describe the publication. */
|
|
466
611
|
adopt(seq) {
|
|
467
612
|
const publications = [];
|
|
613
|
+
// Only when the commit stored held changes: then what Storage got differs from what memory adopted.
|
|
614
|
+
let stored;
|
|
468
615
|
for (const plan of this.#plans) {
|
|
469
616
|
const committed = "createdAt" in plan.record;
|
|
470
617
|
let record = committed ? plan.record : { ...plan.record, createdAt: seq };
|
|
@@ -499,34 +646,26 @@ export class Transaction {
|
|
|
499
646
|
});
|
|
500
647
|
}
|
|
501
648
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
record,
|
|
513
|
-
conversationId: conversationId,
|
|
514
|
-
source: plan.content.source,
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
else if (change !== undefined && publishes(plan)) {
|
|
518
|
-
const ops = change.loaded === undefined ? EMPTY_OPERATIONS : change.prepared.ops;
|
|
519
|
-
publications.push({
|
|
520
|
-
type: "document",
|
|
521
|
-
record,
|
|
522
|
-
conversationId,
|
|
523
|
-
version: change.version,
|
|
524
|
-
value: change.prepared.value,
|
|
525
|
-
ops,
|
|
526
|
-
});
|
|
649
|
+
if (plan.retire && committed)
|
|
650
|
+
this.#host.evict(plan.addressId, record.id);
|
|
651
|
+
const publication = describe(plan, record);
|
|
652
|
+
if (publication === undefined)
|
|
653
|
+
continue;
|
|
654
|
+
const held = plan.held;
|
|
655
|
+
if (held === undefined) {
|
|
656
|
+
publications.push(publication);
|
|
657
|
+
stored?.push(publication);
|
|
658
|
+
continue;
|
|
527
659
|
}
|
|
660
|
+
stored ??= [...publications];
|
|
661
|
+
if (held.observed)
|
|
662
|
+
publications.push(publication);
|
|
663
|
+
// Only a document change of a loaded incarnation carries held operations; a retirement is published alone.
|
|
664
|
+
stored.push(publication.type === "document" && publication.kind === "change"
|
|
665
|
+
? { ...publication, ops: held.ops }
|
|
666
|
+
: publication);
|
|
528
667
|
}
|
|
529
|
-
return publications;
|
|
668
|
+
return { observed: publications, stored: stored ?? publications };
|
|
530
669
|
}
|
|
531
670
|
async #assemble() {
|
|
532
671
|
const storage = this.#host.storage;
|
|
@@ -565,18 +704,30 @@ export class Transaction {
|
|
|
565
704
|
plan.retire = true;
|
|
566
705
|
retiring.add(plan.record.id);
|
|
567
706
|
}
|
|
707
|
+
const retire = (record) => {
|
|
708
|
+
if (retiring.has(record.id))
|
|
709
|
+
return;
|
|
710
|
+
plans.push({ addressId: addressId(record), record, retire: true });
|
|
711
|
+
retiring.add(record.id);
|
|
712
|
+
};
|
|
568
713
|
for (const taskId of terminalTaskIds) {
|
|
569
714
|
if (this.#tasksById.get(taskId)?.write?.kind === "create")
|
|
570
715
|
continue;
|
|
716
|
+
// The Session knows the documents of the tasks it created; any other task's are scanned.
|
|
717
|
+
const gate = this.#host.touch(scopeKey({ kind: "task", taskId }));
|
|
718
|
+
if (gate !== undefined)
|
|
719
|
+
await gate;
|
|
720
|
+
const known = this.#host.taskDocuments(taskId);
|
|
721
|
+
if (known !== undefined) {
|
|
722
|
+
for (const record of known)
|
|
723
|
+
retire(record);
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
571
726
|
let cursor;
|
|
572
727
|
do {
|
|
573
728
|
const page = await storage.scanDocuments({ scope: { kind: "task", taskId }, at: "current" }, INTERNAL_SCAN_PAGE_SIZE, cursor, this.#context);
|
|
574
|
-
for (const record of page.items)
|
|
575
|
-
|
|
576
|
-
continue;
|
|
577
|
-
plans.push({ addressId: addressId(record), record, retire: true });
|
|
578
|
-
retiring.add(record.id);
|
|
579
|
-
}
|
|
729
|
+
for (const record of page.items)
|
|
730
|
+
retire(record);
|
|
580
731
|
cursor = page.next;
|
|
581
732
|
} while (cursor !== undefined);
|
|
582
733
|
}
|
|
@@ -702,7 +853,7 @@ export class Transaction {
|
|
|
702
853
|
try {
|
|
703
854
|
this.#assertOpen();
|
|
704
855
|
if (this.#hasTableWrite)
|
|
705
|
-
throw new ReadAfterWrite(method);
|
|
856
|
+
throw new ReadAfterWrite(`Tx.${method}()`);
|
|
706
857
|
return this.#track(read());
|
|
707
858
|
}
|
|
708
859
|
catch (error) {
|
|
@@ -810,6 +961,42 @@ function planDocument(document) {
|
|
|
810
961
|
}
|
|
811
962
|
}
|
|
812
963
|
}
|
|
964
|
+
/** The publication of one adopted plan, if it publishes (`publishes()`), with `record` as committed. */
|
|
965
|
+
function describe(plan, record) {
|
|
966
|
+
const conversationId = plan.conversationId;
|
|
967
|
+
if (plan.retire)
|
|
968
|
+
return { type: "document", kind: "retire", record, conversationId };
|
|
969
|
+
if (plan.content?.type === "document.copy") {
|
|
970
|
+
return { type: "document.copy", record, conversationId: conversationId, source: plan.content.source };
|
|
971
|
+
}
|
|
972
|
+
const change = plan.change;
|
|
973
|
+
if (change === undefined || !publishes(plan))
|
|
974
|
+
return undefined;
|
|
975
|
+
const ops = change.loaded === undefined ? EMPTY_OPERATIONS : change.prepared.ops;
|
|
976
|
+
return {
|
|
977
|
+
type: "document",
|
|
978
|
+
kind: "change",
|
|
979
|
+
record,
|
|
980
|
+
conversationId,
|
|
981
|
+
version: change.version,
|
|
982
|
+
value: change.prepared.value,
|
|
983
|
+
ops,
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Take a hold's changes after `ops`, a commit's own on top of them, as one stored content for an incarnation now at
|
|
988
|
+
* `value`, and the operations a reader of the stored state applies to reach it: the folded ones, or a root replacement
|
|
989
|
+
* after the queue overflowed, which stores a base, as does a hold whose commits selected one.
|
|
990
|
+
*/
|
|
991
|
+
export function storeHeld(hold, ops, value, version) {
|
|
992
|
+
hold.pending.push(ops);
|
|
993
|
+
const taken = hold.pending.take();
|
|
994
|
+
if (taken.kind === "replace")
|
|
995
|
+
return { content: { version, kind: "base", value }, ops: [["r", value]] };
|
|
996
|
+
if (hold.base)
|
|
997
|
+
return { content: { version, kind: "base", value }, ops: taken.ops };
|
|
998
|
+
return { content: { version, kind: "delta", ops: taken.ops }, ops: taken.ops };
|
|
999
|
+
}
|
|
813
1000
|
/**
|
|
814
1001
|
* Whether adoption publishes the plan: every creation, copy, and retirement, and a loaded incarnation that writes
|
|
815
1002
|
* content, which includes a migration-only base so observers of the older shape receive the new value.
|