korajs 1.0.0-beta.5 → 1.0.0-beta.6
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.cjs +106 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +106 -6
- package/dist/index.js.map +1 -1
- package/package.json +14 -13
package/dist/index.cjs
CHANGED
|
@@ -212,7 +212,7 @@ function detectAdapterType() {
|
|
|
212
212
|
}
|
|
213
213
|
return "better-sqlite3";
|
|
214
214
|
}
|
|
215
|
-
async function createAdapter(type, dbName, workerUrl, emitter, workerResponseTimeoutMs, sharedWorkerUrl) {
|
|
215
|
+
async function createAdapter(type, dbName, workerUrl, emitter, workerResponseTimeoutMs, sharedWorkerUrl, suppressNonPersistentDiagnostic = false) {
|
|
216
216
|
switch (type) {
|
|
217
217
|
case "tauri-sqlite": {
|
|
218
218
|
const { TauriSqliteAdapter } = await importOptionalPeer("@korajs/tauri");
|
|
@@ -233,7 +233,8 @@ async function createAdapter(type, dbName, workerUrl, emitter, workerResponseTim
|
|
|
233
233
|
workerUrl,
|
|
234
234
|
sharedWorkerUrl,
|
|
235
235
|
workerResponseTimeoutMs,
|
|
236
|
-
emitter
|
|
236
|
+
emitter,
|
|
237
|
+
emitNonPersistentDiagnostic: !suppressNonPersistentDiagnostic
|
|
237
238
|
});
|
|
238
239
|
}
|
|
239
240
|
case "indexeddb": {
|
|
@@ -1055,6 +1056,51 @@ function createSyncTransport(sync) {
|
|
|
1055
1056
|
return new import_sync.WebSocketTransport();
|
|
1056
1057
|
}
|
|
1057
1058
|
|
|
1059
|
+
// src/local-operation-bus.ts
|
|
1060
|
+
var MESSAGE_TYPE = "kora-local-operation";
|
|
1061
|
+
function wireLocalOperationBus(dbName, store, emitter) {
|
|
1062
|
+
if (typeof BroadcastChannel === "undefined") {
|
|
1063
|
+
return () => {
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
const originId = createOriginId();
|
|
1067
|
+
const channel = new BroadcastChannel(`kora-local-ops-${dbName}`);
|
|
1068
|
+
const onMessage = (event) => {
|
|
1069
|
+
const message = event.data;
|
|
1070
|
+
if (message?.type !== MESSAGE_TYPE || message.originId === originId || !isOperationLike(message.operation)) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
store.notifyExternalOperation(message.operation);
|
|
1074
|
+
};
|
|
1075
|
+
channel.addEventListener("message", onMessage);
|
|
1076
|
+
const unsubscribe = emitter.on("operation:created", (event) => {
|
|
1077
|
+
const message = {
|
|
1078
|
+
type: MESSAGE_TYPE,
|
|
1079
|
+
originId,
|
|
1080
|
+
operation: event.operation
|
|
1081
|
+
};
|
|
1082
|
+
channel.postMessage(message);
|
|
1083
|
+
});
|
|
1084
|
+
return () => {
|
|
1085
|
+
unsubscribe();
|
|
1086
|
+
channel.removeEventListener("message", onMessage);
|
|
1087
|
+
channel.close();
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function createOriginId() {
|
|
1091
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
1092
|
+
return crypto.randomUUID();
|
|
1093
|
+
}
|
|
1094
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1095
|
+
}
|
|
1096
|
+
function isOperationLike(value) {
|
|
1097
|
+
if (!value || typeof value !== "object") {
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
1100
|
+
const candidate = value;
|
|
1101
|
+
return typeof candidate.id === "string" && typeof candidate.collection === "string" && typeof candidate.recordId === "string" && typeof candidate.nodeId === "string" && typeof candidate.sequenceNumber === "number";
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1058
1104
|
// src/merge-aware-sync-store.ts
|
|
1059
1105
|
var MergeAwareSyncStore = class {
|
|
1060
1106
|
constructor(store, mergeEngine, emitter, options) {
|
|
@@ -1279,22 +1325,23 @@ function createSyncQuerySubscriptionHook(getSyncEngine) {
|
|
|
1279
1325
|
async function initializeApp(config, emitter, mergeEngine) {
|
|
1280
1326
|
const adapterType = config.store?.adapter ?? detectAdapterType();
|
|
1281
1327
|
const dbName = config.store?.name ?? "kora-db";
|
|
1282
|
-
|
|
1328
|
+
let adapter = await createAdapter(
|
|
1283
1329
|
adapterType,
|
|
1284
1330
|
dbName,
|
|
1285
1331
|
config.store?.workerUrl,
|
|
1286
1332
|
emitter,
|
|
1287
1333
|
config.store?.workerResponseTimeoutMs,
|
|
1288
|
-
config.store?.sharedWorkerUrl
|
|
1334
|
+
config.store?.sharedWorkerUrl,
|
|
1335
|
+
adapterType === "sqlite-wasm"
|
|
1289
1336
|
);
|
|
1290
1337
|
const authBinding = config.sync?.authClient ?? null;
|
|
1291
1338
|
const authNodeId = authBinding?.resolveNodeId ? await authBinding.resolveNodeId() : void 0;
|
|
1292
1339
|
let syncEngine = null;
|
|
1293
1340
|
const encryptionKey = config.sync?.encryption?.key;
|
|
1294
1341
|
const secretKeyProvider = encryptionKey ? typeof encryptionKey === "string" ? () => encryptionKey : () => encryptionKey() : void 0;
|
|
1295
|
-
const
|
|
1342
|
+
const buildStore = (storeAdapter) => new import_store8.Store({
|
|
1296
1343
|
schema: config.schema,
|
|
1297
|
-
adapter,
|
|
1344
|
+
adapter: storeAdapter,
|
|
1298
1345
|
emitter,
|
|
1299
1346
|
dbName,
|
|
1300
1347
|
nodeId: authNodeId,
|
|
@@ -1302,7 +1349,44 @@ async function initializeApp(config, emitter, mergeEngine) {
|
|
|
1302
1349
|
...secretKeyProvider ? { secretKeyProvider } : {},
|
|
1303
1350
|
...config.sync ? { onQuerySubscribed: createSyncQuerySubscriptionHook(() => syncEngine) } : {}
|
|
1304
1351
|
});
|
|
1352
|
+
let store = buildStore(adapter);
|
|
1305
1353
|
await store.open();
|
|
1354
|
+
if (adapterType === "sqlite-wasm" && adapter.getStorageOpenState?.()?.persistent === false) {
|
|
1355
|
+
const fallbackReason = adapter.getStorageOpenState()?.fallbackReason ?? "unsupported";
|
|
1356
|
+
await store.close();
|
|
1357
|
+
try {
|
|
1358
|
+
adapter = await createAdapter(
|
|
1359
|
+
"indexeddb",
|
|
1360
|
+
dbName,
|
|
1361
|
+
config.store?.workerUrl,
|
|
1362
|
+
emitter,
|
|
1363
|
+
config.store?.workerResponseTimeoutMs
|
|
1364
|
+
);
|
|
1365
|
+
store = buildStore(adapter);
|
|
1366
|
+
await store.open();
|
|
1367
|
+
emitter.emit({
|
|
1368
|
+
type: "store:storage-fallback",
|
|
1369
|
+
dbName,
|
|
1370
|
+
from: "opfs",
|
|
1371
|
+
to: "indexeddb",
|
|
1372
|
+
reason: fallbackReason,
|
|
1373
|
+
message: `OPFS persistence is unavailable (${fallbackReason}) for database "${dbName}"; Kora is using durable IndexedDB instead.`
|
|
1374
|
+
});
|
|
1375
|
+
} catch {
|
|
1376
|
+
adapter = await createAdapter(
|
|
1377
|
+
"sqlite-wasm",
|
|
1378
|
+
dbName,
|
|
1379
|
+
config.store?.workerUrl,
|
|
1380
|
+
emitter,
|
|
1381
|
+
config.store?.workerResponseTimeoutMs,
|
|
1382
|
+
config.store?.sharedWorkerUrl,
|
|
1383
|
+
true
|
|
1384
|
+
);
|
|
1385
|
+
store = buildStore(adapter);
|
|
1386
|
+
await store.open();
|
|
1387
|
+
emitOpfsUnavailable(emitter, dbName, fallbackReason);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1306
1390
|
let recordConflict;
|
|
1307
1391
|
const applyPipeline = new ApplyPipeline({
|
|
1308
1392
|
store,
|
|
@@ -1312,6 +1396,7 @@ async function initializeApp(config, emitter, mergeEngine) {
|
|
|
1312
1396
|
});
|
|
1313
1397
|
store.setLocalMutationHandler(applyPipeline);
|
|
1314
1398
|
const unsubscribeAudit = wireAuditPersistence(store, emitter);
|
|
1399
|
+
const unsubscribeLocalOperations = wireLocalOperationBus(dbName, store, emitter);
|
|
1315
1400
|
const blobStore = await resolveBlobStore(config.blob, dbName);
|
|
1316
1401
|
let blobChunkProvider = null;
|
|
1317
1402
|
let unsubscribeSync = null;
|
|
@@ -1366,11 +1451,20 @@ async function initializeApp(config, emitter, mergeEngine) {
|
|
|
1366
1451
|
syncEngine,
|
|
1367
1452
|
unsubscribeSync,
|
|
1368
1453
|
unsubscribeAudit,
|
|
1454
|
+
unsubscribeLocalOperations,
|
|
1369
1455
|
authBinding,
|
|
1370
1456
|
blobStore,
|
|
1371
1457
|
blobChunkProvider
|
|
1372
1458
|
};
|
|
1373
1459
|
}
|
|
1460
|
+
function emitOpfsUnavailable(emitter, dbName, reason) {
|
|
1461
|
+
emitter.emit({
|
|
1462
|
+
type: "store:opfs-unavailable",
|
|
1463
|
+
dbName,
|
|
1464
|
+
reason,
|
|
1465
|
+
message: `OPFS persistence is unavailable (${reason}) for database "${dbName}", and IndexedDB fallback could not open; the store is running in memory and data will not survive a reload.`
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1374
1468
|
|
|
1375
1469
|
// src/sequences-accessor.ts
|
|
1376
1470
|
function createSequencesAccessor(ready, getStore) {
|
|
@@ -1831,6 +1925,7 @@ function createApp(config) {
|
|
|
1831
1925
|
let blobApi = null;
|
|
1832
1926
|
let unsubscribeSync = null;
|
|
1833
1927
|
let unsubscribeAudit = null;
|
|
1928
|
+
let unsubscribeLocalOperations = null;
|
|
1834
1929
|
const syncState = {
|
|
1835
1930
|
syncEngine: null,
|
|
1836
1931
|
syncStatusBridge: null,
|
|
@@ -1847,6 +1942,7 @@ function createApp(config) {
|
|
|
1847
1942
|
store = init.store;
|
|
1848
1943
|
unsubscribeSync = init.unsubscribeSync;
|
|
1849
1944
|
unsubscribeAudit = init.unsubscribeAudit;
|
|
1945
|
+
unsubscribeLocalOperations = init.unsubscribeLocalOperations;
|
|
1850
1946
|
blobApi = createBlobApi(
|
|
1851
1947
|
init.blobStore,
|
|
1852
1948
|
init.blobChunkProvider,
|
|
@@ -1929,6 +2025,10 @@ function createApp(config) {
|
|
|
1929
2025
|
unsubscribeAudit();
|
|
1930
2026
|
unsubscribeAudit = null;
|
|
1931
2027
|
}
|
|
2028
|
+
if (unsubscribeLocalOperations) {
|
|
2029
|
+
unsubscribeLocalOperations();
|
|
2030
|
+
unsubscribeLocalOperations = null;
|
|
2031
|
+
}
|
|
1932
2032
|
if (syncState.syncEngine) {
|
|
1933
2033
|
await syncState.syncEngine.stop();
|
|
1934
2034
|
syncState.syncEngine = null;
|