@voltro/database 0.11.1 → 0.11.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/CHANGELOG.md +95 -0
- package/dist/{fileBased-Cc-F1oFv.js → fileBased-BrYB2u6E.js} +88 -79
- package/dist/index.d.ts +9 -3
- package/dist/index.js +508 -508
- package/dist/sql.js +100 -100
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,99 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.11.3] — 2026-07-24
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- **@voltro/runtime** — `crud.*` secure-default CRUD handler helpers + `redactColumns` (A1 core). Each returns an executor you export as a `*.query.server.ts` / `*.mutation.server.ts` default — the descriptor (schemas + `guards`) stays hand-written and browser-safe:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// accounts.list.query.server.ts
|
|
50
|
+
import { crud } from '@voltro/runtime'
|
|
51
|
+
export default crud.list('accounts', { redact: ['apiSecret'] })
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
They bake in the invariants a hand-rolled CRUD generator kept getting wrong (the leak class was in the HANDLERS, not the schemas):
|
|
55
|
+
|
|
56
|
+
- **Tenant scope** — `list` / `getById` read through `ctx.store`, which auto-scopes a `tenant()` table; they never `.unscoped()`, so a cross-tenant read is impossible. - **Redaction** — `redact` columns are stripped from every returned row (a credential / secret / salary a read must never ship), on reads AND on the row a `create` / `update` echoes. `redactColumns(rows, cols)` is exported standalone for a hand-written handler that isn't plain CRUD. - **`getById` returns `null`, never throws** — a reactive getter that throws stalls its shared-WS siblings (pairs with the per-subscription error isolation).
|
|
57
|
+
|
|
58
|
+
What they deliberately DON'T do is authorize: a guard runs before the executor, so gating stays on the DESCRIPTOR (`guards: [...]`) — an executor can't gate itself. Keep write descriptors guarded.
|
|
59
|
+
|
|
60
|
+
Scope note: this is the browser-safe, codegen-free core. Deriving the descriptor SCHEMAS from a table (to drop the hand-written `Schema.Struct`) is structurally a codegen concern — a table VALUE can't be imported into a browser-loaded descriptor (it drags the store into the bundle; `rowSchema` is server-only for exactly this reason) — so full schema-derivation + a `.crud()` boot audit for the scope/gating discipline are a separate, planned pass. See `plans/framework-a1-defineCrud.md`.
|
|
61
|
+
- **@voltro/runtime** — `ctx.store.links(junctionTable, anchor)` — a diff-based writer for a many-to-many JUNCTION table (A2). It reconciles the links from one anchor row against a target-id list by writing only the DIFFERENCE:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
await ctx.store.links('post_tags', { postId: post.id }).set(tagIds) // add missing, remove surplus
|
|
65
|
+
await ctx.store.links('post_tags', { postId: post.id }).add([tagId]) // idempotent
|
|
66
|
+
await ctx.store.links('post_tags', { postId: post.id }).remove([tagId])
|
|
67
|
+
await ctx.store.links('post_tags', { postId: post.id }).list() // current target ids
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Why it belongs in the framework rather than every app: a drop-all-then-reinsert `setLinks` loses data when two writers overlap and makes a reactive subscription on the junction churn every row (flicker) even when nothing changed. `links().set()` touches only the rows that actually differ — the added are inserted, the removed deleted, the unchanged left in place — so a reactive consumer sees a change only for what changed, and `set()` returns `{ added, removed }`. `add`/`remove` are likewise idempotent (they read first and act only on the genuine delta).
|
|
71
|
+
|
|
72
|
+
`anchor` names the source column and its id (`{ postId: 'p1' }`); the target column is the junction's OTHER `reference()` column, auto-detected. A junction with anything but exactly two reference columns is refused with a message naming what it found — use plain `insertMany`/`deleteMany` for a non-standard junction. The writes go through the normal stamped/tenant-scoped store path, so tenant and audit columns are filled as usual. Additive: a new `links` method on `FluentStore` + the `JunctionLinks` interface.
|
|
73
|
+
- **@voltro/client, @voltro/web** — `useSubscription(..., { initialSnapshot })` — the last mile of "SSR-correct first paint, then live" (A5). Pass the value an SSR loader already fetched with `ctx.query` (read it in the component with `useLoaderData()`) and the subscription shows it at the first paint with `loading: false` — it IS real server data — then swaps to the live stream the instant its first snapshot arrives:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
const seed = useLoaderData<Employee>()
|
|
77
|
+
const { data } = useSubscription('app', 'employees.me', {}, { initialSnapshot: seed })
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The SSR markup and the hydration render read the same loader value, so they match (no hydration flicker), and the app no longer hand-builds a seed store to bridge loader data into the first render. This is the difference from `fallback`, whose value never came from the server and so keeps `loading: true`; use exactly one of the two. Like `fallback`, `initialSnapshot` guarantees `data` is present, so the call gets the non-union result and needs no `loading` branch. Additive: a new `initialSnapshot` field on `SubscriptionOptions` + an overload; `@voltro/web` re-exports the client surface.
|
|
81
|
+
- **@voltro/cli** — `apis.<name>.authHeaders` in a web `app.config.ts` — a declarative per-reconnect auth-header resolver, so an authenticated split-origin web app no longer hand-mounts `VoltroRuntimeProvider` just to inject a rotating-token thunk (A4). The framework owns the client mount, the reconnect re-resolve, and the SSR-null case (the resolver runs browser-only — it never fires on the server):
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// app.config.ts
|
|
85
|
+
apis: {
|
|
86
|
+
api: {
|
|
87
|
+
package: '@app/api',
|
|
88
|
+
authHeaders: async () => ({ authorization: `Bearer ${await getToken()}` }),
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Because it's a FUNCTION, the codegen imports it from `app.config.ts` into the client bundle rather than serializing it — so a config that declares `authHeaders` must stay browser-safe (no `node:*` / server-only value imports; a pure env schema is fine, and tree-shakes out). It supersedes a static `headers` on the same api. The provider already resolved a `ResolvableHeaders` thunk fresh per connection generation; this just lets you declare it in config instead of hand-writing a `mount()` call.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## [0.11.2] — 2026-07-24
|
|
98
|
+
|
|
99
|
+
### Added
|
|
100
|
+
|
|
101
|
+
- **@voltro/i18n** — Two escapes for adopting typed messages (`createTypedMessages`, #16) app-wide (#19):
|
|
102
|
+
|
|
103
|
+
- **`t.dynamic(runtimeKey, values?)`** — a first-class escape for a genuinely runtime-computed key, on both `useT` and the `useTFn()` result. It takes a plain string with NO forced ICU args, so it doesn't fight the strict literal-key surface. Until now the natural escape — casting a computed key to the catalog key union — made things WORSE: that union spans placeholder-bearing keys, so the call then demanded a spurious 2nd ICU arg. `t.dynamic` is the documented, discoverable alternative. - **`LooseTFunction`** — the widened `(id: string, values?) => string` signature to type a `t` pass-through across a package boundary that can't import the app catalog, instead of falling back to `(...args: any[]) => string`. A strict `TypedTFunction` is deliberately NOT assignable to it (a narrowed key param can't satisfy a wider one — that would erase the checking); pass `t.dynamic` at the boundary, which IS a `LooseTFunction`.
|
|
104
|
+
|
|
105
|
+
Additive: `TypedTFunction<C>` gains a `.dynamic` member (the callable surface is unchanged, so `Parameters<TypedTFunction<C>>[0]` and existing typed call sites still resolve). `createTypedMessages` attaches `.dynamic` in place on the two translate functions — no new per-render closure, so a captured `t`'s identity stays stable.
|
|
106
|
+
- **@voltro/testing, @voltro/database** — `fixtureRow(table, overrides)` (`@voltro/testing`) completes a partial test row so it satisfies the 0.11.1 required-column insert validation — WITHOUT disabling the check. It fills every NOT-NULL, no-default, non-auto-stamped column the payload omits with a schema-typed placeholder (a `oneOf` column takes its first allowed value; a `unique` column gets a distinct value per call so two fixtures don't collide; `timestamp`/`date` get a fixed epoch), then merges your overrides on top (an explicit value always wins). It leaves out exactly what a caller may omit — nullable, defaulted, and framework auto-stamped columns (id / tenant / audit) — and refuses to guess a structured type (`json` / `bytes` / `vector` / `array` / `interval` / `raw`), throwing a message that names the column and says to pass it explicitly.
|
|
107
|
+
|
|
108
|
+
The motivating case: 0.11.1 made the in-memory/test store reject the same partial inserts real Postgres always would (correct — it surfaced a latent prod bug), which turned lean fixtures (`insert(users, { id })`, an omitted required FK) into `TableValidationFailed`. The wrong fix is a `validateInserts: false` knob — it re-hides that bug class, and a test store laxer than production is a fake testing itself. `fixtureRow` is the right one: it makes the fixture COMPLETE.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
await ctx.store.insert('journal_entries', fixtureRow(journalEntries, {
|
|
112
|
+
tenantId, amount: '100.00', // the columns THIS test cares about
|
|
113
|
+
})) // entryNumber, postedAt, … auto-filled + unique
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
It is a runtime filler for the loose `store.insert(name, row)` path (what fixtures use). For COMPILE-time payload typing, use `insertRow` / `upsertRow` from `@voltro/database`. The auto-stamped column set it skips is now exported as `AUTO_FILLED_COLUMNS` from `@voltro/database` — the same list `InferInsertRow` derives its optional columns from, single-sourced so the two can't drift.
|
|
117
|
+
|
|
118
|
+
### Changed
|
|
119
|
+
|
|
120
|
+
- **@voltro/cli** — `voltro build` now emits **directly-executable** boot bundles for BOTH app kinds: the web start bundle (`.framework/dist-web/startBundle/startEntry.js`) and the api serve bundle (`.framework/dist-api/serveBundle/serveEntry.js`) each carry a main-guard that boots the app when run as `node <entry>.js`, and stays inert when imported (the `voltro start` / `voltro serve` dev fast paths are unchanged). Production containers can now use `CMD ["node", "…/startEntry.js"]` (or `serveEntry.js`) instead of `pnpm voltro start` / `pnpm voltro serve` — no pnpm process, no `@voltro/cli` bin at runtime — which is what makes `voltro prune-runtime` safe to enable on both: with the self-contained bundle as the real entrypoint, the @vercel/nft trace roots there and legitimately drops `@voltro/cli` and the whole inlined framework tree (a static site's `node_modules` collapses to ~0; a memory api's 146 MB → 11 MB). `prune-runtime` now also roots the trace at the serve bundle. The serve entry chdir's to the app root BEFORE its app-module registry keys are computed from cwd, preserving relocation-safety. Existing `pnpm voltro start` / `pnpm voltro serve` entrypoints keep working. The standalone Dockerfiles gain a build-time boot smoke that fails the build unless the pruned tree reaches ready.
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
|
|
124
|
+
- **@voltro/i18n** — `createTypedMessages` (#16) no longer extracts phantom required vars from a nested plural/select message (#19). For `'{count, plural, one {# day} other {# days total duration}}'`, the type-level `ICUVars` parse was reading a branch's TEXT (`"# days total duration"`) as a bogus required arg name, so `useT('key', { count })` failed to typecheck even though it renders perfectly — and a real var nested inside a branch was dropped. `ICUArgName` now resolves to `never` for any candidate that isn't a valid ICU identifier (`^[A-Za-z0-9_]+$`), so branch text — which contains spaces / `#` / `—` — is never mistaken for a var. Only the top-level arg (`count`) is required, matching what the message actually needs.
|
|
125
|
+
|
|
126
|
+
Scope note: a REAL var nested inside a plural branch (`other {# — {discipline}}`) is still not collected, so it reads as not-required rather than wrongly-required — the safe direction. Apps that pluralise in JS over simple `{count}` messages (the Voltro idiom) were already fully typed and are unaffected.
|
|
127
|
+
- **@voltro/database, @voltro/runtime** — `InferInsertRow` (and thus `insertRow` / `upsertRow`, #15) no longer requires a non-nullable DB-generated (`generatedAs`) column (#20). A stored/virtual generated column declared without `.nullable()` and without a default was typed **required**, but MariaDB/Postgres REJECT an explicit value for a generated column — so the type forced the caller to pass a value the database refuses at runtime. `.generatedAs()` now marks the column optional-for-insert exactly like a `.default()` column (the DB supplies it), so it may be omitted; the whole payload guard on every real column stays intact.
|
|
128
|
+
|
|
129
|
+
Two runtime halves complete it, so the loose `store.insert(name, row)` path agrees: the required-column validation (`missingRequiredColumns`) skips generated columns — omitting one is correct, never a missing-column error — and the store write path now STRIPS any value a caller supplied for a generated column before the INSERT reaches the dialect (tracked on the schema registry as `generatedColumns`), so a value from an untyped insert can't blow up on MariaDB. A generated column is never caller-supplied; the framework and the DB own it end to end.
|
|
130
|
+
|
|
131
|
+
The `.generatedAs()` return type narrows from `this` to `ColumnBuilder<…, true>` (the HasDefault flag) — a purely more-permissive refinement: it only makes the column omittable, so no existing code stops compiling.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
42
135
|
## [0.11.1] — 2026-07-23
|
|
43
136
|
|
|
44
137
|
### Added
|
|
@@ -71,6 +164,8 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
71
164
|
- **@voltro/database, @voltro/runtime** — `store.insert` / `upsert` / `insertIgnore` now raise a clear, typed `TableValidationFailed` naming the column when the payload omits one that is NOT NULL, has no default, and isn't auto-stamped — instead of a raw dialect `SqlError: Failed to execute statement` (`Field '…' doesn't have a default value`) surfaced only on the INSERT path (so it lay dormant until the first row with no existing cache entry). An upsert / insertIgnore whose payload is missing one of its own `conflictColumns` is likewise named at the call (an absent conflict key can't match its target). The check runs AFTER stamping, so auto-id / tenant / audit columns never trip it, and skips nullable, defaulted, and id (`idScheme`) columns — exactly the ones a caller may legitimately omit.
|
|
72
165
|
|
|
73
166
|
Two pure helpers back it — `missingRequiredColumns(table, row)` and `missingConflictColumns(conflictColumns, row)` (exported from `@voltro/database`). This is the runtime half of the "handler data silently disagrees with the schema" class; a compile-time payload type needs the column DSL to track `hasDefault` at the type level, which is a separate change.
|
|
167
|
+
|
|
168
|
+
**Migration impact — behaviour-breaking for lenient test fixtures.** The in-memory/test store now rejects the same partial inserts a real Postgres always would, so it stops being laxer than production — which is the point (it surfaced at least one latent prod bug where a NOT-NULL `text().unique()` column was written without a value). But a fixture that inserted a partial row (`{ id }` parents, an omitted required FK) and passed against the old lenient memory store now throws `TableValidationFailed`. There is no code-level codemod — the fix is fixture DATA: fill the required columns. Use the new `fixtureRow(table, overrides)` helper in `@voltro/testing`, which fills every NOT-NULL-no-default column with a schema-typed placeholder and merges your overrides on top, so a fixture complies without disabling the check. There is deliberately no opt-out to turn the validation off: a test store that accepts rows production rejects is a fake testing itself.
|
|
74
169
|
- **@voltro/database, @voltro/plugin-ai-flows, @voltro/plugin-audit, @voltro/plugin-deactivation, @voltro/plugin-soft-delete** — Compile-time payload typing for writes (#15) — the type-level half that the runtime `TableValidationFailed` guard flagged as a separate change. `insertRow` / `upsertRow` take the TABLE OBJECT (not a string name), so the payload is checked against `InferInsertRow<T>`: every column is required EXCEPT nullable ones, columns with a default, and the framework-filled id/tenant/audit columns. A missing NOT-NULL-no-default column — the exact `lastRefreshedAt` / `teamId` omission from the report — is now a COMPILE error at the call, not a runtime SqlError only on the INSERT path; `upsertRow`'s `conflictColumns` are constrained to the table's own columns too.
|
|
75
170
|
|
|
76
171
|
import { insertRow } from '@voltro/database' await insertRow(ctx.store, roadmapEpicStats, { teamId, lastRefreshedAt: new Date() })
|
|
@@ -167,7 +167,7 @@ var r = class e {
|
|
|
167
167
|
enumValues: t
|
|
168
168
|
})
|
|
169
169
|
};
|
|
170
|
-
}, ie = () => i("boolean"), ae = () => i("timestamp"), oe = () => i("date"),
|
|
170
|
+
}, ie = () => i("boolean"), ae = () => i("timestamp"), oe = () => i("date"), l = () => i("json"), se = () => i("bytes"), ce = (e, t) => new r({
|
|
171
171
|
type: "reference",
|
|
172
172
|
nullable: !1,
|
|
173
173
|
unique: !1,
|
|
@@ -177,13 +177,13 @@ var r = class e {
|
|
|
177
177
|
refOnUpdate: t?.onUpdate ?? "noAction",
|
|
178
178
|
fkAutoIndex: t?.index ?? !0,
|
|
179
179
|
...t?.orphanPolicy ? { orphanPolicy: t.orphanPolicy } : {}
|
|
180
|
-
}),
|
|
180
|
+
}), le = () => new r({
|
|
181
181
|
type: "text",
|
|
182
182
|
nullable: !0,
|
|
183
183
|
unique: !1,
|
|
184
184
|
hasDefault: !1,
|
|
185
185
|
dropped: !0
|
|
186
|
-
}),
|
|
186
|
+
}), ue = (e, t) => {
|
|
187
187
|
if (!Number.isInteger(e) || e <= 0) throw Error(`vector(${String(e)}): dimension must be a positive integer (the embedding length, e.g. vector(1536)).`);
|
|
188
188
|
return new r({
|
|
189
189
|
type: "vector",
|
|
@@ -193,13 +193,13 @@ var r = class e {
|
|
|
193
193
|
vectorDim: e,
|
|
194
194
|
vectorPrecision: t?.precision ?? "float32"
|
|
195
195
|
});
|
|
196
|
-
},
|
|
196
|
+
}, de = (e) => new r({
|
|
197
197
|
type: "array",
|
|
198
198
|
nullable: !1,
|
|
199
199
|
unique: !1,
|
|
200
200
|
hasDefault: !1,
|
|
201
201
|
arrayElement: e.__definition().type
|
|
202
|
-
}),
|
|
202
|
+
}), fe = () => i("interval"), pe = (e, t = 4326) => new r({
|
|
203
203
|
type: "text",
|
|
204
204
|
nullable: !1,
|
|
205
205
|
unique: !1,
|
|
@@ -209,7 +209,7 @@ var r = class e {
|
|
|
209
209
|
geomKind: e,
|
|
210
210
|
srid: t
|
|
211
211
|
}
|
|
212
|
-
}),
|
|
212
|
+
}), me = (e, t = 4326) => new r({
|
|
213
213
|
type: "text",
|
|
214
214
|
nullable: !1,
|
|
215
215
|
unique: !1,
|
|
@@ -219,27 +219,27 @@ var r = class e {
|
|
|
219
219
|
geomKind: e,
|
|
220
220
|
srid: t
|
|
221
221
|
}
|
|
222
|
-
}),
|
|
222
|
+
}), he = (e) => new r({
|
|
223
223
|
type: "raw",
|
|
224
224
|
nullable: !1,
|
|
225
225
|
unique: !1,
|
|
226
226
|
hasDefault: !1,
|
|
227
227
|
rawDdl: e
|
|
228
|
-
}),
|
|
228
|
+
}), u = (e) => {
|
|
229
229
|
let t = {};
|
|
230
230
|
for (let [n, r] of Object.entries(e)) t[n] = r.__definition();
|
|
231
231
|
return t;
|
|
232
|
-
},
|
|
232
|
+
}, d = /* @__PURE__ */ new Map(), ge = (e, t) => {
|
|
233
233
|
if (t.from === t.to) throw Error(`declareEnumRename('${e}'): \`from\` and \`to\` are both '${t.from}' — a rename must change the value.`);
|
|
234
|
-
let n =
|
|
234
|
+
let n = d.get(e) ?? [];
|
|
235
235
|
for (let r of n) {
|
|
236
236
|
if (r.from === t.from && r.to === t.to) return;
|
|
237
237
|
if (r.from === t.from && r.to !== t.to) throw Error(`declareEnumRename('${e}'): value '${t.from}' is already declared as renamed to '${r.to}', cannot also rename it to '${t.to}'.`);
|
|
238
238
|
}
|
|
239
|
-
n.push(t),
|
|
240
|
-
},
|
|
241
|
-
|
|
242
|
-
},
|
|
239
|
+
n.push(t), d.set(e, n);
|
|
240
|
+
}, _e = (e) => d.get(e) ?? [], ve = () => d, f = () => {
|
|
241
|
+
d.clear();
|
|
242
|
+
}, ye = 63, p = 60, m = [], h = (e) => {
|
|
243
243
|
m.includes(e) || m.push(e);
|
|
244
244
|
}, be = () => {
|
|
245
245
|
let e = m.slice();
|
|
@@ -339,7 +339,7 @@ var r = class e {
|
|
|
339
339
|
case "custom": return n.generate(r);
|
|
340
340
|
}
|
|
341
341
|
}, F = (e) => ({
|
|
342
|
-
fields:
|
|
342
|
+
fields: u(e.fields),
|
|
343
343
|
...e.id === void 0 ? {} : { id: e.id },
|
|
344
344
|
...e.requires === void 0 ? {} : { requires: e.requires },
|
|
345
345
|
...e.indexes === void 0 ? {} : { indexes: e.indexes },
|
|
@@ -373,7 +373,16 @@ var r = class e {
|
|
|
373
373
|
case "l2": return `${n}_l2_ops`;
|
|
374
374
|
case "inner": return `${n}_ip_ops`;
|
|
375
375
|
}
|
|
376
|
-
},
|
|
376
|
+
}, ke = [
|
|
377
|
+
"id",
|
|
378
|
+
"tenantId",
|
|
379
|
+
"createdAt",
|
|
380
|
+
"updatedAt",
|
|
381
|
+
"createdBy",
|
|
382
|
+
"updatedBy",
|
|
383
|
+
"deletedAt",
|
|
384
|
+
"deletedBy"
|
|
385
|
+
], L = (e) => e.id ?? `<anonymous mixin: ${Object.keys(e.fields).join(", ")}>`, R = (e, t) => `${e}_${t.join("_")}_idx`, Ae = (e, t) => `${e}_${t.join("_")}_uq`, z = (e, t, n, r, i) => {
|
|
377
386
|
if (n !== "hnsw") return;
|
|
378
387
|
if (r.length !== 1) throw Error(`hnsw index '${t}' on table '${e}' must cover exactly one vector column — got ${r.length} fields. pgvector's HNSW access method indexes a single vector column.`);
|
|
379
388
|
let a = r[0];
|
|
@@ -381,7 +390,7 @@ var r = class e {
|
|
|
381
390
|
let o = i[a];
|
|
382
391
|
if (o === void 0) throw Error(`hnsw index '${t}' on table '${e}' references unknown column '${a}'.`);
|
|
383
392
|
if (o.type !== "vector") throw Error(`hnsw index '${t}' on table '${e}' requires a vector() column — column '${a}' is '${o.type}'. HNSW is the pgvector approximate-nearest-neighbour method and only applies to vectors.`);
|
|
384
|
-
},
|
|
393
|
+
}, je = (e, t) => {
|
|
385
394
|
let n;
|
|
386
395
|
for (let [r, i] of Object.entries(e)) i.type === "id" && i.idScheme === void 0 && (n === void 0 && (n = { ...e }), n[r] = {
|
|
387
396
|
...i,
|
|
@@ -510,7 +519,7 @@ var r = class e {
|
|
|
510
519
|
}),
|
|
511
520
|
unique: ((...t) => {
|
|
512
521
|
let n, r, i, a = Array.isArray(t[0]);
|
|
513
|
-
if (a ? (r = t[0], i = t[1], n =
|
|
522
|
+
if (a ? (r = t[0], i = t[1], n = Ae(e.tableName, r)) : (n = t[0], r = t[1], i = t[2]), r.length === 0) throw Error(`unique '${n}' on table '${e.tableName}' has no fields — a UNIQUE constraint must cover at least one column.`);
|
|
514
523
|
if (y(e.tableName, n, a, r), e.appliedUniques.some((e) => e.name === n)) throw Error(a ? `duplicate auto-named UNIQUE '${n}' on table '${e.tableName}' — you've called .unique(${JSON.stringify(r)}) twice. Pass an explicit name to the second one if both are intended (very rare).` : `duplicate UNIQUE '${n}' on table '${e.tableName}': each constraint name must be unique within a table.`);
|
|
515
524
|
return B({
|
|
516
525
|
tableName: e.tableName,
|
|
@@ -638,9 +647,9 @@ var r = class e {
|
|
|
638
647
|
validatePatchSchema: n.partial(t)
|
|
639
648
|
}))
|
|
640
649
|
};
|
|
641
|
-
},
|
|
650
|
+
}, Me = (e, t) => {
|
|
642
651
|
_(e);
|
|
643
|
-
let n =
|
|
652
|
+
let n = je(u(t), e);
|
|
644
653
|
for (let t of Object.keys(n)) v(e, t);
|
|
645
654
|
return B({
|
|
646
655
|
tableName: e,
|
|
@@ -652,7 +661,7 @@ var r = class e {
|
|
|
652
661
|
appliedFullText: [],
|
|
653
662
|
appliedChecks: []
|
|
654
663
|
});
|
|
655
|
-
},
|
|
664
|
+
}, Ne = (e) => e === "sqlite" || e === "turso", V = (e, t) => {
|
|
656
665
|
switch (t) {
|
|
657
666
|
case "mysql":
|
|
658
667
|
case "mariadb": return `\`${e}\``;
|
|
@@ -668,9 +677,9 @@ var r = class e {
|
|
|
668
677
|
return;
|
|
669
678
|
}
|
|
670
679
|
if (t !== e) throw Error(`duplicate table '${e.tableName}' registration: two different table descriptors claim the same name. Rename one or import the shared module that defines it.`);
|
|
671
|
-
},
|
|
680
|
+
}, K = (e) => {
|
|
672
681
|
W.get(e.tableName) === void 0 && W.set(e.tableName, e);
|
|
673
|
-
},
|
|
682
|
+
}, q = (e) => W.get(e), Pe = (e) => {
|
|
674
683
|
let t = W.get(e);
|
|
675
684
|
if (t === void 0) throw Error(`table '${e}' is not registered. Register it by wrapping it in \`databaseHandle({ ... })\` (the usual path) or call \`registerTable(t)\` explicitly before this runs. Note a \`queryFor(t)\` query carries its own source table, so eager-loads don't hit this — this error means a reconstructed descriptor or by-name tooling reached an unregistered table.`);
|
|
676
685
|
return t;
|
|
@@ -703,9 +712,9 @@ var r = class e {
|
|
|
703
712
|
op: "column",
|
|
704
713
|
column: e,
|
|
705
714
|
alias: t ?? e
|
|
706
|
-
}),
|
|
715
|
+
}), J = (e) => (...t) => {
|
|
707
716
|
if (t.length < 2) throw Error(`${e}: requires at least two queries`);
|
|
708
|
-
return
|
|
717
|
+
return Z({
|
|
709
718
|
table: t[0].descriptor.table,
|
|
710
719
|
predicate: void 0,
|
|
711
720
|
order: [],
|
|
@@ -717,55 +726,55 @@ var r = class e {
|
|
|
717
726
|
queries: t.map((e) => e.descriptor)
|
|
718
727
|
}
|
|
719
728
|
});
|
|
720
|
-
}, We =
|
|
729
|
+
}, We = J("union"), Ge = J("union-all"), Ke = J("intersect"), qe = J("except"), Y = (e) => ({ over: (t) => ({
|
|
721
730
|
...e,
|
|
722
731
|
window: t
|
|
723
|
-
}) }), Je = (e = "rowNumber") =>
|
|
732
|
+
}) }), Je = (e = "rowNumber") => Y({
|
|
724
733
|
op: "window-row-number",
|
|
725
734
|
alias: e
|
|
726
|
-
}), Ye = (e = "rank") =>
|
|
735
|
+
}), Ye = (e = "rank") => Y({
|
|
727
736
|
op: "window-rank",
|
|
728
737
|
alias: e
|
|
729
|
-
}), Xe = (e = "denseRank") =>
|
|
738
|
+
}), Xe = (e = "denseRank") => Y({
|
|
730
739
|
op: "window-dense-rank",
|
|
731
740
|
alias: e
|
|
732
|
-
}), Ze = (e, t = 1, n) =>
|
|
741
|
+
}), Ze = (e, t = 1, n) => Y({
|
|
733
742
|
op: "window-lag",
|
|
734
743
|
column: e,
|
|
735
744
|
alias: n ?? `lag_${e}`,
|
|
736
745
|
offset: t
|
|
737
|
-
}), Qe = (e, t = 1, n) =>
|
|
746
|
+
}), Qe = (e, t = 1, n) => Y({
|
|
738
747
|
op: "window-lead",
|
|
739
748
|
column: e,
|
|
740
749
|
alias: n ?? `lead_${e}`,
|
|
741
750
|
offset: t
|
|
742
|
-
}), $e = (e, t) =>
|
|
751
|
+
}), $e = (e, t) => Y({
|
|
743
752
|
op: "window-sum-over",
|
|
744
753
|
column: e,
|
|
745
754
|
alias: t ?? `sum_${e}_over`
|
|
746
|
-
}), et = (e, t) =>
|
|
755
|
+
}), et = (e, t) => Y({
|
|
747
756
|
op: "window-avg-over",
|
|
748
757
|
column: e,
|
|
749
758
|
alias: t ?? `avg_${e}_over`
|
|
750
|
-
}),
|
|
751
|
-
let r = n?.find((e) => e.name === t) ??
|
|
759
|
+
}), X = (e, t, n) => {
|
|
760
|
+
let r = n?.find((e) => e.name === t) ?? q(e)?.appliedFullText?.find((e) => e.name === t);
|
|
752
761
|
if (r === void 0) throw Error(`.matching('${t}', ...): no full-text index named '${t}' is declared on table '${e}'. Declare it with \`table(...).fullTextIndex('${t}', [columns])\` first.`);
|
|
753
762
|
return {
|
|
754
763
|
columns: r.columns,
|
|
755
764
|
...r.config === void 0 ? {} : { config: r.config },
|
|
756
765
|
...r.weights === void 0 ? {} : { weights: r.weights }
|
|
757
766
|
};
|
|
758
|
-
},
|
|
767
|
+
}, Z = (e) => ({
|
|
759
768
|
descriptor: e,
|
|
760
769
|
where(t) {
|
|
761
770
|
let n = e.predicate ? { and: [e.predicate, t] } : t;
|
|
762
|
-
return
|
|
771
|
+
return Z({
|
|
763
772
|
...e,
|
|
764
773
|
predicate: n
|
|
765
774
|
});
|
|
766
775
|
},
|
|
767
776
|
orderBy(t, n = "asc") {
|
|
768
|
-
return
|
|
777
|
+
return Z({
|
|
769
778
|
...e,
|
|
770
779
|
order: [...e.order, {
|
|
771
780
|
column: t,
|
|
@@ -774,37 +783,37 @@ var r = class e {
|
|
|
774
783
|
});
|
|
775
784
|
},
|
|
776
785
|
limit(t) {
|
|
777
|
-
return
|
|
786
|
+
return Z({
|
|
778
787
|
...e,
|
|
779
788
|
take: t
|
|
780
789
|
});
|
|
781
790
|
},
|
|
782
791
|
offset(t) {
|
|
783
|
-
return
|
|
792
|
+
return Z({
|
|
784
793
|
...e,
|
|
785
794
|
skip: t
|
|
786
795
|
});
|
|
787
796
|
},
|
|
788
797
|
select(...t) {
|
|
789
|
-
return
|
|
798
|
+
return Z({
|
|
790
799
|
...e,
|
|
791
800
|
projection: t
|
|
792
801
|
});
|
|
793
802
|
},
|
|
794
803
|
using(t) {
|
|
795
|
-
return
|
|
804
|
+
return Z({
|
|
796
805
|
...e,
|
|
797
806
|
usingIndex: t
|
|
798
807
|
});
|
|
799
808
|
},
|
|
800
809
|
withDeleted() {
|
|
801
|
-
return
|
|
810
|
+
return Z({
|
|
802
811
|
...e,
|
|
803
812
|
includeDeleted: !0
|
|
804
813
|
});
|
|
805
814
|
},
|
|
806
815
|
unscoped() {
|
|
807
|
-
return
|
|
816
|
+
return Z({
|
|
808
817
|
...e,
|
|
809
818
|
crossTenant: !0
|
|
810
819
|
});
|
|
@@ -814,13 +823,13 @@ var r = class e {
|
|
|
814
823
|
...e.eager ?? {},
|
|
815
824
|
...t
|
|
816
825
|
};
|
|
817
|
-
return
|
|
826
|
+
return Z({
|
|
818
827
|
...e,
|
|
819
828
|
eager: n
|
|
820
829
|
});
|
|
821
830
|
},
|
|
822
831
|
count() {
|
|
823
|
-
return
|
|
832
|
+
return Z({
|
|
824
833
|
...e,
|
|
825
834
|
projection: void 0,
|
|
826
835
|
aggregations: [{
|
|
@@ -834,15 +843,15 @@ var r = class e {
|
|
|
834
843
|
...t,
|
|
835
844
|
alias: e
|
|
836
845
|
}));
|
|
837
|
-
return
|
|
846
|
+
return Z({
|
|
838
847
|
...e,
|
|
839
848
|
projection: void 0,
|
|
840
849
|
aggregations: n
|
|
841
850
|
});
|
|
842
851
|
},
|
|
843
852
|
matching(t, n) {
|
|
844
|
-
let r =
|
|
845
|
-
return
|
|
853
|
+
let r = X(e.table, t, e.fullTextIndexes);
|
|
854
|
+
return Z({
|
|
846
855
|
...e,
|
|
847
856
|
fullTextSearch: {
|
|
848
857
|
indexName: t,
|
|
@@ -856,7 +865,7 @@ var r = class e {
|
|
|
856
865
|
rankBy(t) {
|
|
857
866
|
let n = e.fullTextSearch;
|
|
858
867
|
if (n === void 0) throw Error(".rankBy() requires a preceding .matching(...) clause — there is no full-text search to rank on this query.");
|
|
859
|
-
return
|
|
868
|
+
return Z({
|
|
860
869
|
...e,
|
|
861
870
|
fullTextSearch: {
|
|
862
871
|
...n,
|
|
@@ -875,7 +884,7 @@ var r = class e {
|
|
|
875
884
|
distance: r?.distance ?? "cosine",
|
|
876
885
|
...e.annClause?.ef === void 0 ? {} : { ef: e.annClause.ef }
|
|
877
886
|
};
|
|
878
|
-
return
|
|
887
|
+
return Z({
|
|
879
888
|
...e,
|
|
880
889
|
annClause: i
|
|
881
890
|
});
|
|
@@ -887,7 +896,7 @@ var r = class e {
|
|
|
887
896
|
distance: "cosine",
|
|
888
897
|
...e.annClause?.ef === void 0 ? {} : { ef: e.annClause.ef }
|
|
889
898
|
};
|
|
890
|
-
return
|
|
899
|
+
return Z({
|
|
891
900
|
...e,
|
|
892
901
|
annClause: r,
|
|
893
902
|
take: n
|
|
@@ -900,7 +909,7 @@ var r = class e {
|
|
|
900
909
|
column: "",
|
|
901
910
|
distance: "cosine"
|
|
902
911
|
};
|
|
903
|
-
return
|
|
912
|
+
return Z({
|
|
904
913
|
...e,
|
|
905
914
|
annClause: {
|
|
906
915
|
...n,
|
|
@@ -909,22 +918,22 @@ var r = class e {
|
|
|
909
918
|
});
|
|
910
919
|
},
|
|
911
920
|
use(t) {
|
|
912
|
-
return
|
|
921
|
+
return Z({ ...t(e) });
|
|
913
922
|
},
|
|
914
923
|
distinct() {
|
|
915
|
-
return
|
|
924
|
+
return Z({
|
|
916
925
|
...e,
|
|
917
926
|
distinct: !0
|
|
918
927
|
});
|
|
919
928
|
},
|
|
920
929
|
distinctOn(t) {
|
|
921
|
-
return
|
|
930
|
+
return Z({
|
|
922
931
|
...e,
|
|
923
932
|
distinctOn: [...t]
|
|
924
933
|
});
|
|
925
934
|
},
|
|
926
935
|
as(t) {
|
|
927
|
-
return
|
|
936
|
+
return Z({
|
|
928
937
|
...e,
|
|
929
938
|
alias: t
|
|
930
939
|
});
|
|
@@ -936,7 +945,7 @@ var r = class e {
|
|
|
936
945
|
alias: n,
|
|
937
946
|
on: r
|
|
938
947
|
}];
|
|
939
|
-
return
|
|
948
|
+
return Z({
|
|
940
949
|
...e,
|
|
941
950
|
joins: a
|
|
942
951
|
});
|
|
@@ -948,7 +957,7 @@ var r = class e {
|
|
|
948
957
|
alias: n,
|
|
949
958
|
on: r
|
|
950
959
|
}];
|
|
951
|
-
return
|
|
960
|
+
return Z({
|
|
952
961
|
...e,
|
|
953
962
|
joins: a
|
|
954
963
|
});
|
|
@@ -958,7 +967,7 @@ var r = class e {
|
|
|
958
967
|
outputKey: e,
|
|
959
968
|
source: t
|
|
960
969
|
}));
|
|
961
|
-
return
|
|
970
|
+
return Z({
|
|
962
971
|
...e,
|
|
963
972
|
projection: void 0,
|
|
964
973
|
joinedProjection: n
|
|
@@ -967,7 +976,7 @@ var r = class e {
|
|
|
967
976
|
withCte(t, n) {
|
|
968
977
|
let r = e.ctes ?? [];
|
|
969
978
|
if (r.some((e) => e.name === t)) throw Error(`Query.withCte: duplicate CTE name '${t}'`);
|
|
970
|
-
return
|
|
979
|
+
return Z({
|
|
971
980
|
...e,
|
|
972
981
|
ctes: [...r, {
|
|
973
982
|
name: t,
|
|
@@ -978,7 +987,7 @@ var r = class e {
|
|
|
978
987
|
recursiveCte(t, n) {
|
|
979
988
|
let r = e.ctes ?? [];
|
|
980
989
|
if (r.some((e) => e.name === t)) throw Error(`Query.recursiveCte: duplicate CTE name '${t}'`);
|
|
981
|
-
return
|
|
990
|
+
return Z({
|
|
982
991
|
...e,
|
|
983
992
|
ctes: [...r, {
|
|
984
993
|
name: t,
|
|
@@ -989,20 +998,20 @@ var r = class e {
|
|
|
989
998
|
},
|
|
990
999
|
groupBy(t) {
|
|
991
1000
|
let n = e.groupBy ? [...e.groupBy, ...t] : [...t];
|
|
992
|
-
return
|
|
1001
|
+
return Z({
|
|
993
1002
|
...e,
|
|
994
1003
|
groupBy: n
|
|
995
1004
|
});
|
|
996
1005
|
},
|
|
997
1006
|
having(t) {
|
|
998
1007
|
let n = e.having ? { and: [e.having, t] } : t;
|
|
999
|
-
return
|
|
1008
|
+
return Z({
|
|
1000
1009
|
...e,
|
|
1001
1010
|
having: n
|
|
1002
1011
|
});
|
|
1003
1012
|
},
|
|
1004
1013
|
exists() {
|
|
1005
|
-
return
|
|
1014
|
+
return Z({
|
|
1006
1015
|
...e,
|
|
1007
1016
|
projection: void 0,
|
|
1008
1017
|
aggregations: [{
|
|
@@ -1013,8 +1022,8 @@ var r = class e {
|
|
|
1013
1022
|
});
|
|
1014
1023
|
}
|
|
1015
1024
|
});
|
|
1016
|
-
function
|
|
1017
|
-
return
|
|
1025
|
+
function Q(e) {
|
|
1026
|
+
return Z({
|
|
1018
1027
|
table: e.tableName,
|
|
1019
1028
|
predicate: void 0,
|
|
1020
1029
|
order: [],
|
|
@@ -1025,7 +1034,7 @@ function Z(e) {
|
|
|
1025
1034
|
sourceTable: e
|
|
1026
1035
|
});
|
|
1027
1036
|
}
|
|
1028
|
-
var tt = (e, t, n) =>
|
|
1037
|
+
var tt = (e, t, n) => $(e, "id", t, n), $ = (e, t, n, r, i = "asc") => {
|
|
1029
1038
|
let a = [...e.order.filter((e) => e.column !== t), {
|
|
1030
1039
|
column: t,
|
|
1031
1040
|
direction: i
|
|
@@ -1042,11 +1051,11 @@ var tt = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
|
|
|
1042
1051
|
};
|
|
1043
1052
|
}, nt = (e) => {
|
|
1044
1053
|
let t = {};
|
|
1045
|
-
for (let [n, r] of Object.entries(e)) G(r), t[n] =
|
|
1054
|
+
for (let [n, r] of Object.entries(e)) G(r), t[n] = Q(r);
|
|
1046
1055
|
return t;
|
|
1047
1056
|
}, rt = (e) => e.isView === !0, it = (e, t, n) => {
|
|
1048
1057
|
if (_(e), n.trim().length === 0) throw Error(`view('${e}', …): the SELECT body is empty — a view must define a query.`);
|
|
1049
|
-
let r =
|
|
1058
|
+
let r = u(t);
|
|
1050
1059
|
for (let t of Object.keys(r)) v(e, t);
|
|
1051
1060
|
return {
|
|
1052
1061
|
tableName: e,
|
|
@@ -1054,7 +1063,7 @@ var tt = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
|
|
|
1054
1063
|
isView: !0,
|
|
1055
1064
|
viewSelect: n
|
|
1056
1065
|
};
|
|
1057
|
-
},
|
|
1066
|
+
}, at = (e) => Q({
|
|
1058
1067
|
...e,
|
|
1059
1068
|
isReactive: !1,
|
|
1060
1069
|
appliedMixins: [],
|
|
@@ -1062,7 +1071,7 @@ var tt = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
|
|
|
1062
1071
|
appliedUniques: [],
|
|
1063
1072
|
appliedFullText: [],
|
|
1064
1073
|
appliedChecks: []
|
|
1065
|
-
}),
|
|
1074
|
+
}), ot = (e, t, n = null) => {
|
|
1066
1075
|
let r = n === null ? V(e.tableName, t) : `${V(n, t)}.${V(e.tableName, t)}`, i = e.viewSelect.trim();
|
|
1067
1076
|
switch (t) {
|
|
1068
1077
|
case "postgres":
|
|
@@ -1072,16 +1081,16 @@ var tt = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
|
|
|
1072
1081
|
case "sqlite":
|
|
1073
1082
|
case "turso": return `DROP VIEW IF EXISTS ${r};\nCREATE VIEW ${r} AS ${i};`;
|
|
1074
1083
|
}
|
|
1075
|
-
},
|
|
1084
|
+
}, st = /^[A-Za-z_][A-Za-z0-9_]*$/, ct = (e) => "$" + e.map((e) => typeof e == "number" ? `[${e}]` : st.test(e) ? `.${e}` : `."${e.replace(/"/g, "\\\"")}"`).join(""), lt = (e) => "{" + e.map((e) => {
|
|
1076
1085
|
let t = String(e);
|
|
1077
1086
|
return /^[A-Za-z0-9_]+$/.test(t) ? t : `"${t.replace(/(["\\])/g, "\\$1")}"`;
|
|
1078
|
-
}).join(",") + "}",
|
|
1087
|
+
}).join(",") + "}", ut = (e, t, n, r = {}) => {
|
|
1079
1088
|
let i = V(e, n), a = r.numeric ?? !1;
|
|
1080
1089
|
if (n === "postgres") {
|
|
1081
|
-
let e = `(${i} #>> '${
|
|
1090
|
+
let e = `(${i} #>> '${lt(t)}'::text[])`;
|
|
1082
1091
|
return a ? `${e}::numeric` : e;
|
|
1083
1092
|
}
|
|
1084
|
-
let o =
|
|
1093
|
+
let o = ct(t);
|
|
1085
1094
|
switch (n) {
|
|
1086
1095
|
case "mysql":
|
|
1087
1096
|
case "mariadb": {
|
|
@@ -1098,10 +1107,10 @@ var tt = (e, t, n) => Q(e, "id", t, n), Q = (e, t, n, r, i = "asc") => {
|
|
|
1098
1107
|
return a ? `CAST(${e} AS REAL)` : e;
|
|
1099
1108
|
}
|
|
1100
1109
|
}
|
|
1101
|
-
},
|
|
1110
|
+
}, dt = (e) => {
|
|
1102
1111
|
if (!e.id || e.id.length === 0) throw Error("migration: `id` is required + must be non-empty");
|
|
1103
1112
|
if (!e.up || !e.down) throw Error(`migration ${e.id}: both \`up\` and \`down\` must be functions`);
|
|
1104
1113
|
return e;
|
|
1105
|
-
},
|
|
1114
|
+
}, ft = /^\d{8}_\d{6}_[a-z0-9_]+\.ts$/, pt = (e) => typeof e == "object" && !!e && "id" in e && "up" in e && "down" in e && typeof e.id == "string" && typeof e.up == "function" && typeof e.down == "function";
|
|
1106
1115
|
//#endregion
|
|
1107
|
-
export {
|
|
1116
|
+
export { p as $, We as A, ae as At, ke as B, tt as C, l as Ct, Je as D, ee as Dt, X as E, he as Et, q as F, F as G, Me as H, G as I, Te as J, I as K, Pe as L, Fe as M, Ie as N, ze as O, ce as Ot, K as P, ye as Q, Ne as R, $ as S, fe as St, Ye as T, te as Tt, Oe as U, De as V, Ee as W, M as X, P as Y, A as Z, Ke as _, le as _t, rt as a, ve as at, He as b, a as bt, ot as c, _e as ct, Ue as d, ne as dt, be as et, Le as f, ie as ft, qe as g, c as gt, Xe as h, re as ht, ut as i, b as it, Ge as j, ue as jt, $e as k, o as kt, Be as l, r as lt, nt as m, oe as mt, pt as n, y as nt, at as o, f as ot, Re as p, se as pt, N as q, dt as r, _ as rt, it as s, ge as st, ft as t, v as tt, et as u, de as ut, Ze as v, pe as vt, Q as w, u as wt, Ve as x, s as xt, Qe as y, me as yt, V as z };
|