tila-sdk 0.1.0 → 0.2.0

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
@@ -49,6 +49,133 @@ const updated = await entities.update("task-1", {
49
49
  await entities.archive("task-1");
50
50
  ```
51
51
 
52
+ ### `createTila` — one facade, local or remote
53
+
54
+ `createTila(config, token?)` returns a uniform facade exposing the same resource
55
+ methods (`tasks`, `records`, `claims`, `artifacts`, `gates`, `signals`,
56
+ `journal`, `presence`, `schema`, `summary`, `search`, `templates`, `tokens`)
57
+ regardless of backend. Swap `config.backend` without changing any call site.
58
+
59
+ ```typescript
60
+ import { createTila } from "tila-sdk";
61
+
62
+ // Cloudflare (HTTP) — token required
63
+ const tila = await createTila(
64
+ { project_id: "my-project", backend: "cloudflare", worker_url: process.env.TILA_URL!, schema_version: 1, tila_version: "0", created_at: "" },
65
+ process.env.TILA_TOKEN!,
66
+ );
67
+
68
+ // Local (in-process SQLite) — no token; requires the optional `better-sqlite3` peer dep
69
+ const local = await createTila({
70
+ project_id: "my-project",
71
+ backend: "local",
72
+ local: { db_path: ".tila/project.db", artifacts_path: ".tila/artifacts" },
73
+ schema_version: 1,
74
+ tila_version: "0",
75
+ created_at: "",
76
+ });
77
+
78
+ await tila.tasks.create("task-1", "task", { title: "uniform call site" });
79
+ await local.tasks.create("task-1", "task", { title: "uniform call site" });
80
+ local.close(); // closes the SQLite connection (no-op for cloudflare)
81
+ ```
82
+
83
+ > **`better-sqlite3` peer dep:** the local backend lazily loads `better-sqlite3`
84
+ > (an optional peer dependency). Install it for local mode; cloudflare mode never
85
+ > touches it. Token issuance (`tila.tokens.*`) is HTTP-only and throws in local.
86
+
87
+ The `close()` method is the canonical lifecycle handle for both backends: it is a
88
+ no-op for cloudflare and closes the SQLite connection for local. It is safe to call
89
+ more than once (double-close safe).
90
+
91
+ In local mode, a few facade methods have no in-process equivalent and throw
92
+ `LocalUnsupportedError` instead of silently no-op'ing:
93
+
94
+ - `tokens.issue` / `tokens.revoke` / `tokens.list` (the D1 global token store is a
95
+ Worker/Cloudflare concern).
96
+ - `artifacts.upload` and `artifacts.download` (binary R2 multipart upload/download —
97
+ local consumers use the content-addressed text primitives `artifacts.writeText` /
98
+ `artifacts.readText` instead).
99
+
100
+ ### `tila-sdk/local` — direct local backend
101
+
102
+ For full control over the local stack (without the `createTila` facade), import the
103
+ heavy entry directly. It is a separate package export so the SQLite/`node:fs` stack
104
+ never loads from the main (zod-only) entry:
105
+
106
+ ```typescript
107
+ import { createTilaLocal } from "tila-sdk/local";
108
+
109
+ const { project, artifacts, close } = await createTilaLocal({
110
+ dbPath: ".tila/project.db", // SQLite file (created if absent)
111
+ artifactsPath: ".tila/artifacts", // blob root directory
112
+ project: "my-project", // required — scopes artifact keys
113
+ org: "my-org", // optional, defaults to "local"
114
+ });
115
+
116
+ // `project` is the full @tila/core backend surface (Entity/Coordination/Journal/
117
+ // Gate/Signal/Schema/Summary/Record); `artifacts` is the ArtifactBackend.
118
+ close(); // closes the underlying better-sqlite3 connection
119
+ ```
120
+
121
+ > Note: the `createTilaLocal` option keys are camelCase — `dbPath`, `artifactsPath`,
122
+ > `org`, `project`. (The `createTila` facade's `config.local` section uses snake_case
123
+ > `db_path` / `artifacts_path` to mirror the `[local]` config file; the direct
124
+ > `createTilaLocal` options object is camelCase.)
125
+
126
+ ### `better-sqlite3` — optional peer dependency
127
+
128
+ `better-sqlite3` is an **optional peer dependency** with range **`>=11 <13`**. The
129
+ **tested / CI-exercised** version is **12.x (currently `12.10.0`)**; 11.x is declared
130
+ supported but is *not* exercised in CI. Install it only when you use the local
131
+ backend:
132
+
133
+ ```bash
134
+ npm i better-sqlite3
135
+ ```
136
+
137
+ If it (or its drizzle adapter) is missing, calling into the local backend throws
138
+ `MissingNativeDriverError` with the exact message:
139
+
140
+ ```
141
+ tila-sdk/local requires the optional peer dependency 'better-sqlite3'. Run: npm i better-sqlite3
142
+ ```
143
+
144
+ Importing `tila-sdk/local` never loads the native binary — only *calling*
145
+ `createTilaLocal` (or `createTila({ backend: "local" })`) does.
146
+
147
+ > **No prebuilt binaries for musl/Alpine or Windows-arm64.** `better-sqlite3` ships
148
+ > prebuilt binaries for common platforms but **not** musl-libc (Alpine) or
149
+ > Windows-arm64. On those, the consumer needs a build toolchain (Python + make + a C
150
+ > compiler) so `better-sqlite3` can compile from source on install.
151
+
152
+ > **`skipLibCheck: true` required** in your `tsconfig.json` when consuming
153
+ > `tila-sdk/local` types. The bundled `better-sqlite3` / `drizzle-orm` declarations
154
+ > do not fully round-trip through the dts rollup (a known rollup-dts limitation). This
155
+ > does **not** affect type-checking of *your own* code — `skipLibCheck` only skips
156
+ > re-checking library `.d.ts` internals (the ecosystem default, also used in this
157
+ > monorepo).
158
+
159
+ ### Browser / HTTP-only consumers
160
+
161
+ The main `tila-sdk` entry stays zod-only — no native stack is statically reachable
162
+ from it (enforced by a bundle-hygiene test). Browsers and any HTTP-only environment
163
+ use the cloudflare backend; they never touch `better-sqlite3` or `node:fs`.
164
+
165
+ ### Local-mode behavior divergences vs remote
166
+
167
+ Local mode presents the same facade shape, but a handful of methods diverge from the
168
+ HTTP backend. These are intentional and called out so consumers are not surprised:
169
+
170
+ | Method | Local behavior | Why |
171
+ |--------|----------------|-----|
172
+ | `schema.history` | Returns `[]` | Dead on **both** sides — the Worker exposes no schema-history route either, so the cloudflare branch would 404. (The data exists in `_schema_history`; it is simply not surfaced.) |
173
+ | `presence.listAll` | Returns only **active** machines (every row `active: true`) | The embedded backend's `listPresence()` already filters to active machines by TTL; remote additionally includes stale machines as `active: false`. |
174
+ | `artifacts.writeText` | Returns `deduplicated: false` and drops `tags` | The embedded artifacts table has no `tags` column, and the local write path does not report dedup. |
175
+ | `tasks.list` | Ignores `compact`, emits no pagination cursor | `compact` is an HTTP-only projection; the local list is non-paginated (no `next_cursor`/`total`). |
176
+ | `templates.list` | `variables` derived from `{{placeholders}}` | Local derives variables by scanning each template's entity data for `{{name}}` placeholders (`/\{\{(\w+)\}\}/`). |
177
+ | `idempotency_key` (e.g. on `claims.acquire`) | Accepted but **not honored** | Remote dedups retries via D1; local relies on primary-key-level dedup instead — a retried create of an existing id fails rather than duplicating. Full idempotency is single-machine-low-risk and remote-only. (The embedded `_idempotency` table + `check/storeIdempotency` exist but are intentionally unwired.) |
178
+
52
179
  ### Constructor Options
53
180
 
54
181
  | Option | Type | Default | Description |