syncorejs 0.2.3 → 0.2.5

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.
Files changed (94) hide show
  1. package/dist/_dashboard/assets/ConfirmActionDialog-Db4VzVp6.js +1 -0
  2. package/dist/_dashboard/assets/circle-x-VsB4Z8W4.js +1 -0
  3. package/dist/_dashboard/assets/data.lazy-DjdU9CzX.js +18 -0
  4. package/dist/_dashboard/assets/file-code-BrOKjG4n.js +1 -0
  5. package/dist/_dashboard/assets/functions.lazy-DvDwAGHq.js +1 -0
  6. package/dist/_dashboard/assets/funnel-BH8EMMJI.js +1 -0
  7. package/dist/_dashboard/assets/index-DT9ZEELb.css +1 -0
  8. package/dist/_dashboard/assets/index-DrSG4qZZ.js +54 -0
  9. package/dist/_dashboard/assets/loader-circle-CmJFSYga.js +1 -0
  10. package/dist/_dashboard/assets/logs.lazy-50KTk5yd.js +1 -0
  11. package/dist/_dashboard/assets/play-DS52VsLN.js +1 -0
  12. package/dist/_dashboard/assets/queries.lazy-CfysRWkz.js +1 -0
  13. package/dist/_dashboard/assets/scheduler.lazy-BB88mZk-.js +1 -0
  14. package/dist/_dashboard/assets/select-THYcR8Wt.js +1 -0
  15. package/dist/_dashboard/assets/separator-BU7xg615.js +1 -0
  16. package/dist/_dashboard/assets/shared-Bh0wwC2k.js +1 -0
  17. package/dist/_dashboard/assets/sql.lazy-CHtU9Qnt.js +13 -0
  18. package/dist/_dashboard/assets/storage.lazy-CneN7wVU.js +1 -0
  19. package/dist/_dashboard/assets/table-2-CH8JoMXf.js +1 -0
  20. package/dist/_dashboard/index.html +18 -0
  21. package/dist/_vendor/cli/app.d.mts.map +1 -1
  22. package/dist/_vendor/cli/app.mjs +16 -5
  23. package/dist/_vendor/cli/app.mjs.map +1 -1
  24. package/dist/_vendor/core/cli.d.mts.map +1 -1
  25. package/dist/_vendor/core/cli.mjs +358 -16
  26. package/dist/_vendor/core/cli.mjs.map +1 -1
  27. package/dist/_vendor/core/index.d.mts +3 -3
  28. package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
  29. package/dist/_vendor/core/runtime/devtools.mjs +131 -0
  30. package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
  31. package/dist/_vendor/core/runtime/functions.d.mts +3 -3
  32. package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
  33. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +1 -1
  34. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -1
  35. package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs +4 -1
  36. package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs.map +1 -1
  37. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +6 -3
  38. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -1
  39. package/dist/_vendor/core/runtime/internal/engines/shared.mjs +5 -1
  40. package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -1
  41. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +99 -13
  42. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -1
  43. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +38 -4
  44. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -1
  45. package/dist/_vendor/core/runtime/runtime.d.mts +65 -8
  46. package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
  47. package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
  48. package/dist/_vendor/core/transport.d.mts.map +1 -1
  49. package/dist/_vendor/core/transport.mjs +30 -5
  50. package/dist/_vendor/core/transport.mjs.map +1 -1
  51. package/dist/_vendor/devtools-protocol/index.d.ts +75 -1
  52. package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
  53. package/dist/_vendor/devtools-protocol/index.js.map +1 -1
  54. package/dist/_vendor/next/index.js +9 -1
  55. package/dist/_vendor/next/index.js.map +1 -1
  56. package/dist/_vendor/platform-expo/index.d.ts +1 -1
  57. package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
  58. package/dist/_vendor/platform-expo/index.js +6 -1
  59. package/dist/_vendor/platform-expo/index.js.map +1 -1
  60. package/dist/_vendor/platform-node/index.d.mts +2 -1
  61. package/dist/_vendor/platform-node/index.d.mts.map +1 -1
  62. package/dist/_vendor/platform-node/index.mjs +27 -2
  63. package/dist/_vendor/platform-node/index.mjs.map +1 -1
  64. package/dist/_vendor/platform-node/ipc-react.mjs +4 -0
  65. package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
  66. package/dist/_vendor/platform-web/external-change.d.ts +2 -2
  67. package/dist/_vendor/platform-web/external-change.js +2 -2
  68. package/dist/_vendor/platform-web/external-change.js.map +1 -1
  69. package/dist/_vendor/platform-web/index.d.ts +13 -10
  70. package/dist/_vendor/platform-web/index.d.ts.map +1 -1
  71. package/dist/_vendor/platform-web/index.js +66 -10
  72. package/dist/_vendor/platform-web/index.js.map +1 -1
  73. package/dist/_vendor/platform-web/indexeddb.d.ts +3 -3
  74. package/dist/_vendor/platform-web/indexeddb.js +3 -3
  75. package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
  76. package/dist/_vendor/platform-web/opfs.d.ts +3 -1
  77. package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
  78. package/dist/_vendor/platform-web/opfs.js +29 -3
  79. package/dist/_vendor/platform-web/opfs.js.map +1 -1
  80. package/dist/_vendor/platform-web/persistence.d.ts +31 -1
  81. package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
  82. package/dist/_vendor/platform-web/persistence.js.map +1 -1
  83. package/dist/_vendor/platform-web/react.d.ts.map +1 -1
  84. package/dist/_vendor/platform-web/react.js +9 -1
  85. package/dist/_vendor/platform-web/react.js.map +1 -1
  86. package/dist/_vendor/react/index.d.ts +6 -5
  87. package/dist/_vendor/react/index.d.ts.map +1 -1
  88. package/dist/_vendor/react/index.js +6 -5
  89. package/dist/_vendor/react/index.js.map +1 -1
  90. package/dist/_vendor/svelte/index.d.ts +8 -6
  91. package/dist/_vendor/svelte/index.d.ts.map +1 -1
  92. package/dist/_vendor/svelte/index.js +7 -5
  93. package/dist/_vendor/svelte/index.js.map +1 -1
  94. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"indexeddb.js","names":[],"sources":["../src/indexeddb.ts"],"sourcesContent":["import type { SyncoreWebPersistence, StoredWebFile } from \"./persistence.js\";\n\n/** Options for constructing a {@link SyncoreIndexedDbPersistence}. */\nexport interface IndexedDbPersistenceOptions {\n /** IndexedDB database name. Defaults to `\"syncore-web\"`. */\n databaseName?: string;\n}\n\ntype StoredDatabaseRecord = {\n key: string;\n bytes: ArrayBuffer;\n updatedAt: number;\n};\n\ntype StoredFileRecord = {\n key: string;\n bytes: ArrayBuffer;\n contentType: string | null;\n size: number;\n updatedAt: number;\n};\n\n/**\n * IndexedDB-backed {@link SyncoreWebPersistence} implementation.\n *\n * Stores the SQLite database blob and file objects in dedicated IndexedDB\n * object stores. Used automatically when OPFS is unavailable or when the\n * persistence mode is explicitly set to `\"indexeddb\"`.\n *\n * Prefer `createWebPersistence()` over constructing this directly unless\n * you need to pass a specific IndexedDB database name.\n */\nexport class SyncoreIndexedDbPersistence implements SyncoreWebPersistence {\n readonly storageProtocol = \"idb\" as const;\n private readonly databaseName: string;\n\n constructor(options?: IndexedDbPersistenceOptions) {\n this.databaseName = options?.databaseName ?? \"syncore-web\";\n }\n\n async loadDatabase(key: string): Promise<Uint8Array | null> {\n const record = await this.getRecord<StoredDatabaseRecord>(\"databases\", key);\n if (!record) {\n return null;\n }\n return new Uint8Array(record.bytes);\n }\n\n async saveDatabase(key: string, bytes: Uint8Array): Promise<void> {\n await this.putRecord<StoredDatabaseRecord>(\"databases\", {\n key,\n bytes: sliceToArrayBuffer(bytes),\n updatedAt: Date.now()\n });\n }\n\n async getFile(\n namespace: string,\n id: string\n ): Promise<StoredWebFile | null> {\n const record = await this.getRecord<StoredFileRecord>(\n \"files\",\n createNamespacedKey(namespace, id)\n );\n if (!record) {\n return null;\n }\n return {\n id,\n bytes: new Uint8Array(record.bytes),\n contentType: record.contentType,\n size: record.size\n };\n }\n\n async putFile(\n namespace: string,\n id: string,\n bytes: Uint8Array,\n contentType: string | null\n ): Promise<void> {\n await this.putRecord<StoredFileRecord>(\"files\", {\n key: createNamespacedKey(namespace, id),\n bytes: sliceToArrayBuffer(bytes),\n contentType,\n size: bytes.byteLength,\n updatedAt: Date.now()\n });\n }\n\n async deleteFile(namespace: string, id: string): Promise<void> {\n await this.deleteRecord(\"files\", createNamespacedKey(namespace, id));\n }\n\n async listFiles(namespace: string): Promise<StoredWebFile[]> {\n const prefix = `${namespace}:`;\n const records = await this.listRecords<StoredFileRecord>(\"files\");\n return records\n .filter((record) => record.key.startsWith(prefix))\n .map((record) => ({\n id: record.key.slice(prefix.length),\n bytes: new Uint8Array(record.bytes),\n contentType: record.contentType,\n size: record.size\n }));\n }\n\n private async getDatabase(): Promise<IDBDatabase> {\n const indexedDb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!indexedDb) {\n throw new Error(\"IndexedDB is not available in this environment.\");\n }\n\n return new Promise((resolve, reject) => {\n const request = indexedDb.open(this.databaseName, 1);\n request.onupgradeneeded = () => {\n const database = request.result;\n if (!database.objectStoreNames.contains(\"databases\")) {\n database.createObjectStore(\"databases\", { keyPath: \"key\" });\n }\n if (!database.objectStoreNames.contains(\"files\")) {\n database.createObjectStore(\"files\", { keyPath: \"key\" });\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error(\"Failed to open IndexedDB.\"));\n });\n }\n\n private async getRecord<TRecord>(\n storeName: \"databases\" | \"files\",\n key: string\n ): Promise<TRecord | null> {\n const database = await this.getDatabase();\n try {\n return await new Promise<TRecord | null>((resolve, reject) => {\n const transaction = database.transaction(storeName, \"readonly\");\n const request = transaction.objectStore(storeName).get(key);\n request.onsuccess = () =>\n resolve((request.result as TRecord | undefined) ?? null);\n request.onerror = () =>\n reject(\n request.error ?? new Error(`Failed to read ${storeName}/${key}.`)\n );\n });\n } finally {\n database.close();\n }\n }\n\n private async putRecord<TRecord extends { key: string }>(\n storeName: \"databases\" | \"files\",\n record: TRecord\n ): Promise<void> {\n const database = await this.getDatabase();\n try {\n await new Promise<void>((resolve, reject) => {\n const transaction = database.transaction(storeName, \"readwrite\");\n transaction.oncomplete = () => resolve();\n transaction.onerror = () =>\n reject(\n transaction.error ??\n new Error(`Failed to write ${storeName}/${record.key}.`)\n );\n transaction.objectStore(storeName).put(record);\n });\n } finally {\n database.close();\n }\n }\n\n private async deleteRecord(\n storeName: \"databases\" | \"files\",\n key: string\n ): Promise<void> {\n const database = await this.getDatabase();\n try {\n await new Promise<void>((resolve, reject) => {\n const transaction = database.transaction(storeName, \"readwrite\");\n transaction.oncomplete = () => resolve();\n transaction.onerror = () =>\n reject(\n transaction.error ??\n new Error(`Failed to delete ${storeName}/${key}.`)\n );\n transaction.objectStore(storeName).delete(key);\n });\n } finally {\n database.close();\n }\n }\n\n private async listRecords<TRecord>(\n storeName: \"databases\" | \"files\"\n ): Promise<TRecord[]> {\n const database = await this.getDatabase();\n try {\n return await new Promise<TRecord[]>((resolve, reject) => {\n const transaction = database.transaction(storeName, \"readonly\");\n const request = transaction.objectStore(storeName).getAll();\n request.onsuccess = () =>\n resolve((request.result as TRecord[] | undefined) ?? []);\n request.onerror = () =>\n reject(\n request.error ??\n new Error(`Failed to list records from ${storeName}.`)\n );\n });\n } finally {\n database.close();\n }\n }\n}\n\nfunction createNamespacedKey(namespace: string, id: string): string {\n return `${namespace}:${id}`;\n}\n\nfunction sliceToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength\n ) as ArrayBuffer;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"indexeddb.js","names":[],"sources":["../src/indexeddb.ts"],"sourcesContent":["import type { SyncoreWebPersistence, StoredWebFile } from \"./persistence.js\";\n\n/** Options for constructing a {@link SyncoreIndexedDbPersistence}. */\nexport interface IndexedDbPersistenceOptions {\n /** IndexedDB database name. Defaults to `\"syncore-web\"`. */\n databaseName?: string;\n}\n\ntype StoredDatabaseRecord = {\n key: string;\n bytes: ArrayBuffer;\n updatedAt: number;\n};\n\ntype StoredFileRecord = {\n key: string;\n bytes: ArrayBuffer;\n contentType: string | null;\n size: number;\n updatedAt: number;\n};\n\n/**\n * IndexedDB-backed {@link SyncoreWebPersistence} implementation.\n *\n * Stores the SQLite database blob in IndexedDB. It still satisfies the lower\n * level persistence interface for compatibility, but Syncore's default browser\n * file storage is OPFS-only and will not use IndexedDB for blobs.\n *\n * Prefer `createWebPersistence()` over constructing this directly unless\n * you need to pass a specific IndexedDB database name.\n */\nexport class SyncoreIndexedDbPersistence implements SyncoreWebPersistence {\n readonly storageProtocol = \"idb\" as const;\n private readonly databaseName: string;\n\n constructor(options?: IndexedDbPersistenceOptions) {\n this.databaseName = options?.databaseName ?? \"syncore-web\";\n }\n\n async loadDatabase(key: string): Promise<Uint8Array | null> {\n const record = await this.getRecord<StoredDatabaseRecord>(\"databases\", key);\n if (!record) {\n return null;\n }\n return new Uint8Array(record.bytes);\n }\n\n async saveDatabase(key: string, bytes: Uint8Array): Promise<void> {\n await this.putRecord<StoredDatabaseRecord>(\"databases\", {\n key,\n bytes: sliceToArrayBuffer(bytes),\n updatedAt: Date.now()\n });\n }\n\n async getFile(\n namespace: string,\n id: string\n ): Promise<StoredWebFile | null> {\n const record = await this.getRecord<StoredFileRecord>(\n \"files\",\n createNamespacedKey(namespace, id)\n );\n if (!record) {\n return null;\n }\n return {\n id,\n bytes: new Uint8Array(record.bytes),\n contentType: record.contentType,\n size: record.size\n };\n }\n\n async putFile(\n namespace: string,\n id: string,\n bytes: Uint8Array,\n contentType: string | null\n ): Promise<void> {\n await this.putRecord<StoredFileRecord>(\"files\", {\n key: createNamespacedKey(namespace, id),\n bytes: sliceToArrayBuffer(bytes),\n contentType,\n size: bytes.byteLength,\n updatedAt: Date.now()\n });\n }\n\n async deleteFile(namespace: string, id: string): Promise<void> {\n await this.deleteRecord(\"files\", createNamespacedKey(namespace, id));\n }\n\n async listFiles(namespace: string): Promise<StoredWebFile[]> {\n const prefix = `${namespace}:`;\n const records = await this.listRecords<StoredFileRecord>(\"files\");\n return records\n .filter((record) => record.key.startsWith(prefix))\n .map((record) => ({\n id: record.key.slice(prefix.length),\n bytes: new Uint8Array(record.bytes),\n contentType: record.contentType,\n size: record.size\n }));\n }\n\n private async getDatabase(): Promise<IDBDatabase> {\n const indexedDb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!indexedDb) {\n throw new Error(\"IndexedDB is not available in this environment.\");\n }\n\n return new Promise((resolve, reject) => {\n const request = indexedDb.open(this.databaseName, 1);\n request.onupgradeneeded = () => {\n const database = request.result;\n if (!database.objectStoreNames.contains(\"databases\")) {\n database.createObjectStore(\"databases\", { keyPath: \"key\" });\n }\n if (!database.objectStoreNames.contains(\"files\")) {\n database.createObjectStore(\"files\", { keyPath: \"key\" });\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error(\"Failed to open IndexedDB.\"));\n });\n }\n\n private async getRecord<TRecord>(\n storeName: \"databases\" | \"files\",\n key: string\n ): Promise<TRecord | null> {\n const database = await this.getDatabase();\n try {\n return await new Promise<TRecord | null>((resolve, reject) => {\n const transaction = database.transaction(storeName, \"readonly\");\n const request = transaction.objectStore(storeName).get(key);\n request.onsuccess = () =>\n resolve((request.result as TRecord | undefined) ?? null);\n request.onerror = () =>\n reject(\n request.error ?? new Error(`Failed to read ${storeName}/${key}.`)\n );\n });\n } finally {\n database.close();\n }\n }\n\n private async putRecord<TRecord extends { key: string }>(\n storeName: \"databases\" | \"files\",\n record: TRecord\n ): Promise<void> {\n const database = await this.getDatabase();\n try {\n await new Promise<void>((resolve, reject) => {\n const transaction = database.transaction(storeName, \"readwrite\");\n transaction.oncomplete = () => resolve();\n transaction.onerror = () =>\n reject(\n transaction.error ??\n new Error(`Failed to write ${storeName}/${record.key}.`)\n );\n transaction.objectStore(storeName).put(record);\n });\n } finally {\n database.close();\n }\n }\n\n private async deleteRecord(\n storeName: \"databases\" | \"files\",\n key: string\n ): Promise<void> {\n const database = await this.getDatabase();\n try {\n await new Promise<void>((resolve, reject) => {\n const transaction = database.transaction(storeName, \"readwrite\");\n transaction.oncomplete = () => resolve();\n transaction.onerror = () =>\n reject(\n transaction.error ??\n new Error(`Failed to delete ${storeName}/${key}.`)\n );\n transaction.objectStore(storeName).delete(key);\n });\n } finally {\n database.close();\n }\n }\n\n private async listRecords<TRecord>(\n storeName: \"databases\" | \"files\"\n ): Promise<TRecord[]> {\n const database = await this.getDatabase();\n try {\n return await new Promise<TRecord[]>((resolve, reject) => {\n const transaction = database.transaction(storeName, \"readonly\");\n const request = transaction.objectStore(storeName).getAll();\n request.onsuccess = () =>\n resolve((request.result as TRecord[] | undefined) ?? []);\n request.onerror = () =>\n reject(\n request.error ??\n new Error(`Failed to list records from ${storeName}.`)\n );\n });\n } finally {\n database.close();\n }\n }\n}\n\nfunction createNamespacedKey(namespace: string, id: string): string {\n return `${namespace}:${id}`;\n}\n\nfunction sliceToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength\n ) as ArrayBuffer;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { StoredWebFile, SyncoreWebPersistence } from "./persistence.js";
1
+ import { StoredWebFile, StoredWebFileMetadata, StoredWebFileRange, SyncoreWebPersistence } from "./persistence.js";
2
2
 
3
3
  //#region src/opfs.d.ts
4
4
  /** Options for constructing a {@link SyncoreOpfsPersistence}. */
@@ -25,9 +25,11 @@ declare class SyncoreOpfsPersistence implements SyncoreWebPersistence {
25
25
  loadDatabase(key: string): Promise<Uint8Array | null>;
26
26
  saveDatabase(key: string, bytes: Uint8Array): Promise<void>;
27
27
  getFile(namespace: string, id: string): Promise<StoredWebFile | null>;
28
+ getFileRange(namespace: string, id: string, offset: number, length: number): Promise<StoredWebFileRange | null>;
28
29
  putFile(namespace: string, id: string, bytes: Uint8Array, contentType: string | null): Promise<void>;
29
30
  deleteFile(namespace: string, id: string): Promise<void>;
30
31
  listFiles(namespace: string): Promise<StoredWebFile[]>;
32
+ listFileMetadata(namespace: string): Promise<StoredWebFileMetadata[]>;
31
33
  private ensureDirectory;
32
34
  private getOptionalDirectory;
33
35
  private getOptionalFileHandle;
@@ -1 +1 @@
1
- {"version":3,"file":"opfs.d.ts","names":[],"sources":["../src/opfs.ts"],"mappings":";;;;UAGiB,sBAAA;EAAA;EAEf,iBAAiB;AAAA;;AAAA;AAsBnB;;;;;;;;;cAAa,sBAAA,YAAkC,qBAAA;EAAA,iBAIhB,OAAA;EAAA,SAHpB,eAAA;EAAA,QACD,oBAAA;cAEqB,OAAA,GAAS,sBAAA;EAEhC,YAAA,CAAa,GAAA,WAAc,OAAA,CAAQ,UAAA;EAWnC,YAAA,CAAa,GAAA,UAAa,KAAA,EAAO,UAAA,GAAa,OAAA;EAU9C,OAAA,CAAQ,SAAA,UAAmB,EAAA,WAAa,OAAA,CAAQ,aAAA;EA0BhD,OAAA,CACJ,SAAA,UACA,EAAA,UACA,KAAA,EAAO,UAAA,EACP,WAAA,kBACC,OAAA;EAcG,UAAA,CAAW,SAAA,UAAmB,EAAA,WAAa,OAAA;EAW3C,SAAA,CAAU,SAAA,WAAoB,OAAA,CAAQ,aAAA;EAAA,QA4B9B,eAAA;EAAA,QAUA,oBAAA;EAAA,QAiBA,qBAAA;EAAA,QAWA,kCAAA;EAAA,QAcA,YAAA;EAAA,QAaA,gBAAA;AAAA"}
1
+ {"version":3,"file":"opfs.d.ts","names":[],"sources":["../src/opfs.ts"],"mappings":";;;;UAQiB,sBAAA;EAAA;EAEf,iBAAiB;AAAA;;AAAA;AAsBnB;;;;;;;;;cAAa,sBAAA,YAAkC,qBAAA;EAAA,iBAIhB,OAAA;EAAA,SAHpB,eAAA;EAAA,QACD,oBAAA;cAEqB,OAAA,GAAS,sBAAA;EAEhC,YAAA,CAAa,GAAA,WAAc,OAAA,CAAQ,UAAA;EAWnC,YAAA,CAAa,GAAA,UAAa,KAAA,EAAO,UAAA,GAAa,OAAA;EAU9C,OAAA,CAAQ,SAAA,UAAmB,EAAA,WAAa,OAAA,CAAQ,aAAA;EAgChD,YAAA,CACJ,SAAA,UACA,EAAA,UACA,MAAA,UACA,MAAA,WACC,OAAA,CAAQ,kBAAA;EAqCL,OAAA,CACJ,SAAA,UACA,EAAA,UACA,KAAA,EAAO,UAAA,EACP,WAAA,kBACC,OAAA;EAiBG,UAAA,CAAW,SAAA,UAAmB,EAAA,WAAa,OAAA;EAc3C,SAAA,CAAU,SAAA,WAAoB,OAAA,CAAQ,aAAA;EAUtC,gBAAA,CAAiB,SAAA,WAAoB,OAAA,CAAQ,qBAAA;EAAA,QAiCrC,eAAA;EAAA,QAUA,oBAAA;EAAA,QAiBA,qBAAA;EAAA,QAWA,kCAAA;EAAA,QAcA,YAAA;EAAA,QAgBA,gBAAA;AAAA"}
@@ -40,6 +40,24 @@ var SyncoreOpfsPersistence = class {
40
40
  contentType: metadata?.contentType ?? null
41
41
  };
42
42
  }
43
+ async getFileRange(namespace, id, offset, length) {
44
+ const directory = await this.getOptionalDirectory(["files", encodePathComponent(namespace)]);
45
+ if (!directory) return null;
46
+ const encodedId = encodePathComponent(id);
47
+ const fileHandle = await this.getOptionalFileHandleFromDirectory(directory, `${encodedId}.bin`);
48
+ if (!fileHandle) return null;
49
+ const [file, metadata] = await Promise.all([fileHandle.getFile(), this.readMetadata(directory, `${encodedId}.meta.json`)]);
50
+ const normalizedOffset = Math.max(offset, 0);
51
+ const normalizedLength = Math.max(length, 0);
52
+ const slice = file.slice(normalizedOffset, normalizedOffset + normalizedLength);
53
+ return {
54
+ id,
55
+ bytes: new Uint8Array(await slice.arrayBuffer()),
56
+ size: file.size,
57
+ contentType: metadata?.contentType ?? null,
58
+ offset: normalizedOffset
59
+ };
60
+ }
43
61
  async putFile(namespace, id, bytes, contentType) {
44
62
  const directory = await this.ensureDirectory(["files", encodePathComponent(namespace)]);
45
63
  const encodedId = encodePathComponent(id);
@@ -54,6 +72,15 @@ var SyncoreOpfsPersistence = class {
54
72
  await removeEntryIfExists(directory, `${encodedId}.meta.json`);
55
73
  }
56
74
  async listFiles(namespace) {
75
+ const metadata = await this.listFileMetadata(namespace);
76
+ return Promise.all(metadata.map(async (file) => {
77
+ return await this.getFile(namespace, file.id) ?? {
78
+ ...file,
79
+ bytes: new Uint8Array()
80
+ };
81
+ }));
82
+ }
83
+ async listFileMetadata(namespace) {
57
84
  const directory = await this.getOptionalDirectory(["files", encodePathComponent(namespace)]);
58
85
  if (!directory) return [];
59
86
  const files = [];
@@ -62,12 +89,11 @@ var SyncoreOpfsPersistence = class {
62
89
  if (handle.kind !== "file" || !name.endsWith(".bin")) continue;
63
90
  const encodedId = name.slice(0, -4);
64
91
  const id = decodeURIComponent(encodedId);
65
- const bytes = await readFileBytes(handle);
92
+ const file = await handle.getFile();
66
93
  const metadata = await this.readMetadata(directory, `${encodedId}.meta.json`);
67
94
  files.push({
68
95
  id,
69
- bytes,
70
- size: bytes.byteLength,
96
+ size: file.size,
71
97
  contentType: metadata?.contentType ?? null
72
98
  });
73
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"opfs.js","names":[],"sources":["../src/opfs.ts"],"sourcesContent":["import type { SyncoreWebPersistence, StoredWebFile } from \"./persistence.js\";\n\n/** Options for constructing a {@link SyncoreOpfsPersistence}. */\nexport interface OpfsPersistenceOptions {\n /** Root directory name inside the Origin Private File System bucket. Defaults to `\"syncore\"`. */\n rootDirectoryName?: string;\n}\n\ntype StoredFileMetadata = {\n contentType: string | null;\n};\n\ntype OpfsStorageManager = StorageManager & {\n getDirectory?: () => Promise<FileSystemDirectoryHandle>;\n};\n\n/**\n * Origin Private File System (OPFS) backed {@link SyncoreWebPersistence}.\n *\n * Stores the SQLite database blob as a `.sqlite` file and binary file objects\n * as individual OPFS entries under a configurable root directory. OPFS offers\n * significantly better I/O throughput than IndexedDB and is the preferred\n * persistence backend when available.\n *\n * Used automatically by `createWebPersistence()` in `\"opfs\"` or `\"auto\"` mode\n * when `isOpfsAvailable()` returns `true`.\n */\nexport class SyncoreOpfsPersistence implements SyncoreWebPersistence {\n readonly storageProtocol = \"opfs\" as const;\n private rootDirectoryPromise: Promise<FileSystemDirectoryHandle> | undefined;\n\n constructor(private readonly options: OpfsPersistenceOptions = {}) {}\n\n async loadDatabase(key: string): Promise<Uint8Array | null> {\n const handle = await this.getOptionalFileHandle(\n [\"databases\"],\n `${encodePathComponent(key)}.sqlite`\n );\n if (!handle) {\n return null;\n }\n return readFileBytes(handle);\n }\n\n async saveDatabase(key: string, bytes: Uint8Array): Promise<void> {\n const directory = await this.ensureDirectory([\"databases\"]);\n await writeBytes(\n await directory.getFileHandle(`${encodePathComponent(key)}.sqlite`, {\n create: true\n }),\n bytes\n );\n }\n\n async getFile(namespace: string, id: string): Promise<StoredWebFile | null> {\n const directory = await this.getOptionalDirectory([\"files\", encodePathComponent(namespace)]);\n if (!directory) {\n return null;\n }\n\n const fileName = `${encodePathComponent(id)}.bin`;\n const metadataName = `${encodePathComponent(id)}.meta.json`;\n const fileHandle = await this.getOptionalFileHandleFromDirectory(directory, fileName);\n if (!fileHandle) {\n return null;\n }\n\n const [bytes, metadata] = await Promise.all([\n readFileBytes(fileHandle),\n this.readMetadata(directory, metadataName)\n ]);\n\n return {\n id,\n bytes,\n size: bytes.byteLength,\n contentType: metadata?.contentType ?? null\n };\n }\n\n async putFile(\n namespace: string,\n id: string,\n bytes: Uint8Array,\n contentType: string | null\n ): Promise<void> {\n const directory = await this.ensureDirectory([\"files\", encodePathComponent(namespace)]);\n const encodedId = encodePathComponent(id);\n\n await writeBytes(\n await directory.getFileHandle(`${encodedId}.bin`, { create: true }),\n bytes\n );\n await writeText(\n await directory.getFileHandle(`${encodedId}.meta.json`, { create: true }),\n JSON.stringify({ contentType } satisfies StoredFileMetadata)\n );\n }\n\n async deleteFile(namespace: string, id: string): Promise<void> {\n const directory = await this.getOptionalDirectory([\"files\", encodePathComponent(namespace)]);\n if (!directory) {\n return;\n }\n\n const encodedId = encodePathComponent(id);\n await removeEntryIfExists(directory, `${encodedId}.bin`);\n await removeEntryIfExists(directory, `${encodedId}.meta.json`);\n }\n\n async listFiles(namespace: string): Promise<StoredWebFile[]> {\n const directory = await this.getOptionalDirectory([\"files\", encodePathComponent(namespace)]);\n if (!directory) {\n return [];\n }\n\n const files: StoredWebFile[] = [];\n const iterableDirectory = directory as FileSystemDirectoryHandle & {\n entries(): AsyncIterable<[string, FileSystemHandle]>;\n };\n for await (const [name, handle] of iterableDirectory.entries()) {\n if (handle.kind !== \"file\" || !name.endsWith(\".bin\")) {\n continue;\n }\n const encodedId = name.slice(0, -4);\n const id = decodeURIComponent(encodedId);\n const bytes = await readFileBytes(handle as FileSystemFileHandle);\n const metadata = await this.readMetadata(directory, `${encodedId}.meta.json`);\n files.push({\n id,\n bytes,\n size: bytes.byteLength,\n contentType: metadata?.contentType ?? null\n });\n }\n return files;\n }\n\n private async ensureDirectory(\n pathSegments: string[]\n ): Promise<FileSystemDirectoryHandle> {\n let directory = await this.getRootDirectory();\n for (const segment of pathSegments) {\n directory = await directory.getDirectoryHandle(segment, { create: true });\n }\n return directory;\n }\n\n private async getOptionalDirectory(\n pathSegments: string[]\n ): Promise<FileSystemDirectoryHandle | null> {\n try {\n let directory = await this.getRootDirectory();\n for (const segment of pathSegments) {\n directory = await directory.getDirectoryHandle(segment);\n }\n return directory;\n } catch (error) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n private async getOptionalFileHandle(\n pathSegments: string[],\n fileName: string\n ): Promise<FileSystemFileHandle | null> {\n const directory = await this.getOptionalDirectory(pathSegments);\n if (!directory) {\n return null;\n }\n return this.getOptionalFileHandleFromDirectory(directory, fileName);\n }\n\n private async getOptionalFileHandleFromDirectory(\n directory: FileSystemDirectoryHandle,\n fileName: string\n ): Promise<FileSystemFileHandle | null> {\n try {\n return await directory.getFileHandle(fileName);\n } catch (error) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n private async readMetadata(\n directory: FileSystemDirectoryHandle,\n fileName: string\n ): Promise<StoredFileMetadata | null> {\n const handle = await this.getOptionalFileHandleFromDirectory(directory, fileName);\n if (!handle) {\n return null;\n }\n\n const bytes = await readFileBytes(handle);\n return JSON.parse(new TextDecoder().decode(bytes)) as StoredFileMetadata;\n }\n\n private async getRootDirectory(): Promise<FileSystemDirectoryHandle> {\n if (!this.rootDirectoryPromise) {\n this.rootDirectoryPromise = (async () => {\n const storageManager = getOpfsStorageManager();\n if (!storageManager?.getDirectory) {\n throw new Error(\"OPFS is not available in this environment.\");\n }\n const root = await storageManager.getDirectory();\n return root.getDirectoryHandle(\n this.options.rootDirectoryName ?? \"syncore\",\n { create: true }\n );\n })();\n }\n return this.rootDirectoryPromise;\n }\n}\n\nasync function readFileBytes(handle: FileSystemFileHandle): Promise<Uint8Array> {\n const file = await handle.getFile();\n return new Uint8Array(await file.arrayBuffer());\n}\n\nasync function writeBytes(\n handle: FileSystemFileHandle,\n bytes: Uint8Array\n): Promise<void> {\n const writable = await handle.createWritable();\n try {\n await writable.write(sliceToArrayBuffer(bytes));\n await writable.truncate(bytes.byteLength);\n } finally {\n await writable.close();\n }\n}\n\nasync function writeText(\n handle: FileSystemFileHandle,\n value: string\n): Promise<void> {\n await writeBytes(handle, new TextEncoder().encode(value));\n}\n\nasync function removeEntryIfExists(\n directory: FileSystemDirectoryHandle,\n name: string\n): Promise<void> {\n try {\n await directory.removeEntry(name);\n } catch (error) {\n if (!isNotFoundError(error)) {\n throw error;\n }\n }\n}\n\nfunction encodePathComponent(value: string): string {\n return encodeURIComponent(value);\n}\n\nfunction sliceToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength\n ) as ArrayBuffer;\n}\n\nfunction getOpfsStorageManager(): OpfsStorageManager | undefined {\n if (typeof navigator === \"undefined\") {\n return undefined;\n }\n return navigator.storage as OpfsStorageManager | undefined;\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"name\" in error &&\n error.name === \"NotFoundError\"\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"opfs.js","names":[],"sources":["../src/opfs.ts"],"sourcesContent":["import type {\n SyncoreWebPersistence,\n StoredWebFile,\n StoredWebFileMetadata,\n StoredWebFileRange\n} from \"./persistence.js\";\n\n/** Options for constructing a {@link SyncoreOpfsPersistence}. */\nexport interface OpfsPersistenceOptions {\n /** Root directory name inside the Origin Private File System bucket. Defaults to `\"syncore\"`. */\n rootDirectoryName?: string;\n}\n\ntype StoredFileMetadata = {\n contentType: string | null;\n};\n\ntype OpfsStorageManager = StorageManager & {\n getDirectory?: () => Promise<FileSystemDirectoryHandle>;\n};\n\n/**\n * Origin Private File System (OPFS) backed {@link SyncoreWebPersistence}.\n *\n * Stores the SQLite database blob as a `.sqlite` file and binary file objects\n * as individual OPFS entries under a configurable root directory. OPFS offers\n * significantly better I/O throughput than IndexedDB and is the preferred\n * persistence backend when available.\n *\n * Used automatically by `createWebPersistence()` in `\"opfs\"` or `\"auto\"` mode\n * when `isOpfsAvailable()` returns `true`.\n */\nexport class SyncoreOpfsPersistence implements SyncoreWebPersistence {\n readonly storageProtocol = \"opfs\" as const;\n private rootDirectoryPromise: Promise<FileSystemDirectoryHandle> | undefined;\n\n constructor(private readonly options: OpfsPersistenceOptions = {}) {}\n\n async loadDatabase(key: string): Promise<Uint8Array | null> {\n const handle = await this.getOptionalFileHandle(\n [\"databases\"],\n `${encodePathComponent(key)}.sqlite`\n );\n if (!handle) {\n return null;\n }\n return readFileBytes(handle);\n }\n\n async saveDatabase(key: string, bytes: Uint8Array): Promise<void> {\n const directory = await this.ensureDirectory([\"databases\"]);\n await writeBytes(\n await directory.getFileHandle(`${encodePathComponent(key)}.sqlite`, {\n create: true\n }),\n bytes\n );\n }\n\n async getFile(namespace: string, id: string): Promise<StoredWebFile | null> {\n const directory = await this.getOptionalDirectory([\n \"files\",\n encodePathComponent(namespace)\n ]);\n if (!directory) {\n return null;\n }\n\n const fileName = `${encodePathComponent(id)}.bin`;\n const metadataName = `${encodePathComponent(id)}.meta.json`;\n const fileHandle = await this.getOptionalFileHandleFromDirectory(\n directory,\n fileName\n );\n if (!fileHandle) {\n return null;\n }\n\n const [bytes, metadata] = await Promise.all([\n readFileBytes(fileHandle),\n this.readMetadata(directory, metadataName)\n ]);\n\n return {\n id,\n bytes,\n size: bytes.byteLength,\n contentType: metadata?.contentType ?? null\n };\n }\n\n async getFileRange(\n namespace: string,\n id: string,\n offset: number,\n length: number\n ): Promise<StoredWebFileRange | null> {\n const directory = await this.getOptionalDirectory([\n \"files\",\n encodePathComponent(namespace)\n ]);\n if (!directory) {\n return null;\n }\n\n const encodedId = encodePathComponent(id);\n const fileHandle = await this.getOptionalFileHandleFromDirectory(\n directory,\n `${encodedId}.bin`\n );\n if (!fileHandle) {\n return null;\n }\n\n const [file, metadata] = await Promise.all([\n fileHandle.getFile(),\n this.readMetadata(directory, `${encodedId}.meta.json`)\n ]);\n const normalizedOffset = Math.max(offset, 0);\n const normalizedLength = Math.max(length, 0);\n const slice = file.slice(\n normalizedOffset,\n normalizedOffset + normalizedLength\n );\n return {\n id,\n bytes: new Uint8Array(await slice.arrayBuffer()),\n size: file.size,\n contentType: metadata?.contentType ?? null,\n offset: normalizedOffset\n };\n }\n\n async putFile(\n namespace: string,\n id: string,\n bytes: Uint8Array,\n contentType: string | null\n ): Promise<void> {\n const directory = await this.ensureDirectory([\n \"files\",\n encodePathComponent(namespace)\n ]);\n const encodedId = encodePathComponent(id);\n\n await writeBytes(\n await directory.getFileHandle(`${encodedId}.bin`, { create: true }),\n bytes\n );\n await writeText(\n await directory.getFileHandle(`${encodedId}.meta.json`, { create: true }),\n JSON.stringify({ contentType } satisfies StoredFileMetadata)\n );\n }\n\n async deleteFile(namespace: string, id: string): Promise<void> {\n const directory = await this.getOptionalDirectory([\n \"files\",\n encodePathComponent(namespace)\n ]);\n if (!directory) {\n return;\n }\n\n const encodedId = encodePathComponent(id);\n await removeEntryIfExists(directory, `${encodedId}.bin`);\n await removeEntryIfExists(directory, `${encodedId}.meta.json`);\n }\n\n async listFiles(namespace: string): Promise<StoredWebFile[]> {\n const metadata = await this.listFileMetadata(namespace);\n return Promise.all(\n metadata.map(async (file) => {\n const fullFile = await this.getFile(namespace, file.id);\n return fullFile ?? { ...file, bytes: new Uint8Array() };\n })\n );\n }\n\n async listFileMetadata(namespace: string): Promise<StoredWebFileMetadata[]> {\n const directory = await this.getOptionalDirectory([\n \"files\",\n encodePathComponent(namespace)\n ]);\n if (!directory) {\n return [];\n }\n\n const files: StoredWebFileMetadata[] = [];\n const iterableDirectory = directory as FileSystemDirectoryHandle & {\n entries(): AsyncIterable<[string, FileSystemHandle]>;\n };\n for await (const [name, handle] of iterableDirectory.entries()) {\n if (handle.kind !== \"file\" || !name.endsWith(\".bin\")) {\n continue;\n }\n const encodedId = name.slice(0, -4);\n const id = decodeURIComponent(encodedId);\n const file = await (handle as FileSystemFileHandle).getFile();\n const metadata = await this.readMetadata(\n directory,\n `${encodedId}.meta.json`\n );\n files.push({\n id,\n size: file.size,\n contentType: metadata?.contentType ?? null\n });\n }\n return files;\n }\n\n private async ensureDirectory(\n pathSegments: string[]\n ): Promise<FileSystemDirectoryHandle> {\n let directory = await this.getRootDirectory();\n for (const segment of pathSegments) {\n directory = await directory.getDirectoryHandle(segment, { create: true });\n }\n return directory;\n }\n\n private async getOptionalDirectory(\n pathSegments: string[]\n ): Promise<FileSystemDirectoryHandle | null> {\n try {\n let directory = await this.getRootDirectory();\n for (const segment of pathSegments) {\n directory = await directory.getDirectoryHandle(segment);\n }\n return directory;\n } catch (error) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n private async getOptionalFileHandle(\n pathSegments: string[],\n fileName: string\n ): Promise<FileSystemFileHandle | null> {\n const directory = await this.getOptionalDirectory(pathSegments);\n if (!directory) {\n return null;\n }\n return this.getOptionalFileHandleFromDirectory(directory, fileName);\n }\n\n private async getOptionalFileHandleFromDirectory(\n directory: FileSystemDirectoryHandle,\n fileName: string\n ): Promise<FileSystemFileHandle | null> {\n try {\n return await directory.getFileHandle(fileName);\n } catch (error) {\n if (isNotFoundError(error)) {\n return null;\n }\n throw error;\n }\n }\n\n private async readMetadata(\n directory: FileSystemDirectoryHandle,\n fileName: string\n ): Promise<StoredFileMetadata | null> {\n const handle = await this.getOptionalFileHandleFromDirectory(\n directory,\n fileName\n );\n if (!handle) {\n return null;\n }\n\n const bytes = await readFileBytes(handle);\n return JSON.parse(new TextDecoder().decode(bytes)) as StoredFileMetadata;\n }\n\n private async getRootDirectory(): Promise<FileSystemDirectoryHandle> {\n if (!this.rootDirectoryPromise) {\n this.rootDirectoryPromise = (async () => {\n const storageManager = getOpfsStorageManager();\n if (!storageManager?.getDirectory) {\n throw new Error(\"OPFS is not available in this environment.\");\n }\n const root = await storageManager.getDirectory();\n return root.getDirectoryHandle(\n this.options.rootDirectoryName ?? \"syncore\",\n { create: true }\n );\n })();\n }\n return this.rootDirectoryPromise;\n }\n}\n\nasync function readFileBytes(\n handle: FileSystemFileHandle\n): Promise<Uint8Array> {\n const file = await handle.getFile();\n return new Uint8Array(await file.arrayBuffer());\n}\n\nasync function writeBytes(\n handle: FileSystemFileHandle,\n bytes: Uint8Array\n): Promise<void> {\n const writable = await handle.createWritable();\n try {\n await writable.write(sliceToArrayBuffer(bytes));\n await writable.truncate(bytes.byteLength);\n } finally {\n await writable.close();\n }\n}\n\nasync function writeText(\n handle: FileSystemFileHandle,\n value: string\n): Promise<void> {\n await writeBytes(handle, new TextEncoder().encode(value));\n}\n\nasync function removeEntryIfExists(\n directory: FileSystemDirectoryHandle,\n name: string\n): Promise<void> {\n try {\n await directory.removeEntry(name);\n } catch (error) {\n if (!isNotFoundError(error)) {\n throw error;\n }\n }\n}\n\nfunction encodePathComponent(value: string): string {\n return encodeURIComponent(value);\n}\n\nfunction sliceToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength\n ) as ArrayBuffer;\n}\n\nfunction getOpfsStorageManager(): OpfsStorageManager | undefined {\n if (typeof navigator === \"undefined\") {\n return undefined;\n }\n return navigator.storage as OpfsStorageManager | undefined;\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"name\" in error &&\n error.name === \"NotFoundError\"\n );\n}\n"],"mappings":"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"}
@@ -13,6 +13,32 @@ interface StoredWebFile {
13
13
  /** File size in bytes. */
14
14
  size: number;
15
15
  }
16
+ /**
17
+ * A byte range from a web persistence file, plus total object metadata.
18
+ */
19
+ interface StoredWebFileRange {
20
+ /** Unique file identifier within its namespace. */
21
+ id: string;
22
+ /** Raw bytes for the requested range. */
23
+ bytes: Uint8Array;
24
+ /** MIME type, or `null` if none was recorded at write time. */
25
+ contentType: string | null;
26
+ /** Total file size in bytes. */
27
+ size: number;
28
+ /** Offset used for this range. */
29
+ offset: number;
30
+ }
31
+ /**
32
+ * Metadata for a web persistence file without the raw bytes.
33
+ */
34
+ interface StoredWebFileMetadata {
35
+ /** Unique file identifier within its namespace. */
36
+ id: string;
37
+ /** MIME type, or `null` if none was recorded at write time. */
38
+ contentType: string | null;
39
+ /** Total file size in bytes. */
40
+ size: number;
41
+ }
16
42
  /**
17
43
  * Abstraction over browser storage backends (OPFS or IndexedDB).
18
44
  *
@@ -31,12 +57,16 @@ interface SyncoreWebPersistence {
31
57
  saveDatabase(key: string, bytes: Uint8Array): Promise<void>;
32
58
  /** Retrieve a stored file from `namespace` by `id`, or `null` if not found. */
33
59
  getFile(namespace: string, id: string): Promise<StoredWebFile | null>;
60
+ /** Retrieve a byte range without loading the whole file when supported. */
61
+ getFileRange?(namespace: string, id: string, offset: number, length: number): Promise<StoredWebFileRange | null>;
34
62
  /** Write a file into `namespace` under `id`, replacing any existing entry. */
35
63
  putFile(namespace: string, id: string, bytes: Uint8Array, contentType: string | null): Promise<void>;
36
64
  /** Delete a file from `namespace` by `id`. No-op if the file does not exist. */
37
65
  deleteFile(namespace: string, id: string): Promise<void>;
38
66
  /** List all stored files in `namespace`. */
39
67
  listFiles(namespace: string): Promise<StoredWebFile[]>;
68
+ /** List stored file metadata without loading file bytes when supported. */
69
+ listFileMetadata?(namespace: string): Promise<StoredWebFileMetadata[]>;
40
70
  }
41
71
  /**
42
72
  * Which browser storage backend Syncore should use for SQLite persistence.
@@ -77,5 +107,5 @@ declare function createWebPersistence(options?: CreateWebPersistenceOptions): Pr
77
107
  */
78
108
  declare function isOpfsAvailable(): boolean;
79
109
  //#endregion
80
- export { CreateWebPersistenceOptions, StoredWebFile, SyncoreWebPersistence, WebPersistenceMode, createWebPersistence, isOpfsAvailable };
110
+ export { CreateWebPersistenceOptions, StoredWebFile, StoredWebFileMetadata, StoredWebFileRange, SyncoreWebPersistence, WebPersistenceMode, createWebPersistence, isOpfsAvailable };
81
111
  //# sourceMappingURL=persistence.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.ts","names":[],"sources":["../src/persistence.ts"],"mappings":";;AAOA;;;UAAiB,aAAA;EAEf;EAAA,EAAA;EAEO;EAAP,KAAA,EAAO,UAAU;EAIjB;EAFA,WAAA;EAEI;EAAJ,IAAA;AAAA;;;;;;;;;;UAYe,qBAAA;EAmBuB;EAAA,SAjB7B,eAAA;EAiB4B;EAfrC,YAAA,CAAa,GAAA,WAAc,OAAA,CAAQ,UAAA;EAF1B;EAIT,YAAA,CAAa,GAAA,UAAa,KAAA,EAAO,UAAA,GAAa,OAAA;EAFjC;EAIb,OAAA,CAAQ,SAAA,UAAmB,EAAA,WAAa,OAAA,CAAQ,aAAA;EAJb;EAMnC,OAAA,CACE,SAAA,UACA,EAAA,UACA,KAAA,EAAO,UAAA,EACP,WAAA,kBACC,OAAA;EATU;EAWb,UAAA,CAAW,SAAA,UAAmB,EAAA,WAAa,OAAA;EAXjB;EAa1B,SAAA,CAAU,SAAA,WAAoB,OAAA,CAAQ,aAAA;AAAA;;;;;;;;;;;;KAc5B,kBAAA;;UAGK,2BAAA;EAnB4B;EAqB3C,IAAA,GAAO,kBAAkB;EAnBf;EAqBV,qBAAA;EArBsC;EAuBtC,qBAAA;AAAA;AATF;;;;AAA8B;AAG9B;;;AAHA,iBAoBsB,oBAAA,CACpB,OAAA,GAAS,2BAAA,GACR,OAAA,CAAQ,qBAAA;;;;;;AAbY;AAWvB;iBAsCgB,eAAA,CAAA"}
1
+ {"version":3,"file":"persistence.d.ts","names":[],"sources":["../src/persistence.ts"],"mappings":";;AAOA;;;UAAiB,aAAA;EAEf;EAAA,EAAA;EAEO;EAAP,KAAA,EAAO,UAAU;EAIjB;EAFA,WAAA;EAEI;EAAJ,IAAA;AAAA;;;;UAMe,kBAAA;EAIR;EAFP,EAAA;EAMA;EAJA,KAAA,EAAO,UAAU;EAMX;EAJN,WAAA;EAUe;EARf,IAAA;;EAEA,MAAA;AAAA;;;;UAMe,qBAAA;EAkBA;EAhBf,EAAA;;EAEA,WAAA;EAkB2B;EAhB3B,IAAA;AAAA;;;;;;;;;;UAYe,qBAAA;EA4BuB;EAAA,SA1B7B,eAAA;EA0BoC;EAxB7C,YAAA,CAAa,GAAA,WAAc,OAAA,CAAQ,UAAA;EAAnC;EAEA,YAAA,CAAa,GAAA,UAAa,KAAA,EAAO,UAAA,GAAa,OAAA;EAFnB;EAI3B,OAAA,CAAQ,SAAA,UAAmB,EAAA,WAAa,OAAA,CAAQ,aAAA;EAFhD;EAIA,YAAA,EACE,SAAA,UACA,EAAA,UACA,MAAA,UACA,MAAA,WACC,OAAA,CAAQ,kBAAA;EATsB;EAWjC,OAAA,CACE,SAAA,UACA,EAAA,UACA,KAAA,EAAO,UAAA,EACP,WAAA,kBACC,OAAA;EAhB2C;EAkB9C,UAAA,CAAW,SAAA,UAAmB,EAAA,WAAa,OAAA;EAhBnC;EAkBR,SAAA,CAAU,SAAA,WAAoB,OAAA,CAAQ,aAAA;EAlBE;EAoBxC,gBAAA,EAAkB,SAAA,WAAoB,OAAA,CAAQ,qBAAA;AAAA;;;;;;;;;;;;KAcpC,kBAAA;;UAGK,2BAAA;EArBJ;EAuBX,IAAA,GAAO,kBAAkB;EAvBkB;EAyB3C,qBAAA;EAvBU;EAyBV,qBAAA;AAAA;;;;;;AAvBmE;AAcrE;;iBAoBsB,oBAAA,CACpB,OAAA,GAAS,2BAAA,GACR,OAAA,CAAQ,qBAAA;;AAtBmB;AAG9B;;;;;iBAuDgB,eAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.js","names":[],"sources":["../src/persistence.ts"],"sourcesContent":["import { SyncoreIndexedDbPersistence } from \"./indexeddb.js\";\nimport { SyncoreOpfsPersistence } from \"./opfs.js\";\n\n/**\n * A binary file record stored in web persistence (OPFS or IndexedDB).\n * Returned by `SyncoreWebPersistence.getFile` and `listFiles`.\n */\nexport interface StoredWebFile {\n /** Unique file identifier within its namespace. */\n id: string;\n /** Raw file bytes. */\n bytes: Uint8Array;\n /** MIME type, or `null` if none was recorded at write time. */\n contentType: string | null;\n /** File size in bytes. */\n size: number;\n}\n\n/**\n * Abstraction over browser storage backends (OPFS or IndexedDB).\n *\n * Handles both the SQLite database blob and binary file objects. All\n * implementations must persist data across page reloads.\n *\n * The concrete implementation is chosen by `createWebPersistence` based on\n * browser capabilities and the requested `WebPersistenceMode`.\n */\nexport interface SyncoreWebPersistence {\n /** The storage protocol used: `\"opfs\"` (Origin Private File System) or `\"idb\"` (IndexedDB). */\n readonly storageProtocol: \"idb\" | \"opfs\";\n /** Load the serialized SQLite database for `key`, or `null` if none has been saved yet. */\n loadDatabase(key: string): Promise<Uint8Array | null>;\n /** Persist the serialized SQLite database bytes for `key`. */\n saveDatabase(key: string, bytes: Uint8Array): Promise<void>;\n /** Retrieve a stored file from `namespace` by `id`, or `null` if not found. */\n getFile(namespace: string, id: string): Promise<StoredWebFile | null>;\n /** Write a file into `namespace` under `id`, replacing any existing entry. */\n putFile(\n namespace: string,\n id: string,\n bytes: Uint8Array,\n contentType: string | null\n ): Promise<void>;\n /** Delete a file from `namespace` by `id`. No-op if the file does not exist. */\n deleteFile(namespace: string, id: string): Promise<void>;\n /** List all stored files in `namespace`. */\n listFiles(namespace: string): Promise<StoredWebFile[]>;\n}\n\n/**\n * Which browser storage backend Syncore should use for SQLite persistence.\n *\n * - `\"opfs\"` — Origin Private File System. Fastest option; available in\n * Chrome 102+, Safari 15.2+, and modern Firefox. **Required** for\n * multi-tab coordination using `SharedArrayBuffer`.\n * - `\"indexeddb\"` — Falls back to IndexedDB for browsers without OPFS.\n * Slower due to serialization overhead but universally available.\n * - `\"auto\"` *(default)* — Picks `\"opfs\"` when available, otherwise\n * `\"indexeddb\"`.\n */\nexport type WebPersistenceMode = \"auto\" | \"indexeddb\" | \"opfs\";\n\n/** Options for {@link createWebPersistence}. */\nexport interface CreateWebPersistenceOptions {\n /** Persistence backend to use. Defaults to `\"auto\"`. */\n mode?: WebPersistenceMode;\n /** Custom IndexedDB database name. Defaults to the Syncore database name. */\n indexedDbDatabaseName?: string;\n /** Root directory name inside the OPFS bucket. Defaults to the Syncore database name. */\n opfsRootDirectoryName?: string;\n}\n\n/**\n * Create the appropriate web persistence backend based on browser capabilities\n * and the requested mode.\n *\n * Call this if you need a `SyncoreWebPersistence` instance outside of\n * `createWebSyncoreRuntime` (e.g. in the Expo adapter). In a standard\n * browser setup, `createWebSyncoreRuntime` calls this automatically.\n */\nexport async function createWebPersistence(\n options: CreateWebPersistenceOptions = {}\n): Promise<SyncoreWebPersistence> {\n const mode = options.mode ?? \"auto\";\n\n if (mode === \"opfs\") {\n if (!isOpfsAvailable()) {\n throw new Error(\"OPFS is not available in this environment.\");\n }\n return new SyncoreOpfsPersistence(\n options.opfsRootDirectoryName\n ? { rootDirectoryName: options.opfsRootDirectoryName }\n : undefined\n );\n }\n\n if (mode === \"auto\" && isOpfsAvailable()) {\n return new SyncoreOpfsPersistence(\n options.opfsRootDirectoryName\n ? { rootDirectoryName: options.opfsRootDirectoryName }\n : undefined\n );\n }\n\n return new SyncoreIndexedDbPersistence(\n options.indexedDbDatabaseName\n ? { databaseName: options.indexedDbDatabaseName }\n : undefined\n );\n}\n\n/**\n * Return `true` if the Origin Private File System API is available in the\n * current browser context.\n *\n * Used internally to decide whether to prefer OPFS over IndexedDB in `\"auto\"`\n * mode. Also useful in application code for displaying conditional UI.\n */\nexport function isOpfsAvailable(): boolean {\n return Boolean(getOpfsStorageManager()?.getDirectory);\n}\n\ntype OpfsStorageManager = StorageManager & {\n getDirectory?: () => Promise<FileSystemDirectoryHandle>;\n};\n\nfunction getOpfsStorageManager(): OpfsStorageManager | undefined {\n if (typeof navigator === \"undefined\") {\n return undefined;\n }\n return navigator.storage as OpfsStorageManager | undefined;\n}\n"],"mappings":";;;;;;;;;;;AAgFA,eAAsB,qBACpB,UAAuC,CAAC,GACR;CAChC,MAAM,OAAO,QAAQ,QAAQ;CAE7B,IAAI,SAAS,QAAQ;EACnB,IAAI,CAAC,gBAAgB,GACnB,MAAM,IAAI,MAAM,4CAA4C;EAE9D,OAAO,IAAI,uBACT,QAAQ,wBACJ,EAAE,mBAAmB,QAAQ,sBAAsB,IACnD,KAAA,CACN;CACF;CAEA,IAAI,SAAS,UAAU,gBAAgB,GACrC,OAAO,IAAI,uBACT,QAAQ,wBACJ,EAAE,mBAAmB,QAAQ,sBAAsB,IACnD,KAAA,CACN;CAGF,OAAO,IAAI,4BACT,QAAQ,wBACJ,EAAE,cAAc,QAAQ,sBAAsB,IAC9C,KAAA,CACN;AACF;;;;;;;;AASA,SAAgB,kBAA2B;CACzC,OAAO,QAAQ,sBAAsB,GAAG,YAAY;AACtD;AAMA,SAAS,wBAAwD;CAC/D,IAAI,OAAO,cAAc,aACvB;CAEF,OAAO,UAAU;AACnB"}
1
+ {"version":3,"file":"persistence.js","names":[],"sources":["../src/persistence.ts"],"sourcesContent":["import { SyncoreIndexedDbPersistence } from \"./indexeddb.js\";\nimport { SyncoreOpfsPersistence } from \"./opfs.js\";\n\n/**\n * A binary file record stored in web persistence (OPFS or IndexedDB).\n * Returned by `SyncoreWebPersistence.getFile` and `listFiles`.\n */\nexport interface StoredWebFile {\n /** Unique file identifier within its namespace. */\n id: string;\n /** Raw file bytes. */\n bytes: Uint8Array;\n /** MIME type, or `null` if none was recorded at write time. */\n contentType: string | null;\n /** File size in bytes. */\n size: number;\n}\n\n/**\n * A byte range from a web persistence file, plus total object metadata.\n */\nexport interface StoredWebFileRange {\n /** Unique file identifier within its namespace. */\n id: string;\n /** Raw bytes for the requested range. */\n bytes: Uint8Array;\n /** MIME type, or `null` if none was recorded at write time. */\n contentType: string | null;\n /** Total file size in bytes. */\n size: number;\n /** Offset used for this range. */\n offset: number;\n}\n\n/**\n * Metadata for a web persistence file without the raw bytes.\n */\nexport interface StoredWebFileMetadata {\n /** Unique file identifier within its namespace. */\n id: string;\n /** MIME type, or `null` if none was recorded at write time. */\n contentType: string | null;\n /** Total file size in bytes. */\n size: number;\n}\n\n/**\n * Abstraction over browser storage backends (OPFS or IndexedDB).\n *\n * Handles both the SQLite database blob and binary file objects. All\n * implementations must persist data across page reloads.\n *\n * The concrete implementation is chosen by `createWebPersistence` based on\n * browser capabilities and the requested `WebPersistenceMode`.\n */\nexport interface SyncoreWebPersistence {\n /** The storage protocol used: `\"opfs\"` (Origin Private File System) or `\"idb\"` (IndexedDB). */\n readonly storageProtocol: \"idb\" | \"opfs\";\n /** Load the serialized SQLite database for `key`, or `null` if none has been saved yet. */\n loadDatabase(key: string): Promise<Uint8Array | null>;\n /** Persist the serialized SQLite database bytes for `key`. */\n saveDatabase(key: string, bytes: Uint8Array): Promise<void>;\n /** Retrieve a stored file from `namespace` by `id`, or `null` if not found. */\n getFile(namespace: string, id: string): Promise<StoredWebFile | null>;\n /** Retrieve a byte range without loading the whole file when supported. */\n getFileRange?(\n namespace: string,\n id: string,\n offset: number,\n length: number\n ): Promise<StoredWebFileRange | null>;\n /** Write a file into `namespace` under `id`, replacing any existing entry. */\n putFile(\n namespace: string,\n id: string,\n bytes: Uint8Array,\n contentType: string | null\n ): Promise<void>;\n /** Delete a file from `namespace` by `id`. No-op if the file does not exist. */\n deleteFile(namespace: string, id: string): Promise<void>;\n /** List all stored files in `namespace`. */\n listFiles(namespace: string): Promise<StoredWebFile[]>;\n /** List stored file metadata without loading file bytes when supported. */\n listFileMetadata?(namespace: string): Promise<StoredWebFileMetadata[]>;\n}\n\n/**\n * Which browser storage backend Syncore should use for SQLite persistence.\n *\n * - `\"opfs\"` — Origin Private File System. Fastest option; available in\n * Chrome 102+, Safari 15.2+, and modern Firefox. **Required** for\n * multi-tab coordination using `SharedArrayBuffer`.\n * - `\"indexeddb\"` — Falls back to IndexedDB for browsers without OPFS.\n * Slower due to serialization overhead but universally available.\n * - `\"auto\"` *(default)* — Picks `\"opfs\"` when available, otherwise\n * `\"indexeddb\"`.\n */\nexport type WebPersistenceMode = \"auto\" | \"indexeddb\" | \"opfs\";\n\n/** Options for {@link createWebPersistence}. */\nexport interface CreateWebPersistenceOptions {\n /** Persistence backend to use. Defaults to `\"auto\"`. */\n mode?: WebPersistenceMode;\n /** Custom IndexedDB database name. Defaults to the Syncore database name. */\n indexedDbDatabaseName?: string;\n /** Root directory name inside the OPFS bucket. Defaults to the Syncore database name. */\n opfsRootDirectoryName?: string;\n}\n\n/**\n * Create the appropriate web persistence backend based on browser capabilities\n * and the requested mode.\n *\n * Call this if you need a `SyncoreWebPersistence` instance outside of\n * `createWebSyncoreRuntime` (e.g. in the Expo adapter). In a standard\n * browser setup, `createWebSyncoreRuntime` calls this automatically.\n */\nexport async function createWebPersistence(\n options: CreateWebPersistenceOptions = {}\n): Promise<SyncoreWebPersistence> {\n const mode = options.mode ?? \"auto\";\n\n if (mode === \"opfs\") {\n if (!isOpfsAvailable()) {\n throw new Error(\"OPFS is not available in this environment.\");\n }\n return new SyncoreOpfsPersistence(\n options.opfsRootDirectoryName\n ? { rootDirectoryName: options.opfsRootDirectoryName }\n : undefined\n );\n }\n\n if (mode === \"auto\" && isOpfsAvailable()) {\n return new SyncoreOpfsPersistence(\n options.opfsRootDirectoryName\n ? { rootDirectoryName: options.opfsRootDirectoryName }\n : undefined\n );\n }\n\n return new SyncoreIndexedDbPersistence(\n options.indexedDbDatabaseName\n ? { databaseName: options.indexedDbDatabaseName }\n : undefined\n );\n}\n\n/**\n * Return `true` if the Origin Private File System API is available in the\n * current browser context.\n *\n * Used internally to decide whether to prefer OPFS over IndexedDB in `\"auto\"`\n * mode. Also useful in application code for displaying conditional UI.\n */\nexport function isOpfsAvailable(): boolean {\n return Boolean(getOpfsStorageManager()?.getDirectory);\n}\n\ntype OpfsStorageManager = StorageManager & {\n getDirectory?: () => Promise<FileSystemDirectoryHandle>;\n};\n\nfunction getOpfsStorageManager(): OpfsStorageManager | undefined {\n if (typeof navigator === \"undefined\") {\n return undefined;\n }\n return navigator.storage as OpfsStorageManager | undefined;\n}\n"],"mappings":";;;;;;;;;;;AAqHA,eAAsB,qBACpB,UAAuC,CAAC,GACR;CAChC,MAAM,OAAO,QAAQ,QAAQ;CAE7B,IAAI,SAAS,QAAQ;EACnB,IAAI,CAAC,gBAAgB,GACnB,MAAM,IAAI,MAAM,4CAA4C;EAE9D,OAAO,IAAI,uBACT,QAAQ,wBACJ,EAAE,mBAAmB,QAAQ,sBAAsB,IACnD,KAAA,CACN;CACF;CAEA,IAAI,SAAS,UAAU,gBAAgB,GACrC,OAAO,IAAI,uBACT,QAAQ,wBACJ,EAAE,mBAAmB,QAAQ,sBAAsB,IACnD,KAAA,CACN;CAGF,OAAO,IAAI,4BACT,QAAQ,wBACJ,EAAE,cAAc,QAAQ,sBAAsB,IAC9C,KAAA,CACN;AACF;;;;;;;;AASA,SAAgB,kBAA2B;CACzC,OAAO,QAAQ,sBAAsB,GAAG,YAAY;AACtD;AAMA,SAAS,wBAAwD;CAC/D,IAAI,OAAO,cAAc,aACvB;CAEF,OAAO,UAAU;AACnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","names":[],"sources":["../src/react.tsx"],"mappings":";;;;;;AAgBA;UAAiB,uBAAA,SAAgC,oCAAA;;EAE/C,QAAA,EAAU,SAAA;EAGC;EAAX,QAAA,GAAW,SAAA;AAAA;;;;KAMD,2BAAA,GAA8B,uBAAuB;;;;iBAKjD,kBAAA,CAAA;EACd,QAAA;EACA,SAAA;EACA,UAAA;EACA,UAAA;EACA;AAAA,GACC,uBAAA,GAA0B,SAAA;AAX7B;;;AAAA,iBA2DgB,sBAAA,CAAuB,KAAA,EAAO,2BAA2B,+BAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"react.d.ts","names":[],"sources":["../src/react.tsx"],"mappings":";;;;;;AAgBA;UAAiB,uBAAA,SAAgC,oCAAA;;EAE/C,QAAA,EAAU,SAAA;EAGC;EAAX,QAAA,GAAW,SAAA;AAAA;;;;KAMD,2BAAA,GAA8B,uBAAuB;;;;iBAKjD,kBAAA,CAAA;EACd,QAAA;EACA,SAAA;EACA,UAAA;EACA,UAAA;EACA;AAAA,GACC,uBAAA,GAA0B,SAAA;AAX7B;;;AAAA,iBAoEgB,sBAAA,CAAuB,KAAA,EAAO,2BAA2B,+BAAA,GAAA,CAAA,OAAA"}
@@ -10,7 +10,11 @@ import { jsx } from "react/jsx-runtime";
10
10
  function SyncoreWebProvider({ children, workerUrl, workerType, workerName, fallback = null }) {
11
11
  const bootingClient = useMemo(() => createUnavailableSyncoreClient({
12
12
  kind: "starting",
13
- reason: "booting"
13
+ reason: "booting",
14
+ capabilities: { storage: {
15
+ available: false,
16
+ reason: "Syncore worker is booting."
17
+ } }
14
18
  }), []);
15
19
  const [client, setClient] = useState(bootingClient);
16
20
  useEffect(() => {
@@ -27,6 +31,10 @@ function SyncoreWebProvider({ children, workerUrl, workerType, workerName, fallb
27
31
  setClient(createUnavailableSyncoreClient({
28
32
  kind: "unavailable",
29
33
  reason: "worker-unavailable",
34
+ capabilities: { storage: {
35
+ available: false,
36
+ reason: "Syncore worker is unavailable."
37
+ } },
30
38
  ...error instanceof Error ? { error } : {}
31
39
  }));
32
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","names":[],"sources":["../src/react.tsx"],"sourcesContent":["import {\n createUnavailableSyncoreClient,\n type SyncoreClient\n} from \"@syncore/core\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { SyncoreProvider } from \"@syncore/react\";\nimport {\n createSyncoreWebWorkerClient,\n type CreateWebWorkerClientProviderOptions,\n type ManagedWebWorkerClient\n} from \"./worker.js\";\n\n/**\n * Props for {@link SyncoreWebProvider}.\n */\nexport interface SyncoreWebProviderProps extends CreateWebWorkerClientProviderOptions {\n /** The React subtree that should receive the Syncore client. */\n children: ReactNode;\n\n /** Optional fallback content rendered before the worker client is ready. */\n fallback?: ReactNode;\n}\n\n/**\n * Props for {@link SyncoreBrowserProvider}.\n */\nexport type SyncoreBrowserProviderProps = SyncoreWebProviderProps;\n\n/**\n * Start a worker-backed Syncore client and provide it to React descendants.\n */\nexport function SyncoreWebProvider({\n children,\n workerUrl,\n workerType,\n workerName,\n fallback = null\n}: SyncoreWebProviderProps): ReactNode {\n const bootingClient = useMemo(\n () =>\n createUnavailableSyncoreClient({\n kind: \"starting\",\n reason: \"booting\"\n }),\n []\n );\n const [client, setClient] = useState<SyncoreClient>(bootingClient);\n\n useEffect(() => {\n let managedClient: ManagedWebWorkerClient | undefined;\n\n setClient(bootingClient);\n\n try {\n managedClient = createSyncoreWebWorkerClient({\n workerUrl,\n ...(workerType ? { workerType } : {}),\n ...(workerName ? { workerName } : {})\n });\n setClient(managedClient.client);\n } catch (error) {\n setClient(\n createUnavailableSyncoreClient({\n kind: \"unavailable\",\n reason: \"worker-unavailable\",\n ...(error instanceof Error ? { error } : {})\n })\n );\n }\n\n return () => {\n managedClient?.dispose();\n };\n }, [bootingClient, workerName, workerType, workerUrl]);\n\n return (\n <SyncoreProvider client={client}>\n {children ?? fallback}\n </SyncoreProvider>\n );\n}\n\n/**\n * Start a worker-backed Syncore client and provide it to React descendants.\n */\nexport function SyncoreBrowserProvider(props: SyncoreBrowserProviderProps) {\n return <SyncoreWebProvider {...props} />;\n}\n"],"mappings":";;;;;;;;;AAgCA,SAAgB,mBAAmB,EACjC,UACA,WACA,YACA,YACA,WAAW,QAC0B;CACrC,MAAM,gBAAgB,cAElB,+BAA+B;EAC7B,MAAM;EACN,QAAQ;CACV,CAAC,GACH,CAAC,CACH;CACA,MAAM,CAAC,QAAQ,aAAa,SAAwB,aAAa;CAEjE,gBAAgB;EACd,IAAI;EAEJ,UAAU,aAAa;EAEvB,IAAI;GACF,gBAAgB,6BAA6B;IAC3C;IACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;IACnC,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;GACrC,CAAC;GACD,UAAU,cAAc,MAAM;EAChC,SAAS,OAAO;GACd,UACE,+BAA+B;IAC7B,MAAM;IACN,QAAQ;IACR,GAAI,iBAAiB,QAAQ,EAAE,MAAM,IAAI,CAAC;GAC5C,CAAC,CACH;EACF;EAEA,aAAa;GACX,eAAe,QAAQ;EACzB;CACF,GAAG;EAAC;EAAe;EAAY;EAAY;CAAS,CAAC;CAErD,OACE,oBAAC,iBAAD;EAAyB;YACtB,YAAY;CACE,CAAA;AAErB;;;;AAKA,SAAgB,uBAAuB,OAAoC;CACzE,OAAO,oBAAC,oBAAD,EAAoB,GAAI,MAAQ,CAAA;AACzC"}
1
+ {"version":3,"file":"react.js","names":[],"sources":["../src/react.tsx"],"sourcesContent":["import {\n createUnavailableSyncoreClient,\n type SyncoreClient\n} from \"@syncore/core\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { SyncoreProvider } from \"@syncore/react\";\nimport {\n createSyncoreWebWorkerClient,\n type CreateWebWorkerClientProviderOptions,\n type ManagedWebWorkerClient\n} from \"./worker.js\";\n\n/**\n * Props for {@link SyncoreWebProvider}.\n */\nexport interface SyncoreWebProviderProps extends CreateWebWorkerClientProviderOptions {\n /** The React subtree that should receive the Syncore client. */\n children: ReactNode;\n\n /** Optional fallback content rendered before the worker client is ready. */\n fallback?: ReactNode;\n}\n\n/**\n * Props for {@link SyncoreBrowserProvider}.\n */\nexport type SyncoreBrowserProviderProps = SyncoreWebProviderProps;\n\n/**\n * Start a worker-backed Syncore client and provide it to React descendants.\n */\nexport function SyncoreWebProvider({\n children,\n workerUrl,\n workerType,\n workerName,\n fallback = null\n}: SyncoreWebProviderProps): ReactNode {\n const bootingClient = useMemo(\n () =>\n createUnavailableSyncoreClient({\n kind: \"starting\",\n reason: \"booting\",\n capabilities: {\n storage: { available: false, reason: \"Syncore worker is booting.\" }\n }\n }),\n []\n );\n const [client, setClient] = useState<SyncoreClient>(bootingClient);\n\n useEffect(() => {\n let managedClient: ManagedWebWorkerClient | undefined;\n\n setClient(bootingClient);\n\n try {\n managedClient = createSyncoreWebWorkerClient({\n workerUrl,\n ...(workerType ? { workerType } : {}),\n ...(workerName ? { workerName } : {})\n });\n setClient(managedClient.client);\n } catch (error) {\n setClient(\n createUnavailableSyncoreClient({\n kind: \"unavailable\",\n reason: \"worker-unavailable\",\n capabilities: {\n storage: {\n available: false,\n reason: \"Syncore worker is unavailable.\"\n }\n },\n ...(error instanceof Error ? { error } : {})\n })\n );\n }\n\n return () => {\n managedClient?.dispose();\n };\n }, [bootingClient, workerName, workerType, workerUrl]);\n\n return (\n <SyncoreProvider client={client}>\n {children ?? fallback}\n </SyncoreProvider>\n );\n}\n\n/**\n * Start a worker-backed Syncore client and provide it to React descendants.\n */\nexport function SyncoreBrowserProvider(props: SyncoreBrowserProviderProps) {\n return <SyncoreWebProvider {...props} />;\n}\n"],"mappings":";;;;;;;;;AAgCA,SAAgB,mBAAmB,EACjC,UACA,WACA,YACA,YACA,WAAW,QAC0B;CACrC,MAAM,gBAAgB,cAElB,+BAA+B;EAC7B,MAAM;EACN,QAAQ;EACR,cAAc,EACZ,SAAS;GAAE,WAAW;GAAO,QAAQ;EAA6B,EACpE;CACF,CAAC,GACH,CAAC,CACH;CACA,MAAM,CAAC,QAAQ,aAAa,SAAwB,aAAa;CAEjE,gBAAgB;EACd,IAAI;EAEJ,UAAU,aAAa;EAEvB,IAAI;GACF,gBAAgB,6BAA6B;IAC3C;IACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;IACnC,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;GACrC,CAAC;GACD,UAAU,cAAc,MAAM;EAChC,SAAS,OAAO;GACd,UACE,+BAA+B;IAC7B,MAAM;IACN,QAAQ;IACR,cAAc,EACZ,SAAS;KACP,WAAW;KACX,QAAQ;IACV,EACF;IACA,GAAI,iBAAiB,QAAQ,EAAE,MAAM,IAAI,CAAC;GAC5C,CAAC,CACH;EACF;EAEA,aAAa;GACX,eAAe,QAAQ;EACzB;CACF,GAAG;EAAC;EAAe;EAAY;EAAY;CAAS,CAAC;CAErD,OACE,oBAAC,iBAAD;EAAyB;YACtB,YAAY;CACE,CAAA;AAErB;;;;AAKA,SAAgB,uBAAuB,OAAoC;CACzE,OAAO,oBAAC,oBAAD,EAAoB,GAAI,MAAQ,CAAA;AACzC"}
@@ -84,7 +84,7 @@ declare function useSyncore(): SyncoreClient;
84
84
  /**
85
85
  * Subscribe to the runtime’s lifecycle status.
86
86
  *
87
- * Returns a {@link SyncoreRuntimeStatus} that updates whenever the underlying
87
+ * Returns a SyncoreRuntimeStatus that updates whenever the underlying
88
88
  * runtime changes state (e.g. starting, ready, error). Use it to gate your UI
89
89
  * on the runtime being ready or to display an error boundary:
90
90
  *
@@ -127,7 +127,7 @@ declare function useSyncoreStatus(): SyncoreRuntimeStatus;
127
127
  declare function useQuery<TArgs, TResult>(reference: FunctionReference<"query", TArgs, TResult>, ...args: OptionalArgsTuple<TArgs> | [Skip]): TResult | undefined;
128
128
  /**
129
129
  * Subscribe to a reactive Syncore query and return the full
130
- * {@link SyncoreQueryState} including loading, error, and runtime status.
130
+ * SyncoreQueryState including loading, error, and runtime status.
131
131
  *
132
132
  * Use this instead of {@link useQuery} when you need to:
133
133
  * - Differentiate between `undefined` data and an error.
@@ -206,7 +206,7 @@ declare function useAction<TArgs, TResult>(reference: FunctionReference<"action"
206
206
  *
207
207
  * @param entries - A record of named query requests. Each entry can include
208
208
  * `args: skip` to suppress that specific subscription.
209
- * @returns A record with the same keys, each holding a {@link SyncoreQueryState}.
209
+ * @returns A record with the same keys, each holding a SyncoreQueryState.
210
210
  */
211
211
  declare function useQueries<TEntries extends QueriesRequestInput>(entries: TEntries): UseQueriesResult<TEntries>;
212
212
  /**
@@ -245,8 +245,9 @@ declare function useQueries<TEntries extends QueriesRequestInput>(entries: TEntr
245
245
  * `ctx.db.query(…).paginate(paginationOpts)`.
246
246
  * @param args - Arguments for the query (excluding
247
247
  * `paginationOpts`, which is managed internally), or `skip`.
248
- * @param options.initialNumItems - Number of items to load on the first page.
249
- * @returns A {@link UsePaginatedQueryResult} with the accumulated results and
248
+ * @param options - Pagination options. `initialNumItems` controls the number
249
+ * of items to load on the first page.
250
+ * @returns A UsePaginatedQueryResult with the accumulated results and
250
251
  * a `loadMore` callback.
251
252
  */
252
253
  declare function usePaginatedQuery<TReference extends PaginatedQueryReference>(reference: TReference, args: PaginatedQueryArgs<TReference> | Skip, options: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;;;KA0BK,iBAAA,UACH,MAAA,uBAA6B,KAAA,IAAS,IAAA,GAAO,KAAA,KAAU,IAAA,EAAM,KAAA;AAAA,KAE1D,iBAAA,oBACgB,iBAAA,YAA6B,iBAAA,aAC9C,MAAA,uBAA6B,YAAA,CAAa,UAAA;EAExC,KAAA,EAAO,UAAA;EACP,IAAA,GAAO,YAAA,CAAa,UAAA,IAAc,IAAA;AAAA;EAGlC,KAAA,EAAO,UAAA;EACP,IAAA,EAAM,YAAA,CAAa,UAAA,IAAc,IAAA;AAAA;AAAA,KAGlC,mBAAA,GAAsB,MAAM,SAAS,iBAAA;AAAA,KAErC,kBAAA,WAA6B,MAAA,SAAe,iBAAA,qBAG7C,iBAAA,CAAkB,cAAA,CAAe,UAAA;AAAA,KAGzB,gBAAA,kBAAkC,mBAAA,qBAC7B,QAAA,GAAW,kBAAA,CAAmB,QAAA,CAAS,IAAA;AAAA,KAGnD,uBAAA,GAA0B,iBAAA,UAE7B,MAAA,mBACA,gBAAA;AAAA,KAGG,kBAAA,oBAAsC,iBAAA,aACzC,YAAA,CAAa,UAAA;EAAsB,cAAA,EAAgB,iBAAA;AAAA,IAC/C,IAAA,CAAK,YAAA,CAAa,UAAA;AAAA,KAGnB,kBAAA,oBAAsC,iBAAA,aACzC,cAAA,CAAe,UAAA,UAAoB,gBAAA,gBAC/B,KAAA;;AAvC8D;AAAA;;;;;;;;;;;;;;cAuFvD,IAAA;AAAA,KACR,IAAA,UAAc,IAAI;;;;;;;;;;;;;;;;;;;;;;AA7EoB;AAAA;;iBA8G3B,eAAA,CAAA;EACd,MAAA;EACA;AAAA;EAEA,MAAA,EAAQ,aAAA;EACR,QAAA,EAAU,SAAA;AAAA,gCACX,GAAA,CAAA,OAAA;;;;;;;;;;;;;;iBAmBe,UAAA,CAAA,GAAc,aAAa;;;;AA/HI;AAG/C;;;;;;;;;;;;;;;iBAuJgB,gBAAA,CAAA,GAAoB,oBAAoB;;;AAtJI;AAC1D;;;;;;;;;;;;AAKgB;AAAA;;;;;;;iBAmMF,QAAA,gBAAA,CACd,SAAA,EAAW,iBAAA,UAA2B,KAAA,EAAO,OAAA,MAC1C,IAAA,EAAM,iBAAA,CAAkB,KAAA,KAAU,IAAA,IACpC,OAAA;;;;;;;;;;;;;;;AAjM+B;AAAA;;iBA0NlB,aAAA,gBAAA,CACd,SAAA,EAAW,iBAAA,UAA2B,KAAA,EAAO,OAAA,MAC1C,IAAA,EAAM,iBAAA,CAAkB,KAAA,KAAU,IAAA,IACpC,iBAAA,CAAkB,OAAA;;;;;;;;;;;;;;;AAxNV;AAgDX;;;;AAAmC;AAAC;iBA4NpB,WAAA,gBAAA,CACd,SAAA,EAAW,iBAAA,aAA8B,KAAA,EAAO,OAAA,QAC3C,IAAA,EAAM,iBAAA,CAAkB,KAAA,MAAW,OAAA,CAAQ,OAAA;;;AA7N3B;AAiCvB;;;;;;;;;;;;;;;;;;iBAsNgB,SAAA,gBAAA,CACd,SAAA,EAAW,iBAAA,WAA4B,KAAA,EAAO,OAAA,QACzC,IAAA,EAAM,iBAAA,CAAkB,KAAA,MAAW,OAAA,CAAQ,OAAA;;AAlNjD;AAmBD;;;;AAA2C;AA2B3C;;;;AAAwD;AAmDxD;;;;;;;;;iBA2IgB,UAAA,kBAA4B,mBAAA,CAAA,CAC1C,OAAA,EAAS,QAAA,GACR,gBAAA,CAAiB,QAAA;;;;;;;;;;;;;;;AA1IV;AAyBV;;;;;;;;;;;;;;;;;;;;;;;;;iBA8MgB,iBAAA,oBAAqC,uBAAA,CAAA,CACnD,SAAA,EAAW,UAAA,EACX,IAAA,EAAM,kBAAA,CAAmB,UAAA,IAAc,IAAA,EACvC,OAAA;EACE,eAAA;AAAA,IAED,uBAAA,CAAwB,kBAAA,CAAmB,UAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;;;KA0BK,iBAAA,UACH,MAAA,uBAA6B,KAAA,IAAS,IAAA,GAAO,KAAA,KAAU,IAAA,EAAM,KAAA;AAAA,KAE1D,iBAAA,oBACgB,iBAAA,YAA6B,iBAAA,aAC9C,MAAA,uBAA6B,YAAA,CAAa,UAAA;EAExC,KAAA,EAAO,UAAA;EACP,IAAA,GAAO,YAAA,CAAa,UAAA,IAAc,IAAA;AAAA;EAGlC,KAAA,EAAO,UAAA;EACP,IAAA,EAAM,YAAA,CAAa,UAAA,IAAc,IAAA;AAAA;AAAA,KAGlC,mBAAA,GAAsB,MAAM,SAAS,iBAAA;AAAA,KAErC,kBAAA,WAA6B,MAAA,SAAe,iBAAA,qBAG7C,iBAAA,CAAkB,cAAA,CAAe,UAAA;AAAA,KAGzB,gBAAA,kBAAkC,mBAAA,qBAC7B,QAAA,GAAW,kBAAA,CAAmB,QAAA,CAAS,IAAA;AAAA,KAGnD,uBAAA,GAA0B,iBAAA,UAE7B,MAAA,mBACA,gBAAA;AAAA,KAGG,kBAAA,oBAAsC,iBAAA,aACzC,YAAA,CAAa,UAAA;EAAsB,cAAA,EAAgB,iBAAA;AAAA,IAC/C,IAAA,CAAK,YAAA,CAAa,UAAA;AAAA,KAGnB,kBAAA,oBAAsC,iBAAA,aACzC,cAAA,CAAe,UAAA,UAAoB,gBAAA,gBAC/B,KAAA;;AAvC8D;AAAA;;;;;;;;;;;;;;cAuFvD,IAAA;AAAA,KACR,IAAA,UAAc,IAAI;;;;;;;;;;;;;;;;;;;;;;AA7EoB;AAAA;;iBA8G3B,eAAA,CAAA;EACd,MAAA;EACA;AAAA;EAEA,MAAA,EAAQ,aAAA;EACR,QAAA,EAAU,SAAA;AAAA,gCACX,GAAA,CAAA,OAAA;;;;;;;;;;;;;;iBAmBe,UAAA,CAAA,GAAc,aAAa;;;;AA/HI;AAG/C;;;;;;;;;;;;;;;iBAuJgB,gBAAA,CAAA,GAAoB,oBAAoB;;;AAtJI;AAC1D;;;;;;;;;;;;AAKgB;AAAA;;;;;;;iBAmMF,QAAA,gBAAA,CACd,SAAA,EAAW,iBAAA,UAA2B,KAAA,EAAO,OAAA,MAC1C,IAAA,EAAM,iBAAA,CAAkB,KAAA,KAAU,IAAA,IACpC,OAAA;;;;;;;;;;;;;;;AAjM+B;AAAA;;iBA0NlB,aAAA,gBAAA,CACd,SAAA,EAAW,iBAAA,UAA2B,KAAA,EAAO,OAAA,MAC1C,IAAA,EAAM,iBAAA,CAAkB,KAAA,KAAU,IAAA,IACpC,iBAAA,CAAkB,OAAA;;;;;;;;;;;;;;;AAxNV;AAgDX;;;;AAAmC;AAAC;iBA4NpB,WAAA,gBAAA,CACd,SAAA,EAAW,iBAAA,aAA8B,KAAA,EAAO,OAAA,QAC3C,IAAA,EAAM,iBAAA,CAAkB,KAAA,MAAW,OAAA,CAAQ,OAAA;;;AA7N3B;AAiCvB;;;;;;;;;;;;;;;;;;iBAsNgB,SAAA,gBAAA,CACd,SAAA,EAAW,iBAAA,WAA4B,KAAA,EAAO,OAAA,QACzC,IAAA,EAAM,iBAAA,CAAkB,KAAA,MAAW,OAAA,CAAQ,OAAA;;AAlNjD;AAmBD;;;;AAA2C;AA2B3C;;;;AAAwD;AAmDxD;;;;;;;;;iBA2IgB,UAAA,kBAA4B,mBAAA,CAAA,CAC1C,OAAA,EAAS,QAAA,GACR,gBAAA,CAAiB,QAAA;;;;;;;;;;;;;;;AA1IV;AAyBV;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+MgB,iBAAA,oBAAqC,uBAAA,CAAA,CACnD,SAAA,EAAW,UAAA,EACX,IAAA,EAAM,kBAAA,CAAmB,UAAA,IAAc,IAAA,EACvC,OAAA;EACE,eAAA;AAAA,IAED,uBAAA,CAAwB,kBAAA,CAAmB,UAAA"}
@@ -74,7 +74,7 @@ function useSyncore() {
74
74
  /**
75
75
  * Subscribe to the runtime’s lifecycle status.
76
76
  *
77
- * Returns a {@link SyncoreRuntimeStatus} that updates whenever the underlying
77
+ * Returns a SyncoreRuntimeStatus that updates whenever the underlying
78
78
  * runtime changes state (e.g. starting, ready, error). Use it to gate your UI
79
79
  * on the runtime being ready or to display an error boundary:
80
80
  *
@@ -136,7 +136,7 @@ function useQuery(reference, ...args) {
136
136
  }
137
137
  /**
138
138
  * Subscribe to a reactive Syncore query and return the full
139
- * {@link SyncoreQueryState} including loading, error, and runtime status.
139
+ * SyncoreQueryState including loading, error, and runtime status.
140
140
  *
141
141
  * Use this instead of {@link useQuery} when you need to:
142
142
  * - Differentiate between `undefined` data and an error.
@@ -239,7 +239,7 @@ function useAction(reference) {
239
239
  *
240
240
  * @param entries - A record of named query requests. Each entry can include
241
241
  * `args: skip` to suppress that specific subscription.
242
- * @returns A record with the same keys, each holding a {@link SyncoreQueryState}.
242
+ * @returns A record with the same keys, each holding a SyncoreQueryState.
243
243
  */
244
244
  function useQueries(entries) {
245
245
  const client = useSyncore();
@@ -307,8 +307,9 @@ function useQueries(entries) {
307
307
  * `ctx.db.query(…).paginate(paginationOpts)`.
308
308
  * @param args - Arguments for the query (excluding
309
309
  * `paginationOpts`, which is managed internally), or `skip`.
310
- * @param options.initialNumItems - Number of items to load on the first page.
311
- * @returns A {@link UsePaginatedQueryResult} with the accumulated results and
310
+ * @param options - Pagination options. `initialNumItems` controls the number
311
+ * of items to load on the first page.
312
+ * @returns A UsePaginatedQueryResult with the accumulated results and
312
313
  * a `loadMore` callback.
313
314
  */
314
315
  function usePaginatedQuery(reference, args, options) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.tsx"],"sourcesContent":["import {\n createContext,\n type ReactNode,\n useContext,\n useEffect,\n useMemo,\n useState\n} from \"react\";\nimport type {\n FunctionArgs,\n FunctionReference,\n FunctionResult,\n PaginationOptions,\n PaginationResult,\n SyncoreClient,\n SyncorePaginatedQueryStatus,\n SyncoreQueryState,\n SyncoreRuntimeStatus,\n SyncoreWatch,\n UsePaginatedQueryResult\n} from \"@syncore/core\";\n\ntype ManagedSyncoreWatch<TResult> = SyncoreWatch<TResult> & {\n dispose?: () => void;\n};\n\ntype OptionalArgsTuple<TArgs> =\n Record<never, never> extends TArgs ? [args?: TArgs] : [args: TArgs];\n\ntype QueryRequestInput<\n TReference extends FunctionReference<\"query\"> = FunctionReference<\"query\">\n> = Record<never, never> extends FunctionArgs<TReference>\n ? {\n query: TReference;\n args?: FunctionArgs<TReference> | Skip;\n }\n : {\n query: TReference;\n args: FunctionArgs<TReference> | Skip;\n };\n\ntype QueriesRequestInput = Record<string, QueryRequestInput>;\n\ntype QueryStateForEntry<TEntry> = TEntry extends QueryRequestInput<\n infer TReference\n>\n ? SyncoreQueryState<FunctionResult<TReference>>\n : never;\n\nexport type UseQueriesResult<TEntries extends QueriesRequestInput> = {\n [TKey in keyof TEntries]: QueryStateForEntry<TEntries[TKey]>;\n};\n\ntype PaginatedQueryReference = FunctionReference<\n \"query\",\n Record<string, unknown>,\n PaginationResult<unknown>\n>;\n\ntype PaginatedQueryArgs<TReference extends FunctionReference<\"query\">> =\n FunctionArgs<TReference> extends { paginationOpts: PaginationOptions }\n ? Omit<FunctionArgs<TReference>, \"paginationOpts\">\n : never;\n\ntype PaginatedQueryItem<TReference extends FunctionReference<\"query\">> =\n FunctionResult<TReference> extends PaginationResult<infer TItem>\n ? TItem\n : never;\n\ntype QuerySnapshot<TResult> = {\n data: TResult | undefined;\n error: Error | undefined;\n};\n\ntype NormalizedQueryEntry = {\n key: string;\n referenceName: string;\n args: Record<string, unknown>;\n skipped: boolean;\n};\n\ntype PaginatedQueryInternalState = {\n requestKey: string;\n nextPageKey: number;\n pages: Array<{\n key: string;\n cursor: string | null;\n numItems: number;\n }>;\n};\n\ntype QueryObserverRecord = {\n requestKey: string;\n snapshot: QuerySnapshot<unknown>;\n unsubscribe: () => void;\n watch?: ManagedSyncoreWatch<unknown>;\n};\n\n/**\n * Pass `skip` as the `args` argument to any Syncore React hook to suppress\n * that subscription entirely.\n *\n * Useful when the query arguments depend on state that is not yet available\n * (e.g. a selected item ID) — instead of conditionally calling the hook\n * (which violates the Rules of Hooks), pass `skip` to deactivate it:\n *\n * ```tsx\n * const task = useQuery(api.tasks.get, selectedId ? { id: selectedId } : skip);\n * // task is `undefined` while selectedId is null/undefined\n * ```\n *\n * Skipped queries return `undefined` for `data`, `\"skipped\"` for `status`,\n * and `false` for `isLoading`.\n */\nexport const skip = \"skip\" as const;\ntype Skip = typeof skip;\n\nconst defaultRuntimeStatus: SyncoreRuntimeStatus = {\n kind: \"starting\",\n reason: \"booting\"\n};\n\nconst SyncoreContext = createContext<SyncoreClient | null>(null);\n\n/**\n * Provides a Syncore client to all React descendants via context.\n *\n * Wrap your app (or any subtree that uses Syncore hooks) with\n * `SyncoreProvider`. All `useQuery`, `useMutation`, `useAction`, and\n * `useQueries` calls inside the tree will automatically use the client you\n * supply.\n *\n * ```tsx\n * // For a browser worker setup\n * const client = createBrowserWorkerClient();\n *\n * function App() {\n * return (\n * <SyncoreProvider client={client}>\n * <TaskList />\n * </SyncoreProvider>\n * );\n * }\n * ```\n *\n * For Next.js apps use `SyncoreNextProvider` which also handles service worker\n * and worker URL configuration.\n */\nexport function SyncoreProvider({\n client,\n children\n}: {\n client: SyncoreClient;\n children: ReactNode;\n}) {\n return (\n <SyncoreContext.Provider value={client}>{children}</SyncoreContext.Provider>\n );\n}\n\n/**\n * Returns the active `SyncoreClient` from the nearest {@link SyncoreProvider}\n * in the React tree.\n *\n * Throws if called outside of a `SyncoreProvider`. Prefer the higher-level\n * hooks (`useQuery`, `useMutation`, etc.) for common operations — use\n * `useSyncore` only when you need direct access to the client object.\n *\n * ```ts\n * const client = useSyncore();\n * const tasks = await client.query(api.tasks.list);\n * ```\n */\nexport function useSyncore(): SyncoreClient {\n const client = useContext(SyncoreContext);\n if (!client) {\n throw new Error(\"SyncoreProvider is missing from the React tree.\");\n }\n return client;\n}\n\n/**\n * Subscribe to the runtime’s lifecycle status.\n *\n * Returns a {@link SyncoreRuntimeStatus} that updates whenever the underlying\n * runtime changes state (e.g. starting, ready, error). Use it to gate your UI\n * on the runtime being ready or to display an error boundary:\n *\n * ```tsx\n * function TaskList() {\n * const status = useSyncoreStatus();\n * if (status.kind === \"starting\") return <Spinner />;\n * if (status.kind === \"error\") return <ErrorScreen error={status.error} />;\n * return <Tasks />;\n * }\n * ```\n *\n * Most components do not need this — `useQuery` already incorporates runtime\n * status into the `SyncoreQueryState.runtimeStatus` field.\n */\nexport function useSyncoreStatus(): SyncoreRuntimeStatus {\n const client = useSyncore();\n const watch = useMemo(\n () => client.watchRuntimeStatus() as ManagedSyncoreWatch<SyncoreRuntimeStatus>,\n [client]\n );\n const [status, setStatus] = useState<SyncoreRuntimeStatus>(() =>\n readRuntimeStatusSnapshot(watch)\n );\n\n useEffect(() => {\n const sync = () => {\n setStatus(readRuntimeStatusSnapshot(watch));\n };\n sync();\n return watch.onUpdate(sync);\n }, [watch]);\n\n useEffect(\n () => () => {\n watch.dispose?.();\n },\n [watch]\n );\n\n return status;\n}\n\n/**\n * Subscribe to a reactive Syncore query and return the current data.\n *\n * The component re-renders automatically whenever the query result changes.\n * If the query throws, `useQuery` re-throws the error so a React error\n * boundary can catch it — use {@link useQueryState} if you need to handle\n * errors inline.\n *\n * ```tsx\n * // Basic usage\n * const tasks = useQuery(api.tasks.list);\n *\n * // With arguments\n * const task = useQuery(api.tasks.get, { id: taskId });\n *\n * // Conditionally skip when arguments are not yet available\n * const task = useQuery(api.tasks.get, taskId ? { id: taskId } : skip);\n * ```\n *\n * @param reference - A typed function reference (from the generated `api` object).\n * @param args - The query’s arguments, or `skip` to suppress the subscription.\n * @returns The current query result, or `undefined` while loading.\n */\nexport function useQuery<TArgs, TResult>(\n reference: FunctionReference<\"query\", TArgs, TResult>,\n ...args: OptionalArgsTuple<TArgs> | [Skip]\n): TResult | undefined {\n const state = useQueryState(reference, ...(args as OptionalArgsTuple<TArgs> | [Skip]));\n if (state.error) {\n throw state.error;\n }\n return state.data;\n}\n\n/**\n * Subscribe to a reactive Syncore query and return the full\n * {@link SyncoreQueryState} including loading, error, and runtime status.\n *\n * Use this instead of {@link useQuery} when you need to:\n * - Differentiate between `undefined` data and an error.\n * - React to `isLoading` / `isError` without relying on error boundaries.\n * - Inspect `runtimeStatus` for the underlying runtime’s health.\n *\n * ```tsx\n * const { data, isLoading, isError, error } = useQueryState(api.tasks.list);\n *\n * if (isLoading) return <Spinner />;\n * if (isError) return <ErrorBanner message={error.message} />;\n * return <TaskList tasks={data} />;\n * ```\n */\nexport function useQueryState<TArgs, TResult>(\n reference: FunctionReference<\"query\", TArgs, TResult>,\n ...args: OptionalArgsTuple<TArgs> | [Skip]\n): SyncoreQueryState<TResult> {\n const isSkipped = args[0] === skip;\n const client = useSyncore();\n const runtimeStatus = useSyncoreStatus();\n const watch = useManagedQueryWatch(\n client,\n reference,\n isSkipped\n ? undefined\n : normalizeOptionalArgs(args as OptionalArgsTuple<TArgs>),\n isSkipped\n );\n const [snapshot, setSnapshot] = useState<QuerySnapshot<TResult>>(() =>\n isSkipped ? noOpSnapshot : readWatchSnapshot(watch)\n );\n\n useEffect(() => {\n if (isSkipped) {\n setSnapshot(noOpSnapshot);\n return;\n }\n const sync = () => {\n setSnapshot(readWatchSnapshot(watch));\n };\n sync();\n return watch.onUpdate(sync);\n }, [watch, isSkipped]);\n\n return toQueryState(snapshot, runtimeStatus, isSkipped);\n}\n\n/**\n * Returns a stable callback for executing a Syncore mutation.\n *\n * The returned function is type-safe: its parameter types are inferred from\n * the mutation definition and remain stable across re-renders (no need to\n * wrap in `useCallback`).\n *\n * ```tsx\n * const createTask = useMutation(api.tasks.create);\n *\n * return (\n * <button onClick={() => createTask({ title: \"New task\" })}>\n * Add task\n * </button>\n * );\n * ```\n *\n * @param reference - A typed mutation reference from the generated `api` object.\n * @returns A function that, when called, executes the mutation and returns a\n * promise that resolves to the mutation’s return value.\n */\nexport function useMutation<TArgs, TResult>(\n reference: FunctionReference<\"mutation\", TArgs, TResult>\n): (...args: OptionalArgsTuple<TArgs>) => Promise<TResult> {\n const client = useSyncore();\n return (...args) => client.mutation(reference, normalizeOptionalArgs(args));\n}\n\n/**\n * Returns a stable callback for executing a Syncore action.\n *\n * Identical to {@link useMutation} but for actions. Use this when the work you\n * need to do cannot run inside a transaction (external API calls, long-running\n * tasks, etc.).\n *\n * ```tsx\n * const importTasks = useAction(api.tasks.importFromCsv);\n *\n * return (\n * <button onClick={() => importTasks({ url: csvUrl })}>\n * Import\n * </button>\n * );\n * ```\n *\n * @param reference - A typed action reference from the generated `api` object.\n * @returns A function that, when called, executes the action and returns a\n * promise that resolves to the action’s return value.\n */\nexport function useAction<TArgs, TResult>(\n reference: FunctionReference<\"action\", TArgs, TResult>\n): (...args: OptionalArgsTuple<TArgs>) => Promise<TResult> {\n const client = useSyncore();\n return (...args) => client.action(reference, normalizeOptionalArgs(args));\n}\n\n/**\n * Subscribe to multiple Syncore queries simultaneously and receive per-entry\n * state objects in a single hook call.\n *\n * More efficient than calling `useQuery` in a loop when the set of queries is\n * known at component render time. The hook maintains only one subscription per\n * unique `(reference, args)` combination even if entries are duplicated.\n *\n * ```tsx\n * const { header, sidebar } = useQueries({\n * header: { query: api.layout.header },\n * sidebar: { query: api.layout.sidebar, args: { userId } },\n * });\n *\n * if (header.isLoading || sidebar.isLoading) return <Spinner />;\n * ```\n *\n * @param entries - A record of named query requests. Each entry can include\n * `args: skip` to suppress that specific subscription.\n * @returns A record with the same keys, each holding a {@link SyncoreQueryState}.\n */\nexport function useQueries<TEntries extends QueriesRequestInput>(\n entries: TEntries\n): UseQueriesResult<TEntries> {\n const client = useSyncore();\n const runtimeStatus = useSyncoreStatus();\n const entriesKey = stableStringify(\n Object.entries(entries)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => ({\n key,\n referenceName: entry.query.name,\n skipped: entry.args === skip,\n args:\n entry.args === skip\n ? {}\n : normalizeOptionalArgs([entry.args ?? {}] as [] | [unknown])\n }))\n );\n const normalizedEntries = useMemo(\n () => JSON.parse(entriesKey) as NormalizedQueryEntry[],\n [entriesKey]\n );\n const [observer] = useState(() => new ReactQueriesObserver(client));\n const [, setVersion] = useState(0);\n\n if (observer.client !== client) {\n observer.replaceClient(client);\n }\n\n useEffect(() => () => observer.destroy(), [observer]);\n\n useEffect(() => {\n observer.setEntries(normalizedEntries);\n setVersion((value) => value + 1);\n return observer.subscribe(() => {\n setVersion((value) => value + 1);\n });\n }, [normalizedEntries, observer]);\n\n const snapshot = observer.getSnapshot(normalizedEntries);\n\n return useMemo(() => {\n return Object.fromEntries(\n normalizedEntries.map((entry) => [\n entry.key,\n toQueryState(\n snapshot[entry.key] ?? noOpSnapshot,\n runtimeStatus,\n entry.skipped\n )\n ])\n ) as UseQueriesResult<TEntries>;\n }, [normalizedEntries, runtimeStatus, snapshot]);\n}\n\n/**\n * Subscribe to a paginated Syncore query, incrementally loading more pages.\n *\n * The query must accept a `paginationOpts` argument and return a\n * `PaginationResult`. The hook manages cursors automatically — call the\n * returned `loadMore` function to append the next page to the results.\n *\n * ```tsx\n * const { results, status, loadMore, hasMore } = usePaginatedQuery(\n * api.tasks.list,\n * { projectId },\n * { initialNumItems: 20 },\n * );\n *\n * return (\n * <>\n * {results.map((t) => <TaskRow key={t._id} task={t} />)}\n * {hasMore && (\n * <button\n * onClick={() => loadMore(20)}\n * disabled={status === \"loadingMore\"}\n * >\n * Load more\n * </button>\n * )}\n * </>\n * );\n * ```\n *\n * Pass `skip` as `args` to suppress the subscription until arguments are\n * ready.\n *\n * @param reference - A typed query reference whose handler calls\n * `ctx.db.query(…).paginate(paginationOpts)`.\n * @param args - Arguments for the query (excluding\n * `paginationOpts`, which is managed internally), or `skip`.\n * @param options.initialNumItems - Number of items to load on the first page.\n * @returns A {@link UsePaginatedQueryResult} with the accumulated results and\n * a `loadMore` callback.\n */\nexport function usePaginatedQuery<TReference extends PaginatedQueryReference>(\n reference: TReference,\n args: PaginatedQueryArgs<TReference> | Skip,\n options: {\n initialNumItems: number;\n }\n): UsePaginatedQueryResult<PaginatedQueryItem<TReference>> {\n if (\n typeof options.initialNumItems !== \"number\" ||\n options.initialNumItems <= 0\n ) {\n throw new Error(\n `options.initialNumItems must be a positive number. Received ${String(\n options.initialNumItems\n )}.`\n );\n }\n\n const runtimeStatus = useSyncoreStatus();\n const isSkipped = args === skip;\n const normalizedArgs = isSkipped ? {} : (args ?? {});\n const requestKey = stableStringify({\n referenceName: reference.name,\n args: normalizedArgs,\n initialNumItems: options.initialNumItems,\n skipped: isSkipped\n });\n const createInitialState = useMemo(\n () => () =>\n ({\n requestKey,\n nextPageKey: 1,\n pages: isSkipped\n ? []\n : [\n {\n key: \"0\",\n cursor: null,\n numItems: options.initialNumItems\n }\n ]\n }) satisfies PaginatedQueryInternalState,\n [isSkipped, options.initialNumItems, requestKey]\n );\n const [state, setState] = useState<PaginatedQueryInternalState>(\n createInitialState\n );\n\n let currentState = state;\n if (currentState.requestKey !== requestKey) {\n currentState = createInitialState();\n setState(currentState);\n }\n\n const pageQueries = useMemo(() => {\n const requests: Record<string, QueryRequestInput> = {};\n for (const page of currentState.pages) {\n requests[page.key] = {\n query: reference,\n args: {\n ...(normalizedArgs as Record<string, unknown>),\n paginationOpts: {\n cursor: page.cursor,\n numItems: page.numItems\n }\n }\n };\n }\n return requests;\n }, [currentState.pages, normalizedArgs, reference]);\n const pageStates = useQueries(pageQueries);\n\n const derived = useMemo(() => {\n const pages: Array<PaginationResult<PaginatedQueryItem<TReference>>> = [];\n let error: Error | undefined;\n\n for (const page of currentState.pages) {\n const pageState =\n pageStates[page.key as keyof typeof pageStates] as\n | SyncoreQueryState<PaginationResult<PaginatedQueryItem<TReference>>>\n | undefined;\n if (!pageState || pageState.status === \"loading\") {\n break;\n }\n if (pageState.status === \"error\") {\n error = pageState.error;\n break;\n }\n if (pageState.data) {\n pages.push(pageState.data);\n }\n }\n\n const results = pages.flatMap((page) => page.page);\n const lastLoadedPage = pages.at(-1);\n const lastRequestedKey = currentState.pages.at(-1)?.key;\n const lastRequestedState = lastRequestedKey\n ? (pageStates[lastRequestedKey as keyof typeof pageStates] as\n | SyncoreQueryState<PaginationResult<PaginatedQueryItem<TReference>>>\n | undefined)\n : undefined;\n const isLoading = !isSkipped && pages.length === 0 && !error;\n const isLoadingMore =\n currentState.pages.length > pages.length ||\n (!!lastRequestedState && lastRequestedState.status === \"loading\" && pages.length > 0);\n const hasMore = !!lastLoadedPage && !lastLoadedPage.isDone;\n const status: SyncorePaginatedQueryStatus = error\n ? \"error\"\n : isSkipped\n ? \"ready\"\n : isLoading\n ? \"loading\"\n : isLoadingMore\n ? \"loadingMore\"\n : hasMore\n ? \"ready\"\n : \"exhausted\";\n\n return {\n pages,\n results,\n error,\n isLoading,\n isLoadingMore,\n hasMore,\n cursor: lastLoadedPage?.cursor ?? null,\n status\n };\n }, [currentState.pages, isSkipped, pageStates]);\n\n return {\n ...derived,\n runtimeStatus,\n loadMore(numItems = options.initialNumItems) {\n if (\n isSkipped ||\n derived.error ||\n derived.isLoadingMore ||\n !derived.hasMore ||\n !derived.cursor\n ) {\n return;\n }\n\n setState((previous) => ({\n ...previous,\n nextPageKey: previous.nextPageKey + 1,\n pages: [\n ...previous.pages,\n {\n key: String(previous.nextPageKey),\n cursor: derived.cursor,\n numItems\n }\n ]\n }));\n }\n };\n}\n\nconst noOpSnapshot: QuerySnapshot<never> = {\n data: undefined,\n error: undefined\n};\n\nconst noOpWatch: ManagedSyncoreWatch<never> = {\n onUpdate: () => () => undefined,\n localQueryResult: () => undefined,\n localQueryError: () => undefined\n};\n\nfunction useManagedQueryWatch<TArgs, TResult>(\n client: SyncoreClient,\n reference: FunctionReference<\"query\", TArgs, TResult>,\n args?: TArgs,\n isSkipped = false\n): ManagedSyncoreWatch<TResult> {\n const argsKey = isSkipped ? skip : stableStringify(args ?? {});\n const [watch, setWatch] = useState<ManagedSyncoreWatch<TResult>>(\n () => noOpWatch\n );\n\n useEffect(() => {\n if (isSkipped) {\n setWatch(noOpWatch);\n return;\n }\n\n const nextWatch = client.watchQuery(\n reference,\n JSON.parse(argsKey) as TArgs\n ) as ManagedSyncoreWatch<TResult>;\n setWatch(nextWatch);\n\n return () => {\n nextWatch.dispose?.();\n };\n }, [argsKey, client, isSkipped, reference]);\n\n return watch;\n}\n\nfunction normalizeOptionalArgs<TArgs>(\n args: [] | [TArgs] | readonly unknown[]\n): TArgs {\n return (args[0] ?? {}) as TArgs;\n}\n\nfunction readWatchSnapshot<TResult>(\n watch: SyncoreWatch<TResult>\n): QuerySnapshot<TResult> {\n return {\n data: watch.localQueryResult(),\n error: watch.localQueryError()\n };\n}\n\nfunction readQueriesSnapshot(\n records: Array<{\n key: string;\n snapshot: QuerySnapshot<unknown>;\n }>\n): Record<string, QuerySnapshot<unknown>> {\n return Object.fromEntries(\n records.map((entry) => [entry.key, entry.snapshot])\n );\n}\n\nfunction readRuntimeStatusSnapshot(\n watch: SyncoreWatch<SyncoreRuntimeStatus>\n): SyncoreRuntimeStatus {\n return watch.localQueryResult() ?? defaultRuntimeStatus;\n}\n\nfunction toQueryState<TResult>(\n snapshot: QuerySnapshot<TResult>,\n runtimeStatus: SyncoreRuntimeStatus,\n isSkipped: boolean\n): SyncoreQueryState<TResult> {\n if (isSkipped) {\n return {\n data: undefined,\n error: undefined,\n status: \"skipped\",\n runtimeStatus,\n isLoading: false,\n isError: false,\n isReady: false\n };\n }\n\n const status =\n snapshot.error !== undefined\n ? \"error\"\n : snapshot.data === undefined\n ? \"loading\"\n : \"success\";\n\n return {\n data: snapshot.data,\n error: snapshot.error,\n status,\n runtimeStatus,\n isLoading: status === \"loading\",\n isError: status === \"error\",\n isReady: status === \"success\"\n };\n}\n\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortValue(value));\n}\n\nfunction sortValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortValue);\n }\n if (value && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, nested]) => [key, sortValue(nested)])\n );\n }\n return value;\n}\n\nclass ReactQueriesObserver {\n readonly client: SyncoreClient;\n private readonly listeners = new Set<() => void>();\n private readonly records = new Map<string, QueryObserverRecord>();\n\n constructor(client: SyncoreClient) {\n this.client = client;\n }\n\n replaceClient(client: SyncoreClient): void {\n this.destroy();\n (this as { client: SyncoreClient }).client = client;\n }\n\n setEntries(entries: NormalizedQueryEntry[]): void {\n const activeKeys = new Set(entries.map((entry) => entry.key));\n\n for (const entry of entries) {\n const requestKey = `${entry.referenceName}:${stableStringify(entry.args)}:${String(\n entry.skipped\n )}`;\n const current = this.records.get(entry.key);\n if (current?.requestKey === requestKey) {\n continue;\n }\n\n current?.unsubscribe();\n current?.watch?.dispose?.();\n\n if (entry.skipped) {\n this.records.set(entry.key, {\n requestKey,\n snapshot: noOpSnapshot,\n unsubscribe: () => undefined\n });\n continue;\n }\n\n const watch = this.client.watchQuery(\n { kind: \"query\", name: entry.referenceName },\n entry.args\n ) as ManagedSyncoreWatch<unknown>;\n const record: QueryObserverRecord = {\n requestKey,\n snapshot: readWatchSnapshot(watch),\n unsubscribe: () => undefined,\n watch\n };\n record.unsubscribe = watch.onUpdate(() => {\n record.snapshot = readWatchSnapshot(watch);\n this.notify();\n });\n this.records.set(entry.key, record);\n }\n\n for (const [key, record] of this.records.entries()) {\n if (activeKeys.has(key)) {\n continue;\n }\n record.unsubscribe();\n record.watch?.dispose?.();\n this.records.delete(key);\n }\n }\n\n getSnapshot(entries: NormalizedQueryEntry[]): Record<string, QuerySnapshot<unknown>> {\n return readQueriesSnapshot(\n entries.map((entry) => ({\n key: entry.key,\n snapshot: this.records.get(entry.key)?.snapshot ?? noOpSnapshot\n }))\n );\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n destroy(): void {\n for (const record of this.records.values()) {\n record.unsubscribe();\n record.watch?.dispose?.();\n }\n this.records.clear();\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.tsx"],"sourcesContent":["import {\n createContext,\n type ReactNode,\n useContext,\n useEffect,\n useMemo,\n useState\n} from \"react\";\nimport type {\n FunctionArgs,\n FunctionReference,\n FunctionResult,\n PaginationOptions,\n PaginationResult,\n SyncoreClient,\n SyncorePaginatedQueryStatus,\n SyncoreQueryState,\n SyncoreRuntimeStatus,\n SyncoreWatch,\n UsePaginatedQueryResult\n} from \"@syncore/core\";\n\ntype ManagedSyncoreWatch<TResult> = SyncoreWatch<TResult> & {\n dispose?: () => void;\n};\n\ntype OptionalArgsTuple<TArgs> =\n Record<never, never> extends TArgs ? [args?: TArgs] : [args: TArgs];\n\ntype QueryRequestInput<\n TReference extends FunctionReference<\"query\"> = FunctionReference<\"query\">\n> = Record<never, never> extends FunctionArgs<TReference>\n ? {\n query: TReference;\n args?: FunctionArgs<TReference> | Skip;\n }\n : {\n query: TReference;\n args: FunctionArgs<TReference> | Skip;\n };\n\ntype QueriesRequestInput = Record<string, QueryRequestInput>;\n\ntype QueryStateForEntry<TEntry> = TEntry extends QueryRequestInput<\n infer TReference\n>\n ? SyncoreQueryState<FunctionResult<TReference>>\n : never;\n\nexport type UseQueriesResult<TEntries extends QueriesRequestInput> = {\n [TKey in keyof TEntries]: QueryStateForEntry<TEntries[TKey]>;\n};\n\ntype PaginatedQueryReference = FunctionReference<\n \"query\",\n Record<string, unknown>,\n PaginationResult<unknown>\n>;\n\ntype PaginatedQueryArgs<TReference extends FunctionReference<\"query\">> =\n FunctionArgs<TReference> extends { paginationOpts: PaginationOptions }\n ? Omit<FunctionArgs<TReference>, \"paginationOpts\">\n : never;\n\ntype PaginatedQueryItem<TReference extends FunctionReference<\"query\">> =\n FunctionResult<TReference> extends PaginationResult<infer TItem>\n ? TItem\n : never;\n\ntype QuerySnapshot<TResult> = {\n data: TResult | undefined;\n error: Error | undefined;\n};\n\ntype NormalizedQueryEntry = {\n key: string;\n referenceName: string;\n args: Record<string, unknown>;\n skipped: boolean;\n};\n\ntype PaginatedQueryInternalState = {\n requestKey: string;\n nextPageKey: number;\n pages: Array<{\n key: string;\n cursor: string | null;\n numItems: number;\n }>;\n};\n\ntype QueryObserverRecord = {\n requestKey: string;\n snapshot: QuerySnapshot<unknown>;\n unsubscribe: () => void;\n watch?: ManagedSyncoreWatch<unknown>;\n};\n\n/**\n * Pass `skip` as the `args` argument to any Syncore React hook to suppress\n * that subscription entirely.\n *\n * Useful when the query arguments depend on state that is not yet available\n * (e.g. a selected item ID) — instead of conditionally calling the hook\n * (which violates the Rules of Hooks), pass `skip` to deactivate it:\n *\n * ```tsx\n * const task = useQuery(api.tasks.get, selectedId ? { id: selectedId } : skip);\n * // task is `undefined` while selectedId is null/undefined\n * ```\n *\n * Skipped queries return `undefined` for `data`, `\"skipped\"` for `status`,\n * and `false` for `isLoading`.\n */\nexport const skip = \"skip\" as const;\ntype Skip = typeof skip;\n\nconst defaultRuntimeStatus: SyncoreRuntimeStatus = {\n kind: \"starting\",\n reason: \"booting\"\n};\n\nconst SyncoreContext = createContext<SyncoreClient | null>(null);\n\n/**\n * Provides a Syncore client to all React descendants via context.\n *\n * Wrap your app (or any subtree that uses Syncore hooks) with\n * `SyncoreProvider`. All `useQuery`, `useMutation`, `useAction`, and\n * `useQueries` calls inside the tree will automatically use the client you\n * supply.\n *\n * ```tsx\n * // For a browser worker setup\n * const client = createBrowserWorkerClient();\n *\n * function App() {\n * return (\n * <SyncoreProvider client={client}>\n * <TaskList />\n * </SyncoreProvider>\n * );\n * }\n * ```\n *\n * For Next.js apps use `SyncoreNextProvider` which also handles service worker\n * and worker URL configuration.\n */\nexport function SyncoreProvider({\n client,\n children\n}: {\n client: SyncoreClient;\n children: ReactNode;\n}) {\n return (\n <SyncoreContext.Provider value={client}>{children}</SyncoreContext.Provider>\n );\n}\n\n/**\n * Returns the active `SyncoreClient` from the nearest {@link SyncoreProvider}\n * in the React tree.\n *\n * Throws if called outside of a `SyncoreProvider`. Prefer the higher-level\n * hooks (`useQuery`, `useMutation`, etc.) for common operations — use\n * `useSyncore` only when you need direct access to the client object.\n *\n * ```ts\n * const client = useSyncore();\n * const tasks = await client.query(api.tasks.list);\n * ```\n */\nexport function useSyncore(): SyncoreClient {\n const client = useContext(SyncoreContext);\n if (!client) {\n throw new Error(\"SyncoreProvider is missing from the React tree.\");\n }\n return client;\n}\n\n/**\n * Subscribe to the runtime’s lifecycle status.\n *\n * Returns a SyncoreRuntimeStatus that updates whenever the underlying\n * runtime changes state (e.g. starting, ready, error). Use it to gate your UI\n * on the runtime being ready or to display an error boundary:\n *\n * ```tsx\n * function TaskList() {\n * const status = useSyncoreStatus();\n * if (status.kind === \"starting\") return <Spinner />;\n * if (status.kind === \"error\") return <ErrorScreen error={status.error} />;\n * return <Tasks />;\n * }\n * ```\n *\n * Most components do not need this — `useQuery` already incorporates runtime\n * status into the `SyncoreQueryState.runtimeStatus` field.\n */\nexport function useSyncoreStatus(): SyncoreRuntimeStatus {\n const client = useSyncore();\n const watch = useMemo(\n () => client.watchRuntimeStatus() as ManagedSyncoreWatch<SyncoreRuntimeStatus>,\n [client]\n );\n const [status, setStatus] = useState<SyncoreRuntimeStatus>(() =>\n readRuntimeStatusSnapshot(watch)\n );\n\n useEffect(() => {\n const sync = () => {\n setStatus(readRuntimeStatusSnapshot(watch));\n };\n sync();\n return watch.onUpdate(sync);\n }, [watch]);\n\n useEffect(\n () => () => {\n watch.dispose?.();\n },\n [watch]\n );\n\n return status;\n}\n\n/**\n * Subscribe to a reactive Syncore query and return the current data.\n *\n * The component re-renders automatically whenever the query result changes.\n * If the query throws, `useQuery` re-throws the error so a React error\n * boundary can catch it — use {@link useQueryState} if you need to handle\n * errors inline.\n *\n * ```tsx\n * // Basic usage\n * const tasks = useQuery(api.tasks.list);\n *\n * // With arguments\n * const task = useQuery(api.tasks.get, { id: taskId });\n *\n * // Conditionally skip when arguments are not yet available\n * const task = useQuery(api.tasks.get, taskId ? { id: taskId } : skip);\n * ```\n *\n * @param reference - A typed function reference (from the generated `api` object).\n * @param args - The query’s arguments, or `skip` to suppress the subscription.\n * @returns The current query result, or `undefined` while loading.\n */\nexport function useQuery<TArgs, TResult>(\n reference: FunctionReference<\"query\", TArgs, TResult>,\n ...args: OptionalArgsTuple<TArgs> | [Skip]\n): TResult | undefined {\n const state = useQueryState(reference, ...(args as OptionalArgsTuple<TArgs> | [Skip]));\n if (state.error) {\n throw state.error;\n }\n return state.data;\n}\n\n/**\n * Subscribe to a reactive Syncore query and return the full\n * SyncoreQueryState including loading, error, and runtime status.\n *\n * Use this instead of {@link useQuery} when you need to:\n * - Differentiate between `undefined` data and an error.\n * - React to `isLoading` / `isError` without relying on error boundaries.\n * - Inspect `runtimeStatus` for the underlying runtime’s health.\n *\n * ```tsx\n * const { data, isLoading, isError, error } = useQueryState(api.tasks.list);\n *\n * if (isLoading) return <Spinner />;\n * if (isError) return <ErrorBanner message={error.message} />;\n * return <TaskList tasks={data} />;\n * ```\n */\nexport function useQueryState<TArgs, TResult>(\n reference: FunctionReference<\"query\", TArgs, TResult>,\n ...args: OptionalArgsTuple<TArgs> | [Skip]\n): SyncoreQueryState<TResult> {\n const isSkipped = args[0] === skip;\n const client = useSyncore();\n const runtimeStatus = useSyncoreStatus();\n const watch = useManagedQueryWatch(\n client,\n reference,\n isSkipped\n ? undefined\n : normalizeOptionalArgs(args as OptionalArgsTuple<TArgs>),\n isSkipped\n );\n const [snapshot, setSnapshot] = useState<QuerySnapshot<TResult>>(() =>\n isSkipped ? noOpSnapshot : readWatchSnapshot(watch)\n );\n\n useEffect(() => {\n if (isSkipped) {\n setSnapshot(noOpSnapshot);\n return;\n }\n const sync = () => {\n setSnapshot(readWatchSnapshot(watch));\n };\n sync();\n return watch.onUpdate(sync);\n }, [watch, isSkipped]);\n\n return toQueryState(snapshot, runtimeStatus, isSkipped);\n}\n\n/**\n * Returns a stable callback for executing a Syncore mutation.\n *\n * The returned function is type-safe: its parameter types are inferred from\n * the mutation definition and remain stable across re-renders (no need to\n * wrap in `useCallback`).\n *\n * ```tsx\n * const createTask = useMutation(api.tasks.create);\n *\n * return (\n * <button onClick={() => createTask({ title: \"New task\" })}>\n * Add task\n * </button>\n * );\n * ```\n *\n * @param reference - A typed mutation reference from the generated `api` object.\n * @returns A function that, when called, executes the mutation and returns a\n * promise that resolves to the mutation’s return value.\n */\nexport function useMutation<TArgs, TResult>(\n reference: FunctionReference<\"mutation\", TArgs, TResult>\n): (...args: OptionalArgsTuple<TArgs>) => Promise<TResult> {\n const client = useSyncore();\n return (...args) => client.mutation(reference, normalizeOptionalArgs(args));\n}\n\n/**\n * Returns a stable callback for executing a Syncore action.\n *\n * Identical to {@link useMutation} but for actions. Use this when the work you\n * need to do cannot run inside a transaction (external API calls, long-running\n * tasks, etc.).\n *\n * ```tsx\n * const importTasks = useAction(api.tasks.importFromCsv);\n *\n * return (\n * <button onClick={() => importTasks({ url: csvUrl })}>\n * Import\n * </button>\n * );\n * ```\n *\n * @param reference - A typed action reference from the generated `api` object.\n * @returns A function that, when called, executes the action and returns a\n * promise that resolves to the action’s return value.\n */\nexport function useAction<TArgs, TResult>(\n reference: FunctionReference<\"action\", TArgs, TResult>\n): (...args: OptionalArgsTuple<TArgs>) => Promise<TResult> {\n const client = useSyncore();\n return (...args) => client.action(reference, normalizeOptionalArgs(args));\n}\n\n/**\n * Subscribe to multiple Syncore queries simultaneously and receive per-entry\n * state objects in a single hook call.\n *\n * More efficient than calling `useQuery` in a loop when the set of queries is\n * known at component render time. The hook maintains only one subscription per\n * unique `(reference, args)` combination even if entries are duplicated.\n *\n * ```tsx\n * const { header, sidebar } = useQueries({\n * header: { query: api.layout.header },\n * sidebar: { query: api.layout.sidebar, args: { userId } },\n * });\n *\n * if (header.isLoading || sidebar.isLoading) return <Spinner />;\n * ```\n *\n * @param entries - A record of named query requests. Each entry can include\n * `args: skip` to suppress that specific subscription.\n * @returns A record with the same keys, each holding a SyncoreQueryState.\n */\nexport function useQueries<TEntries extends QueriesRequestInput>(\n entries: TEntries\n): UseQueriesResult<TEntries> {\n const client = useSyncore();\n const runtimeStatus = useSyncoreStatus();\n const entriesKey = stableStringify(\n Object.entries(entries)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, entry]) => ({\n key,\n referenceName: entry.query.name,\n skipped: entry.args === skip,\n args:\n entry.args === skip\n ? {}\n : normalizeOptionalArgs([entry.args ?? {}] as [] | [unknown])\n }))\n );\n const normalizedEntries = useMemo(\n () => JSON.parse(entriesKey) as NormalizedQueryEntry[],\n [entriesKey]\n );\n const [observer] = useState(() => new ReactQueriesObserver(client));\n const [, setVersion] = useState(0);\n\n if (observer.client !== client) {\n observer.replaceClient(client);\n }\n\n useEffect(() => () => observer.destroy(), [observer]);\n\n useEffect(() => {\n observer.setEntries(normalizedEntries);\n setVersion((value) => value + 1);\n return observer.subscribe(() => {\n setVersion((value) => value + 1);\n });\n }, [normalizedEntries, observer]);\n\n const snapshot = observer.getSnapshot(normalizedEntries);\n\n return useMemo(() => {\n return Object.fromEntries(\n normalizedEntries.map((entry) => [\n entry.key,\n toQueryState(\n snapshot[entry.key] ?? noOpSnapshot,\n runtimeStatus,\n entry.skipped\n )\n ])\n ) as UseQueriesResult<TEntries>;\n }, [normalizedEntries, runtimeStatus, snapshot]);\n}\n\n/**\n * Subscribe to a paginated Syncore query, incrementally loading more pages.\n *\n * The query must accept a `paginationOpts` argument and return a\n * `PaginationResult`. The hook manages cursors automatically — call the\n * returned `loadMore` function to append the next page to the results.\n *\n * ```tsx\n * const { results, status, loadMore, hasMore } = usePaginatedQuery(\n * api.tasks.list,\n * { projectId },\n * { initialNumItems: 20 },\n * );\n *\n * return (\n * <>\n * {results.map((t) => <TaskRow key={t._id} task={t} />)}\n * {hasMore && (\n * <button\n * onClick={() => loadMore(20)}\n * disabled={status === \"loadingMore\"}\n * >\n * Load more\n * </button>\n * )}\n * </>\n * );\n * ```\n *\n * Pass `skip` as `args` to suppress the subscription until arguments are\n * ready.\n *\n * @param reference - A typed query reference whose handler calls\n * `ctx.db.query(…).paginate(paginationOpts)`.\n * @param args - Arguments for the query (excluding\n * `paginationOpts`, which is managed internally), or `skip`.\n * @param options - Pagination options. `initialNumItems` controls the number\n * of items to load on the first page.\n * @returns A UsePaginatedQueryResult with the accumulated results and\n * a `loadMore` callback.\n */\nexport function usePaginatedQuery<TReference extends PaginatedQueryReference>(\n reference: TReference,\n args: PaginatedQueryArgs<TReference> | Skip,\n options: {\n initialNumItems: number;\n }\n): UsePaginatedQueryResult<PaginatedQueryItem<TReference>> {\n if (\n typeof options.initialNumItems !== \"number\" ||\n options.initialNumItems <= 0\n ) {\n throw new Error(\n `options.initialNumItems must be a positive number. Received ${String(\n options.initialNumItems\n )}.`\n );\n }\n\n const runtimeStatus = useSyncoreStatus();\n const isSkipped = args === skip;\n const normalizedArgs = isSkipped ? {} : (args ?? {});\n const requestKey = stableStringify({\n referenceName: reference.name,\n args: normalizedArgs,\n initialNumItems: options.initialNumItems,\n skipped: isSkipped\n });\n const createInitialState = useMemo(\n () => () =>\n ({\n requestKey,\n nextPageKey: 1,\n pages: isSkipped\n ? []\n : [\n {\n key: \"0\",\n cursor: null,\n numItems: options.initialNumItems\n }\n ]\n }) satisfies PaginatedQueryInternalState,\n [isSkipped, options.initialNumItems, requestKey]\n );\n const [state, setState] = useState<PaginatedQueryInternalState>(\n createInitialState\n );\n\n let currentState = state;\n if (currentState.requestKey !== requestKey) {\n currentState = createInitialState();\n setState(currentState);\n }\n\n const pageQueries = useMemo(() => {\n const requests: Record<string, QueryRequestInput> = {};\n for (const page of currentState.pages) {\n requests[page.key] = {\n query: reference,\n args: {\n ...(normalizedArgs as Record<string, unknown>),\n paginationOpts: {\n cursor: page.cursor,\n numItems: page.numItems\n }\n }\n };\n }\n return requests;\n }, [currentState.pages, normalizedArgs, reference]);\n const pageStates = useQueries(pageQueries);\n\n const derived = useMemo(() => {\n const pages: Array<PaginationResult<PaginatedQueryItem<TReference>>> = [];\n let error: Error | undefined;\n\n for (const page of currentState.pages) {\n const pageState =\n pageStates[page.key as keyof typeof pageStates] as\n | SyncoreQueryState<PaginationResult<PaginatedQueryItem<TReference>>>\n | undefined;\n if (!pageState || pageState.status === \"loading\") {\n break;\n }\n if (pageState.status === \"error\") {\n error = pageState.error;\n break;\n }\n if (pageState.data) {\n pages.push(pageState.data);\n }\n }\n\n const results = pages.flatMap((page) => page.page);\n const lastLoadedPage = pages.at(-1);\n const lastRequestedKey = currentState.pages.at(-1)?.key;\n const lastRequestedState = lastRequestedKey\n ? (pageStates[lastRequestedKey as keyof typeof pageStates] as\n | SyncoreQueryState<PaginationResult<PaginatedQueryItem<TReference>>>\n | undefined)\n : undefined;\n const isLoading = !isSkipped && pages.length === 0 && !error;\n const isLoadingMore =\n currentState.pages.length > pages.length ||\n (!!lastRequestedState && lastRequestedState.status === \"loading\" && pages.length > 0);\n const hasMore = !!lastLoadedPage && !lastLoadedPage.isDone;\n const status: SyncorePaginatedQueryStatus = error\n ? \"error\"\n : isSkipped\n ? \"ready\"\n : isLoading\n ? \"loading\"\n : isLoadingMore\n ? \"loadingMore\"\n : hasMore\n ? \"ready\"\n : \"exhausted\";\n\n return {\n pages,\n results,\n error,\n isLoading,\n isLoadingMore,\n hasMore,\n cursor: lastLoadedPage?.cursor ?? null,\n status\n };\n }, [currentState.pages, isSkipped, pageStates]);\n\n return {\n ...derived,\n runtimeStatus,\n loadMore(numItems = options.initialNumItems) {\n if (\n isSkipped ||\n derived.error ||\n derived.isLoadingMore ||\n !derived.hasMore ||\n !derived.cursor\n ) {\n return;\n }\n\n setState((previous) => ({\n ...previous,\n nextPageKey: previous.nextPageKey + 1,\n pages: [\n ...previous.pages,\n {\n key: String(previous.nextPageKey),\n cursor: derived.cursor,\n numItems\n }\n ]\n }));\n }\n };\n}\n\nconst noOpSnapshot: QuerySnapshot<never> = {\n data: undefined,\n error: undefined\n};\n\nconst noOpWatch: ManagedSyncoreWatch<never> = {\n onUpdate: () => () => undefined,\n localQueryResult: () => undefined,\n localQueryError: () => undefined\n};\n\nfunction useManagedQueryWatch<TArgs, TResult>(\n client: SyncoreClient,\n reference: FunctionReference<\"query\", TArgs, TResult>,\n args?: TArgs,\n isSkipped = false\n): ManagedSyncoreWatch<TResult> {\n const argsKey = isSkipped ? skip : stableStringify(args ?? {});\n const [watch, setWatch] = useState<ManagedSyncoreWatch<TResult>>(\n () => noOpWatch\n );\n\n useEffect(() => {\n if (isSkipped) {\n setWatch(noOpWatch);\n return;\n }\n\n const nextWatch = client.watchQuery(\n reference,\n JSON.parse(argsKey) as TArgs\n ) as ManagedSyncoreWatch<TResult>;\n setWatch(nextWatch);\n\n return () => {\n nextWatch.dispose?.();\n };\n }, [argsKey, client, isSkipped, reference]);\n\n return watch;\n}\n\nfunction normalizeOptionalArgs<TArgs>(\n args: [] | [TArgs] | readonly unknown[]\n): TArgs {\n return (args[0] ?? {}) as TArgs;\n}\n\nfunction readWatchSnapshot<TResult>(\n watch: SyncoreWatch<TResult>\n): QuerySnapshot<TResult> {\n return {\n data: watch.localQueryResult(),\n error: watch.localQueryError()\n };\n}\n\nfunction readQueriesSnapshot(\n records: Array<{\n key: string;\n snapshot: QuerySnapshot<unknown>;\n }>\n): Record<string, QuerySnapshot<unknown>> {\n return Object.fromEntries(\n records.map((entry) => [entry.key, entry.snapshot])\n );\n}\n\nfunction readRuntimeStatusSnapshot(\n watch: SyncoreWatch<SyncoreRuntimeStatus>\n): SyncoreRuntimeStatus {\n return watch.localQueryResult() ?? defaultRuntimeStatus;\n}\n\nfunction toQueryState<TResult>(\n snapshot: QuerySnapshot<TResult>,\n runtimeStatus: SyncoreRuntimeStatus,\n isSkipped: boolean\n): SyncoreQueryState<TResult> {\n if (isSkipped) {\n return {\n data: undefined,\n error: undefined,\n status: \"skipped\",\n runtimeStatus,\n isLoading: false,\n isError: false,\n isReady: false\n };\n }\n\n const status =\n snapshot.error !== undefined\n ? \"error\"\n : snapshot.data === undefined\n ? \"loading\"\n : \"success\";\n\n return {\n data: snapshot.data,\n error: snapshot.error,\n status,\n runtimeStatus,\n isLoading: status === \"loading\",\n isError: status === \"error\",\n isReady: status === \"success\"\n };\n}\n\nfunction stableStringify(value: unknown): string {\n return JSON.stringify(sortValue(value));\n}\n\nfunction sortValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortValue);\n }\n if (value && typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, nested]) => [key, sortValue(nested)])\n );\n }\n return value;\n}\n\nclass ReactQueriesObserver {\n readonly client: SyncoreClient;\n private readonly listeners = new Set<() => void>();\n private readonly records = new Map<string, QueryObserverRecord>();\n\n constructor(client: SyncoreClient) {\n this.client = client;\n }\n\n replaceClient(client: SyncoreClient): void {\n this.destroy();\n (this as { client: SyncoreClient }).client = client;\n }\n\n setEntries(entries: NormalizedQueryEntry[]): void {\n const activeKeys = new Set(entries.map((entry) => entry.key));\n\n for (const entry of entries) {\n const requestKey = `${entry.referenceName}:${stableStringify(entry.args)}:${String(\n entry.skipped\n )}`;\n const current = this.records.get(entry.key);\n if (current?.requestKey === requestKey) {\n continue;\n }\n\n current?.unsubscribe();\n current?.watch?.dispose?.();\n\n if (entry.skipped) {\n this.records.set(entry.key, {\n requestKey,\n snapshot: noOpSnapshot,\n unsubscribe: () => undefined\n });\n continue;\n }\n\n const watch = this.client.watchQuery(\n { kind: \"query\", name: entry.referenceName },\n entry.args\n ) as ManagedSyncoreWatch<unknown>;\n const record: QueryObserverRecord = {\n requestKey,\n snapshot: readWatchSnapshot(watch),\n unsubscribe: () => undefined,\n watch\n };\n record.unsubscribe = watch.onUpdate(() => {\n record.snapshot = readWatchSnapshot(watch);\n this.notify();\n });\n this.records.set(entry.key, record);\n }\n\n for (const [key, record] of this.records.entries()) {\n if (activeKeys.has(key)) {\n continue;\n }\n record.unsubscribe();\n record.watch?.dispose?.();\n this.records.delete(key);\n }\n }\n\n getSnapshot(entries: NormalizedQueryEntry[]): Record<string, QuerySnapshot<unknown>> {\n return readQueriesSnapshot(\n entries.map((entry) => ({\n key: entry.key,\n snapshot: this.records.get(entry.key)?.snapshot ?? noOpSnapshot\n }))\n );\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n destroy(): void {\n for (const record of this.records.values()) {\n record.unsubscribe();\n record.watch?.dispose?.();\n }\n this.records.clear();\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n"],"mappings":"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"}