@noy-db/in-vue 0.2.0-pre.2 → 0.2.0-pre.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +36 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -2
- package/dist/index.d.ts +17 -2
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
NoydbPlugin: () => NoydbPlugin,
|
|
25
25
|
useBlobURL: () => useBlobURL,
|
|
26
26
|
useCollection: () => useCollection,
|
|
27
|
+
useMigrationState: () => useMigrationState,
|
|
27
28
|
useNoydb: () => useNoydb,
|
|
28
29
|
useSync: () => useSync
|
|
29
30
|
});
|
|
@@ -180,12 +181,47 @@ function useBlobURL(collection, idGetter, options = {}) {
|
|
|
180
181
|
}
|
|
181
182
|
return url;
|
|
182
183
|
}
|
|
184
|
+
|
|
185
|
+
// src/useMigrationState.ts
|
|
186
|
+
var import_vue5 = require("vue");
|
|
187
|
+
function useMigrationState(dbOrVault, maybeVault) {
|
|
188
|
+
const db = typeof dbOrVault === "object" ? dbOrVault : useNoydb();
|
|
189
|
+
const vaultName = typeof dbOrVault === "string" ? dbOrVault : maybeVault;
|
|
190
|
+
const fenceState = (0, import_vue5.ref)("normal");
|
|
191
|
+
const schemaVersion = (0, import_vue5.ref)(0);
|
|
192
|
+
if (vaultName !== void 0) {
|
|
193
|
+
try {
|
|
194
|
+
void db.vault(vaultName).schemaFenceState().then(
|
|
195
|
+
(s) => {
|
|
196
|
+
fenceState.value = s.fenceState;
|
|
197
|
+
schemaVersion.value = s.currentSchemaVersion;
|
|
198
|
+
},
|
|
199
|
+
() => {
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const handler = (e) => {
|
|
206
|
+
if (vaultName !== void 0 && e.vault !== vaultName) return;
|
|
207
|
+
fenceState.value = e.fenceState;
|
|
208
|
+
schemaVersion.value = e.currentSchemaVersion;
|
|
209
|
+
};
|
|
210
|
+
db.on("schema:fence-changed", handler);
|
|
211
|
+
if ((0, import_vue5.getCurrentScope)()) {
|
|
212
|
+
(0, import_vue5.onScopeDispose)(() => {
|
|
213
|
+
db.off("schema:fence-changed", handler);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return { fenceState, schemaVersion };
|
|
217
|
+
}
|
|
183
218
|
// Annotate the CommonJS export names for ESM import in node:
|
|
184
219
|
0 && (module.exports = {
|
|
185
220
|
NoydbKey,
|
|
186
221
|
NoydbPlugin,
|
|
187
222
|
useBlobURL,
|
|
188
223
|
useCollection,
|
|
224
|
+
useMigrationState,
|
|
189
225
|
useNoydb,
|
|
190
226
|
useSync
|
|
191
227
|
});
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/plugin.ts","../src/useNoydb.ts","../src/useCollection.ts","../src/useSync.ts","../src/useBlobURL.ts"],"sourcesContent":["export { NoydbPlugin, NoydbKey } from './plugin.js'\nexport type { NoydbPluginOptions } from './plugin.js'\n\nexport { useNoydb } from './useNoydb.js'\nexport { useCollection } from './useCollection.js'\nexport type { UseCollectionReturn } from './useCollection.js'\nexport { useSync } from './useSync.js'\nexport type { UseSyncReturn } from './useSync.js'\nexport { useBlobURL } from './useBlobURL.js'\nexport type { UseBlobURLOptions } from './useBlobURL.js'\n","import type { App, InjectionKey } from 'vue'\nimport type { Noydb } from '@noy-db/hub'\n\n/**\n * Vue injection key for the NOYDB instance provided by `NoydbPlugin`.\n * Use with `inject(NoydbKey)` inside any component under the plugin's scope.\n */\nexport const NoydbKey: InjectionKey<Noydb> = Symbol('noydb')\n\n/**\n * Options passed to `app.use(NoydbPlugin, options)` when registering\n * the NOYDB Vue plugin.\n */\nexport interface NoydbPluginOptions {\n /** The NOYDB instance to provide to all components. */\n instance: Noydb\n}\n\n/** Vue plugin that provides a NOYDB instance to all components. */\nexport const NoydbPlugin = {\n install(app: App, options: NoydbPluginOptions): void {\n app.provide(NoydbKey, options.instance)\n },\n}\n","import { inject } from 'vue'\nimport type { Noydb } from '@noy-db/hub'\nimport { NoydbKey } from './plugin.js'\n\n/** Composable to access the injected NOYDB instance. */\nexport function useNoydb(): Noydb {\n const db = inject(NoydbKey)\n if (!db) {\n throw new Error(\n 'NOYDB instance not found. Did you install the NoydbPlugin?\\n' +\n 'Example: app.use(NoydbPlugin, { instance: db })',\n )\n }\n return db\n}\n","import { ref, onUnmounted, type Ref } from 'vue'\nimport type { Noydb, ChangeEvent } from '@noy-db/hub'\n\n/**\n * Return value of `useCollection<T>()`.\n *\n * All `Ref` values are reactive — bind them directly to Vue templates.\n * The collection subscribes to NOYDB change events and auto-refreshes\n * `data` when the underlying collection is mutated by any code path.\n */\nexport interface UseCollectionReturn<T> {\n /** Reactive list of all records in the collection. */\n data: Ref<T[]>\n /** Loading state — true during initial hydration. */\n loading: Ref<boolean>\n /** Error state — set if hydration or refresh fails. */\n error: Ref<Error | null>\n /** Manually refresh data from the adapter. */\n refresh: () => Promise<void>\n}\n\n/**\n * Composable for reactive collection data.\n * Auto-refreshes when the collection changes (via NOYDB change events).\n */\nexport function useCollection<T>(\n db: Noydb,\n compartmentName: string,\n collectionName: string,\n): UseCollectionReturn<T> {\n const data = ref<T[]>([]) as Ref<T[]>\n const loading = ref(true)\n const error = ref<Error | null>(null)\n\n let compartmentPromise: ReturnType<Noydb['openVault']> | null = null\n\n async function refresh(): Promise<void> {\n try {\n if (!compartmentPromise) {\n compartmentPromise = db.openVault(compartmentName)\n }\n const comp = await compartmentPromise\n const coll = comp.collection<T>(collectionName)\n data.value = await coll.list()\n error.value = null\n } catch (err) {\n error.value = err instanceof Error ? err : new Error(String(err))\n } finally {\n loading.value = false\n }\n }\n\n // Listen for changes to auto-refresh\n const handler = (event: ChangeEvent) => {\n if (event.collection === collectionName) {\n void refresh()\n }\n }\n db.on('change', handler)\n\n onUnmounted(() => {\n db.off('change', handler)\n })\n\n // Initial load\n void refresh()\n\n return { data, loading, error, refresh }\n}\n","import { ref, onUnmounted, type Ref } from 'vue'\nimport type { Noydb, SyncStatus, PushResult, PullResult } from '@noy-db/hub'\n\n/**\n * Return value of `useSync()`.\n *\n * Wraps the NOYDB sync engine in reactive Vue refs. `status` updates\n * automatically as sync operations progress, so binding it to a\n * status indicator (e.g. a toolbar sync icon) requires no manual polling.\n */\nexport interface UseSyncReturn {\n /** Reactive sync status. */\n status: Ref<SyncStatus>\n /** Whether a sync operation is in progress. */\n syncing: Ref<boolean>\n /** Push local changes to remote. */\n push: () => Promise<PushResult>\n /** Pull remote changes to local. */\n pull: () => Promise<PullResult>\n /** Bidirectional sync (pull then push). */\n sync: () => Promise<void>\n}\n\n/**\n * Composable for reactive sync status and controls.\n */\nexport function useSync(db: Noydb, compartmentName: string): UseSyncReturn {\n const status = ref<SyncStatus>({\n dirty: 0,\n lastPush: null,\n lastPull: null,\n online: true,\n }) as Ref<SyncStatus>\n const syncing = ref(false)\n\n function refreshStatus(): void {\n status.value = db.syncStatus(compartmentName)\n }\n\n // Listen for sync events to auto-refresh status\n const onPush = () => refreshStatus()\n const onPull = () => refreshStatus()\n db.on('sync:push', onPush)\n db.on('sync:pull', onPull)\n\n onUnmounted(() => {\n db.off('sync:push', onPush)\n db.off('sync:pull', onPull)\n })\n\n async function push(): Promise<PushResult> {\n syncing.value = true\n try {\n const result = await db.push(compartmentName)\n refreshStatus()\n return result\n } finally {\n syncing.value = false\n }\n }\n\n async function pull(): Promise<PullResult> {\n syncing.value = true\n try {\n const result = await db.pull(compartmentName)\n refreshStatus()\n return result\n } finally {\n syncing.value = false\n }\n }\n\n async function sync(): Promise<void> {\n syncing.value = true\n try {\n await db.sync(compartmentName)\n refreshStatus()\n } finally {\n syncing.value = false\n }\n }\n\n // Initial status\n refreshStatus()\n\n return { status, syncing, push, pull, sync }\n}\n","/**\n * `useBlobURL` — Vue composable that decrypts an encrypted blob slot\n * and surfaces a browser ObjectURL ready to feed into `<img src>`,\n * `<a href>`, etc. Auto-revokes the prior URL when the reactive id\n * changes and on scope dispose, so long-lived pages (attachment\n * viewers, image grids) cannot leak ObjectURLs.\n *\n * SSR-safe: when `URL.createObjectURL` is unavailable (Node without\n * DOM, restricted workers), the ref stays at `null` instead of\n * throwing.\n *\n * @module\n */\n\nimport {\n ref,\n watch,\n getCurrentScope,\n onScopeDispose,\n type Ref,\n} from 'vue'\nimport type { Collection } from '@noy-db/hub'\n\n/** Options accepted by {@link useBlobURL}. */\nexport interface UseBlobURLOptions {\n /**\n * Slot name within the record's blob set. Defaults to `'default'`.\n * Pass an explicit name when records carry multiple attachments.\n */\n readonly slot?: string\n /**\n * MIME type override. Either a static string or a function called\n * each time a fresh URL is built. When omitted, the slot's stored\n * `mimeType` (set at upload time) is used.\n */\n readonly mimeType?: string | (() => string | undefined)\n}\n\n/**\n * Build a reactive ObjectURL for a blob slot on a record. The URL is\n * recomputed whenever the id getter's return value changes; the prior\n * URL is revoked **before** the new one is created, so consumers\n * never see two live URLs for the same composable instance.\n *\n * @example\n * ```ts\n * const recordId = ref('inv-001')\n * const url = useBlobURL(invoiceCollection, () => recordId.value, {\n * slot: 'pdf',\n * mimeType: 'application/pdf',\n * })\n * ```\n *\n * @param collection - Hub collection that owns the record.\n * @param idGetter - Function returning the current record id (or\n * `null` / `undefined` to clear the URL).\n * @param options - Optional slot name and mimeType override.\n * @returns A `Ref<string | null>` — `null` while loading, after a\n * stop, or when no slot is found.\n */\nexport function useBlobURL<T>(\n collection: Collection<T>,\n idGetter: () => string | null | undefined,\n options: UseBlobURLOptions = {},\n): Ref<string | null> {\n const url = ref<string | null>(null)\n const slot = options.slot ?? 'default'\n\n // SSR / non-DOM hosts: bail out, leave url at null. The hub layer\n // would throw here, but the composable's contract is \"stay quiet so\n // server-rendered output is empty.\"\n const browserAware =\n typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function'\n\n let revokeCurrent: (() => void) | null = null\n let stopped = false\n let loadToken = 0\n\n const revoke = (): void => {\n if (revokeCurrent) {\n revokeCurrent()\n revokeCurrent = null\n }\n }\n\n async function load(id: string | null | undefined): Promise<void> {\n // Revoke FIRST — the spec is explicit that the prior URL is\n // released before the next one is created, even if the next\n // create fails or the id resolves to null.\n revoke()\n url.value = null\n if (!browserAware || stopped || !id) return\n\n // Token-guard against stale resolutions: if the id changes again\n // before this call's awaits settle, the older promise's result\n // must be discarded so we don't strand an URL that no upstream\n // ref points at.\n const myToken = ++loadToken\n const mimeType =\n typeof options.mimeType === 'function'\n ? options.mimeType()\n : options.mimeType\n const built = await collection\n .blob(id)\n .objectURL(slot, mimeType !== undefined ? { mimeType } : {})\n if (myToken !== loadToken || stopped) {\n // A newer load won the race or the scope was disposed mid-flight.\n // Drop this URL on the floor.\n built?.revoke()\n return\n }\n if (!built) return\n revokeCurrent = built.revoke\n url.value = built.url\n }\n\n // watch with `immediate: true` covers the initial load — no separate\n // `load(idGetter())` call needed, and watch automatically tracks\n // reactive deps inside the getter.\n watch(idGetter, (next) => {\n void load(next)\n }, { immediate: true })\n\n if (getCurrentScope()) {\n onScopeDispose(() => {\n stopped = true\n revoke()\n url.value = null\n })\n }\n\n return url\n}\n"],"mappings":"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","names":["import_vue","import_vue","import_vue"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/plugin.ts","../src/useNoydb.ts","../src/useCollection.ts","../src/useSync.ts","../src/useBlobURL.ts","../src/useMigrationState.ts"],"sourcesContent":["export { NoydbPlugin, NoydbKey } from './plugin.js'\nexport type { NoydbPluginOptions } from './plugin.js'\n\nexport { useNoydb } from './useNoydb.js'\nexport { useCollection } from './useCollection.js'\nexport type { UseCollectionReturn } from './useCollection.js'\nexport { useSync } from './useSync.js'\nexport type { UseSyncReturn } from './useSync.js'\nexport { useBlobURL } from './useBlobURL.js'\nexport type { UseBlobURLOptions } from './useBlobURL.js'\nexport { useMigrationState } from './useMigrationState.js'\nexport type { UseMigrationStateReturn } from './useMigrationState.js'\n","import type { App, InjectionKey } from 'vue'\nimport type { Noydb } from '@noy-db/hub'\n\n/**\n * Vue injection key for the NOYDB instance provided by `NoydbPlugin`.\n * Use with `inject(NoydbKey)` inside any component under the plugin's scope.\n */\nexport const NoydbKey: InjectionKey<Noydb> = Symbol('noydb')\n\n/**\n * Options passed to `app.use(NoydbPlugin, options)` when registering\n * the NOYDB Vue plugin.\n */\nexport interface NoydbPluginOptions {\n /** The NOYDB instance to provide to all components. */\n instance: Noydb\n}\n\n/** Vue plugin that provides a NOYDB instance to all components. */\nexport const NoydbPlugin = {\n install(app: App, options: NoydbPluginOptions): void {\n app.provide(NoydbKey, options.instance)\n },\n}\n","import { inject } from 'vue'\nimport type { Noydb } from '@noy-db/hub'\nimport { NoydbKey } from './plugin.js'\n\n/** Composable to access the injected NOYDB instance. */\nexport function useNoydb(): Noydb {\n const db = inject(NoydbKey)\n if (!db) {\n throw new Error(\n 'NOYDB instance not found. Did you install the NoydbPlugin?\\n' +\n 'Example: app.use(NoydbPlugin, { instance: db })',\n )\n }\n return db\n}\n","import { ref, onUnmounted, type Ref } from 'vue'\nimport type { Noydb, ChangeEvent } from '@noy-db/hub'\n\n/**\n * Return value of `useCollection<T>()`.\n *\n * All `Ref` values are reactive — bind them directly to Vue templates.\n * The collection subscribes to NOYDB change events and auto-refreshes\n * `data` when the underlying collection is mutated by any code path.\n */\nexport interface UseCollectionReturn<T> {\n /** Reactive list of all records in the collection. */\n data: Ref<T[]>\n /** Loading state — true during initial hydration. */\n loading: Ref<boolean>\n /** Error state — set if hydration or refresh fails. */\n error: Ref<Error | null>\n /** Manually refresh data from the adapter. */\n refresh: () => Promise<void>\n}\n\n/**\n * Composable for reactive collection data.\n * Auto-refreshes when the collection changes (via NOYDB change events).\n */\nexport function useCollection<T>(\n db: Noydb,\n compartmentName: string,\n collectionName: string,\n): UseCollectionReturn<T> {\n const data = ref<T[]>([]) as Ref<T[]>\n const loading = ref(true)\n const error = ref<Error | null>(null)\n\n let compartmentPromise: ReturnType<Noydb['openVault']> | null = null\n\n async function refresh(): Promise<void> {\n try {\n if (!compartmentPromise) {\n compartmentPromise = db.openVault(compartmentName)\n }\n const comp = await compartmentPromise\n const coll = comp.collection<T>(collectionName)\n data.value = await coll.list()\n error.value = null\n } catch (err) {\n error.value = err instanceof Error ? err : new Error(String(err))\n } finally {\n loading.value = false\n }\n }\n\n // Listen for changes to auto-refresh\n const handler = (event: ChangeEvent) => {\n if (event.collection === collectionName) {\n void refresh()\n }\n }\n db.on('change', handler)\n\n onUnmounted(() => {\n db.off('change', handler)\n })\n\n // Initial load\n void refresh()\n\n return { data, loading, error, refresh }\n}\n","import { ref, onUnmounted, type Ref } from 'vue'\nimport type { Noydb, SyncStatus, PushResult, PullResult } from '@noy-db/hub'\n\n/**\n * Return value of `useSync()`.\n *\n * Wraps the NOYDB sync engine in reactive Vue refs. `status` updates\n * automatically as sync operations progress, so binding it to a\n * status indicator (e.g. a toolbar sync icon) requires no manual polling.\n */\nexport interface UseSyncReturn {\n /** Reactive sync status. */\n status: Ref<SyncStatus>\n /** Whether a sync operation is in progress. */\n syncing: Ref<boolean>\n /** Push local changes to remote. */\n push: () => Promise<PushResult>\n /** Pull remote changes to local. */\n pull: () => Promise<PullResult>\n /** Bidirectional sync (pull then push). */\n sync: () => Promise<void>\n}\n\n/**\n * Composable for reactive sync status and controls.\n */\nexport function useSync(db: Noydb, compartmentName: string): UseSyncReturn {\n const status = ref<SyncStatus>({\n dirty: 0,\n lastPush: null,\n lastPull: null,\n online: true,\n }) as Ref<SyncStatus>\n const syncing = ref(false)\n\n function refreshStatus(): void {\n status.value = db.syncStatus(compartmentName)\n }\n\n // Listen for sync events to auto-refresh status\n const onPush = () => refreshStatus()\n const onPull = () => refreshStatus()\n db.on('sync:push', onPush)\n db.on('sync:pull', onPull)\n\n onUnmounted(() => {\n db.off('sync:push', onPush)\n db.off('sync:pull', onPull)\n })\n\n async function push(): Promise<PushResult> {\n syncing.value = true\n try {\n const result = await db.push(compartmentName)\n refreshStatus()\n return result\n } finally {\n syncing.value = false\n }\n }\n\n async function pull(): Promise<PullResult> {\n syncing.value = true\n try {\n const result = await db.pull(compartmentName)\n refreshStatus()\n return result\n } finally {\n syncing.value = false\n }\n }\n\n async function sync(): Promise<void> {\n syncing.value = true\n try {\n await db.sync(compartmentName)\n refreshStatus()\n } finally {\n syncing.value = false\n }\n }\n\n // Initial status\n refreshStatus()\n\n return { status, syncing, push, pull, sync }\n}\n","/**\n * `useBlobURL` — Vue composable that decrypts an encrypted blob slot\n * and surfaces a browser ObjectURL ready to feed into `<img src>`,\n * `<a href>`, etc. Auto-revokes the prior URL when the reactive id\n * changes and on scope dispose, so long-lived pages (attachment\n * viewers, image grids) cannot leak ObjectURLs.\n *\n * SSR-safe: when `URL.createObjectURL` is unavailable (Node without\n * DOM, restricted workers), the ref stays at `null` instead of\n * throwing.\n *\n * @module\n */\n\nimport {\n ref,\n watch,\n getCurrentScope,\n onScopeDispose,\n type Ref,\n} from 'vue'\nimport type { Collection } from '@noy-db/hub'\n\n/** Options accepted by {@link useBlobURL}. */\nexport interface UseBlobURLOptions {\n /**\n * Slot name within the record's blob set. Defaults to `'default'`.\n * Pass an explicit name when records carry multiple attachments.\n */\n readonly slot?: string\n /**\n * MIME type override. Either a static string or a function called\n * each time a fresh URL is built. When omitted, the slot's stored\n * `mimeType` (set at upload time) is used.\n */\n readonly mimeType?: string | (() => string | undefined)\n}\n\n/**\n * Build a reactive ObjectURL for a blob slot on a record. The URL is\n * recomputed whenever the id getter's return value changes; the prior\n * URL is revoked **before** the new one is created, so consumers\n * never see two live URLs for the same composable instance.\n *\n * @example\n * ```ts\n * const recordId = ref('inv-001')\n * const url = useBlobURL(invoiceCollection, () => recordId.value, {\n * slot: 'pdf',\n * mimeType: 'application/pdf',\n * })\n * ```\n *\n * @param collection - Hub collection that owns the record.\n * @param idGetter - Function returning the current record id (or\n * `null` / `undefined` to clear the URL).\n * @param options - Optional slot name and mimeType override.\n * @returns A `Ref<string | null>` — `null` while loading, after a\n * stop, or when no slot is found.\n */\nexport function useBlobURL<T>(\n collection: Collection<T>,\n idGetter: () => string | null | undefined,\n options: UseBlobURLOptions = {},\n): Ref<string | null> {\n const url = ref<string | null>(null)\n const slot = options.slot ?? 'default'\n\n // SSR / non-DOM hosts: bail out, leave url at null. The hub layer\n // would throw here, but the composable's contract is \"stay quiet so\n // server-rendered output is empty.\"\n const browserAware =\n typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function'\n\n let revokeCurrent: (() => void) | null = null\n let stopped = false\n let loadToken = 0\n\n const revoke = (): void => {\n if (revokeCurrent) {\n revokeCurrent()\n revokeCurrent = null\n }\n }\n\n async function load(id: string | null | undefined): Promise<void> {\n // Revoke FIRST — the spec is explicit that the prior URL is\n // released before the next one is created, even if the next\n // create fails or the id resolves to null.\n revoke()\n url.value = null\n if (!browserAware || stopped || !id) return\n\n // Token-guard against stale resolutions: if the id changes again\n // before this call's awaits settle, the older promise's result\n // must be discarded so we don't strand an URL that no upstream\n // ref points at.\n const myToken = ++loadToken\n const mimeType =\n typeof options.mimeType === 'function'\n ? options.mimeType()\n : options.mimeType\n const built = await collection\n .blob(id)\n .objectURL(slot, mimeType !== undefined ? { mimeType } : {})\n if (myToken !== loadToken || stopped) {\n // A newer load won the race or the scope was disposed mid-flight.\n // Drop this URL on the floor.\n built?.revoke()\n return\n }\n if (!built) return\n revokeCurrent = built.revoke\n url.value = built.url\n }\n\n // watch with `immediate: true` covers the initial load — no separate\n // `load(idGetter())` call needed, and watch automatically tracks\n // reactive deps inside the getter.\n watch(idGetter, (next) => {\n void load(next)\n }, { immediate: true })\n\n if (getCurrentScope()) {\n onScopeDispose(() => {\n stopped = true\n revoke()\n url.value = null\n })\n }\n\n return url\n}\n","import { ref, getCurrentScope, onScopeDispose, type Ref } from 'vue'\nimport type { Noydb, FenceState } from '@noy-db/hub'\nimport { useNoydb } from './useNoydb.js'\n\nexport interface UseMigrationStateReturn {\n /** Live cutover fence state for the watched vault. */\n readonly fenceState: Ref<FenceState>\n /** Live schema generation counter for the watched vault. */\n readonly schemaVersion: Ref<number>\n}\n\n/**\n * Reactive schema-cutover state. Seeds from the current fence on\n * mount, then updates on every `schema:fence-changed` event for `vaultName`\n * (or any vault when omitted). Pass `db` explicitly, or rely on the injected\n * instance (`NoydbPlugin`).\n */\nexport function useMigrationState(vaultName?: string): UseMigrationStateReturn\nexport function useMigrationState(db: Noydb, vaultName?: string): UseMigrationStateReturn\nexport function useMigrationState(\n dbOrVault?: Noydb | string,\n maybeVault?: string,\n): UseMigrationStateReturn {\n const db: Noydb = typeof dbOrVault === 'object' ? dbOrVault : useNoydb()\n const vaultName: string | undefined = typeof dbOrVault === 'string' ? dbOrVault : maybeVault\n\n const fenceState = ref<FenceState>('normal')\n const schemaVersion = ref(0)\n\n // Seed from the live fence (the event fires on change, not on mount).\n if (vaultName !== undefined) {\n try {\n void db.vault(vaultName).schemaFenceState().then(\n (s) => { fenceState.value = s.fenceState; schemaVersion.value = s.currentSchemaVersion },\n () => { /* no fence yet → keep defaults */ },\n )\n } catch {\n /* db.vault() throws if the vault isn't open yet → keep defaults; events catch up */\n }\n }\n\n const handler = (e: { vault: string; currentSchemaVersion: number; fenceState: FenceState }) => {\n if (vaultName !== undefined && e.vault !== vaultName) return\n fenceState.value = e.fenceState\n schemaVersion.value = e.currentSchemaVersion\n }\n db.on('schema:fence-changed', handler)\n\n if (getCurrentScope()) {\n onScopeDispose(() => { db.off('schema:fence-changed', handler) })\n }\n\n return { fenceState, schemaVersion }\n}\n"],"mappings":"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","names":["import_vue","import_vue","import_vue","import_vue"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InjectionKey, App, Ref } from 'vue';
|
|
2
|
-
import { Noydb, SyncStatus, PushResult, PullResult, Collection } from '@noy-db/hub';
|
|
2
|
+
import { Noydb, SyncStatus, PushResult, PullResult, Collection, FenceState } from '@noy-db/hub';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Vue injection key for the NOYDB instance provided by `NoydbPlugin`.
|
|
@@ -121,4 +121,19 @@ interface UseBlobURLOptions {
|
|
|
121
121
|
*/
|
|
122
122
|
declare function useBlobURL<T>(collection: Collection<T>, idGetter: () => string | null | undefined, options?: UseBlobURLOptions): Ref<string | null>;
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
interface UseMigrationStateReturn {
|
|
125
|
+
/** Live cutover fence state for the watched vault. */
|
|
126
|
+
readonly fenceState: Ref<FenceState>;
|
|
127
|
+
/** Live schema generation counter for the watched vault. */
|
|
128
|
+
readonly schemaVersion: Ref<number>;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Reactive schema-cutover state. Seeds from the current fence on
|
|
132
|
+
* mount, then updates on every `schema:fence-changed` event for `vaultName`
|
|
133
|
+
* (or any vault when omitted). Pass `db` explicitly, or rely on the injected
|
|
134
|
+
* instance (`NoydbPlugin`).
|
|
135
|
+
*/
|
|
136
|
+
declare function useMigrationState(vaultName?: string): UseMigrationStateReturn;
|
|
137
|
+
declare function useMigrationState(db: Noydb, vaultName?: string): UseMigrationStateReturn;
|
|
138
|
+
|
|
139
|
+
export { NoydbKey, NoydbPlugin, type NoydbPluginOptions, type UseBlobURLOptions, type UseCollectionReturn, type UseMigrationStateReturn, type UseSyncReturn, useBlobURL, useCollection, useMigrationState, useNoydb, useSync };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InjectionKey, App, Ref } from 'vue';
|
|
2
|
-
import { Noydb, SyncStatus, PushResult, PullResult, Collection } from '@noy-db/hub';
|
|
2
|
+
import { Noydb, SyncStatus, PushResult, PullResult, Collection, FenceState } from '@noy-db/hub';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Vue injection key for the NOYDB instance provided by `NoydbPlugin`.
|
|
@@ -121,4 +121,19 @@ interface UseBlobURLOptions {
|
|
|
121
121
|
*/
|
|
122
122
|
declare function useBlobURL<T>(collection: Collection<T>, idGetter: () => string | null | undefined, options?: UseBlobURLOptions): Ref<string | null>;
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
interface UseMigrationStateReturn {
|
|
125
|
+
/** Live cutover fence state for the watched vault. */
|
|
126
|
+
readonly fenceState: Ref<FenceState>;
|
|
127
|
+
/** Live schema generation counter for the watched vault. */
|
|
128
|
+
readonly schemaVersion: Ref<number>;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Reactive schema-cutover state. Seeds from the current fence on
|
|
132
|
+
* mount, then updates on every `schema:fence-changed` event for `vaultName`
|
|
133
|
+
* (or any vault when omitted). Pass `db` explicitly, or rely on the injected
|
|
134
|
+
* instance (`NoydbPlugin`).
|
|
135
|
+
*/
|
|
136
|
+
declare function useMigrationState(vaultName?: string): UseMigrationStateReturn;
|
|
137
|
+
declare function useMigrationState(db: Noydb, vaultName?: string): UseMigrationStateReturn;
|
|
138
|
+
|
|
139
|
+
export { NoydbKey, NoydbPlugin, type NoydbPluginOptions, type UseBlobURLOptions, type UseCollectionReturn, type UseMigrationStateReturn, type UseSyncReturn, useBlobURL, useCollection, useMigrationState, useNoydb, useSync };
|
package/dist/index.js
CHANGED
|
@@ -154,11 +154,46 @@ function useBlobURL(collection, idGetter, options = {}) {
|
|
|
154
154
|
}
|
|
155
155
|
return url;
|
|
156
156
|
}
|
|
157
|
+
|
|
158
|
+
// src/useMigrationState.ts
|
|
159
|
+
import { ref as ref4, getCurrentScope as getCurrentScope2, onScopeDispose as onScopeDispose2 } from "vue";
|
|
160
|
+
function useMigrationState(dbOrVault, maybeVault) {
|
|
161
|
+
const db = typeof dbOrVault === "object" ? dbOrVault : useNoydb();
|
|
162
|
+
const vaultName = typeof dbOrVault === "string" ? dbOrVault : maybeVault;
|
|
163
|
+
const fenceState = ref4("normal");
|
|
164
|
+
const schemaVersion = ref4(0);
|
|
165
|
+
if (vaultName !== void 0) {
|
|
166
|
+
try {
|
|
167
|
+
void db.vault(vaultName).schemaFenceState().then(
|
|
168
|
+
(s) => {
|
|
169
|
+
fenceState.value = s.fenceState;
|
|
170
|
+
schemaVersion.value = s.currentSchemaVersion;
|
|
171
|
+
},
|
|
172
|
+
() => {
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const handler = (e) => {
|
|
179
|
+
if (vaultName !== void 0 && e.vault !== vaultName) return;
|
|
180
|
+
fenceState.value = e.fenceState;
|
|
181
|
+
schemaVersion.value = e.currentSchemaVersion;
|
|
182
|
+
};
|
|
183
|
+
db.on("schema:fence-changed", handler);
|
|
184
|
+
if (getCurrentScope2()) {
|
|
185
|
+
onScopeDispose2(() => {
|
|
186
|
+
db.off("schema:fence-changed", handler);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return { fenceState, schemaVersion };
|
|
190
|
+
}
|
|
157
191
|
export {
|
|
158
192
|
NoydbKey,
|
|
159
193
|
NoydbPlugin,
|
|
160
194
|
useBlobURL,
|
|
161
195
|
useCollection,
|
|
196
|
+
useMigrationState,
|
|
162
197
|
useNoydb,
|
|
163
198
|
useSync
|
|
164
199
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugin.ts","../src/useNoydb.ts","../src/useCollection.ts","../src/useSync.ts","../src/useBlobURL.ts"],"sourcesContent":["import type { App, InjectionKey } from 'vue'\nimport type { Noydb } from '@noy-db/hub'\n\n/**\n * Vue injection key for the NOYDB instance provided by `NoydbPlugin`.\n * Use with `inject(NoydbKey)` inside any component under the plugin's scope.\n */\nexport const NoydbKey: InjectionKey<Noydb> = Symbol('noydb')\n\n/**\n * Options passed to `app.use(NoydbPlugin, options)` when registering\n * the NOYDB Vue plugin.\n */\nexport interface NoydbPluginOptions {\n /** The NOYDB instance to provide to all components. */\n instance: Noydb\n}\n\n/** Vue plugin that provides a NOYDB instance to all components. */\nexport const NoydbPlugin = {\n install(app: App, options: NoydbPluginOptions): void {\n app.provide(NoydbKey, options.instance)\n },\n}\n","import { inject } from 'vue'\nimport type { Noydb } from '@noy-db/hub'\nimport { NoydbKey } from './plugin.js'\n\n/** Composable to access the injected NOYDB instance. */\nexport function useNoydb(): Noydb {\n const db = inject(NoydbKey)\n if (!db) {\n throw new Error(\n 'NOYDB instance not found. Did you install the NoydbPlugin?\\n' +\n 'Example: app.use(NoydbPlugin, { instance: db })',\n )\n }\n return db\n}\n","import { ref, onUnmounted, type Ref } from 'vue'\nimport type { Noydb, ChangeEvent } from '@noy-db/hub'\n\n/**\n * Return value of `useCollection<T>()`.\n *\n * All `Ref` values are reactive — bind them directly to Vue templates.\n * The collection subscribes to NOYDB change events and auto-refreshes\n * `data` when the underlying collection is mutated by any code path.\n */\nexport interface UseCollectionReturn<T> {\n /** Reactive list of all records in the collection. */\n data: Ref<T[]>\n /** Loading state — true during initial hydration. */\n loading: Ref<boolean>\n /** Error state — set if hydration or refresh fails. */\n error: Ref<Error | null>\n /** Manually refresh data from the adapter. */\n refresh: () => Promise<void>\n}\n\n/**\n * Composable for reactive collection data.\n * Auto-refreshes when the collection changes (via NOYDB change events).\n */\nexport function useCollection<T>(\n db: Noydb,\n compartmentName: string,\n collectionName: string,\n): UseCollectionReturn<T> {\n const data = ref<T[]>([]) as Ref<T[]>\n const loading = ref(true)\n const error = ref<Error | null>(null)\n\n let compartmentPromise: ReturnType<Noydb['openVault']> | null = null\n\n async function refresh(): Promise<void> {\n try {\n if (!compartmentPromise) {\n compartmentPromise = db.openVault(compartmentName)\n }\n const comp = await compartmentPromise\n const coll = comp.collection<T>(collectionName)\n data.value = await coll.list()\n error.value = null\n } catch (err) {\n error.value = err instanceof Error ? err : new Error(String(err))\n } finally {\n loading.value = false\n }\n }\n\n // Listen for changes to auto-refresh\n const handler = (event: ChangeEvent) => {\n if (event.collection === collectionName) {\n void refresh()\n }\n }\n db.on('change', handler)\n\n onUnmounted(() => {\n db.off('change', handler)\n })\n\n // Initial load\n void refresh()\n\n return { data, loading, error, refresh }\n}\n","import { ref, onUnmounted, type Ref } from 'vue'\nimport type { Noydb, SyncStatus, PushResult, PullResult } from '@noy-db/hub'\n\n/**\n * Return value of `useSync()`.\n *\n * Wraps the NOYDB sync engine in reactive Vue refs. `status` updates\n * automatically as sync operations progress, so binding it to a\n * status indicator (e.g. a toolbar sync icon) requires no manual polling.\n */\nexport interface UseSyncReturn {\n /** Reactive sync status. */\n status: Ref<SyncStatus>\n /** Whether a sync operation is in progress. */\n syncing: Ref<boolean>\n /** Push local changes to remote. */\n push: () => Promise<PushResult>\n /** Pull remote changes to local. */\n pull: () => Promise<PullResult>\n /** Bidirectional sync (pull then push). */\n sync: () => Promise<void>\n}\n\n/**\n * Composable for reactive sync status and controls.\n */\nexport function useSync(db: Noydb, compartmentName: string): UseSyncReturn {\n const status = ref<SyncStatus>({\n dirty: 0,\n lastPush: null,\n lastPull: null,\n online: true,\n }) as Ref<SyncStatus>\n const syncing = ref(false)\n\n function refreshStatus(): void {\n status.value = db.syncStatus(compartmentName)\n }\n\n // Listen for sync events to auto-refresh status\n const onPush = () => refreshStatus()\n const onPull = () => refreshStatus()\n db.on('sync:push', onPush)\n db.on('sync:pull', onPull)\n\n onUnmounted(() => {\n db.off('sync:push', onPush)\n db.off('sync:pull', onPull)\n })\n\n async function push(): Promise<PushResult> {\n syncing.value = true\n try {\n const result = await db.push(compartmentName)\n refreshStatus()\n return result\n } finally {\n syncing.value = false\n }\n }\n\n async function pull(): Promise<PullResult> {\n syncing.value = true\n try {\n const result = await db.pull(compartmentName)\n refreshStatus()\n return result\n } finally {\n syncing.value = false\n }\n }\n\n async function sync(): Promise<void> {\n syncing.value = true\n try {\n await db.sync(compartmentName)\n refreshStatus()\n } finally {\n syncing.value = false\n }\n }\n\n // Initial status\n refreshStatus()\n\n return { status, syncing, push, pull, sync }\n}\n","/**\n * `useBlobURL` — Vue composable that decrypts an encrypted blob slot\n * and surfaces a browser ObjectURL ready to feed into `<img src>`,\n * `<a href>`, etc. Auto-revokes the prior URL when the reactive id\n * changes and on scope dispose, so long-lived pages (attachment\n * viewers, image grids) cannot leak ObjectURLs.\n *\n * SSR-safe: when `URL.createObjectURL` is unavailable (Node without\n * DOM, restricted workers), the ref stays at `null` instead of\n * throwing.\n *\n * @module\n */\n\nimport {\n ref,\n watch,\n getCurrentScope,\n onScopeDispose,\n type Ref,\n} from 'vue'\nimport type { Collection } from '@noy-db/hub'\n\n/** Options accepted by {@link useBlobURL}. */\nexport interface UseBlobURLOptions {\n /**\n * Slot name within the record's blob set. Defaults to `'default'`.\n * Pass an explicit name when records carry multiple attachments.\n */\n readonly slot?: string\n /**\n * MIME type override. Either a static string or a function called\n * each time a fresh URL is built. When omitted, the slot's stored\n * `mimeType` (set at upload time) is used.\n */\n readonly mimeType?: string | (() => string | undefined)\n}\n\n/**\n * Build a reactive ObjectURL for a blob slot on a record. The URL is\n * recomputed whenever the id getter's return value changes; the prior\n * URL is revoked **before** the new one is created, so consumers\n * never see two live URLs for the same composable instance.\n *\n * @example\n * ```ts\n * const recordId = ref('inv-001')\n * const url = useBlobURL(invoiceCollection, () => recordId.value, {\n * slot: 'pdf',\n * mimeType: 'application/pdf',\n * })\n * ```\n *\n * @param collection - Hub collection that owns the record.\n * @param idGetter - Function returning the current record id (or\n * `null` / `undefined` to clear the URL).\n * @param options - Optional slot name and mimeType override.\n * @returns A `Ref<string | null>` — `null` while loading, after a\n * stop, or when no slot is found.\n */\nexport function useBlobURL<T>(\n collection: Collection<T>,\n idGetter: () => string | null | undefined,\n options: UseBlobURLOptions = {},\n): Ref<string | null> {\n const url = ref<string | null>(null)\n const slot = options.slot ?? 'default'\n\n // SSR / non-DOM hosts: bail out, leave url at null. The hub layer\n // would throw here, but the composable's contract is \"stay quiet so\n // server-rendered output is empty.\"\n const browserAware =\n typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function'\n\n let revokeCurrent: (() => void) | null = null\n let stopped = false\n let loadToken = 0\n\n const revoke = (): void => {\n if (revokeCurrent) {\n revokeCurrent()\n revokeCurrent = null\n }\n }\n\n async function load(id: string | null | undefined): Promise<void> {\n // Revoke FIRST — the spec is explicit that the prior URL is\n // released before the next one is created, even if the next\n // create fails or the id resolves to null.\n revoke()\n url.value = null\n if (!browserAware || stopped || !id) return\n\n // Token-guard against stale resolutions: if the id changes again\n // before this call's awaits settle, the older promise's result\n // must be discarded so we don't strand an URL that no upstream\n // ref points at.\n const myToken = ++loadToken\n const mimeType =\n typeof options.mimeType === 'function'\n ? options.mimeType()\n : options.mimeType\n const built = await collection\n .blob(id)\n .objectURL(slot, mimeType !== undefined ? { mimeType } : {})\n if (myToken !== loadToken || stopped) {\n // A newer load won the race or the scope was disposed mid-flight.\n // Drop this URL on the floor.\n built?.revoke()\n return\n }\n if (!built) return\n revokeCurrent = built.revoke\n url.value = built.url\n }\n\n // watch with `immediate: true` covers the initial load — no separate\n // `load(idGetter())` call needed, and watch automatically tracks\n // reactive deps inside the getter.\n watch(idGetter, (next) => {\n void load(next)\n }, { immediate: true })\n\n if (getCurrentScope()) {\n onScopeDispose(() => {\n stopped = true\n revoke()\n url.value = null\n })\n }\n\n return url\n}\n"],"mappings":"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","names":["ref","onUnmounted","ref"]}
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts","../src/useNoydb.ts","../src/useCollection.ts","../src/useSync.ts","../src/useBlobURL.ts","../src/useMigrationState.ts"],"sourcesContent":["import type { App, InjectionKey } from 'vue'\nimport type { Noydb } from '@noy-db/hub'\n\n/**\n * Vue injection key for the NOYDB instance provided by `NoydbPlugin`.\n * Use with `inject(NoydbKey)` inside any component under the plugin's scope.\n */\nexport const NoydbKey: InjectionKey<Noydb> = Symbol('noydb')\n\n/**\n * Options passed to `app.use(NoydbPlugin, options)` when registering\n * the NOYDB Vue plugin.\n */\nexport interface NoydbPluginOptions {\n /** The NOYDB instance to provide to all components. */\n instance: Noydb\n}\n\n/** Vue plugin that provides a NOYDB instance to all components. */\nexport const NoydbPlugin = {\n install(app: App, options: NoydbPluginOptions): void {\n app.provide(NoydbKey, options.instance)\n },\n}\n","import { inject } from 'vue'\nimport type { Noydb } from '@noy-db/hub'\nimport { NoydbKey } from './plugin.js'\n\n/** Composable to access the injected NOYDB instance. */\nexport function useNoydb(): Noydb {\n const db = inject(NoydbKey)\n if (!db) {\n throw new Error(\n 'NOYDB instance not found. Did you install the NoydbPlugin?\\n' +\n 'Example: app.use(NoydbPlugin, { instance: db })',\n )\n }\n return db\n}\n","import { ref, onUnmounted, type Ref } from 'vue'\nimport type { Noydb, ChangeEvent } from '@noy-db/hub'\n\n/**\n * Return value of `useCollection<T>()`.\n *\n * All `Ref` values are reactive — bind them directly to Vue templates.\n * The collection subscribes to NOYDB change events and auto-refreshes\n * `data` when the underlying collection is mutated by any code path.\n */\nexport interface UseCollectionReturn<T> {\n /** Reactive list of all records in the collection. */\n data: Ref<T[]>\n /** Loading state — true during initial hydration. */\n loading: Ref<boolean>\n /** Error state — set if hydration or refresh fails. */\n error: Ref<Error | null>\n /** Manually refresh data from the adapter. */\n refresh: () => Promise<void>\n}\n\n/**\n * Composable for reactive collection data.\n * Auto-refreshes when the collection changes (via NOYDB change events).\n */\nexport function useCollection<T>(\n db: Noydb,\n compartmentName: string,\n collectionName: string,\n): UseCollectionReturn<T> {\n const data = ref<T[]>([]) as Ref<T[]>\n const loading = ref(true)\n const error = ref<Error | null>(null)\n\n let compartmentPromise: ReturnType<Noydb['openVault']> | null = null\n\n async function refresh(): Promise<void> {\n try {\n if (!compartmentPromise) {\n compartmentPromise = db.openVault(compartmentName)\n }\n const comp = await compartmentPromise\n const coll = comp.collection<T>(collectionName)\n data.value = await coll.list()\n error.value = null\n } catch (err) {\n error.value = err instanceof Error ? err : new Error(String(err))\n } finally {\n loading.value = false\n }\n }\n\n // Listen for changes to auto-refresh\n const handler = (event: ChangeEvent) => {\n if (event.collection === collectionName) {\n void refresh()\n }\n }\n db.on('change', handler)\n\n onUnmounted(() => {\n db.off('change', handler)\n })\n\n // Initial load\n void refresh()\n\n return { data, loading, error, refresh }\n}\n","import { ref, onUnmounted, type Ref } from 'vue'\nimport type { Noydb, SyncStatus, PushResult, PullResult } from '@noy-db/hub'\n\n/**\n * Return value of `useSync()`.\n *\n * Wraps the NOYDB sync engine in reactive Vue refs. `status` updates\n * automatically as sync operations progress, so binding it to a\n * status indicator (e.g. a toolbar sync icon) requires no manual polling.\n */\nexport interface UseSyncReturn {\n /** Reactive sync status. */\n status: Ref<SyncStatus>\n /** Whether a sync operation is in progress. */\n syncing: Ref<boolean>\n /** Push local changes to remote. */\n push: () => Promise<PushResult>\n /** Pull remote changes to local. */\n pull: () => Promise<PullResult>\n /** Bidirectional sync (pull then push). */\n sync: () => Promise<void>\n}\n\n/**\n * Composable for reactive sync status and controls.\n */\nexport function useSync(db: Noydb, compartmentName: string): UseSyncReturn {\n const status = ref<SyncStatus>({\n dirty: 0,\n lastPush: null,\n lastPull: null,\n online: true,\n }) as Ref<SyncStatus>\n const syncing = ref(false)\n\n function refreshStatus(): void {\n status.value = db.syncStatus(compartmentName)\n }\n\n // Listen for sync events to auto-refresh status\n const onPush = () => refreshStatus()\n const onPull = () => refreshStatus()\n db.on('sync:push', onPush)\n db.on('sync:pull', onPull)\n\n onUnmounted(() => {\n db.off('sync:push', onPush)\n db.off('sync:pull', onPull)\n })\n\n async function push(): Promise<PushResult> {\n syncing.value = true\n try {\n const result = await db.push(compartmentName)\n refreshStatus()\n return result\n } finally {\n syncing.value = false\n }\n }\n\n async function pull(): Promise<PullResult> {\n syncing.value = true\n try {\n const result = await db.pull(compartmentName)\n refreshStatus()\n return result\n } finally {\n syncing.value = false\n }\n }\n\n async function sync(): Promise<void> {\n syncing.value = true\n try {\n await db.sync(compartmentName)\n refreshStatus()\n } finally {\n syncing.value = false\n }\n }\n\n // Initial status\n refreshStatus()\n\n return { status, syncing, push, pull, sync }\n}\n","/**\n * `useBlobURL` — Vue composable that decrypts an encrypted blob slot\n * and surfaces a browser ObjectURL ready to feed into `<img src>`,\n * `<a href>`, etc. Auto-revokes the prior URL when the reactive id\n * changes and on scope dispose, so long-lived pages (attachment\n * viewers, image grids) cannot leak ObjectURLs.\n *\n * SSR-safe: when `URL.createObjectURL` is unavailable (Node without\n * DOM, restricted workers), the ref stays at `null` instead of\n * throwing.\n *\n * @module\n */\n\nimport {\n ref,\n watch,\n getCurrentScope,\n onScopeDispose,\n type Ref,\n} from 'vue'\nimport type { Collection } from '@noy-db/hub'\n\n/** Options accepted by {@link useBlobURL}. */\nexport interface UseBlobURLOptions {\n /**\n * Slot name within the record's blob set. Defaults to `'default'`.\n * Pass an explicit name when records carry multiple attachments.\n */\n readonly slot?: string\n /**\n * MIME type override. Either a static string or a function called\n * each time a fresh URL is built. When omitted, the slot's stored\n * `mimeType` (set at upload time) is used.\n */\n readonly mimeType?: string | (() => string | undefined)\n}\n\n/**\n * Build a reactive ObjectURL for a blob slot on a record. The URL is\n * recomputed whenever the id getter's return value changes; the prior\n * URL is revoked **before** the new one is created, so consumers\n * never see two live URLs for the same composable instance.\n *\n * @example\n * ```ts\n * const recordId = ref('inv-001')\n * const url = useBlobURL(invoiceCollection, () => recordId.value, {\n * slot: 'pdf',\n * mimeType: 'application/pdf',\n * })\n * ```\n *\n * @param collection - Hub collection that owns the record.\n * @param idGetter - Function returning the current record id (or\n * `null` / `undefined` to clear the URL).\n * @param options - Optional slot name and mimeType override.\n * @returns A `Ref<string | null>` — `null` while loading, after a\n * stop, or when no slot is found.\n */\nexport function useBlobURL<T>(\n collection: Collection<T>,\n idGetter: () => string | null | undefined,\n options: UseBlobURLOptions = {},\n): Ref<string | null> {\n const url = ref<string | null>(null)\n const slot = options.slot ?? 'default'\n\n // SSR / non-DOM hosts: bail out, leave url at null. The hub layer\n // would throw here, but the composable's contract is \"stay quiet so\n // server-rendered output is empty.\"\n const browserAware =\n typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function'\n\n let revokeCurrent: (() => void) | null = null\n let stopped = false\n let loadToken = 0\n\n const revoke = (): void => {\n if (revokeCurrent) {\n revokeCurrent()\n revokeCurrent = null\n }\n }\n\n async function load(id: string | null | undefined): Promise<void> {\n // Revoke FIRST — the spec is explicit that the prior URL is\n // released before the next one is created, even if the next\n // create fails or the id resolves to null.\n revoke()\n url.value = null\n if (!browserAware || stopped || !id) return\n\n // Token-guard against stale resolutions: if the id changes again\n // before this call's awaits settle, the older promise's result\n // must be discarded so we don't strand an URL that no upstream\n // ref points at.\n const myToken = ++loadToken\n const mimeType =\n typeof options.mimeType === 'function'\n ? options.mimeType()\n : options.mimeType\n const built = await collection\n .blob(id)\n .objectURL(slot, mimeType !== undefined ? { mimeType } : {})\n if (myToken !== loadToken || stopped) {\n // A newer load won the race or the scope was disposed mid-flight.\n // Drop this URL on the floor.\n built?.revoke()\n return\n }\n if (!built) return\n revokeCurrent = built.revoke\n url.value = built.url\n }\n\n // watch with `immediate: true` covers the initial load — no separate\n // `load(idGetter())` call needed, and watch automatically tracks\n // reactive deps inside the getter.\n watch(idGetter, (next) => {\n void load(next)\n }, { immediate: true })\n\n if (getCurrentScope()) {\n onScopeDispose(() => {\n stopped = true\n revoke()\n url.value = null\n })\n }\n\n return url\n}\n","import { ref, getCurrentScope, onScopeDispose, type Ref } from 'vue'\nimport type { Noydb, FenceState } from '@noy-db/hub'\nimport { useNoydb } from './useNoydb.js'\n\nexport interface UseMigrationStateReturn {\n /** Live cutover fence state for the watched vault. */\n readonly fenceState: Ref<FenceState>\n /** Live schema generation counter for the watched vault. */\n readonly schemaVersion: Ref<number>\n}\n\n/**\n * Reactive schema-cutover state. Seeds from the current fence on\n * mount, then updates on every `schema:fence-changed` event for `vaultName`\n * (or any vault when omitted). Pass `db` explicitly, or rely on the injected\n * instance (`NoydbPlugin`).\n */\nexport function useMigrationState(vaultName?: string): UseMigrationStateReturn\nexport function useMigrationState(db: Noydb, vaultName?: string): UseMigrationStateReturn\nexport function useMigrationState(\n dbOrVault?: Noydb | string,\n maybeVault?: string,\n): UseMigrationStateReturn {\n const db: Noydb = typeof dbOrVault === 'object' ? dbOrVault : useNoydb()\n const vaultName: string | undefined = typeof dbOrVault === 'string' ? dbOrVault : maybeVault\n\n const fenceState = ref<FenceState>('normal')\n const schemaVersion = ref(0)\n\n // Seed from the live fence (the event fires on change, not on mount).\n if (vaultName !== undefined) {\n try {\n void db.vault(vaultName).schemaFenceState().then(\n (s) => { fenceState.value = s.fenceState; schemaVersion.value = s.currentSchemaVersion },\n () => { /* no fence yet → keep defaults */ },\n )\n } catch {\n /* db.vault() throws if the vault isn't open yet → keep defaults; events catch up */\n }\n }\n\n const handler = (e: { vault: string; currentSchemaVersion: number; fenceState: FenceState }) => {\n if (vaultName !== undefined && e.vault !== vaultName) return\n fenceState.value = e.fenceState\n schemaVersion.value = e.currentSchemaVersion\n }\n db.on('schema:fence-changed', handler)\n\n if (getCurrentScope()) {\n onScopeDispose(() => { db.off('schema:fence-changed', handler) })\n }\n\n return { fenceState, schemaVersion }\n}\n"],"mappings":"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","names":["ref","onUnmounted","ref","ref","getCurrentScope","onScopeDispose","ref","getCurrentScope","onScopeDispose"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noy-db/in-vue",
|
|
3
|
-
"version": "0.2.0-pre.
|
|
3
|
+
"version": "0.2.0-pre.20",
|
|
4
4
|
"description": "Vue 3 / Nuxt composables for noy-db — reactive useNoydb, useCollection, useSync, and biometric plugin",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "vLannaAi <vicio@lanna.ai>",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"vue": "^3.0.0",
|
|
43
|
-
"@noy-db/hub": "0.2.0-pre.
|
|
43
|
+
"@noy-db/hub": "0.2.0-pre.20"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"vue": "^3.5.32",
|
|
47
|
-
"@noy-db/hub": "0.2.0-pre.
|
|
47
|
+
"@noy-db/hub": "0.2.0-pre.20"
|
|
48
48
|
},
|
|
49
49
|
"keywords": [
|
|
50
50
|
"noy-db",
|