@aelionsdk/transaction 0.1.0-beta.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.
@@ -0,0 +1,45 @@
1
+ import type { AelionProject } from '@aelionsdk/project-schema';
2
+ import { TransactionEngine } from './transaction.js';
3
+ import type { EditOptions, ProjectChangeListener, TransactionCommit } from './types.js';
4
+ export interface TransactionHost {
5
+ readonly revision: bigint;
6
+ getSnapshot(): Readonly<AelionProject>;
7
+ edit(options: EditOptions, callback: Parameters<TransactionEngine['edit']>[1]): TransactionCommit;
8
+ subscribe(listener: ProjectChangeListener): () => void;
9
+ }
10
+ export interface TransactionHistoryOptions {
11
+ /** Maximum number of semantic edits retained in each history direction. */
12
+ readonly maxEntries?: number;
13
+ }
14
+ export interface HistoryState {
15
+ readonly canUndo: boolean;
16
+ readonly canRedo: boolean;
17
+ readonly undoDepth: number;
18
+ readonly redoDepth: number;
19
+ readonly undoLabel?: string;
20
+ readonly redoLabel?: string;
21
+ }
22
+ /**
23
+ * Bounded undo/redo history for local semantic transactions.
24
+ *
25
+ * The manager intentionally detects edits made directly through the wrapped
26
+ * engine. Applying an inverse across an unknown revision would be unsafe; a
27
+ * collaboration adapter must instead create a fresh compensating command.
28
+ */
29
+ export declare class TransactionHistory implements TransactionHost {
30
+ #private;
31
+ constructor(engine: TransactionEngine, options?: TransactionHistoryOptions);
32
+ get revision(): bigint;
33
+ get state(): HistoryState;
34
+ getSnapshot(): Readonly<AelionProject>;
35
+ subscribe(listener: ProjectChangeListener): () => void;
36
+ edit(options: EditOptions, callback: Parameters<TransactionEngine['edit']>[1]): TransactionCommit;
37
+ /** Ends a coalescing group so a later edit with the same key starts a new undo entry. */
38
+ finishGroup(group: string): void;
39
+ /** Reverts the active group without creating redo history. */
40
+ cancelGroup(group: string): TransactionCommit | null;
41
+ undo(): TransactionCommit;
42
+ redo(): TransactionCommit;
43
+ clear(): void;
44
+ }
45
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAEV,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,WAAW,IAAI,QAAQ,CAAC,aAAa,CAAC,CAAC;IACvC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC;IAClG,SAAS,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAAC;CACxD;AAED,MAAM,WAAW,yBAAyB;IACxC,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAuCD;;;;;;GAMG;AACH,qBAAa,kBAAmB,YAAW,eAAe;;gBASrC,MAAM,EAAE,iBAAiB,EAAE,OAAO,GAAE,yBAA8B;IAUrF,IAAW,QAAQ,IAAI,MAAM,CAE5B;IAED,IAAW,KAAK,IAAI,YAAY,CAW/B;IAEM,WAAW,IAAI,QAAQ,CAAC,aAAa,CAAC;IAItC,SAAS,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI;IAKtD,IAAI,CACT,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,UAAU,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GACjD,iBAAiB;IAoCpB,yFAAyF;IAClF,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAYvC,8DAA8D;IACvD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAqBpD,IAAI,IAAI,iBAAiB;IAoBzB,IAAI,IAAI,iBAAiB;IAoBzB,KAAK,IAAI,IAAI;CA0CrB"}
@@ -0,0 +1,210 @@
1
+ import { AelionError } from '@aelionsdk/core';
2
+ import { TransactionEngine } from './transaction.js';
3
+ function historyError(code, message) {
4
+ return new AelionError([{ code, severity: 'error', message, recoverable: true }]);
5
+ }
6
+ function cloneOperations(operations) {
7
+ return operations.map(operation => structuredClone(operation));
8
+ }
9
+ function sameField(left, right) {
10
+ return (left.op === 'setField' &&
11
+ right.op === 'setField' &&
12
+ left.collection === right.collection &&
13
+ left.id === right.id &&
14
+ left.path.length === right.path.length &&
15
+ left.path.every((segment, index) => segment === right.path[index]));
16
+ }
17
+ function canReplaceGroupedFields(previous, next) {
18
+ return (previous.operations.length === next.operations.length &&
19
+ previous.inverse.length === next.inverse.length &&
20
+ previous.operations.every((operation, index) => {
21
+ const nextOperation = next.operations[index];
22
+ return nextOperation !== undefined && sameField(operation, nextOperation);
23
+ }));
24
+ }
25
+ /**
26
+ * Bounded undo/redo history for local semantic transactions.
27
+ *
28
+ * The manager intentionally detects edits made directly through the wrapped
29
+ * engine. Applying an inverse across an unknown revision would be unsafe; a
30
+ * collaboration adapter must instead create a fresh compensating command.
31
+ */
32
+ export class TransactionHistory {
33
+ #engine;
34
+ #maxEntries;
35
+ #undo = [];
36
+ #redo = [];
37
+ #listeners = new Set();
38
+ #expectedRevision;
39
+ #publishing = false;
40
+ constructor(engine, options = {}) {
41
+ const maxEntries = options.maxEntries ?? 100;
42
+ if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
43
+ throw new RangeError('maxEntries must be a positive safe integer');
44
+ }
45
+ this.#engine = engine;
46
+ this.#maxEntries = maxEntries;
47
+ this.#expectedRevision = engine.revision;
48
+ }
49
+ get revision() {
50
+ return this.#engine.revision;
51
+ }
52
+ get state() {
53
+ const undoLabel = this.#undo.at(-1)?.label;
54
+ const redoLabel = this.#redo.at(-1)?.label;
55
+ return {
56
+ canUndo: this.#undo.length > 0,
57
+ canRedo: this.#redo.length > 0,
58
+ undoDepth: this.#undo.length,
59
+ redoDepth: this.#redo.length,
60
+ ...(undoLabel === undefined ? {} : { undoLabel }),
61
+ ...(redoLabel === undefined ? {} : { redoLabel }),
62
+ };
63
+ }
64
+ getSnapshot() {
65
+ return this.#engine.getSnapshot();
66
+ }
67
+ subscribe(listener) {
68
+ this.#listeners.add(listener);
69
+ return () => this.#listeners.delete(listener);
70
+ }
71
+ edit(options, callback) {
72
+ this.#assertNotPublishing();
73
+ this.#assertSynchronized();
74
+ if (options.historyGroup !== undefined && options.historyGroup.length === 0) {
75
+ throw new TypeError('historyGroup must be a non-empty string');
76
+ }
77
+ const commit = this.#engine.edit(options, callback);
78
+ const entry = {
79
+ ...(commit.changeSet.label === undefined ? {} : { label: commit.changeSet.label }),
80
+ ...(options.historyGroup === undefined ? {} : { group: options.historyGroup }),
81
+ operations: cloneOperations(commit.changeSet.operations),
82
+ inverse: cloneOperations(commit.inverse),
83
+ };
84
+ const previous = this.#undo.at(-1);
85
+ if (options.historyGroup !== undefined && previous?.group === options.historyGroup) {
86
+ const replaceFields = canReplaceGroupedFields(previous, entry);
87
+ this.#undo[this.#undo.length - 1] = {
88
+ ...((entry.label ?? previous.label) === undefined
89
+ ? {}
90
+ : { label: entry.label ?? previous.label }),
91
+ group: options.historyGroup,
92
+ operations: replaceFields
93
+ ? entry.operations
94
+ : [...previous.operations, ...entry.operations],
95
+ inverse: replaceFields ? previous.inverse : [...entry.inverse, ...previous.inverse],
96
+ };
97
+ }
98
+ else {
99
+ this.#undo.push(entry);
100
+ }
101
+ this.#trim(this.#undo);
102
+ this.#redo.length = 0;
103
+ this.#expectedRevision = commit.revision;
104
+ this.#notify(commit);
105
+ return commit;
106
+ }
107
+ /** Ends a coalescing group so a later edit with the same key starts a new undo entry. */
108
+ finishGroup(group) {
109
+ this.#assertNotPublishing();
110
+ this.#assertSynchronized();
111
+ const entry = this.#undo.at(-1);
112
+ if (entry?.group !== group)
113
+ return;
114
+ this.#undo[this.#undo.length - 1] = {
115
+ ...(entry.label === undefined ? {} : { label: entry.label }),
116
+ operations: entry.operations,
117
+ inverse: entry.inverse,
118
+ };
119
+ }
120
+ /** Reverts the active group without creating redo history. */
121
+ cancelGroup(group) {
122
+ this.#assertNotPublishing();
123
+ this.#assertSynchronized();
124
+ const entry = this.#undo.at(-1);
125
+ if (entry === undefined)
126
+ return null;
127
+ if (entry.group !== group) {
128
+ throw historyError('HISTORY_GROUP_NOT_ACTIVE', `History group ${group} is not active`);
129
+ }
130
+ const commit = this.#engine.edit({
131
+ baseRevision: this.#engine.revision,
132
+ label: entry.label === undefined ? 'Cancel interactive edit' : `Cancel: ${entry.label}`,
133
+ }, transaction => transaction.appendOperations(entry.inverse));
134
+ this.#undo.pop();
135
+ this.#expectedRevision = commit.revision;
136
+ this.#notify(commit);
137
+ return commit;
138
+ }
139
+ undo() {
140
+ this.#assertNotPublishing();
141
+ this.#assertSynchronized();
142
+ const entry = this.#undo.at(-1);
143
+ if (entry === undefined)
144
+ throw historyError('HISTORY_UNDO_EMPTY', 'There is no edit to undo');
145
+ const commit = this.#engine.edit({
146
+ baseRevision: this.#engine.revision,
147
+ label: entry.label === undefined ? 'Undo' : `Undo: ${entry.label}`,
148
+ }, transaction => transaction.appendOperations(entry.inverse));
149
+ this.#undo.pop();
150
+ this.#redo.push(entry);
151
+ this.#trim(this.#redo);
152
+ this.#expectedRevision = commit.revision;
153
+ this.#notify(commit);
154
+ return commit;
155
+ }
156
+ redo() {
157
+ this.#assertNotPublishing();
158
+ this.#assertSynchronized();
159
+ const entry = this.#redo.at(-1);
160
+ if (entry === undefined)
161
+ throw historyError('HISTORY_REDO_EMPTY', 'There is no edit to redo');
162
+ const commit = this.#engine.edit({
163
+ baseRevision: this.#engine.revision,
164
+ label: entry.label === undefined ? 'Redo' : `Redo: ${entry.label}`,
165
+ }, transaction => transaction.appendOperations(entry.operations));
166
+ this.#redo.pop();
167
+ this.#undo.push(entry);
168
+ this.#trim(this.#undo);
169
+ this.#expectedRevision = commit.revision;
170
+ this.#notify(commit);
171
+ return commit;
172
+ }
173
+ clear() {
174
+ this.#assertNotPublishing();
175
+ this.#undo.length = 0;
176
+ this.#redo.length = 0;
177
+ this.#expectedRevision = this.#engine.revision;
178
+ }
179
+ #assertSynchronized() {
180
+ if (this.#engine.revision === this.#expectedRevision)
181
+ return;
182
+ throw historyError('HISTORY_REVISION_DIVERGED', `History expected revision ${this.#expectedRevision}, current revision is ${this.#engine.revision}`);
183
+ }
184
+ #trim(entries) {
185
+ const overflow = entries.length - this.#maxEntries;
186
+ if (overflow > 0)
187
+ entries.splice(0, overflow);
188
+ }
189
+ #assertNotPublishing() {
190
+ if (!this.#publishing)
191
+ return;
192
+ throw historyError('HISTORY_REENTRANT', 'History cannot be mutated while a committed change is being published');
193
+ }
194
+ #notify(commit) {
195
+ this.#publishing = true;
196
+ try {
197
+ for (const listener of [...this.#listeners]) {
198
+ try {
199
+ listener(commit);
200
+ }
201
+ catch {
202
+ // History is already synchronized; observers cannot make it diverge.
203
+ }
204
+ }
205
+ }
206
+ finally {
207
+ this.#publishing = false;
208
+ }
209
+ }
210
+ }
@@ -0,0 +1,7 @@
1
+ export * from './affected-ranges.js';
2
+ export * from './commands.js';
3
+ export * from './history.js';
4
+ export * from './operations.js';
5
+ export * from './transaction.js';
6
+ export * from './types.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './affected-ranges.js';
2
+ export * from './commands.js';
3
+ export * from './history.js';
4
+ export * from './operations.js';
5
+ export * from './transaction.js';
6
+ export * from './types.js';
@@ -0,0 +1,5 @@
1
+ import type { AelionProject } from '@aelionsdk/project-schema';
2
+ import type { AtomicOperation } from './types.js';
3
+ export declare function applyOperation(project: AelionProject, operation: AtomicOperation): AtomicOperation;
4
+ export declare function applyOperations(project: AelionProject, operations: readonly AtomicOperation[]): readonly AtomicOperation[];
5
+ //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,2BAA2B,CAAC;AAE9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA+GlD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,eAAe,GACzB,eAAe,CAyJjB;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,SAAS,eAAe,EAAE,GACrC,SAAS,eAAe,EAAE,CAM5B"}
@@ -0,0 +1,187 @@
1
+ import { AelionError } from '@aelionsdk/core';
2
+ function operationError(code, message, operation) {
3
+ return new AelionError([
4
+ {
5
+ code,
6
+ severity: 'error',
7
+ message,
8
+ path: [operation.collection, operation.id, ...('path' in operation ? operation.path : [])],
9
+ entityId: operation.id,
10
+ recoverable: true,
11
+ },
12
+ ]);
13
+ }
14
+ function cloneValue(value) {
15
+ return structuredClone(value);
16
+ }
17
+ function getEntity(project, operation) {
18
+ const entity = project[operation.collection][operation.id];
19
+ if (entity === undefined) {
20
+ throw operationError('TRANSACTION_ENTITY_MISSING', `${operation.collection}/${operation.id} does not exist`, operation);
21
+ }
22
+ return entity;
23
+ }
24
+ function resolveTarget(entity, path, operation) {
25
+ if (path.length === 0) {
26
+ throw operationError('TRANSACTION_PATH_INVALID', 'Field path must contain at least one segment', operation);
27
+ }
28
+ let current = entity;
29
+ for (const segment of path.slice(0, -1)) {
30
+ const next = current[segment];
31
+ if (next === null || Array.isArray(next) || typeof next !== 'object') {
32
+ throw operationError('TRANSACTION_PATH_INVALID', `Path segment ${segment} is not an object`, operation);
33
+ }
34
+ current = next;
35
+ }
36
+ const key = path.at(-1);
37
+ if (key === undefined) {
38
+ throw operationError('TRANSACTION_PATH_INVALID', 'Field path must contain at least one segment', operation);
39
+ }
40
+ return { parent: current, key };
41
+ }
42
+ function getList(entity, path, operation) {
43
+ const { parent, key } = resolveTarget(entity, path, operation);
44
+ const value = parent[key];
45
+ if (!Array.isArray(value) || value.some(entry => typeof entry !== 'string')) {
46
+ throw operationError('TRANSACTION_LIST_INVALID', `Field ${path.join('.')} is not a string ID list`, operation);
47
+ }
48
+ return value;
49
+ }
50
+ function insertionIndex(list, beforeId, operation) {
51
+ if (beforeId === undefined)
52
+ return list.length;
53
+ const index = list.indexOf(beforeId);
54
+ if (index < 0) {
55
+ throw operationError('TRANSACTION_LIST_ANCHOR_MISSING', `List anchor ${beforeId} does not exist`, operation);
56
+ }
57
+ return index;
58
+ }
59
+ function nextId(list, index) {
60
+ return list[index + 1];
61
+ }
62
+ export function applyOperation(project, operation) {
63
+ const collection = project[operation.collection];
64
+ switch (operation.op) {
65
+ case 'createEntity': {
66
+ if (collection[operation.id] !== undefined) {
67
+ throw operationError('TRANSACTION_ENTITY_EXISTS', `${operation.collection}/${operation.id} already exists`, operation);
68
+ }
69
+ if (operation.value.id !== operation.id) {
70
+ throw operationError('TRANSACTION_ENTITY_ID_MISMATCH', 'Entity value.id must match operation id', operation);
71
+ }
72
+ collection[operation.id] = cloneValue(operation.value);
73
+ return {
74
+ op: 'deleteEntity',
75
+ collection: operation.collection,
76
+ id: operation.id,
77
+ };
78
+ }
79
+ case 'deleteEntity': {
80
+ const entity = getEntity(project, operation);
81
+ Reflect.deleteProperty(collection, operation.id);
82
+ return {
83
+ op: 'createEntity',
84
+ collection: operation.collection,
85
+ id: operation.id,
86
+ value: cloneValue(entity),
87
+ };
88
+ }
89
+ case 'setField': {
90
+ const entity = getEntity(project, operation);
91
+ const { parent, key } = resolveTarget(entity, operation.path, operation);
92
+ const existed = Object.hasOwn(parent, key);
93
+ const previous = parent[key];
94
+ parent[key] = cloneValue(operation.value);
95
+ if (!existed) {
96
+ return {
97
+ op: 'removeField',
98
+ collection: operation.collection,
99
+ id: operation.id,
100
+ path: operation.path,
101
+ };
102
+ }
103
+ return {
104
+ op: 'setField',
105
+ collection: operation.collection,
106
+ id: operation.id,
107
+ path: operation.path,
108
+ value: cloneValue(previous),
109
+ };
110
+ }
111
+ case 'removeField': {
112
+ const entity = getEntity(project, operation);
113
+ const { parent, key } = resolveTarget(entity, operation.path, operation);
114
+ if (!Object.hasOwn(parent, key)) {
115
+ throw operationError('TRANSACTION_FIELD_MISSING', `Field ${operation.path.join('.')} does not exist`, operation);
116
+ }
117
+ const previous = parent[key];
118
+ Reflect.deleteProperty(parent, key);
119
+ return {
120
+ op: 'setField',
121
+ collection: operation.collection,
122
+ id: operation.id,
123
+ path: operation.path,
124
+ value: cloneValue(previous),
125
+ };
126
+ }
127
+ case 'listInsert': {
128
+ const list = getList(getEntity(project, operation), operation.path, operation);
129
+ if (list.includes(operation.valueId)) {
130
+ throw operationError('TRANSACTION_LIST_DUPLICATE', `List already contains ${operation.valueId}`, operation);
131
+ }
132
+ list.splice(insertionIndex(list, operation.beforeId, operation), 0, operation.valueId);
133
+ return {
134
+ op: 'listRemove',
135
+ collection: operation.collection,
136
+ id: operation.id,
137
+ path: operation.path,
138
+ valueId: operation.valueId,
139
+ };
140
+ }
141
+ case 'listRemove': {
142
+ const list = getList(getEntity(project, operation), operation.path, operation);
143
+ const index = list.indexOf(operation.valueId);
144
+ if (index < 0) {
145
+ throw operationError('TRANSACTION_LIST_VALUE_MISSING', `List does not contain ${operation.valueId}`, operation);
146
+ }
147
+ const beforeId = nextId(list, index);
148
+ list.splice(index, 1);
149
+ return {
150
+ op: 'listInsert',
151
+ collection: operation.collection,
152
+ id: operation.id,
153
+ path: operation.path,
154
+ ...(beforeId === undefined ? {} : { beforeId }),
155
+ valueId: operation.valueId,
156
+ };
157
+ }
158
+ case 'listMove': {
159
+ const list = getList(getEntity(project, operation), operation.path, operation);
160
+ const sourceIndex = list.indexOf(operation.valueId);
161
+ if (sourceIndex < 0) {
162
+ throw operationError('TRANSACTION_LIST_VALUE_MISSING', `List does not contain ${operation.valueId}`, operation);
163
+ }
164
+ if (operation.beforeId === operation.valueId) {
165
+ throw operationError('TRANSACTION_LIST_ANCHOR_INVALID', 'A list entry cannot be moved before itself', operation);
166
+ }
167
+ const previousBeforeId = nextId(list, sourceIndex);
168
+ list.splice(sourceIndex, 1);
169
+ list.splice(insertionIndex(list, operation.beforeId, operation), 0, operation.valueId);
170
+ return {
171
+ op: 'listMove',
172
+ collection: operation.collection,
173
+ id: operation.id,
174
+ path: operation.path,
175
+ ...(previousBeforeId === undefined ? {} : { beforeId: previousBeforeId }),
176
+ valueId: operation.valueId,
177
+ };
178
+ }
179
+ }
180
+ }
181
+ export function applyOperations(project, operations) {
182
+ const inverse = [];
183
+ for (const operation of operations) {
184
+ inverse.unshift(applyOperation(project, operation));
185
+ }
186
+ return inverse;
187
+ }
@@ -0,0 +1,32 @@
1
+ import type { JsonObject, JsonValue } from '@aelionsdk/core';
2
+ import { type AelionProject, type CollectionName, type EntityId } from '@aelionsdk/project-schema';
3
+ import type { AtomicOperation, ChangeSet, EditOptions, ProjectChangeListener, ProjectValidation, TransactionCommit, TransactionEngineOptions } from './types.js';
4
+ export declare const TRANSACTION_MAX_OPERATIONS = 16384;
5
+ export declare class TransactionBuilder {
6
+ #private;
7
+ get operations(): readonly AtomicOperation[];
8
+ /**
9
+ * Appends an already validated operation sequence to this transaction.
10
+ *
11
+ * This is primarily used by history/replay adapters. Operations are cloned so
12
+ * callers cannot mutate a transaction after it has been committed.
13
+ */
14
+ appendOperations(operations: readonly AtomicOperation[]): void;
15
+ createEntity(collection: CollectionName, id: EntityId, value: JsonObject): void;
16
+ deleteEntity(collection: CollectionName, id: EntityId): void;
17
+ setField(collection: CollectionName, id: EntityId, path: readonly string[], value: JsonValue): void;
18
+ removeField(collection: CollectionName, id: EntityId, path: readonly string[]): void;
19
+ listInsert(collection: CollectionName, id: EntityId, path: readonly string[], valueId: EntityId, beforeId?: EntityId): void;
20
+ listRemove(collection: CollectionName, id: EntityId, path: readonly string[], valueId: EntityId): void;
21
+ listMove(collection: CollectionName, id: EntityId, path: readonly string[], valueId: EntityId, beforeId?: EntityId): void;
22
+ }
23
+ export declare class TransactionEngine {
24
+ #private;
25
+ constructor(project: AelionProject, validate: ProjectValidation, options?: TransactionEngineOptions);
26
+ get revision(): bigint;
27
+ getSnapshot(): Readonly<AelionProject>;
28
+ subscribe(listener: ProjectChangeListener): () => void;
29
+ edit(options: EditOptions, callback: (transaction: TransactionBuilder) => void): TransactionCommit;
30
+ applyChangeSet(changeSet: ChangeSet): TransactionCommit;
31
+ }
32
+ //# sourceMappingURL=transaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACd,MAAM,2BAA2B,CAAC;AAInC,OAAO,KAAK,EACV,eAAe,EACf,SAAS,EACT,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACzB,MAAM,YAAY,CAAC;AA8CpB,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAOjD,qBAAa,kBAAkB;;IAG7B,IAAW,UAAU,IAAI,SAAS,eAAe,EAAE,CAElD;IAED;;;;;OAKG;IACI,gBAAgB,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,GAAG,IAAI;IAU9D,YAAY,CAAC,UAAU,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAI/E,YAAY,CAAC,UAAU,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI;IAI5D,QAAQ,CACb,UAAU,EAAE,cAAc,EAC1B,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,KAAK,EAAE,SAAS,GACf,IAAI;IAIA,WAAW,CAAC,UAAU,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI;IAIpF,UAAU,CACf,UAAU,EAAE,cAAc,EAC1B,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,QAAQ,EACjB,QAAQ,CAAC,EAAE,QAAQ,GAClB,IAAI;IAWA,UAAU,CACf,UAAU,EAAE,cAAc,EAC1B,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,QAAQ,GAChB,IAAI;IAIA,QAAQ,CACb,UAAU,EAAE,cAAc,EAC1B,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,QAAQ,EACjB,QAAQ,CAAC,EAAE,QAAQ,GAClB,IAAI;CAoBR;AAED,qBAAa,iBAAiB;;gBAU1B,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,GAAE,wBAA6B;IAUxC,IAAW,QAAQ,IAAI,MAAM,CAE5B;IAEM,WAAW,IAAI,QAAQ,CAAC,aAAa,CAAC;IAItC,SAAS,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI;IAKtD,IAAI,CACT,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,CAAC,WAAW,EAAE,kBAAkB,KAAK,IAAI,GAClD,iBAAiB;IAiFb,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,iBAAiB;CA2B/D"}