tablinum 0.1.0 → 0.1.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/README.md CHANGED
@@ -36,7 +36,7 @@ npm install tablinum
36
36
 
37
37
  ```typescript
38
38
  import { Effect } from "effect";
39
- import { createLocalstr, collection, field } from "tablinum";
39
+ import { createTablinum, collection, field } from "tablinum";
40
40
 
41
41
  const schema = {
42
42
  todos: collection("todos", {
@@ -46,7 +46,7 @@ const schema = {
46
46
  };
47
47
 
48
48
  const program = Effect.gen(function* () {
49
- const db = yield* createLocalstr({
49
+ const db = yield* createTablinum({
50
50
  schema,
51
51
  relays: ["wss://relay.example.com"],
52
52
  });
@@ -72,6 +72,118 @@ const program = Effect.gen(function* () {
72
72
  Effect.runPromise(Effect.scoped(program));
73
73
  ```
74
74
 
75
+ ## Svelte 5
76
+
77
+ Import from `tablinum/svelte` for reactive bindings that use Svelte 5 runes. No Effect knowledge needed — the API is plain async/await.
78
+
79
+ ### Setup
80
+
81
+ Create a database helper that defines your schema and initializes the database:
82
+
83
+ ```typescript
84
+ // src/lib/db.ts
85
+ import { createTablinum, collection, field } from "tablinum/svelte";
86
+
87
+ const schema = {
88
+ todos: collection(
89
+ "todos",
90
+ {
91
+ title: field.string(),
92
+ done: field.boolean(),
93
+ },
94
+ { indices: ["done"] },
95
+ ),
96
+ };
97
+
98
+ export type AppSchema = typeof schema;
99
+
100
+ export async function initDb() {
101
+ return createTablinum({
102
+ schema,
103
+ relays: ["wss://relay.example.com"],
104
+ });
105
+ }
106
+ ```
107
+
108
+ ### Component
109
+
110
+ Collections and live queries are reactive — their `.items` property updates automatically when data changes.
111
+
112
+ ```svelte
113
+ <script lang="ts">
114
+ import { onDestroy } from "svelte";
115
+ import { initDb, type AppSchema } from "$lib/db";
116
+ import type { Database, Collection, LiveQuery } from "tablinum/svelte";
117
+
118
+ let db: Database<AppSchema> | null = $state(null);
119
+ let todos: Collection<AppSchema["todos"]> | null = $state(null);
120
+ let pending: LiveQuery<{ id: string; title: string; done: boolean }> | null = $state(null);
121
+
122
+ let title = $state("");
123
+
124
+ async function init() {
125
+ db = await initDb();
126
+ todos = db.collection("todos");
127
+ pending = todos.where("done").equals(false).live();
128
+ }
129
+
130
+ init();
131
+
132
+ async function addTodo(e: SubmitEvent) {
133
+ e.preventDefault();
134
+ if (!todos || !title.trim()) return;
135
+ await todos.add({ title: title.trim(), done: false });
136
+ title = "";
137
+ }
138
+
139
+ async function toggle(id: string, currentDone: boolean) {
140
+ await todos?.update(id, { done: !currentDone });
141
+ }
142
+
143
+ async function remove(id: string) {
144
+ await todos?.delete(id);
145
+ }
146
+
147
+ onDestroy(() => {
148
+ pending?.destroy();
149
+ db?.close();
150
+ });
151
+ </script>
152
+
153
+ <!-- All todos (reactive via collection.items) -->
154
+ <p>{todos?.items.length ?? 0} total</p>
155
+
156
+ <!-- Filtered todos (reactive via live query) -->
157
+ <form onsubmit={addTodo}>
158
+ <input bind:value={title} placeholder="Add a todo..." />
159
+ <button type="submit">Add</button>
160
+ </form>
161
+
162
+ <ul>
163
+ {#each pending?.items ?? [] as todo (todo.id)}
164
+ <li>
165
+ <input type="checkbox" checked={todo.done} onchange={() => toggle(todo.id, todo.done)} />
166
+ <span>{todo.title}</span>
167
+ <button onclick={() => remove(todo.id)}>Delete</button>
168
+ </li>
169
+ {/each}
170
+ </ul>
171
+
172
+ <!-- Sync status is reactive -->
173
+ {#if db?.status === "syncing"}
174
+ <p>Syncing...</p>
175
+ {/if}
176
+
177
+ <button onclick={() => db?.sync()}>Sync</button>
178
+ ```
179
+
180
+ ### Key concepts
181
+
182
+ - **`createTablinum`** returns a `Promise<Database>` (no Effect boilerplate)
183
+ - **`collection.items`** is a reactive `$state` array of all records in the collection
184
+ - **`.where("field").equals(value).live()`** returns a `LiveQuery` whose `.items` update automatically
185
+ - **Cleanup**: call `.destroy()` on live queries and `.close()` on the database in `onDestroy`
186
+
75
187
  ## License
76
188
 
77
189
  MIT
@@ -2,11 +2,11 @@ import { Effect, Scope } from "effect";
2
2
  import type { SchemaConfig } from "../schema/types.ts";
3
3
  import type { DatabaseHandle } from "./database-handle.ts";
4
4
  import { CryptoError, StorageError, ValidationError } from "../errors.ts";
5
- export interface LocalstrConfig<S extends SchemaConfig> {
5
+ export interface TablinumConfig<S extends SchemaConfig> {
6
6
  readonly schema: S;
7
7
  readonly relays: readonly string[];
8
8
  readonly privateKey?: Uint8Array | undefined;
9
9
  readonly dbName?: string | undefined;
10
10
  readonly onSyncError?: ((error: Error) => void) | undefined;
11
11
  }
12
- export declare function createLocalstr<S extends SchemaConfig>(config: LocalstrConfig<S>): Effect.Effect<DatabaseHandle<S>, ValidationError | StorageError | CryptoError, Scope.Scope>;
12
+ export declare function createTablinum<S extends SchemaConfig>(config: TablinumConfig<S>): Effect.Effect<DatabaseHandle<S>, ValidationError | StorageError | CryptoError, Scope.Scope>;
package/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@ export { collection } from "./schema/collection.ts";
3
3
  export type { CollectionDef, CollectionFields } from "./schema/collection.ts";
4
4
  export type { FieldDef, FieldKind } from "./schema/field.ts";
5
5
  export type { InferRecord, SchemaConfig } from "./schema/types.ts";
6
- export { createLocalstr } from "./db/create-localstr.ts";
7
- export type { LocalstrConfig } from "./db/create-localstr.ts";
6
+ export { createTablinum } from "./db/create-tablinum.ts";
7
+ export type { TablinumConfig } from "./db/create-tablinum.ts";
8
8
  export type { DatabaseHandle, SyncStatus } from "./db/database-handle.ts";
9
9
  export type { CollectionHandle } from "./crud/collection-handle.ts";
10
10
  export type { CollectionOptions } from "./schema/collection.ts";
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ function collection(name, fields, options) {
48
48
  }
49
49
  return { _tag: "CollectionDef", name, fields, indices };
50
50
  }
51
- // src/db/create-localstr.ts
51
+ // src/db/create-tablinum.ts
52
52
  import { Effect as Effect14, PubSub as PubSub2, Ref as Ref5 } from "effect";
53
53
 
54
54
  // src/schema/validate.ts
@@ -160,7 +160,7 @@ function buildPartialValidator(collectionName, def) {
160
160
  // src/storage/idb.ts
161
161
  import { Effect as Effect2 } from "effect";
162
162
  import { openDB } from "idb";
163
- var DB_NAME = "localstr";
163
+ var DB_NAME = "tablinum";
164
164
  function storeName(collection2) {
165
165
  return `col_${collection2}`;
166
166
  }
@@ -1665,7 +1665,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1665
1665
  const result = yield* Effect13.result(relay.publish(giftWrap.event, relayUrls));
1666
1666
  if (result._tag === "Failure") {
1667
1667
  yield* publishQueue.enqueue(giftWrap.id);
1668
- console.error("[localstr:publishLocal] relay error:", result.failure);
1668
+ console.error("[tablinum:publishLocal] relay error:", result.failure);
1669
1669
  if (onSyncError)
1670
1670
  onSyncError(result.failure);
1671
1671
  }
@@ -1685,19 +1685,19 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1685
1685
  }));
1686
1686
  }));
1687
1687
  if (subResult._tag === "Failure") {
1688
- console.error("[localstr:subscribe] failed for", url, subResult.failure);
1688
+ console.error("[tablinum:subscribe] failed for", url, subResult.failure);
1689
1689
  if (onSyncError)
1690
1690
  onSyncError(subResult.failure);
1691
1691
  } else {
1692
- console.log("[localstr:subscribe] listening on", url);
1692
+ console.log("[tablinum:subscribe] listening on", url);
1693
1693
  }
1694
1694
  }
1695
1695
  })
1696
1696
  };
1697
1697
  }
1698
1698
 
1699
- // src/db/create-localstr.ts
1700
- function createLocalstr(config) {
1699
+ // src/db/create-tablinum.ts
1700
+ function createTablinum(config) {
1701
1701
  return Effect14.gen(function* () {
1702
1702
  if (!config.relays || config.relays.length === 0) {
1703
1703
  return yield* new ValidationError({
@@ -1711,7 +1711,7 @@ function createLocalstr(config) {
1711
1711
  });
1712
1712
  }
1713
1713
  let resolvedKey = config.privateKey;
1714
- const storageKeyName = `localstr-key-${config.dbName ?? "localstr"}`;
1714
+ const storageKeyName = `tablinum-key-${config.dbName ?? "tablinum"}`;
1715
1715
  if (!resolvedKey && typeof globalThis.localStorage !== "undefined") {
1716
1716
  const saved = globalThis.localStorage.getItem(storageKeyName);
1717
1717
  if (saved && saved.length === 64) {
@@ -1737,7 +1737,7 @@ function createLocalstr(config) {
1737
1737
  const syncStatus = yield* createSyncStatusHandle();
1738
1738
  const syncHandle = createSyncHandle(storage, giftWrapHandle, relayHandle, publishQueue, syncStatus, watchCtx, config.relays, identity.publicKey, config.onSyncError);
1739
1739
  const onWrite = (event) => Effect14.gen(function* () {
1740
- console.log("[localstr:onWrite]", event.kind, event.collection, event.recordId);
1740
+ console.log("[tablinum:onWrite]", event.kind, event.collection, event.recordId);
1741
1741
  const content = event.kind === "delete" ? JSON.stringify({ _deleted: true }) : JSON.stringify(event.data);
1742
1742
  const dTag = `${event.collection}:${event.recordId}`;
1743
1743
  const wrapResult = yield* Effect14.result(giftWrapHandle.wrap({
@@ -1748,13 +1748,13 @@ function createLocalstr(config) {
1748
1748
  }));
1749
1749
  if (wrapResult._tag === "Success") {
1750
1750
  const gw = wrapResult.success;
1751
- console.log("[localstr:onWrite] gift wrap created:", gw.id, "kind:", gw.kind, "tags:", JSON.stringify(gw.tags));
1751
+ console.log("[tablinum:onWrite] gift wrap created:", gw.id, "kind:", gw.kind, "tags:", JSON.stringify(gw.tags));
1752
1752
  yield* storage.putGiftWrap({
1753
1753
  id: gw.id,
1754
1754
  event: gw,
1755
1755
  createdAt: gw.created_at
1756
1756
  });
1757
- console.log("[localstr:onWrite] gift wrap stored, publishing...");
1757
+ console.log("[tablinum:onWrite] gift wrap stored, publishing...");
1758
1758
  const publishEffect = Effect14.gen(function* () {
1759
1759
  const pubResult = yield* Effect14.result(syncHandle.publishLocal({
1760
1760
  id: gw.id,
@@ -1763,17 +1763,17 @@ function createLocalstr(config) {
1763
1763
  }));
1764
1764
  if (pubResult._tag === "Failure") {
1765
1765
  const err = pubResult.failure;
1766
- console.error("[localstr:publish] failed:", err);
1766
+ console.error("[tablinum:publish] failed:", err);
1767
1767
  if (config.onSyncError)
1768
1768
  config.onSyncError(err);
1769
1769
  } else {
1770
- console.log("[localstr:publish] success");
1770
+ console.log("[tablinum:publish] success");
1771
1771
  }
1772
1772
  });
1773
1773
  yield* Effect14.forkDetach(publishEffect);
1774
1774
  } else {
1775
1775
  const err = wrapResult.failure;
1776
- console.error("[localstr:onWrite] wrap failed:", err);
1776
+ console.error("[tablinum:onWrite] wrap failed:", err);
1777
1777
  if (config.onSyncError)
1778
1778
  config.onSyncError(err);
1779
1779
  }
@@ -1821,7 +1821,7 @@ function createLocalstr(config) {
1821
1821
  }
1822
1822
  export {
1823
1823
  field,
1824
- createLocalstr,
1824
+ createTablinum,
1825
1825
  collection,
1826
1826
  ValidationError,
1827
1827
  SyncError,
@@ -0,0 +1,20 @@
1
+ import type { CollectionDef, CollectionFields } from "../schema/collection.ts";
2
+ import type { InferRecord } from "../schema/types.ts";
3
+ import type { CollectionHandle } from "../crud/collection-handle.ts";
4
+ import { type SvelteWhereClause, type SvelteOrderByBuilder } from "./query.svelte.ts";
5
+ export declare class Collection<C extends CollectionDef<CollectionFields>> {
6
+ #private;
7
+ items: any;
8
+ error: any;
9
+ constructor(handle: CollectionHandle<C>);
10
+ add: (data: Omit<InferRecord<C>, "id">) => Promise<string>;
11
+ update: (id: string, data: Partial<Omit<InferRecord<C>, "id">>) => Promise<void>;
12
+ delete: (id: string) => Promise<void>;
13
+ get: (id: string) => Promise<InferRecord<C>>;
14
+ first: () => Promise<InferRecord<C> | null>;
15
+ count: () => Promise<number>;
16
+ where: (field: string & keyof Omit<InferRecord<C>, "id">) => SvelteWhereClause<InferRecord<C>>;
17
+ orderBy: (field: string & keyof Omit<InferRecord<C>, "id">) => SvelteOrderByBuilder<InferRecord<C>>;
18
+ /** @internal Called by Database.close() */
19
+ _destroy(): void;
20
+ }
@@ -0,0 +1,15 @@
1
+ import { Scope } from "effect";
2
+ import type { SchemaConfig } from "../schema/types.ts";
3
+ import type { DatabaseHandle } from "../db/database-handle.ts";
4
+ import { Collection } from "./collection.svelte.ts";
5
+ export declare class Database<S extends SchemaConfig> {
6
+ #private;
7
+ status: any;
8
+ error: any;
9
+ constructor(handle: DatabaseHandle<S>, scope: Scope.Closeable);
10
+ collection<K extends string & keyof S>(name: K): Collection<S[K]>;
11
+ exportKey(): string;
12
+ close: () => Promise<void>;
13
+ sync: () => Promise<void>;
14
+ rebuild: () => Promise<void>;
15
+ }
@@ -0,0 +1,16 @@
1
+ import type { SchemaConfig } from "../schema/types.ts";
2
+ import { type TablinumConfig } from "../db/create-tablinum.ts";
3
+ import { Database } from "./database.svelte.ts";
4
+ export { field } from "../schema/field.ts";
5
+ export { collection } from "../schema/collection.ts";
6
+ export type { CollectionDef, CollectionFields } from "../schema/collection.ts";
7
+ export type { FieldDef, FieldKind } from "../schema/field.ts";
8
+ export type { InferRecord, SchemaConfig } from "../schema/types.ts";
9
+ export type { TablinumConfig } from "../db/create-tablinum.ts";
10
+ export type { SyncStatus } from "../db/database-handle.ts";
11
+ export { ValidationError, StorageError, CryptoError, RelayError, SyncError, NotFoundError, ClosedError, } from "../errors.ts";
12
+ export { Database } from "./database.svelte.ts";
13
+ export { Collection } from "./collection.svelte.ts";
14
+ export { LiveQuery } from "./live-query.svelte.ts";
15
+ export type { SvelteQueryBuilder, SvelteWhereClause, SvelteOrderByBuilder, } from "./query.svelte.ts";
16
+ export declare function createTablinum<S extends SchemaConfig>(config: TablinumConfig<S>): Promise<Database<S>>;