zerodrift 1.0.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/LICENSE +21 -0
- package/README.md +358 -0
- package/dist/core/BaseModel.d.ts +76 -0
- package/dist/core/BaseModel.js +505 -0
- package/dist/core/BaseSSEConnection.d.ts +31 -0
- package/dist/core/BaseSSEConnection.js +91 -0
- package/dist/core/BatchModelLoader.d.ts +27 -0
- package/dist/core/BatchModelLoader.js +70 -0
- package/dist/core/CompoundIndexFetcher.d.ts +46 -0
- package/dist/core/CompoundIndexFetcher.js +177 -0
- package/dist/core/Database.d.ts +303 -0
- package/dist/core/Database.js +837 -0
- package/dist/core/LazyCollection.d.ts +168 -0
- package/dist/core/LazyCollection.js +403 -0
- package/dist/core/LazyOwnedCollection.d.ts +35 -0
- package/dist/core/LazyOwnedCollection.js +66 -0
- package/dist/core/MemoryAdapter.d.ts +67 -0
- package/dist/core/MemoryAdapter.js +243 -0
- package/dist/core/ModelRegistry.d.ts +64 -0
- package/dist/core/ModelRegistry.js +217 -0
- package/dist/core/ModelStream.d.ts +33 -0
- package/dist/core/ModelStream.js +68 -0
- package/dist/core/ObjectPool.d.ts +113 -0
- package/dist/core/ObjectPool.js +339 -0
- package/dist/core/Store.d.ts +40 -0
- package/dist/core/Store.js +73 -0
- package/dist/core/StoreManager.d.ts +839 -0
- package/dist/core/StoreManager.js +2034 -0
- package/dist/core/SyncConnection.d.ts +105 -0
- package/dist/core/SyncConnection.js +348 -0
- package/dist/core/Transaction.d.ts +114 -0
- package/dist/core/Transaction.js +147 -0
- package/dist/core/TransactionQueue.d.ts +110 -0
- package/dist/core/TransactionQueue.js +601 -0
- package/dist/core/decorators.d.ts +66 -0
- package/dist/core/decorators.js +278 -0
- package/dist/core/hash.d.ts +6 -0
- package/dist/core/hash.js +12 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/index.js +18 -0
- package/dist/core/internal.d.ts +27 -0
- package/dist/core/internal.js +25 -0
- package/dist/core/observability.d.ts +21 -0
- package/dist/core/observability.js +66 -0
- package/dist/core/refAccessors.d.ts +43 -0
- package/dist/core/refAccessors.js +80 -0
- package/dist/core/serializers.d.ts +2 -0
- package/dist/core/serializers.js +2 -0
- package/dist/core/types.d.ts +320 -0
- package/dist/core/types.js +84 -0
- package/dist/react/index.d.ts +82 -0
- package/dist/react/index.js +373 -0
- package/dist/schema/builders.d.ts +29 -0
- package/dist/schema/builders.js +81 -0
- package/dist/schema/compile.d.ts +28 -0
- package/dist/schema/compile.js +334 -0
- package/dist/schema/createStore.d.ts +235 -0
- package/dist/schema/createStore.js +264 -0
- package/dist/schema/extend.d.ts +46 -0
- package/dist/schema/extend.js +6 -0
- package/dist/schema/index.d.ts +13 -0
- package/dist/schema/index.js +8 -0
- package/dist/schema/infer.d.ts +102 -0
- package/dist/schema/infer.js +1 -0
- package/dist/schema/types.d.ts +76 -0
- package/dist/schema/types.js +1 -0
- package/dist/schema/zod.d.ts +90 -0
- package/dist/schema/zod.js +101 -0
- package/package.json +99 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transaction types with batchId for multi-model grouped undo and rebase support.
|
|
3
|
+
*
|
|
4
|
+
* batchId groups transactions from one user action (e.g. moveIssueToTeam
|
|
5
|
+
* updates Issue.teamId AND Team.issueCount). undo() reverts the entire batch.
|
|
6
|
+
*
|
|
7
|
+
* Rebasing (UpdateTransaction): when a delta packet conflicts with our local
|
|
8
|
+
* change, the server value becomes our new baseline and our value is re-applied.
|
|
9
|
+
*/
|
|
10
|
+
import { TransactionState } from "./types";
|
|
11
|
+
export class BaseTransaction {
|
|
12
|
+
constructor(modelId, modelName) {
|
|
13
|
+
this.id = crypto.randomUUID();
|
|
14
|
+
this.timestamp = Date.now();
|
|
15
|
+
this.state = TransactionState.Pending;
|
|
16
|
+
this.batchId = null;
|
|
17
|
+
this.syncIdNeededForCompletion = null;
|
|
18
|
+
this.idbKey = null;
|
|
19
|
+
this.modelId = modelId;
|
|
20
|
+
this.modelName = modelName;
|
|
21
|
+
}
|
|
22
|
+
markCompleted(syncId) {
|
|
23
|
+
this.syncIdNeededForCompletion = syncId;
|
|
24
|
+
this.state = TransactionState.CompletedButUnsynced;
|
|
25
|
+
}
|
|
26
|
+
isSyncedBy(syncId) {
|
|
27
|
+
return (this.syncIdNeededForCompletion !== null &&
|
|
28
|
+
syncId >= this.syncIdNeededForCompletion);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class UpdateTransaction extends BaseTransaction {
|
|
32
|
+
constructor(modelId, modelName, changes) {
|
|
33
|
+
super(modelId, modelName);
|
|
34
|
+
this.action = "U";
|
|
35
|
+
this.changes = new Map(Object.entries(changes));
|
|
36
|
+
}
|
|
37
|
+
revert(model) {
|
|
38
|
+
for (const [prop, { oldValue }] of this.changes) {
|
|
39
|
+
model.setQuiet(prop, oldValue);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Last-writer-wins rebase: update baseline to server value, re-apply ours.
|
|
44
|
+
* Only rebases fields where the server value differs from our intended newValue —
|
|
45
|
+
* an echo of our own change (serverValue === newValue) must not overwrite oldValue,
|
|
46
|
+
* as that would corrupt the undo baseline.
|
|
47
|
+
*/
|
|
48
|
+
rebase(model, serverData) {
|
|
49
|
+
for (const [prop, serverValue] of Object.entries(serverData)) {
|
|
50
|
+
const change = this.changes.get(prop);
|
|
51
|
+
if (change != null && serverValue !== change.newValue) {
|
|
52
|
+
change.oldValue = serverValue;
|
|
53
|
+
// Dynamic property assignment on BaseModel — no better type for runtime field access
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
model[prop] = change.newValue;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Returns true only when the server data has a field we're changing AND the
|
|
60
|
+
* server's value differs from our intended newValue (i.e. a real conflict,
|
|
61
|
+
* not just an echo of our own change). */
|
|
62
|
+
conflictsWith(data) {
|
|
63
|
+
for (const [k, serverValue] of Object.entries(data)) {
|
|
64
|
+
const change = this.changes.get(k);
|
|
65
|
+
if (change != null && serverValue !== change.newValue) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
serialize() {
|
|
72
|
+
return {
|
|
73
|
+
id: this.id,
|
|
74
|
+
action: this.action,
|
|
75
|
+
batchId: this.batchId,
|
|
76
|
+
modelId: this.modelId,
|
|
77
|
+
modelName: this.modelName,
|
|
78
|
+
timestamp: this.timestamp,
|
|
79
|
+
changes: Object.fromEntries([...this.changes.entries()]),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export class CreateTransaction extends BaseTransaction {
|
|
84
|
+
constructor(modelId, modelName, data) {
|
|
85
|
+
super(modelId, modelName);
|
|
86
|
+
this.action = "I";
|
|
87
|
+
this.data = data;
|
|
88
|
+
}
|
|
89
|
+
revert() {
|
|
90
|
+
/* pool removal handled by queue */
|
|
91
|
+
}
|
|
92
|
+
serialize() {
|
|
93
|
+
return {
|
|
94
|
+
id: this.id,
|
|
95
|
+
action: this.action,
|
|
96
|
+
batchId: this.batchId,
|
|
97
|
+
modelId: this.modelId,
|
|
98
|
+
modelName: this.modelName,
|
|
99
|
+
timestamp: this.timestamp,
|
|
100
|
+
data: this.data,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export class DeleteTransaction extends BaseTransaction {
|
|
105
|
+
constructor(modelId, modelName, snapshot) {
|
|
106
|
+
super(modelId, modelName);
|
|
107
|
+
this.action = "D";
|
|
108
|
+
this.snapshot = snapshot;
|
|
109
|
+
}
|
|
110
|
+
revert(model) {
|
|
111
|
+
model.hydrate(this.snapshot);
|
|
112
|
+
model.makeModelObservable();
|
|
113
|
+
}
|
|
114
|
+
serialize() {
|
|
115
|
+
return {
|
|
116
|
+
id: this.id,
|
|
117
|
+
action: this.action,
|
|
118
|
+
batchId: this.batchId,
|
|
119
|
+
modelId: this.modelId,
|
|
120
|
+
modelName: this.modelName,
|
|
121
|
+
timestamp: this.timestamp,
|
|
122
|
+
snapshot: this.snapshot,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export class ArchiveTransaction extends BaseTransaction {
|
|
127
|
+
constructor(modelId, modelName, snapshot) {
|
|
128
|
+
super(modelId, modelName);
|
|
129
|
+
this.action = "A";
|
|
130
|
+
this.snapshot = snapshot;
|
|
131
|
+
}
|
|
132
|
+
revert(model) {
|
|
133
|
+
model.hydrate(this.snapshot);
|
|
134
|
+
model.makeModelObservable();
|
|
135
|
+
}
|
|
136
|
+
serialize() {
|
|
137
|
+
return {
|
|
138
|
+
id: this.id,
|
|
139
|
+
action: this.action,
|
|
140
|
+
batchId: this.batchId,
|
|
141
|
+
modelId: this.modelId,
|
|
142
|
+
modelName: this.modelName,
|
|
143
|
+
timestamp: this.timestamp,
|
|
144
|
+
snapshot: this.snapshot,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TransactionQueue — manages transaction lifecycle and batch undo.
|
|
3
|
+
*
|
|
4
|
+
* Three queues:
|
|
5
|
+
* pending → created, not yet sent
|
|
6
|
+
* executing → sent to server, awaiting response
|
|
7
|
+
* awaitingSync → server ACK'd, waiting for delta packet with syncId
|
|
8
|
+
*
|
|
9
|
+
* Batch undo:
|
|
10
|
+
* beginBatch() opens a batch. All save() calls inside share a batchId.
|
|
11
|
+
* endBatch() closes it. undo() pops the entire batch and reverts all.
|
|
12
|
+
*
|
|
13
|
+
* The undo stack stores "entries" — either a single tx or a batch of txs.
|
|
14
|
+
*/
|
|
15
|
+
import type { StorageAdapter } from "./Database";
|
|
16
|
+
import { ObjectPool } from "./ObjectPool";
|
|
17
|
+
import { type EngineErrorContext } from "./types";
|
|
18
|
+
import { BaseTransaction, UpdateTransaction, type UndoableAction } from "./Transaction";
|
|
19
|
+
import { type PropertyChange } from "./types";
|
|
20
|
+
import type { BaseModel } from "./BaseModel";
|
|
21
|
+
export interface BatchResponse {
|
|
22
|
+
success: boolean;
|
|
23
|
+
lastSyncId: number;
|
|
24
|
+
}
|
|
25
|
+
export type TransactionSender = (batch: ReturnType<BaseTransaction["serialize"]>[]) => Promise<BatchResponse>;
|
|
26
|
+
export type UndoPhase = "undo" | "redo";
|
|
27
|
+
export interface UndoResult {
|
|
28
|
+
txs: BaseTransaction[];
|
|
29
|
+
actions: UndoableAction[];
|
|
30
|
+
}
|
|
31
|
+
/** Handlers the consumer supplies via `StoreManagerConfig.undoableActions`.
|
|
32
|
+
* Each handler talks to the consumer's backend change-log API and returns the
|
|
33
|
+
* compensating action so the engine can place it on the opposite stack.
|
|
34
|
+
* Returning `void` reuses the original entry — fine when the same
|
|
35
|
+
* `changeLogId` is replayable in either direction. */
|
|
36
|
+
export interface UndoableActionHandlers {
|
|
37
|
+
undo: (action: UndoableAction) => Promise<UndoableAction | void>;
|
|
38
|
+
redo?: (action: UndoableAction) => Promise<UndoableAction | void>;
|
|
39
|
+
}
|
|
40
|
+
type Listener = () => void;
|
|
41
|
+
export declare class TransactionQueue {
|
|
42
|
+
private database;
|
|
43
|
+
private pool;
|
|
44
|
+
private sender;
|
|
45
|
+
private pending;
|
|
46
|
+
private executing;
|
|
47
|
+
private awaitingSync;
|
|
48
|
+
private undoStack;
|
|
49
|
+
private redoStack;
|
|
50
|
+
private activeBatchId;
|
|
51
|
+
private activeBatchEntries;
|
|
52
|
+
private actionHandlers;
|
|
53
|
+
private suppressUndoStack;
|
|
54
|
+
private flushTimer;
|
|
55
|
+
private flushDelay;
|
|
56
|
+
private undoLimit;
|
|
57
|
+
private listeners;
|
|
58
|
+
private reportError;
|
|
59
|
+
constructor(database: StorageAdapter, pool: ObjectPool, undoLimit?: number);
|
|
60
|
+
setErrorReporter(reporter: (err: Error, context: EngineErrorContext) => void): void;
|
|
61
|
+
setSender(sender: TransactionSender): void;
|
|
62
|
+
setActionHandlers(handlers: UndoableActionHandlers): void;
|
|
63
|
+
subscribe(listener: Listener): () => void;
|
|
64
|
+
private notify;
|
|
65
|
+
beginBatch(): string;
|
|
66
|
+
endBatch(batchId: string): void;
|
|
67
|
+
get hasActiveBatch(): boolean;
|
|
68
|
+
enqueueUpdate(modelId: string, modelName: string, changes: Record<string, PropertyChange>): Promise<UpdateTransaction>;
|
|
69
|
+
enqueueCreate(modelId: string, modelName: string, data: Record<string, unknown>): Promise<void>;
|
|
70
|
+
enqueueDelete(model: BaseModel): Promise<void>;
|
|
71
|
+
/** Record an already-committed remote side-effect on the undo stack. No
|
|
72
|
+
* pending/executing/awaitingSync involvement and no IDB caching — the
|
|
73
|
+
* consumer's API call already happened, so there's nothing to resend. */
|
|
74
|
+
enqueueAction(action: UndoableAction): void;
|
|
75
|
+
enqueueArchive(model: BaseModel): Promise<void>;
|
|
76
|
+
private enqueue;
|
|
77
|
+
private scheduleFlush;
|
|
78
|
+
private flush;
|
|
79
|
+
resolveBySync(receivedSyncId: number): BaseTransaction[];
|
|
80
|
+
rebaseAll(modelId: string, modelName: string, serverData: Record<string, unknown>): void;
|
|
81
|
+
private revertOne;
|
|
82
|
+
private itemsOf;
|
|
83
|
+
/** Build the inverse-stack entry from a list of replaced items. Single-item
|
|
84
|
+
* entries collapse to `single`, otherwise reuse the original `batchId`. */
|
|
85
|
+
private wrapEntry;
|
|
86
|
+
/** Run the consumer's action handler, surfacing failures through
|
|
87
|
+
* `reportError`. Returns the compensating action for the opposite stack —
|
|
88
|
+
* the handler's return value, or the original action on void/error. */
|
|
89
|
+
private invokeActionHandler;
|
|
90
|
+
/** Reverse a single transaction and enqueue the inverse server call. */
|
|
91
|
+
private revertTx;
|
|
92
|
+
/** Re-apply a single transaction and enqueue the forward server call. */
|
|
93
|
+
private replayTx;
|
|
94
|
+
/** Walk an entry's items in `direction`, applying tx reverts/replays and
|
|
95
|
+
* delegating actions to the consumer's handler. Returns the compensating
|
|
96
|
+
* items for the opposite stack, in original insertion order. */
|
|
97
|
+
private processEntry;
|
|
98
|
+
private partition;
|
|
99
|
+
undo(): Promise<UndoResult | null>;
|
|
100
|
+
redo(): Promise<UndoResult | null>;
|
|
101
|
+
resendCached(): Promise<number>;
|
|
102
|
+
private rebuildTransaction;
|
|
103
|
+
destroy(): void;
|
|
104
|
+
get pendingCount(): number;
|
|
105
|
+
get executingCount(): number;
|
|
106
|
+
get awaitingSyncCount(): number;
|
|
107
|
+
get undoDepth(): number;
|
|
108
|
+
get redoDepth(): number;
|
|
109
|
+
}
|
|
110
|
+
export {};
|