@noy-db/in-svelte 0.1.0-pre.3
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/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/index.cjs +104 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +94 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vLannaAi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @noy-db/in-svelte
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@noy-db/in-svelte)
|
|
4
|
+
|
|
5
|
+
> Svelte stores for noy-db
|
|
6
|
+
|
|
7
|
+
Part of [**`@noy-db/hub`**](https://www.npmjs.com/package/@noy-db/hub) — the zero-knowledge, offline-first, encrypted document store.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @noy-db/hub @noy-db/in-svelte
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What it is
|
|
16
|
+
|
|
17
|
+
Svelte stores for noy-db — collectionStore / queryStore / syncStore backed by noy-db change events. Works with Svelte 4 store contract and Svelte 5 runes (via $store subscription interop).
|
|
18
|
+
|
|
19
|
+
## Status
|
|
20
|
+
|
|
21
|
+
**Pre-release** (`0.1.0-pre.1`). API may change before `1.0`.
|
|
22
|
+
|
|
23
|
+
## Documentation
|
|
24
|
+
|
|
25
|
+
See the [main repository](https://github.com/vLannaAi/noy-db#readme) for setup, examples, and the full subsystem catalog.
|
|
26
|
+
|
|
27
|
+
- Source — [`packages/in-svelte`](https://github.com/vLannaAi/noy-db/tree/main/packages/in-svelte)
|
|
28
|
+
- Issues — [github.com/vLannaAi/noy-db/issues](https://github.com/vLannaAi/noy-db/issues)
|
|
29
|
+
- Spec — [`SPEC.md`](https://github.com/vLannaAi/noy-db/blob/main/SPEC.md)
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
[MIT](./LICENSE) © vLannaAi
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
collectionStore: () => collectionStore,
|
|
24
|
+
queryStore: () => queryStore,
|
|
25
|
+
syncStore: () => syncStore
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
function writable(initial) {
|
|
29
|
+
let value = initial;
|
|
30
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
31
|
+
return {
|
|
32
|
+
set(next) {
|
|
33
|
+
value = next;
|
|
34
|
+
for (const s of subscribers) s(next);
|
|
35
|
+
},
|
|
36
|
+
subscribe(run) {
|
|
37
|
+
subscribers.add(run);
|
|
38
|
+
run(value);
|
|
39
|
+
return () => {
|
|
40
|
+
subscribers.delete(run);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function collectionStore(vault, collectionName) {
|
|
46
|
+
const inner = writable({ records: [], loading: true, error: null });
|
|
47
|
+
const coll = vault.collection(collectionName);
|
|
48
|
+
async function refresh() {
|
|
49
|
+
try {
|
|
50
|
+
const records = await coll.list();
|
|
51
|
+
inner.set({ records, loading: false, error: null });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
inner.set({ records: [], loading: false, error: err });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
void refresh();
|
|
57
|
+
const unsubscribe = coll.subscribe(() => {
|
|
58
|
+
void refresh();
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
subscribe: (run) => inner.subscribe(run),
|
|
62
|
+
refresh,
|
|
63
|
+
stop: unsubscribe
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function queryStore(vault, collectionName, builder) {
|
|
67
|
+
const inner = writable({ data: null, loading: true, error: null });
|
|
68
|
+
const coll = vault.collection(collectionName);
|
|
69
|
+
async function refresh() {
|
|
70
|
+
try {
|
|
71
|
+
const result = await Promise.resolve(builder(coll.query()));
|
|
72
|
+
inner.set({ data: result, loading: false, error: null });
|
|
73
|
+
} catch (err) {
|
|
74
|
+
inner.set({ data: null, loading: false, error: err });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
void refresh();
|
|
78
|
+
const unsubscribe = coll.subscribe(() => {
|
|
79
|
+
void refresh();
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
subscribe: (run) => inner.subscribe(run),
|
|
83
|
+
refresh,
|
|
84
|
+
stop: unsubscribe
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function syncStore(db) {
|
|
88
|
+
const inner = writable({ lastEvent: null, error: null });
|
|
89
|
+
const handler = (event) => {
|
|
90
|
+
inner.set({ lastEvent: event, error: null });
|
|
91
|
+
};
|
|
92
|
+
db.on("change", handler);
|
|
93
|
+
return {
|
|
94
|
+
subscribe: (run) => inner.subscribe(run),
|
|
95
|
+
stop: () => db.off("change", handler)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
99
|
+
0 && (module.exports = {
|
|
100
|
+
collectionStore,
|
|
101
|
+
queryStore,
|
|
102
|
+
syncStore
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/in-svelte** — Svelte stores for noy-db.\n *\n * Three store factories that conform to Svelte's standard store\n * contract (`subscribe(fn) → unsubscribe`). Works with:\n *\n * - Svelte 4 components via the `$store` auto-subscription syntax\n * - Svelte 5 runes via `$state(await fromStore(s).current)` or a\n * small `fromStore()` shim\n * - Any framework that consumes the Svelte store contract\n * (e.g. `@gradio/client`, non-Svelte integrations)\n *\n * ## Factories\n *\n * - {@link collectionStore} — reactive list of records.\n * - {@link queryStore} — reactive result of a query builder.\n * - {@link syncStore} — reactive Noydb-level change feed.\n *\n * ## Zero dependencies\n *\n * Svelte's store contract is ~10 lines of TypeScript. We re-implement\n * it inline rather than take a `svelte` peer-dep — the contract is\n * stable across Svelte 4/5 and the extra dependency is unnecessary\n * for store-only consumers (e.g. SvelteKit load functions).\n *\n * @packageDocumentation\n */\n\nimport type { Noydb, Vault, ChangeEvent, Query } from '@noy-db/hub'\n\n// ─── Svelte store contract (re-implemented, no svelte peer dep) ─────────\n\ntype Subscriber<T> = (value: T) => void\ntype Unsubscriber = () => void\n\nexport interface Readable<T> {\n subscribe(run: Subscriber<T>): Unsubscriber\n}\n\nfunction writable<T>(initial: T): {\n set(value: T): void\n subscribe(run: Subscriber<T>): Unsubscriber\n} {\n let value = initial\n const subscribers = new Set<Subscriber<T>>()\n return {\n set(next) {\n value = next\n for (const s of subscribers) s(next)\n },\n subscribe(run) {\n subscribers.add(run)\n run(value)\n return () => { subscribers.delete(run) }\n },\n }\n}\n\n// ─── collectionStore ───────────────────────────────────────────────────\n\nexport interface CollectionStoreState<T> {\n readonly records: readonly T[]\n readonly loading: boolean\n readonly error: Error | null\n}\n\n/**\n * Reactive collection records. Re-emits on every change event from\n * the collection (put / delete / sync).\n *\n * ```svelte\n * <script>\n * import { collectionStore } from '@noy-db/in-svelte'\n * const invoices = collectionStore(vault, 'invoices')\n * </script>\n *\n * {#each $invoices.records as invoice}\n * {invoice.id}\n * {/each}\n * ```\n */\nexport function collectionStore<T>(\n vault: Vault,\n collectionName: string,\n): Readable<CollectionStoreState<T>> & { refresh(): Promise<void>; stop(): void } {\n const inner = writable<CollectionStoreState<T>>({ records: [], loading: true, error: null })\n const coll = vault.collection<T>(collectionName)\n\n async function refresh(): Promise<void> {\n try {\n const records = await coll.list()\n inner.set({ records, loading: false, error: null })\n } catch (err) {\n inner.set({ records: [], loading: false, error: err as Error })\n }\n }\n\n void refresh()\n const unsubscribe = coll.subscribe(() => { void refresh() })\n\n return {\n subscribe: (run) => inner.subscribe(run),\n refresh,\n stop: unsubscribe,\n }\n}\n\n// ─── queryStore ────────────────────────────────────────────────────────\n\nexport interface QueryStoreState<R> {\n readonly data: R | null\n readonly loading: boolean\n readonly error: Error | null\n}\n\n/**\n * Reactive result of a query builder. The builder is re-run on every\n * change event — useful for aggregates, joins, and filtered lists.\n *\n * ```svelte\n * <script>\n * import { queryStore } from '@noy-db/in-svelte'\n * const paid = queryStore(vault, 'invoices', q => q.where('status', '==', 'paid').toArray())\n * </script>\n *\n * {#if $paid.loading} Loading… {:else} {$paid.data?.length} paid {/if}\n * ```\n */\nexport function queryStore<T, R>(\n vault: Vault,\n collectionName: string,\n builder: (q: Query<T>) => Promise<R> | R,\n): Readable<QueryStoreState<R>> & { refresh(): Promise<void>; stop(): void } {\n const inner = writable<QueryStoreState<R>>({ data: null, loading: true, error: null })\n const coll = vault.collection<T>(collectionName)\n\n async function refresh(): Promise<void> {\n try {\n const result = await Promise.resolve(builder(coll.query() as unknown as Query<T>))\n inner.set({ data: result, loading: false, error: null })\n } catch (err) {\n inner.set({ data: null, loading: false, error: err as Error })\n }\n }\n\n void refresh()\n const unsubscribe = coll.subscribe(() => { void refresh() })\n\n return {\n subscribe: (run) => inner.subscribe(run),\n refresh,\n stop: unsubscribe,\n }\n}\n\n// ─── syncStore ─────────────────────────────────────────────────────────\n\nexport interface SyncStoreState {\n readonly lastEvent: ChangeEvent | null\n readonly error: Error | null\n}\n\n/**\n * Reactive Noydb-level change feed. Useful for top-level status\n * indicators (last-update-time, \"unsynced changes\", offline banners).\n */\nexport function syncStore(db: Noydb): Readable<SyncStoreState> & { stop(): void } {\n const inner = writable<SyncStoreState>({ lastEvent: null, error: null })\n\n const handler = (event: ChangeEvent): void => {\n inner.set({ lastEvent: event, error: null })\n }\n db.on('change', handler)\n\n return {\n subscribe: (run) => inner.subscribe(run),\n stop: () => db.off('change', handler),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuCA,SAAS,SAAY,SAGnB;AACA,MAAI,QAAQ;AACZ,QAAM,cAAc,oBAAI,IAAmB;AAC3C,SAAO;AAAA,IACL,IAAI,MAAM;AACR,cAAQ;AACR,iBAAW,KAAK,YAAa,GAAE,IAAI;AAAA,IACrC;AAAA,IACA,UAAU,KAAK;AACb,kBAAY,IAAI,GAAG;AACnB,UAAI,KAAK;AACT,aAAO,MAAM;AAAE,oBAAY,OAAO,GAAG;AAAA,MAAE;AAAA,IACzC;AAAA,EACF;AACF;AAyBO,SAAS,gBACd,OACA,gBACgF;AAChF,QAAM,QAAQ,SAAkC,EAAE,SAAS,CAAC,GAAG,SAAS,MAAM,OAAO,KAAK,CAAC;AAC3F,QAAM,OAAO,MAAM,WAAc,cAAc;AAE/C,iBAAe,UAAyB;AACtC,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,KAAK;AAChC,YAAM,IAAI,EAAE,SAAS,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,IACpD,SAAS,KAAK;AACZ,YAAM,IAAI,EAAE,SAAS,CAAC,GAAG,SAAS,OAAO,OAAO,IAAa,CAAC;AAAA,IAChE;AAAA,EACF;AAEA,OAAK,QAAQ;AACb,QAAM,cAAc,KAAK,UAAU,MAAM;AAAE,SAAK,QAAQ;AAAA,EAAE,CAAC;AAE3D,SAAO;AAAA,IACL,WAAW,CAAC,QAAQ,MAAM,UAAU,GAAG;AAAA,IACvC;AAAA,IACA,MAAM;AAAA,EACR;AACF;AAuBO,SAAS,WACd,OACA,gBACA,SAC2E;AAC3E,QAAM,QAAQ,SAA6B,EAAE,MAAM,MAAM,SAAS,MAAM,OAAO,KAAK,CAAC;AACrF,QAAM,OAAO,MAAM,WAAc,cAAc;AAE/C,iBAAe,UAAyB;AACtC,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,QAAQ,QAAQ,KAAK,MAAM,CAAwB,CAAC;AACjF,YAAM,IAAI,EAAE,MAAM,QAAQ,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,IACzD,SAAS,KAAK;AACZ,YAAM,IAAI,EAAE,MAAM,MAAM,SAAS,OAAO,OAAO,IAAa,CAAC;AAAA,IAC/D;AAAA,EACF;AAEA,OAAK,QAAQ;AACb,QAAM,cAAc,KAAK,UAAU,MAAM;AAAE,SAAK,QAAQ;AAAA,EAAE,CAAC;AAE3D,SAAO;AAAA,IACL,WAAW,CAAC,QAAQ,MAAM,UAAU,GAAG;AAAA,IACvC;AAAA,IACA,MAAM;AAAA,EACR;AACF;AAaO,SAAS,UAAU,IAAwD;AAChF,QAAM,QAAQ,SAAyB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAEvE,QAAM,UAAU,CAAC,UAA6B;AAC5C,UAAM,IAAI,EAAE,WAAW,OAAO,OAAO,KAAK,CAAC;AAAA,EAC7C;AACA,KAAG,GAAG,UAAU,OAAO;AAEvB,SAAO;AAAA,IACL,WAAW,CAAC,QAAQ,MAAM,UAAU,GAAG;AAAA,IACvC,MAAM,MAAM,GAAG,IAAI,UAAU,OAAO;AAAA,EACtC;AACF;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ChangeEvent, Vault, Query, Noydb } from '@noy-db/hub';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* **@noy-db/in-svelte** — Svelte stores for noy-db.
|
|
5
|
+
*
|
|
6
|
+
* Three store factories that conform to Svelte's standard store
|
|
7
|
+
* contract (`subscribe(fn) → unsubscribe`). Works with:
|
|
8
|
+
*
|
|
9
|
+
* - Svelte 4 components via the `$store` auto-subscription syntax
|
|
10
|
+
* - Svelte 5 runes via `$state(await fromStore(s).current)` or a
|
|
11
|
+
* small `fromStore()` shim
|
|
12
|
+
* - Any framework that consumes the Svelte store contract
|
|
13
|
+
* (e.g. `@gradio/client`, non-Svelte integrations)
|
|
14
|
+
*
|
|
15
|
+
* ## Factories
|
|
16
|
+
*
|
|
17
|
+
* - {@link collectionStore} — reactive list of records.
|
|
18
|
+
* - {@link queryStore} — reactive result of a query builder.
|
|
19
|
+
* - {@link syncStore} — reactive Noydb-level change feed.
|
|
20
|
+
*
|
|
21
|
+
* ## Zero dependencies
|
|
22
|
+
*
|
|
23
|
+
* Svelte's store contract is ~10 lines of TypeScript. We re-implement
|
|
24
|
+
* it inline rather than take a `svelte` peer-dep — the contract is
|
|
25
|
+
* stable across Svelte 4/5 and the extra dependency is unnecessary
|
|
26
|
+
* for store-only consumers (e.g. SvelteKit load functions).
|
|
27
|
+
*
|
|
28
|
+
* @packageDocumentation
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
type Subscriber<T> = (value: T) => void;
|
|
32
|
+
type Unsubscriber = () => void;
|
|
33
|
+
interface Readable<T> {
|
|
34
|
+
subscribe(run: Subscriber<T>): Unsubscriber;
|
|
35
|
+
}
|
|
36
|
+
interface CollectionStoreState<T> {
|
|
37
|
+
readonly records: readonly T[];
|
|
38
|
+
readonly loading: boolean;
|
|
39
|
+
readonly error: Error | null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Reactive collection records. Re-emits on every change event from
|
|
43
|
+
* the collection (put / delete / sync).
|
|
44
|
+
*
|
|
45
|
+
* ```svelte
|
|
46
|
+
* <script>
|
|
47
|
+
* import { collectionStore } from '@noy-db/in-svelte'
|
|
48
|
+
* const invoices = collectionStore(vault, 'invoices')
|
|
49
|
+
* </script>
|
|
50
|
+
*
|
|
51
|
+
* {#each $invoices.records as invoice}
|
|
52
|
+
* {invoice.id}
|
|
53
|
+
* {/each}
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function collectionStore<T>(vault: Vault, collectionName: string): Readable<CollectionStoreState<T>> & {
|
|
57
|
+
refresh(): Promise<void>;
|
|
58
|
+
stop(): void;
|
|
59
|
+
};
|
|
60
|
+
interface QueryStoreState<R> {
|
|
61
|
+
readonly data: R | null;
|
|
62
|
+
readonly loading: boolean;
|
|
63
|
+
readonly error: Error | null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Reactive result of a query builder. The builder is re-run on every
|
|
67
|
+
* change event — useful for aggregates, joins, and filtered lists.
|
|
68
|
+
*
|
|
69
|
+
* ```svelte
|
|
70
|
+
* <script>
|
|
71
|
+
* import { queryStore } from '@noy-db/in-svelte'
|
|
72
|
+
* const paid = queryStore(vault, 'invoices', q => q.where('status', '==', 'paid').toArray())
|
|
73
|
+
* </script>
|
|
74
|
+
*
|
|
75
|
+
* {#if $paid.loading} Loading… {:else} {$paid.data?.length} paid {/if}
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function queryStore<T, R>(vault: Vault, collectionName: string, builder: (q: Query<T>) => Promise<R> | R): Readable<QueryStoreState<R>> & {
|
|
79
|
+
refresh(): Promise<void>;
|
|
80
|
+
stop(): void;
|
|
81
|
+
};
|
|
82
|
+
interface SyncStoreState {
|
|
83
|
+
readonly lastEvent: ChangeEvent | null;
|
|
84
|
+
readonly error: Error | null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Reactive Noydb-level change feed. Useful for top-level status
|
|
88
|
+
* indicators (last-update-time, "unsynced changes", offline banners).
|
|
89
|
+
*/
|
|
90
|
+
declare function syncStore(db: Noydb): Readable<SyncStoreState> & {
|
|
91
|
+
stop(): void;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export { type CollectionStoreState, type QueryStoreState, type Readable, type SyncStoreState, collectionStore, queryStore, syncStore };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ChangeEvent, Vault, Query, Noydb } from '@noy-db/hub';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* **@noy-db/in-svelte** — Svelte stores for noy-db.
|
|
5
|
+
*
|
|
6
|
+
* Three store factories that conform to Svelte's standard store
|
|
7
|
+
* contract (`subscribe(fn) → unsubscribe`). Works with:
|
|
8
|
+
*
|
|
9
|
+
* - Svelte 4 components via the `$store` auto-subscription syntax
|
|
10
|
+
* - Svelte 5 runes via `$state(await fromStore(s).current)` or a
|
|
11
|
+
* small `fromStore()` shim
|
|
12
|
+
* - Any framework that consumes the Svelte store contract
|
|
13
|
+
* (e.g. `@gradio/client`, non-Svelte integrations)
|
|
14
|
+
*
|
|
15
|
+
* ## Factories
|
|
16
|
+
*
|
|
17
|
+
* - {@link collectionStore} — reactive list of records.
|
|
18
|
+
* - {@link queryStore} — reactive result of a query builder.
|
|
19
|
+
* - {@link syncStore} — reactive Noydb-level change feed.
|
|
20
|
+
*
|
|
21
|
+
* ## Zero dependencies
|
|
22
|
+
*
|
|
23
|
+
* Svelte's store contract is ~10 lines of TypeScript. We re-implement
|
|
24
|
+
* it inline rather than take a `svelte` peer-dep — the contract is
|
|
25
|
+
* stable across Svelte 4/5 and the extra dependency is unnecessary
|
|
26
|
+
* for store-only consumers (e.g. SvelteKit load functions).
|
|
27
|
+
*
|
|
28
|
+
* @packageDocumentation
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
type Subscriber<T> = (value: T) => void;
|
|
32
|
+
type Unsubscriber = () => void;
|
|
33
|
+
interface Readable<T> {
|
|
34
|
+
subscribe(run: Subscriber<T>): Unsubscriber;
|
|
35
|
+
}
|
|
36
|
+
interface CollectionStoreState<T> {
|
|
37
|
+
readonly records: readonly T[];
|
|
38
|
+
readonly loading: boolean;
|
|
39
|
+
readonly error: Error | null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Reactive collection records. Re-emits on every change event from
|
|
43
|
+
* the collection (put / delete / sync).
|
|
44
|
+
*
|
|
45
|
+
* ```svelte
|
|
46
|
+
* <script>
|
|
47
|
+
* import { collectionStore } from '@noy-db/in-svelte'
|
|
48
|
+
* const invoices = collectionStore(vault, 'invoices')
|
|
49
|
+
* </script>
|
|
50
|
+
*
|
|
51
|
+
* {#each $invoices.records as invoice}
|
|
52
|
+
* {invoice.id}
|
|
53
|
+
* {/each}
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function collectionStore<T>(vault: Vault, collectionName: string): Readable<CollectionStoreState<T>> & {
|
|
57
|
+
refresh(): Promise<void>;
|
|
58
|
+
stop(): void;
|
|
59
|
+
};
|
|
60
|
+
interface QueryStoreState<R> {
|
|
61
|
+
readonly data: R | null;
|
|
62
|
+
readonly loading: boolean;
|
|
63
|
+
readonly error: Error | null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Reactive result of a query builder. The builder is re-run on every
|
|
67
|
+
* change event — useful for aggregates, joins, and filtered lists.
|
|
68
|
+
*
|
|
69
|
+
* ```svelte
|
|
70
|
+
* <script>
|
|
71
|
+
* import { queryStore } from '@noy-db/in-svelte'
|
|
72
|
+
* const paid = queryStore(vault, 'invoices', q => q.where('status', '==', 'paid').toArray())
|
|
73
|
+
* </script>
|
|
74
|
+
*
|
|
75
|
+
* {#if $paid.loading} Loading… {:else} {$paid.data?.length} paid {/if}
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function queryStore<T, R>(vault: Vault, collectionName: string, builder: (q: Query<T>) => Promise<R> | R): Readable<QueryStoreState<R>> & {
|
|
79
|
+
refresh(): Promise<void>;
|
|
80
|
+
stop(): void;
|
|
81
|
+
};
|
|
82
|
+
interface SyncStoreState {
|
|
83
|
+
readonly lastEvent: ChangeEvent | null;
|
|
84
|
+
readonly error: Error | null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Reactive Noydb-level change feed. Useful for top-level status
|
|
88
|
+
* indicators (last-update-time, "unsynced changes", offline banners).
|
|
89
|
+
*/
|
|
90
|
+
declare function syncStore(db: Noydb): Readable<SyncStoreState> & {
|
|
91
|
+
stop(): void;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export { type CollectionStoreState, type QueryStoreState, type Readable, type SyncStoreState, collectionStore, queryStore, syncStore };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
function writable(initial) {
|
|
3
|
+
let value = initial;
|
|
4
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
5
|
+
return {
|
|
6
|
+
set(next) {
|
|
7
|
+
value = next;
|
|
8
|
+
for (const s of subscribers) s(next);
|
|
9
|
+
},
|
|
10
|
+
subscribe(run) {
|
|
11
|
+
subscribers.add(run);
|
|
12
|
+
run(value);
|
|
13
|
+
return () => {
|
|
14
|
+
subscribers.delete(run);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function collectionStore(vault, collectionName) {
|
|
20
|
+
const inner = writable({ records: [], loading: true, error: null });
|
|
21
|
+
const coll = vault.collection(collectionName);
|
|
22
|
+
async function refresh() {
|
|
23
|
+
try {
|
|
24
|
+
const records = await coll.list();
|
|
25
|
+
inner.set({ records, loading: false, error: null });
|
|
26
|
+
} catch (err) {
|
|
27
|
+
inner.set({ records: [], loading: false, error: err });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
void refresh();
|
|
31
|
+
const unsubscribe = coll.subscribe(() => {
|
|
32
|
+
void refresh();
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
subscribe: (run) => inner.subscribe(run),
|
|
36
|
+
refresh,
|
|
37
|
+
stop: unsubscribe
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function queryStore(vault, collectionName, builder) {
|
|
41
|
+
const inner = writable({ data: null, loading: true, error: null });
|
|
42
|
+
const coll = vault.collection(collectionName);
|
|
43
|
+
async function refresh() {
|
|
44
|
+
try {
|
|
45
|
+
const result = await Promise.resolve(builder(coll.query()));
|
|
46
|
+
inner.set({ data: result, loading: false, error: null });
|
|
47
|
+
} catch (err) {
|
|
48
|
+
inner.set({ data: null, loading: false, error: err });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
void refresh();
|
|
52
|
+
const unsubscribe = coll.subscribe(() => {
|
|
53
|
+
void refresh();
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
subscribe: (run) => inner.subscribe(run),
|
|
57
|
+
refresh,
|
|
58
|
+
stop: unsubscribe
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function syncStore(db) {
|
|
62
|
+
const inner = writable({ lastEvent: null, error: null });
|
|
63
|
+
const handler = (event) => {
|
|
64
|
+
inner.set({ lastEvent: event, error: null });
|
|
65
|
+
};
|
|
66
|
+
db.on("change", handler);
|
|
67
|
+
return {
|
|
68
|
+
subscribe: (run) => inner.subscribe(run),
|
|
69
|
+
stop: () => db.off("change", handler)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
collectionStore,
|
|
74
|
+
queryStore,
|
|
75
|
+
syncStore
|
|
76
|
+
};
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * **@noy-db/in-svelte** — Svelte stores for noy-db.\n *\n * Three store factories that conform to Svelte's standard store\n * contract (`subscribe(fn) → unsubscribe`). Works with:\n *\n * - Svelte 4 components via the `$store` auto-subscription syntax\n * - Svelte 5 runes via `$state(await fromStore(s).current)` or a\n * small `fromStore()` shim\n * - Any framework that consumes the Svelte store contract\n * (e.g. `@gradio/client`, non-Svelte integrations)\n *\n * ## Factories\n *\n * - {@link collectionStore} — reactive list of records.\n * - {@link queryStore} — reactive result of a query builder.\n * - {@link syncStore} — reactive Noydb-level change feed.\n *\n * ## Zero dependencies\n *\n * Svelte's store contract is ~10 lines of TypeScript. We re-implement\n * it inline rather than take a `svelte` peer-dep — the contract is\n * stable across Svelte 4/5 and the extra dependency is unnecessary\n * for store-only consumers (e.g. SvelteKit load functions).\n *\n * @packageDocumentation\n */\n\nimport type { Noydb, Vault, ChangeEvent, Query } from '@noy-db/hub'\n\n// ─── Svelte store contract (re-implemented, no svelte peer dep) ─────────\n\ntype Subscriber<T> = (value: T) => void\ntype Unsubscriber = () => void\n\nexport interface Readable<T> {\n subscribe(run: Subscriber<T>): Unsubscriber\n}\n\nfunction writable<T>(initial: T): {\n set(value: T): void\n subscribe(run: Subscriber<T>): Unsubscriber\n} {\n let value = initial\n const subscribers = new Set<Subscriber<T>>()\n return {\n set(next) {\n value = next\n for (const s of subscribers) s(next)\n },\n subscribe(run) {\n subscribers.add(run)\n run(value)\n return () => { subscribers.delete(run) }\n },\n }\n}\n\n// ─── collectionStore ───────────────────────────────────────────────────\n\nexport interface CollectionStoreState<T> {\n readonly records: readonly T[]\n readonly loading: boolean\n readonly error: Error | null\n}\n\n/**\n * Reactive collection records. Re-emits on every change event from\n * the collection (put / delete / sync).\n *\n * ```svelte\n * <script>\n * import { collectionStore } from '@noy-db/in-svelte'\n * const invoices = collectionStore(vault, 'invoices')\n * </script>\n *\n * {#each $invoices.records as invoice}\n * {invoice.id}\n * {/each}\n * ```\n */\nexport function collectionStore<T>(\n vault: Vault,\n collectionName: string,\n): Readable<CollectionStoreState<T>> & { refresh(): Promise<void>; stop(): void } {\n const inner = writable<CollectionStoreState<T>>({ records: [], loading: true, error: null })\n const coll = vault.collection<T>(collectionName)\n\n async function refresh(): Promise<void> {\n try {\n const records = await coll.list()\n inner.set({ records, loading: false, error: null })\n } catch (err) {\n inner.set({ records: [], loading: false, error: err as Error })\n }\n }\n\n void refresh()\n const unsubscribe = coll.subscribe(() => { void refresh() })\n\n return {\n subscribe: (run) => inner.subscribe(run),\n refresh,\n stop: unsubscribe,\n }\n}\n\n// ─── queryStore ────────────────────────────────────────────────────────\n\nexport interface QueryStoreState<R> {\n readonly data: R | null\n readonly loading: boolean\n readonly error: Error | null\n}\n\n/**\n * Reactive result of a query builder. The builder is re-run on every\n * change event — useful for aggregates, joins, and filtered lists.\n *\n * ```svelte\n * <script>\n * import { queryStore } from '@noy-db/in-svelte'\n * const paid = queryStore(vault, 'invoices', q => q.where('status', '==', 'paid').toArray())\n * </script>\n *\n * {#if $paid.loading} Loading… {:else} {$paid.data?.length} paid {/if}\n * ```\n */\nexport function queryStore<T, R>(\n vault: Vault,\n collectionName: string,\n builder: (q: Query<T>) => Promise<R> | R,\n): Readable<QueryStoreState<R>> & { refresh(): Promise<void>; stop(): void } {\n const inner = writable<QueryStoreState<R>>({ data: null, loading: true, error: null })\n const coll = vault.collection<T>(collectionName)\n\n async function refresh(): Promise<void> {\n try {\n const result = await Promise.resolve(builder(coll.query() as unknown as Query<T>))\n inner.set({ data: result, loading: false, error: null })\n } catch (err) {\n inner.set({ data: null, loading: false, error: err as Error })\n }\n }\n\n void refresh()\n const unsubscribe = coll.subscribe(() => { void refresh() })\n\n return {\n subscribe: (run) => inner.subscribe(run),\n refresh,\n stop: unsubscribe,\n }\n}\n\n// ─── syncStore ─────────────────────────────────────────────────────────\n\nexport interface SyncStoreState {\n readonly lastEvent: ChangeEvent | null\n readonly error: Error | null\n}\n\n/**\n * Reactive Noydb-level change feed. Useful for top-level status\n * indicators (last-update-time, \"unsynced changes\", offline banners).\n */\nexport function syncStore(db: Noydb): Readable<SyncStoreState> & { stop(): void } {\n const inner = writable<SyncStoreState>({ lastEvent: null, error: null })\n\n const handler = (event: ChangeEvent): void => {\n inner.set({ lastEvent: event, error: null })\n }\n db.on('change', handler)\n\n return {\n subscribe: (run) => inner.subscribe(run),\n stop: () => db.off('change', handler),\n }\n}\n"],"mappings":";AAuCA,SAAS,SAAY,SAGnB;AACA,MAAI,QAAQ;AACZ,QAAM,cAAc,oBAAI,IAAmB;AAC3C,SAAO;AAAA,IACL,IAAI,MAAM;AACR,cAAQ;AACR,iBAAW,KAAK,YAAa,GAAE,IAAI;AAAA,IACrC;AAAA,IACA,UAAU,KAAK;AACb,kBAAY,IAAI,GAAG;AACnB,UAAI,KAAK;AACT,aAAO,MAAM;AAAE,oBAAY,OAAO,GAAG;AAAA,MAAE;AAAA,IACzC;AAAA,EACF;AACF;AAyBO,SAAS,gBACd,OACA,gBACgF;AAChF,QAAM,QAAQ,SAAkC,EAAE,SAAS,CAAC,GAAG,SAAS,MAAM,OAAO,KAAK,CAAC;AAC3F,QAAM,OAAO,MAAM,WAAc,cAAc;AAE/C,iBAAe,UAAyB;AACtC,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,KAAK;AAChC,YAAM,IAAI,EAAE,SAAS,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,IACpD,SAAS,KAAK;AACZ,YAAM,IAAI,EAAE,SAAS,CAAC,GAAG,SAAS,OAAO,OAAO,IAAa,CAAC;AAAA,IAChE;AAAA,EACF;AAEA,OAAK,QAAQ;AACb,QAAM,cAAc,KAAK,UAAU,MAAM;AAAE,SAAK,QAAQ;AAAA,EAAE,CAAC;AAE3D,SAAO;AAAA,IACL,WAAW,CAAC,QAAQ,MAAM,UAAU,GAAG;AAAA,IACvC;AAAA,IACA,MAAM;AAAA,EACR;AACF;AAuBO,SAAS,WACd,OACA,gBACA,SAC2E;AAC3E,QAAM,QAAQ,SAA6B,EAAE,MAAM,MAAM,SAAS,MAAM,OAAO,KAAK,CAAC;AACrF,QAAM,OAAO,MAAM,WAAc,cAAc;AAE/C,iBAAe,UAAyB;AACtC,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,QAAQ,QAAQ,KAAK,MAAM,CAAwB,CAAC;AACjF,YAAM,IAAI,EAAE,MAAM,QAAQ,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,IACzD,SAAS,KAAK;AACZ,YAAM,IAAI,EAAE,MAAM,MAAM,SAAS,OAAO,OAAO,IAAa,CAAC;AAAA,IAC/D;AAAA,EACF;AAEA,OAAK,QAAQ;AACb,QAAM,cAAc,KAAK,UAAU,MAAM;AAAE,SAAK,QAAQ;AAAA,EAAE,CAAC;AAE3D,SAAO;AAAA,IACL,WAAW,CAAC,QAAQ,MAAM,UAAU,GAAG;AAAA,IACvC;AAAA,IACA,MAAM;AAAA,EACR;AACF;AAaO,SAAS,UAAU,IAAwD;AAChF,QAAM,QAAQ,SAAyB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAEvE,QAAM,UAAU,CAAC,UAA6B;AAC5C,UAAM,IAAI,EAAE,WAAW,OAAO,OAAO,KAAK,CAAC;AAAA,EAC7C;AACA,KAAG,GAAG,UAAU,OAAO;AAEvB,SAAO;AAAA,IACL,WAAW,CAAC,QAAQ,MAAM,UAAU,GAAG;AAAA,IACvC,MAAM,MAAM,GAAG,IAAI,UAAU,OAAO;AAAA,EACtC;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@noy-db/in-svelte",
|
|
3
|
+
"version": "0.1.0-pre.3",
|
|
4
|
+
"description": "Svelte stores for noy-db — collectionStore / queryStore / syncStore backed by noy-db change events. Works with Svelte 4 store contract and Svelte 5 runes (via $store subscription interop).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "vLannaAi <vicio@lanna.ai>",
|
|
7
|
+
"homepage": "https://github.com/vLannaAi/noy-db/tree/main/packages/in-svelte#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/vLannaAi/noy-db.git",
|
|
11
|
+
"directory": "packages/in-svelte"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/vLannaAi/noy-db/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/index.d.cts",
|
|
26
|
+
"default": "./dist/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@noy-db/hub": "0.1.0-pre.3"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@noy-db/hub": "0.1.0-pre.3"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"noy-db",
|
|
49
|
+
"in-svelte",
|
|
50
|
+
"svelte",
|
|
51
|
+
"stores",
|
|
52
|
+
"sveltekit"
|
|
53
|
+
],
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public",
|
|
56
|
+
"tag": "latest"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsup",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"lint": "eslint src/",
|
|
62
|
+
"typecheck": "tsc --noEmit"
|
|
63
|
+
}
|
|
64
|
+
}
|