korajs 0.3.2 → 0.4.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/dist/index.d.cts CHANGED
@@ -1,10 +1,11 @@
1
- import { KoraEventEmitter, SchemaDefinition, SchemaInput, FieldBuilder, InferRecord, InferInsertInput, InferUpdateInput } from '@korajs/core';
2
- export { CollectionDefinition, ConnectionQuality, Constraint, FieldDescriptor, FieldKindToType, HLCTimestamp, HybridLogicalClock, InferFieldType, InferInsertInput, InferRecord, InferUpdateInput, KoraError, KoraEvent, KoraEventEmitter, KoraEventListener, KoraEventType, MergeStrategy, MergeTrace, Operation, SchemaDefinition, SchemaInput, TypedSchemaDefinition, VersionVector, createOperation, defineSchema, generateUUIDv7, t } from '@korajs/core';
1
+ import { KoraEventEmitter, SequenceConfig, Operation, SchemaDefinition, SchemaInput, FieldBuilder, InferRecord, InferInsertInput, InferUpdateInput } from '@korajs/core';
2
+ export { AtomicOp, AtomicOpType, CollectionDefinition, ConnectionQuality, Constraint, FieldDescriptor, FieldKindToType, HLCTimestamp, HybridLogicalClock, InferFieldType, InferInsertInput, InferRecord, InferUpdateInput, KoraError, KoraEvent, KoraEventEmitter, KoraEventListener, KoraEventType, MergeStrategy, MergeTrace, MigrationDefinition, MigrationStep, Operation, SchemaDefinition, SchemaInput, SequenceConfig, TypedSchemaDefinition, VersionVector, createOperation, defineSchema, generateUUIDv7, migrate, op, t } from '@korajs/core';
3
3
  import * as _korajs_store from '@korajs/store';
4
- import { QueryBuilder, CollectionAccessor } from '@korajs/store';
5
- export { CollectionAccessor, CollectionRecord, StorageAdapter, Store, StoreConfig } from '@korajs/store';
4
+ import { CollectionRecord, QueryBuilder, CollectionAccessor } from '@korajs/store';
5
+ export { CollectionAccessor, CollectionRecord, SequenceManager, StorageAdapter, Store, StoreConfig, TransactionCollectionAccessor, TransactionContext, TransactionContextConfig } from '@korajs/store';
6
+ import * as _korajs_sync from '@korajs/sync';
6
7
  import { SyncStatusInfo, SyncEngine } from '@korajs/sync';
7
- export { SyncConfig, SyncEngine, SyncState, SyncStatus, SyncStatusInfo, SyncStore, WebSocketTransport } from '@korajs/sync';
8
+ export { SyncConfig, SyncDiagnostics, SyncEngine, SyncState, SyncStatus, SyncStatusInfo, SyncStore, WebSocketTransport } from '@korajs/sync';
8
9
  export { MergeEngine, MergeInput, MergeResult } from '@korajs/merge';
9
10
 
10
11
  /**
@@ -40,6 +41,22 @@ interface SyncOptions {
40
41
  }>;
41
42
  /** Sync scopes per collection. */
42
43
  scopes?: Record<string, (ctx: Record<string, unknown>) => Record<string, unknown>>;
44
+ /**
45
+ * Flat scope values. Combined with schema scope declarations to build
46
+ * per-collection scope filters sent to the server during handshake.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * createApp({
51
+ * schema,
52
+ * sync: {
53
+ * url: 'wss://server/kora',
54
+ * scope: { orgId: 'org-123', storeId: 'store-456' },
55
+ * },
56
+ * })
57
+ * ```
58
+ */
59
+ scope?: Record<string, unknown>;
43
60
  /** Number of operations per batch. Defaults to 100. */
44
61
  batchSize?: number;
45
62
  /** Schema version of this client. */
@@ -89,6 +106,71 @@ interface SyncControl {
89
106
  disconnect(): Promise<void>;
90
107
  /** Get the current developer-facing sync status. */
91
108
  getStatus(): SyncStatusInfo;
109
+ /** Force an immediate reconnection attempt. No-op if already connected. */
110
+ retryNow(): Promise<void>;
111
+ /** Export a diagnostics snapshot for debugging and support tickets. */
112
+ exportDiagnostics(): _korajs_sync.SyncDiagnostics;
113
+ }
114
+ /**
115
+ * A transaction collection accessor providing insert, update, delete, and findById.
116
+ */
117
+ interface TransactionCollectionProxy {
118
+ insert(data: Record<string, unknown>): Promise<CollectionRecord>;
119
+ update(id: string, data: Record<string, unknown>): Promise<CollectionRecord>;
120
+ delete(id: string): Promise<void>;
121
+ findById(id: string): Promise<CollectionRecord | null>;
122
+ }
123
+ /**
124
+ * Transaction proxy passed to the transaction callback.
125
+ * Provides collection accessors as direct properties (e.g., tx.todos.insert(...)).
126
+ */
127
+ interface TransactionProxy {
128
+ /** Dynamic collection accessors for transaction operations. */
129
+ [collection: string]: TransactionCollectionProxy;
130
+ }
131
+ /**
132
+ * Accessor for offline-safe sequences.
133
+ * Generates monotonically increasing, collision-free identifiers
134
+ * that work across offline devices.
135
+ */
136
+ interface SequenceAccessor {
137
+ /**
138
+ * Get the next value in a sequence, atomically incrementing the counter.
139
+ *
140
+ * @param name - The sequence name (e.g., 'receipt', 'invoice')
141
+ * @param config - Optional configuration for scope, format, and starting value
142
+ * @returns The formatted sequence value
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * const receiptNo = await app.sequences.next('receipt', {
147
+ * scope: storeId,
148
+ * format: 'S-{date}-{node4}-{seq}',
149
+ * })
150
+ * // → "S-20260508-a1b2-0042"
151
+ * ```
152
+ */
153
+ next(name: string, config?: SequenceConfig): Promise<string>;
154
+ /**
155
+ * Get the current counter value without incrementing.
156
+ *
157
+ * @param name - The sequence name
158
+ * @param config - Optional scope
159
+ * @returns The current counter value, or 0 if never used
160
+ */
161
+ current(name: string, config?: {
162
+ scope?: string;
163
+ }): Promise<number>;
164
+ /**
165
+ * Reset a sequence counter.
166
+ *
167
+ * @param name - The sequence name
168
+ * @param config - Optional scope and target value
169
+ */
170
+ reset(name: string, config?: {
171
+ scope?: string;
172
+ to?: number;
173
+ }): Promise<void>;
92
174
  }
93
175
  /**
94
176
  * The main application object returned by createApp().
@@ -101,12 +183,41 @@ interface KoraApp {
101
183
  events: KoraEventEmitter;
102
184
  /** Sync control (connect/disconnect/status). Null if sync not configured. */
103
185
  sync: SyncControl | null;
186
+ /** Offline-safe sequence generation. */
187
+ sequences: SequenceAccessor;
104
188
  /** Get the underlying Store instance (for advanced use / React integration). */
105
189
  getStore(): _korajs_store.Store;
106
190
  /** Get the underlying SyncEngine instance. Null if sync not configured. */
107
191
  getSyncEngine(): SyncEngine | null;
108
192
  /** Gracefully close the app: stop sync, close store. */
109
193
  close(): Promise<void>;
194
+ /**
195
+ * Execute multiple mutations atomically within a transaction.
196
+ * All operations are committed together or rolled back on error.
197
+ * Subscription notifications are batched after commit.
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * await app.transaction(async (tx) => {
202
+ * await tx.sales.update(saleId, { status: 'completed' })
203
+ * await tx.payments.insert({ saleId, method: 'cash', amount: total })
204
+ * })
205
+ * ```
206
+ */
207
+ transaction(fn: (tx: TransactionProxy) => Promise<void>): Promise<Operation[]>;
208
+ /**
209
+ * Execute a named mutation — a transaction with a human-readable name.
210
+ * The mutation name is attached to all operations and visible in DevTools.
211
+ *
212
+ * @example
213
+ * ```typescript
214
+ * await app.mutation('complete-sale', async (tx) => {
215
+ * await tx.sales.update(saleId, { status: 'completed' })
216
+ * await tx.payments.insert({ saleId, method: 'cash', amount: total })
217
+ * })
218
+ * ```
219
+ */
220
+ mutation(name: string, fn: (tx: TransactionProxy) => Promise<void>): Promise<Operation[]>;
110
221
  /** Dynamic collection accessors (e.g., app.todos). Typed via Object.defineProperty. */
111
222
  [collection: string]: unknown;
112
223
  }
@@ -137,12 +248,18 @@ type TypedKoraApp<S extends SchemaInput> = {
137
248
  events: KoraEventEmitter;
138
249
  /** Sync control (connect/disconnect/status). Null if sync not configured. */
139
250
  sync: SyncControl | null;
251
+ /** Offline-safe sequence generation. */
252
+ sequences: SequenceAccessor;
140
253
  /** Get the underlying Store instance (for advanced use / React integration). */
141
254
  getStore(): _korajs_store.Store;
142
255
  /** Get the underlying SyncEngine instance. Null if sync not configured. */
143
256
  getSyncEngine(): SyncEngine | null;
144
257
  /** Gracefully close the app: stop sync, close store. */
145
258
  close(): Promise<void>;
259
+ /** Execute multiple mutations atomically within a transaction. */
260
+ transaction(fn: (tx: TransactionProxy) => Promise<void>): Promise<Operation[]>;
261
+ /** Execute a named mutation — a transaction with a DevTools-visible name. */
262
+ mutation(name: string, fn: (tx: TransactionProxy) => Promise<void>): Promise<Operation[]>;
146
263
  } & {
147
264
  readonly [C in keyof S['collections'] & string]: S['collections'][C] extends {
148
265
  fields: infer F extends Record<string, FieldBuilder<any, any, any>>;
@@ -190,4 +307,4 @@ declare function createApp<const S extends SchemaInput>(config: TypedKoraConfig<
190
307
  */
191
308
  declare function createApp(config: KoraConfig): KoraApp;
192
309
 
193
- export { type AdapterType, type KoraApp, type KoraConfig, type StoreOptions, type SyncControl, type SyncOptions, type TypedCollectionAccessor, type TypedKoraApp, type TypedKoraConfig, createApp };
310
+ export { type AdapterType, type KoraApp, type KoraConfig, type SequenceAccessor, type StoreOptions, type SyncControl, type SyncOptions, type TransactionCollectionProxy, type TransactionProxy, type TypedCollectionAccessor, type TypedKoraApp, type TypedKoraConfig, createApp };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { KoraEventEmitter, SchemaDefinition, SchemaInput, FieldBuilder, InferRecord, InferInsertInput, InferUpdateInput } from '@korajs/core';
2
- export { CollectionDefinition, ConnectionQuality, Constraint, FieldDescriptor, FieldKindToType, HLCTimestamp, HybridLogicalClock, InferFieldType, InferInsertInput, InferRecord, InferUpdateInput, KoraError, KoraEvent, KoraEventEmitter, KoraEventListener, KoraEventType, MergeStrategy, MergeTrace, Operation, SchemaDefinition, SchemaInput, TypedSchemaDefinition, VersionVector, createOperation, defineSchema, generateUUIDv7, t } from '@korajs/core';
1
+ import { KoraEventEmitter, SequenceConfig, Operation, SchemaDefinition, SchemaInput, FieldBuilder, InferRecord, InferInsertInput, InferUpdateInput } from '@korajs/core';
2
+ export { AtomicOp, AtomicOpType, CollectionDefinition, ConnectionQuality, Constraint, FieldDescriptor, FieldKindToType, HLCTimestamp, HybridLogicalClock, InferFieldType, InferInsertInput, InferRecord, InferUpdateInput, KoraError, KoraEvent, KoraEventEmitter, KoraEventListener, KoraEventType, MergeStrategy, MergeTrace, MigrationDefinition, MigrationStep, Operation, SchemaDefinition, SchemaInput, SequenceConfig, TypedSchemaDefinition, VersionVector, createOperation, defineSchema, generateUUIDv7, migrate, op, t } from '@korajs/core';
3
3
  import * as _korajs_store from '@korajs/store';
4
- import { QueryBuilder, CollectionAccessor } from '@korajs/store';
5
- export { CollectionAccessor, CollectionRecord, StorageAdapter, Store, StoreConfig } from '@korajs/store';
4
+ import { CollectionRecord, QueryBuilder, CollectionAccessor } from '@korajs/store';
5
+ export { CollectionAccessor, CollectionRecord, SequenceManager, StorageAdapter, Store, StoreConfig, TransactionCollectionAccessor, TransactionContext, TransactionContextConfig } from '@korajs/store';
6
+ import * as _korajs_sync from '@korajs/sync';
6
7
  import { SyncStatusInfo, SyncEngine } from '@korajs/sync';
7
- export { SyncConfig, SyncEngine, SyncState, SyncStatus, SyncStatusInfo, SyncStore, WebSocketTransport } from '@korajs/sync';
8
+ export { SyncConfig, SyncDiagnostics, SyncEngine, SyncState, SyncStatus, SyncStatusInfo, SyncStore, WebSocketTransport } from '@korajs/sync';
8
9
  export { MergeEngine, MergeInput, MergeResult } from '@korajs/merge';
9
10
 
10
11
  /**
@@ -40,6 +41,22 @@ interface SyncOptions {
40
41
  }>;
41
42
  /** Sync scopes per collection. */
42
43
  scopes?: Record<string, (ctx: Record<string, unknown>) => Record<string, unknown>>;
44
+ /**
45
+ * Flat scope values. Combined with schema scope declarations to build
46
+ * per-collection scope filters sent to the server during handshake.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * createApp({
51
+ * schema,
52
+ * sync: {
53
+ * url: 'wss://server/kora',
54
+ * scope: { orgId: 'org-123', storeId: 'store-456' },
55
+ * },
56
+ * })
57
+ * ```
58
+ */
59
+ scope?: Record<string, unknown>;
43
60
  /** Number of operations per batch. Defaults to 100. */
44
61
  batchSize?: number;
45
62
  /** Schema version of this client. */
@@ -89,6 +106,71 @@ interface SyncControl {
89
106
  disconnect(): Promise<void>;
90
107
  /** Get the current developer-facing sync status. */
91
108
  getStatus(): SyncStatusInfo;
109
+ /** Force an immediate reconnection attempt. No-op if already connected. */
110
+ retryNow(): Promise<void>;
111
+ /** Export a diagnostics snapshot for debugging and support tickets. */
112
+ exportDiagnostics(): _korajs_sync.SyncDiagnostics;
113
+ }
114
+ /**
115
+ * A transaction collection accessor providing insert, update, delete, and findById.
116
+ */
117
+ interface TransactionCollectionProxy {
118
+ insert(data: Record<string, unknown>): Promise<CollectionRecord>;
119
+ update(id: string, data: Record<string, unknown>): Promise<CollectionRecord>;
120
+ delete(id: string): Promise<void>;
121
+ findById(id: string): Promise<CollectionRecord | null>;
122
+ }
123
+ /**
124
+ * Transaction proxy passed to the transaction callback.
125
+ * Provides collection accessors as direct properties (e.g., tx.todos.insert(...)).
126
+ */
127
+ interface TransactionProxy {
128
+ /** Dynamic collection accessors for transaction operations. */
129
+ [collection: string]: TransactionCollectionProxy;
130
+ }
131
+ /**
132
+ * Accessor for offline-safe sequences.
133
+ * Generates monotonically increasing, collision-free identifiers
134
+ * that work across offline devices.
135
+ */
136
+ interface SequenceAccessor {
137
+ /**
138
+ * Get the next value in a sequence, atomically incrementing the counter.
139
+ *
140
+ * @param name - The sequence name (e.g., 'receipt', 'invoice')
141
+ * @param config - Optional configuration for scope, format, and starting value
142
+ * @returns The formatted sequence value
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * const receiptNo = await app.sequences.next('receipt', {
147
+ * scope: storeId,
148
+ * format: 'S-{date}-{node4}-{seq}',
149
+ * })
150
+ * // → "S-20260508-a1b2-0042"
151
+ * ```
152
+ */
153
+ next(name: string, config?: SequenceConfig): Promise<string>;
154
+ /**
155
+ * Get the current counter value without incrementing.
156
+ *
157
+ * @param name - The sequence name
158
+ * @param config - Optional scope
159
+ * @returns The current counter value, or 0 if never used
160
+ */
161
+ current(name: string, config?: {
162
+ scope?: string;
163
+ }): Promise<number>;
164
+ /**
165
+ * Reset a sequence counter.
166
+ *
167
+ * @param name - The sequence name
168
+ * @param config - Optional scope and target value
169
+ */
170
+ reset(name: string, config?: {
171
+ scope?: string;
172
+ to?: number;
173
+ }): Promise<void>;
92
174
  }
93
175
  /**
94
176
  * The main application object returned by createApp().
@@ -101,12 +183,41 @@ interface KoraApp {
101
183
  events: KoraEventEmitter;
102
184
  /** Sync control (connect/disconnect/status). Null if sync not configured. */
103
185
  sync: SyncControl | null;
186
+ /** Offline-safe sequence generation. */
187
+ sequences: SequenceAccessor;
104
188
  /** Get the underlying Store instance (for advanced use / React integration). */
105
189
  getStore(): _korajs_store.Store;
106
190
  /** Get the underlying SyncEngine instance. Null if sync not configured. */
107
191
  getSyncEngine(): SyncEngine | null;
108
192
  /** Gracefully close the app: stop sync, close store. */
109
193
  close(): Promise<void>;
194
+ /**
195
+ * Execute multiple mutations atomically within a transaction.
196
+ * All operations are committed together or rolled back on error.
197
+ * Subscription notifications are batched after commit.
198
+ *
199
+ * @example
200
+ * ```typescript
201
+ * await app.transaction(async (tx) => {
202
+ * await tx.sales.update(saleId, { status: 'completed' })
203
+ * await tx.payments.insert({ saleId, method: 'cash', amount: total })
204
+ * })
205
+ * ```
206
+ */
207
+ transaction(fn: (tx: TransactionProxy) => Promise<void>): Promise<Operation[]>;
208
+ /**
209
+ * Execute a named mutation — a transaction with a human-readable name.
210
+ * The mutation name is attached to all operations and visible in DevTools.
211
+ *
212
+ * @example
213
+ * ```typescript
214
+ * await app.mutation('complete-sale', async (tx) => {
215
+ * await tx.sales.update(saleId, { status: 'completed' })
216
+ * await tx.payments.insert({ saleId, method: 'cash', amount: total })
217
+ * })
218
+ * ```
219
+ */
220
+ mutation(name: string, fn: (tx: TransactionProxy) => Promise<void>): Promise<Operation[]>;
110
221
  /** Dynamic collection accessors (e.g., app.todos). Typed via Object.defineProperty. */
111
222
  [collection: string]: unknown;
112
223
  }
@@ -137,12 +248,18 @@ type TypedKoraApp<S extends SchemaInput> = {
137
248
  events: KoraEventEmitter;
138
249
  /** Sync control (connect/disconnect/status). Null if sync not configured. */
139
250
  sync: SyncControl | null;
251
+ /** Offline-safe sequence generation. */
252
+ sequences: SequenceAccessor;
140
253
  /** Get the underlying Store instance (for advanced use / React integration). */
141
254
  getStore(): _korajs_store.Store;
142
255
  /** Get the underlying SyncEngine instance. Null if sync not configured. */
143
256
  getSyncEngine(): SyncEngine | null;
144
257
  /** Gracefully close the app: stop sync, close store. */
145
258
  close(): Promise<void>;
259
+ /** Execute multiple mutations atomically within a transaction. */
260
+ transaction(fn: (tx: TransactionProxy) => Promise<void>): Promise<Operation[]>;
261
+ /** Execute a named mutation — a transaction with a DevTools-visible name. */
262
+ mutation(name: string, fn: (tx: TransactionProxy) => Promise<void>): Promise<Operation[]>;
146
263
  } & {
147
264
  readonly [C in keyof S['collections'] & string]: S['collections'][C] extends {
148
265
  fields: infer F extends Record<string, FieldBuilder<any, any, any>>;
@@ -190,4 +307,4 @@ declare function createApp<const S extends SchemaInput>(config: TypedKoraConfig<
190
307
  */
191
308
  declare function createApp(config: KoraConfig): KoraApp;
192
309
 
193
- export { type AdapterType, type KoraApp, type KoraConfig, type StoreOptions, type SyncControl, type SyncOptions, type TypedCollectionAccessor, type TypedKoraApp, type TypedKoraConfig, createApp };
310
+ export { type AdapterType, type KoraApp, type KoraConfig, type SequenceAccessor, type StoreOptions, type SyncControl, type SyncOptions, type TransactionCollectionProxy, type TransactionProxy, type TypedCollectionAccessor, type TypedKoraApp, type TypedKoraConfig, createApp };
package/dist/index.js CHANGED
@@ -1,9 +1,15 @@
1
1
  // src/create-app.ts
2
+ import { buildScopeMap } from "@korajs/core";
2
3
  import { SimpleEventEmitter } from "@korajs/core/internal";
3
4
  import { Instrumenter } from "@korajs/devtools";
4
5
  import { MergeEngine } from "@korajs/merge";
5
6
  import { Store } from "@korajs/store";
6
- import { ConnectionMonitor, ReconnectionManager, SyncEngine, WebSocketTransport } from "@korajs/sync";
7
+ import {
8
+ ConnectionMonitor,
9
+ ReconnectionManager,
10
+ SyncEngine,
11
+ WebSocketTransport
12
+ } from "@korajs/sync";
7
13
 
8
14
  // src/adapter-resolver.ts
9
15
  function detectAdapterType() {
@@ -24,8 +30,10 @@ function detectAdapterType() {
24
30
  async function createAdapter(type, dbName, workerUrl) {
25
31
  switch (type) {
26
32
  case "tauri-sqlite": {
27
- const dynamicImport = new Function("specifier", "return import(specifier)");
28
- const { TauriSqliteAdapter } = await dynamicImport("@korajs/tauri");
33
+ const { TauriSqliteAdapter } = await import(
34
+ /* @vite-ignore */
35
+ "@korajs/tauri"
36
+ );
29
37
  return new TauriSqliteAdapter({ path: `${dbName}.db` });
30
38
  }
31
39
  case "better-sqlite3": {
@@ -69,23 +77,23 @@ var MergeAwareSyncStore = class {
69
77
  async getOperationRange(nodeId, fromSeq, toSeq) {
70
78
  return this.store.getOperationRange(nodeId, fromSeq, toSeq);
71
79
  }
72
- async applyRemoteOperation(op) {
73
- if (op.type !== "update" || !op.data || !op.previousData) {
74
- return this.store.applyRemoteOperation(op);
80
+ async applyRemoteOperation(op2) {
81
+ if (op2.type !== "update" || !op2.data || !op2.previousData) {
82
+ return this.store.applyRemoteOperation(op2);
75
83
  }
76
84
  const schema = this.store.getSchema();
77
- const collectionDef = schema.collections[op.collection];
85
+ const collectionDef = schema.collections[op2.collection];
78
86
  if (!collectionDef) {
79
- return this.store.applyRemoteOperation(op);
87
+ return this.store.applyRemoteOperation(op2);
80
88
  }
81
- const accessor = this.store.collection(op.collection);
82
- const currentRecord = await accessor.findById(op.recordId);
89
+ const accessor = this.store.collection(op2.collection);
90
+ const currentRecord = await accessor.findById(op2.recordId);
83
91
  if (!currentRecord) {
84
- return this.store.applyRemoteOperation(op);
92
+ return this.store.applyRemoteOperation(op2);
85
93
  }
86
94
  let hasConflict = false;
87
- for (const field of Object.keys(op.data)) {
88
- const expectedBase = op.previousData[field];
95
+ for (const field of Object.keys(op2.data)) {
96
+ const expectedBase = op2.previousData[field];
89
97
  const currentLocal = currentRecord[field];
90
98
  if (!deepEqual(expectedBase, currentLocal)) {
91
99
  hasConflict = true;
@@ -93,24 +101,24 @@ var MergeAwareSyncStore = class {
93
101
  }
94
102
  }
95
103
  if (!hasConflict) {
96
- return this.store.applyRemoteOperation(op);
104
+ return this.store.applyRemoteOperation(op2);
97
105
  }
98
106
  this.emitter?.emit({
99
107
  type: "merge:started",
100
- operationA: op,
101
- operationB: op
108
+ operationA: op2,
109
+ operationB: op2
102
110
  });
103
- const baseState = { ...op.previousData };
111
+ const baseState = { ...op2.previousData };
104
112
  const localOp = {
105
- ...op,
113
+ ...op2,
106
114
  // The "local" operation's data is the diff between base and current local state
107
- data: buildLocalDiff(op.previousData, currentRecord, Object.keys(op.data)),
108
- previousData: op.previousData,
115
+ data: buildLocalDiff(op2.previousData, currentRecord, Object.keys(op2.data)),
116
+ previousData: op2.previousData,
109
117
  nodeId: this.store.getNodeId()
110
118
  };
111
119
  const input = {
112
120
  local: localOp,
113
- remote: op,
121
+ remote: op2,
114
122
  baseState,
115
123
  collectionDef
116
124
  };
@@ -123,7 +131,7 @@ var MergeAwareSyncStore = class {
123
131
  this.emitter?.emit({ type: "merge:completed", trace: firstTrace });
124
132
  }
125
133
  const mergedOp = {
126
- ...op,
134
+ ...op2,
127
135
  data: result.mergedData
128
136
  };
129
137
  return this.store.applyRemoteOperation(mergedOp);
@@ -245,13 +253,104 @@ function createApp(config) {
245
253
  if (syncEngine) {
246
254
  return syncEngine.getStatus();
247
255
  }
248
- return { status: "offline", pendingOperations: 0, lastSyncedAt: null };
256
+ return {
257
+ status: "offline",
258
+ pendingOperations: 0,
259
+ lastSyncedAt: null,
260
+ lastSuccessfulPush: null,
261
+ lastSuccessfulPull: null,
262
+ conflicts: 0
263
+ };
264
+ },
265
+ async retryNow() {
266
+ await ready;
267
+ if (syncEngine) {
268
+ await syncEngine.retryNow();
269
+ }
270
+ },
271
+ exportDiagnostics() {
272
+ if (syncEngine) {
273
+ return syncEngine.exportDiagnostics();
274
+ }
275
+ return {
276
+ state: "disconnected",
277
+ status: {
278
+ status: "offline",
279
+ pendingOperations: 0,
280
+ lastSyncedAt: null,
281
+ lastSuccessfulPush: null,
282
+ lastSuccessfulPull: null,
283
+ conflicts: 0
284
+ },
285
+ nodeId: "",
286
+ url: config.sync?.url ?? "",
287
+ schemaVersion: config.schema.version,
288
+ lastSyncedAt: null,
289
+ lastSuccessfulPush: null,
290
+ lastSuccessfulPull: null,
291
+ conflicts: 0,
292
+ pendingOperations: 0,
293
+ hasInFlightBatch: false,
294
+ reconnecting: false,
295
+ timestamp: Date.now()
296
+ };
249
297
  }
250
298
  } : null;
299
+ async function executeTransaction(fn, mutationName) {
300
+ await ready;
301
+ if (!store) {
302
+ throw new Error("Store not initialized. Await app.ready before using transactions.");
303
+ }
304
+ const txContext = store.createTransaction();
305
+ if (mutationName !== void 0) {
306
+ txContext.setMutationName(mutationName);
307
+ }
308
+ const collectionNames = Object.keys(config.schema.collections);
309
+ const proxy = {};
310
+ for (const name of collectionNames) {
311
+ Object.defineProperty(proxy, name, {
312
+ get() {
313
+ return txContext.collection(name);
314
+ },
315
+ enumerable: true,
316
+ configurable: false
317
+ });
318
+ }
319
+ try {
320
+ await fn(proxy);
321
+ const { operations } = await txContext.commit();
322
+ for (const op2 of operations) {
323
+ store.getSubscriptionManager().notify(op2.collection, op2);
324
+ emitter.emit({ type: "operation:created", operation: op2 });
325
+ }
326
+ return operations;
327
+ } catch (error) {
328
+ txContext.rollback();
329
+ throw error;
330
+ }
331
+ }
332
+ const sequences = {
333
+ async next(name, config2) {
334
+ await ready;
335
+ if (!store) throw new Error("Store not initialized. Await app.ready before using sequences.");
336
+ return store.getSequenceManager().next(name, config2);
337
+ },
338
+ async current(name, config2) {
339
+ await ready;
340
+ if (!store) throw new Error("Store not initialized. Await app.ready before using sequences.");
341
+ return store.getSequenceManager().current(name, config2);
342
+ },
343
+ async reset(name, config2) {
344
+ await ready;
345
+ if (!store) throw new Error("Store not initialized. Await app.ready before using sequences.");
346
+ return store.getSequenceManager().reset(name, config2);
347
+ }
348
+ };
251
349
  const app = {
252
350
  ready,
253
351
  events: emitter,
254
352
  sync: syncControl,
353
+ sequences,
255
354
  getStore() {
256
355
  if (!store) {
257
356
  throw new Error("Store not initialized. Await app.ready before accessing the store.");
@@ -261,6 +360,12 @@ function createApp(config) {
261
360
  getSyncEngine() {
262
361
  return syncEngine;
263
362
  },
363
+ async transaction(fn) {
364
+ return executeTransaction(fn);
365
+ },
366
+ async mutation(name, fn) {
367
+ return executeTransaction(fn, name);
368
+ },
264
369
  async close() {
265
370
  await ready;
266
371
  intentionalDisconnect = true;
@@ -314,6 +419,7 @@ async function initializeAsync(config, emitter, mergeEngine) {
314
419
  if (config.sync) {
315
420
  const transport = new WebSocketTransport();
316
421
  const mergeAwareStore = new MergeAwareSyncStore(store, mergeEngine, emitter);
422
+ const scopeMap = config.sync.scope ? buildScopeMap(config.schema, config.sync.scope) : void 0;
317
423
  syncEngine = new SyncEngine({
318
424
  transport,
319
425
  store: mergeAwareStore,
@@ -322,7 +428,8 @@ async function initializeAsync(config, emitter, mergeEngine) {
322
428
  transport: config.sync.transport,
323
429
  auth: config.sync.auth,
324
430
  batchSize: config.sync.batchSize,
325
- schemaVersion: config.sync.schemaVersion ?? config.schema.version
431
+ schemaVersion: config.sync.schemaVersion ?? config.schema.version,
432
+ scopeMap
326
433
  },
327
434
  emitter
328
435
  });
@@ -415,25 +522,31 @@ function createPendingQueryBuilder(initialWhere) {
415
522
  }
416
523
 
417
524
  // src/index.ts
418
- import { defineSchema, t } from "@korajs/core";
525
+ import { defineSchema, migrate, t } from "@korajs/core";
419
526
  import { HybridLogicalClock } from "@korajs/core";
420
527
  import { generateUUIDv7 } from "@korajs/core";
421
528
  import { createOperation } from "@korajs/core";
422
529
  import { KoraError } from "@korajs/core";
423
- import { Store as Store2 } from "@korajs/store";
530
+ import { op } from "@korajs/core";
531
+ import { SequenceManager, Store as Store2 } from "@korajs/store";
532
+ import { TransactionContext } from "@korajs/store";
424
533
  import { MergeEngine as MergeEngine2 } from "@korajs/merge";
425
534
  import { SyncEngine as SyncEngine2, WebSocketTransport as WebSocketTransport2 } from "@korajs/sync";
426
535
  export {
427
536
  HybridLogicalClock,
428
537
  KoraError,
429
538
  MergeEngine2 as MergeEngine,
539
+ SequenceManager,
430
540
  Store2 as Store,
431
541
  SyncEngine2 as SyncEngine,
542
+ TransactionContext,
432
543
  WebSocketTransport2 as WebSocketTransport,
433
544
  createApp,
434
545
  createOperation,
435
546
  defineSchema,
436
547
  generateUUIDv7,
548
+ migrate,
549
+ op,
437
550
  t
438
551
  };
439
552
  //# sourceMappingURL=index.js.map