@voltro/database 0.2.2 → 0.3.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/CHANGELOG.md +20 -0
- package/dist/{fileBased-cc9IzIjU.js → fileBased-CRXgPJkv.js} +24 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +1 -1
- package/dist/sql.d.ts +45 -0
- package/dist/sql.js +9 -7
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,26 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.3.0] — 2026-07-18
|
|
43
|
+
|
|
44
|
+
### ⚠ BREAKING
|
|
45
|
+
|
|
46
|
+
- **@voltro/cli** — `voltro build` precompiles the whole serve path (framework + effect + `@voltro` inlined, app modules as lazy chunks) into a single **serve bundle**, and `voltro serve` boots from it in-process — cutting `serve: ready` from ~1000 ms to ~180 ms (5–6×; the win is larger on a cold scale-to-zero container). The app's declared SQL driver is inlined (only the native leaf like `pg` stays external, resolved at runtime so it survives the deploy relocation). **Production now serves ONLY from the bundle and NEVER transpiles on demand:** the bundle build externalises unresolvable optional peers (e.g. `@react-email/render` behind `@voltro/plugin-mail`) so it always builds; a bundle-build failure is **fatal** (`voltro build` exits non-zero); and an unbuilt production `voltro serve` fails loud instead of falling back to tsx. The build toolchain (`tsx`, `esbuild`, `vite`, `@vitejs/plugin-react`, `@tailwindcss/vite` + their native tree: rolldown/lightningcss/postcss/jiti) moves to **`optionalDependencies`** of `@voltro/cli`, so `pnpm --prod --no-optional deploy` yields a serve image with none of it — a prod API image's `node_modules` drops ~305 MB → ~131 MB, structurally, with no fragile prune list. `voltro dev` and a non-production local `voltro serve` are unchanged (still tsx). **Migration:** in production (`NODE_ENV=production`) run `voltro build` before `voltro serve`. The generated Dockerfiles already do; a custom Dockerfile / start script adds a `voltro build .` step before `voltro serve .` (`voltro update` prints this — see the 0.3.0 codemod note).
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **@voltro/protocol, @voltro/runtime, @voltro/plugin-rbac** — Declarative authorization `guards:` on `defineMutation` / `defineQuery` / `defineAction`. The framework enforces the declared scope(s) in the dispatch spine BEFORE the executor (for a mutation, before the transaction opens), fails with a typed `ScopeError`, and auto-merges `ScopeError` into the wire error union so the client decodes the denial typed. Guards are browser-safe DATA (scope strings + a pure `resource: (input) => id` extractor). Checks run against the caller's EFFECTIVE scope set — raw subject scopes ∪ `@voltro/plugin-rbac` role-derived scopes — via a new canonical effective-scope seam in `@voltro/protocol` (`effectiveScopes` / `setEffectiveScopes` / `checkGuards`), which rbac now publishes to (so a role-granted scope satisfies a `guards:` entry and the in-handler `permission()` identically). Adds `ctx.access` (`has` / `hasAny` / `require` / `scopes`) — the cast-free typed authorization slice on every handler context. Enforcement is single-sourced in the shared serve pipeline, so `voltro dev` and `voltro serve` can't drift.
|
|
51
|
+
- **@voltro/protocol, @voltro/client** — Nested / path-targeted auto-optimistic. A mutation `target` can now patch a nested array INSIDE a query's value — a JSON array column (`snapshot.projects`) or a computed/shaped result — at item granularity, via `path` (dot-path to the array), `by` (item key, default `id`), and `match` (a pure predicate that scopes the patch to the entries whose current value satisfies it, preventing a patch bleeding across sibling subscriptions that share a source table). Previously auto-optimistic only patched the flat top-level row array keyed by `id`; nested values needed a hand-written `.withOptimistic` reducer. `path`/`by`/`match` are browser-safe descriptor data (a dot-path string + pure predicate), same discipline as `identify`/`shape`. A path insert is applied even on a computed entry (it targets a known document, not a blind top-level add).
|
|
52
|
+
- **@voltro/runtime** — `ctx.store.applyDefined(input, keys)` (and a standalone `applyDefined` export from `@voltro/runtime`) — builds a partial-update patch keeping only the listed keys whose value the caller actually provided (`!== undefined`; a defined falsy value like `0`/`''`/`false` is kept). Collapses the per-field `if (input.x !== undefined) patch.x = input.x` idiom every partial-update mutation hand-writes.
|
|
53
|
+
- **@voltro/database** — `.uniqueActive([cols], opts?)` on the table builder — a portable partial-UNIQUE constraint that holds only among the rows matching a predicate (default `"deletedAt" IS NULL`, pairing with `.softDelete()`). Emits `CREATE UNIQUE INDEX … WHERE` on postgres / sqlite / mssql, so a soft-deleted row leaves the active set and a NEW row with the same key inserts cleanly — no hand-written `generatedAs("CASE WHEN …")` column and no resurrection footgun. On mysql / mariadb (no partial-index support) it FAILS LOUDLY at migrate time rather than silently emitting a full unique index that would forbid re-creating a soft-deleted key — the generated-STORED-column lowering for those dialects is a follow-up. Kept out of the declarative index snapshot (the incremental planner is predicate-blind and would misclassify a unique+partial index as a full constraint), so the fresh-schema DDL path is its sole emitter and there is no re-diff churn. Live-verified against postgres.
|
|
54
|
+
- **@voltro/cli** — `voltro update` upgrades an app to the latest framework: it bumps every `@voltro/*` dependency, installs with the detected package manager, and runs the codemods shipped with the target version. Codemods are authored with `defineCodemod` + an import-scoped ts-morph helper toolkit (`renameImport`, `renameModuleSpecifier`, `renameJsxProp`, `renameObjectKey`, `add`/`removeImport`, structural `changeCallArgs`/`wrapCall`, `annotate`) and run against the app source; a `manual` kind surfaces written steps for changes that can't be automated. Breaking public-API changes now ship a codemod (or an explicit `codemod: none`), enforced by the changelog gate. Framework-owned `_voltro_*` table changes continue to ride the declarative differ on `voltro db apply` / `voltro dev` boot — `update` does not touch the database.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- **@voltro/database** — The core-table registry (`registerCoreTables` / `requireActors` / `requireTenants`) now stores its state on a process-global `Symbol.for` singleton, the same mechanism the main table registry already uses — instead of module-local `let` bindings. Module-local state splits when the `@voltro/database` module is duplicated in a process (e.g. resolved through both the `.` and `./sql` entry points, or a bundled framework copy alongside an externally-resolved one): one instance's `registerCoreTables` becomes invisible to the instance that reads it, surfacing as a spurious `core 'actors' table not registered` at store construction. Pinning it to `globalThis` makes every copy share one store, matching the table registry's already-global behaviour.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
42
62
|
## [0.2.2] — 2026-07-17
|
|
43
63
|
|
|
44
64
|
### Added
|
|
@@ -530,6 +530,30 @@ var r = class e {
|
|
|
530
530
|
...e.validatePatchSchema === void 0 ? {} : { validatePatchSchema: e.validatePatchSchema }
|
|
531
531
|
});
|
|
532
532
|
}),
|
|
533
|
+
uniqueActive: ((...t) => {
|
|
534
|
+
let n, r, i, a = Array.isArray(t[0]);
|
|
535
|
+
if (a ? (r = t[0], i = t[1], n = z(e.tableName, r)) : (n = t[0], r = t[1], i = t[2]), r.length === 0) throw Error(`uniqueActive '${n}' on table '${e.tableName}' has no fields — a partial UNIQUE must cover at least one column.`);
|
|
536
|
+
if (y(e.tableName, n, a, r), e.appliedIndexes.some((e) => e.name === n)) throw Error(`duplicate index '${n}' on table '${e.tableName}': each index name must be unique within a table (uniqueActive shares the index namespace).`);
|
|
537
|
+
let o = i?.where ?? "\"deletedAt\" IS NULL";
|
|
538
|
+
return V({
|
|
539
|
+
tableName: e.tableName,
|
|
540
|
+
fields: e.fields,
|
|
541
|
+
isReactive: e.isReactive,
|
|
542
|
+
appliedMixins: e.appliedMixins,
|
|
543
|
+
appliedIndexes: [...e.appliedIndexes, {
|
|
544
|
+
name: n,
|
|
545
|
+
fields: [...r],
|
|
546
|
+
where: o,
|
|
547
|
+
unique: !0
|
|
548
|
+
}],
|
|
549
|
+
appliedUniques: e.appliedUniques,
|
|
550
|
+
appliedFullText: e.appliedFullText,
|
|
551
|
+
appliedChecks: e.appliedChecks,
|
|
552
|
+
appliedPrimaryKey: e.appliedPrimaryKey,
|
|
553
|
+
...e.insertSchema === void 0 ? {} : { insertSchema: e.insertSchema },
|
|
554
|
+
...e.validatePatchSchema === void 0 ? {} : { validatePatchSchema: e.validatePatchSchema }
|
|
555
|
+
});
|
|
556
|
+
}),
|
|
533
557
|
check: ((t, n) => {
|
|
534
558
|
if (n.trim().length === 0) throw Error(`CHECK '${t}' on table '${e.tableName}' has an empty expression.`);
|
|
535
559
|
if (y(e.tableName, t, !1, []), e.appliedChecks.some((e) => e.name === t)) throw Error(`duplicate CHECK '${t}' on table '${e.tableName}': each constraint name must be unique within a table.`);
|
package/dist/index.d.ts
CHANGED
|
@@ -4532,6 +4532,37 @@ export declare interface Table<Name extends string, Fields extends Record<string
|
|
|
4532
4532
|
dedup?: TableUnique['dedup'];
|
|
4533
4533
|
}): Table<Name, Fields, Reactive, IxNames>;
|
|
4534
4534
|
};
|
|
4535
|
+
/**
|
|
4536
|
+
* Declare a **partial UNIQUE** constraint that holds only among the rows
|
|
4537
|
+
* matching a predicate — the portable answer to "unique among the rows that
|
|
4538
|
+
* aren't soft-deleted". Emits a `CREATE UNIQUE INDEX … WHERE` on postgres /
|
|
4539
|
+
* sqlite / mssql.
|
|
4540
|
+
*
|
|
4541
|
+
* ```ts
|
|
4542
|
+
* table('project_roadmaps', { id, projectId, year, deletedAt, ... })
|
|
4543
|
+
* .softDelete()
|
|
4544
|
+
* .uniqueActive(['projectId', 'year']) // one ACTIVE roadmap per (project, year)
|
|
4545
|
+
* ```
|
|
4546
|
+
*
|
|
4547
|
+
* By default the predicate is `"deletedAt" IS NULL` (pairs with
|
|
4548
|
+
* `.softDelete()`); override with `{ where }` for a custom active-set
|
|
4549
|
+
* (`"status" = 'open'`). A soft-deleted row leaves the active set, so a NEW
|
|
4550
|
+
* row with the same key is allowed — no hand-written
|
|
4551
|
+
* `generatedAs("CASE WHEN …")` column, no resurrection footgun.
|
|
4552
|
+
*
|
|
4553
|
+
* **mysql / mariadb:** these engines have no partial-index support, so
|
|
4554
|
+
* `.uniqueActive()` FAILS LOUDLY at migrate time (a full unique index would
|
|
4555
|
+
* silently forbid re-creating a soft-deleted row). Use a generated STORED
|
|
4556
|
+
* column + `.unique()` there until the framework lowers it for you.
|
|
4557
|
+
*/
|
|
4558
|
+
uniqueActive: {
|
|
4559
|
+
<const F extends readonly [keyof Fields & string, ...Array<keyof Fields & string>]>(fields: F, options?: {
|
|
4560
|
+
where?: string;
|
|
4561
|
+
}): Table<Name, Fields, Reactive, IxNames>;
|
|
4562
|
+
<const IxName extends string, const F extends readonly [keyof Fields & string, ...Array<keyof Fields & string>]>(name: IxName, fields: F, options?: {
|
|
4563
|
+
where?: string;
|
|
4564
|
+
}): Table<Name, Fields, Reactive, IxNames | IxName>;
|
|
4565
|
+
};
|
|
4535
4566
|
/**
|
|
4536
4567
|
* Declare a named table-level `CHECK` constraint — a DB-ENFORCED
|
|
4537
4568
|
* invariant that holds regardless of which client writes the row
|
|
@@ -4700,6 +4731,20 @@ export declare interface TableIndex {
|
|
|
4700
4731
|
* efConstruction, opclass}`). Ignored for kinds that don't read it.
|
|
4701
4732
|
*/
|
|
4702
4733
|
readonly kindOptions?: IndexKindOptions;
|
|
4734
|
+
/**
|
|
4735
|
+
* UNIQUE partial index — set ONLY by `.uniqueActive(...)`. Emits
|
|
4736
|
+
* `CREATE UNIQUE INDEX … WHERE <where>` on postgres / sqlite / mssql, so
|
|
4737
|
+
* uniqueness holds only among rows matching `where` (e.g. the non-soft-
|
|
4738
|
+
* deleted rows). Distinct from `.index(..., { where })` (non-unique) and
|
|
4739
|
+
* from `.unique(...)` (a full constraint with no predicate). Because a
|
|
4740
|
+
* unique+partial index has no `information_schema` constraint form and the
|
|
4741
|
+
* declarative planner is predicate-blind, these are emitted by the
|
|
4742
|
+
* fresh-schema DDL path (like every partial predicate) and kept OUT of the
|
|
4743
|
+
* declarative index snapshot. mysql/mariadb have no partial-index support —
|
|
4744
|
+
* `.uniqueActive()` fails loudly there (use a generated column) rather than
|
|
4745
|
+
* silently dropping the predicate into a full unique index.
|
|
4746
|
+
*/
|
|
4747
|
+
readonly unique?: boolean;
|
|
4703
4748
|
}
|
|
4704
4749
|
|
|
4705
4750
|
export declare interface TableLike {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as e, A as t, B as n, C as r, Ct as i, D as a, Dt as o, E as s, Et as c, F as l, G as u, H as d, I as f, J as p, K as m, L as h, M as g, N as _, O as ee, Ot as v, P as y, Q as b, R as x, S as te, St as S, T as C, Tt as ne, U as re, V as ie, W as ae, X as oe, Y as se, Z as ce, _ as le, _t as ue, a as de, at as fe, b as pe, bt as me, c as he, ct as ge, d as _e, dt as ve, et as ye, f as be, ft as xe, g as Se, gt as Ce, h as we, ht as Te, i as w, it as Ee, j as De, k as Oe, kt as ke, l as Ae, lt as je, m as Me, mt as Ne, n as Pe, nt as Fe, o as Ie, ot as Le, p as Re, pt as ze, q as Be, r as Ve, rt as He, s as Ue, st as We, t as Ge, tt as Ke, u as qe, ut as Je, v as Ye, vt as T, w as Xe, wt as Ze, x as Qe, xt as E, y as $e, yt as D, z as et } from "./fileBased-
|
|
1
|
+
import { $ as e, A as t, B as n, C as r, Ct as i, D as a, Dt as o, E as s, Et as c, F as l, G as u, H as d, I as f, J as p, K as m, L as h, M as g, N as _, O as ee, Ot as v, P as y, Q as b, R as x, S as te, St as S, T as C, Tt as ne, U as re, V as ie, W as ae, X as oe, Y as se, Z as ce, _ as le, _t as ue, a as de, at as fe, b as pe, bt as me, c as he, ct as ge, d as _e, dt as ve, et as ye, f as be, ft as xe, g as Se, gt as Ce, h as we, ht as Te, i as w, it as Ee, j as De, k as Oe, kt as ke, l as Ae, lt as je, m as Me, mt as Ne, n as Pe, nt as Fe, o as Ie, ot as Le, p as Re, pt as ze, q as Be, r as Ve, rt as He, s as Ue, st as We, t as Ge, tt as Ke, u as qe, ut as Je, v as Ye, vt as T, w as Xe, wt as Ze, x as Qe, xt as E, y as $e, yt as D, z as et } from "./fileBased-CRXgPJkv.js";
|
|
2
2
|
import { Chunk as tt, Data as nt, Effect as rt, Option as O, Stream as it } from "effect";
|
|
3
3
|
import { ansi as k } from "@voltro/logger";
|
|
4
4
|
//#region src/arrayCodec.ts
|
package/dist/sql.d.ts
CHANGED
|
@@ -1754,6 +1754,37 @@ declare interface Table<Name extends string, Fields extends Record<string, Colum
|
|
|
1754
1754
|
dedup?: TableUnique['dedup'];
|
|
1755
1755
|
}): Table<Name, Fields, Reactive, IxNames>;
|
|
1756
1756
|
};
|
|
1757
|
+
/**
|
|
1758
|
+
* Declare a **partial UNIQUE** constraint that holds only among the rows
|
|
1759
|
+
* matching a predicate — the portable answer to "unique among the rows that
|
|
1760
|
+
* aren't soft-deleted". Emits a `CREATE UNIQUE INDEX … WHERE` on postgres /
|
|
1761
|
+
* sqlite / mssql.
|
|
1762
|
+
*
|
|
1763
|
+
* ```ts
|
|
1764
|
+
* table('project_roadmaps', { id, projectId, year, deletedAt, ... })
|
|
1765
|
+
* .softDelete()
|
|
1766
|
+
* .uniqueActive(['projectId', 'year']) // one ACTIVE roadmap per (project, year)
|
|
1767
|
+
* ```
|
|
1768
|
+
*
|
|
1769
|
+
* By default the predicate is `"deletedAt" IS NULL` (pairs with
|
|
1770
|
+
* `.softDelete()`); override with `{ where }` for a custom active-set
|
|
1771
|
+
* (`"status" = 'open'`). A soft-deleted row leaves the active set, so a NEW
|
|
1772
|
+
* row with the same key is allowed — no hand-written
|
|
1773
|
+
* `generatedAs("CASE WHEN …")` column, no resurrection footgun.
|
|
1774
|
+
*
|
|
1775
|
+
* **mysql / mariadb:** these engines have no partial-index support, so
|
|
1776
|
+
* `.uniqueActive()` FAILS LOUDLY at migrate time (a full unique index would
|
|
1777
|
+
* silently forbid re-creating a soft-deleted row). Use a generated STORED
|
|
1778
|
+
* column + `.unique()` there until the framework lowers it for you.
|
|
1779
|
+
*/
|
|
1780
|
+
uniqueActive: {
|
|
1781
|
+
<const F extends readonly [keyof Fields & string, ...Array<keyof Fields & string>]>(fields: F, options?: {
|
|
1782
|
+
where?: string;
|
|
1783
|
+
}): Table<Name, Fields, Reactive, IxNames>;
|
|
1784
|
+
<const IxName extends string, const F extends readonly [keyof Fields & string, ...Array<keyof Fields & string>]>(name: IxName, fields: F, options?: {
|
|
1785
|
+
where?: string;
|
|
1786
|
+
}): Table<Name, Fields, Reactive, IxNames | IxName>;
|
|
1787
|
+
};
|
|
1757
1788
|
/**
|
|
1758
1789
|
* Declare a named table-level `CHECK` constraint — a DB-ENFORCED
|
|
1759
1790
|
* invariant that holds regardless of which client writes the row
|
|
@@ -1920,6 +1951,20 @@ declare interface TableIndex {
|
|
|
1920
1951
|
* efConstruction, opclass}`). Ignored for kinds that don't read it.
|
|
1921
1952
|
*/
|
|
1922
1953
|
readonly kindOptions?: IndexKindOptions;
|
|
1954
|
+
/**
|
|
1955
|
+
* UNIQUE partial index — set ONLY by `.uniqueActive(...)`. Emits
|
|
1956
|
+
* `CREATE UNIQUE INDEX … WHERE <where>` on postgres / sqlite / mssql, so
|
|
1957
|
+
* uniqueness holds only among rows matching `where` (e.g. the non-soft-
|
|
1958
|
+
* deleted rows). Distinct from `.index(..., { where })` (non-unique) and
|
|
1959
|
+
* from `.unique(...)` (a full constraint with no predicate). Because a
|
|
1960
|
+
* unique+partial index has no `information_schema` constraint form and the
|
|
1961
|
+
* declarative planner is predicate-blind, these are emitted by the
|
|
1962
|
+
* fresh-schema DDL path (like every partial predicate) and kept OUT of the
|
|
1963
|
+
* declarative index snapshot. mysql/mariadb have no partial-index support —
|
|
1964
|
+
* `.uniqueActive()` fails loudly there (use a generated column) rather than
|
|
1965
|
+
* silently dropping the predicate into a full unique index.
|
|
1966
|
+
*/
|
|
1967
|
+
readonly unique?: boolean;
|
|
1923
1968
|
}
|
|
1924
1969
|
|
|
1925
1970
|
declare interface TableLike {
|
package/dist/sql.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as e, R as t, V as n, a as r, c as i, et as a, i as o, n as s, ot as c, r as l, t as u } from "./fileBased-
|
|
1
|
+
import { L as e, R as t, V as n, a as r, c as i, et as a, i as o, n as s, ot as c, r as l, t as u } from "./fileBased-CRXgPJkv.js";
|
|
2
2
|
import { Effect as d } from "effect";
|
|
3
3
|
import { createLogger as f } from "@voltro/logger";
|
|
4
4
|
import { SqlClient as p } from "@effect/sql";
|
|
@@ -347,11 +347,11 @@ var _ = (e, t, n, r = 400) => {
|
|
|
347
347
|
return `(${t.expr})`;
|
|
348
348
|
});
|
|
349
349
|
return n !== void 0 && i.length === 1 ? `${i[0]} ${n}` : i.join(", ");
|
|
350
|
-
}, d = (e, n, a, o, s, c = "") => {
|
|
351
|
-
let
|
|
352
|
-
if (
|
|
353
|
-
let
|
|
354
|
-
return r === "mssql" ? `IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = '${e}') CREATE INDEX ${t(e, r)} ON ${
|
|
350
|
+
}, d = (e, n, a, o, s, c = "", d = !1) => {
|
|
351
|
+
let f = l(e, n, o, s);
|
|
352
|
+
if (f.skip) return "";
|
|
353
|
+
let p = u(a, f.opclass), m = `${f.withSuffix}${c}`, h = pe(n, r, i), g = d ? "UNIQUE " : "";
|
|
354
|
+
return r === "mssql" ? `IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = '${e}') CREATE ${g}INDEX ${t(e, r)} ON ${h}${f.using} (${p})${m};` : r === "mysql" ? `CREATE ${g}INDEX ${t(e, r)} ON ${h}${f.using} (${p})${m};` : `CREATE ${g}INDEX IF NOT EXISTS ${t(e, r)} ON ${h}${f.using} (${p})${m};`;
|
|
355
355
|
};
|
|
356
356
|
for (let n of e.appliedFullText ?? []) if (r === "postgres") {
|
|
357
357
|
let i = n.config ?? "english", o = `${n.name}_tsv`, s = n.columns.map((e) => {
|
|
@@ -369,9 +369,10 @@ var _ = (e, t, n, r = 400) => {
|
|
|
369
369
|
D(`[voltro:migrate] json-path index '${t.name}' on '${e.tableName}': ${r} cannot index a JSON expression directly — skipping. For an indexed JSON path on ${r}, add a generated column for the path (text().generatedAs(...) / .stored()) and .index([...]) it, then filter on that column instead of jsonField(...).`);
|
|
370
370
|
continue;
|
|
371
371
|
}
|
|
372
|
+
if (t.unique && t.where !== void 0 && t.where.length > 0 && (r === "mysql" || r === "mariadb")) throw Error(`@voltro/migrate: uniqueActive index '${t.name}' on '${e.tableName}' cannot be expressed on ${r} — it has no partial (WHERE) index support, and a full UNIQUE index would forbid re-creating a soft-deleted row. Use a generated STORED column (text().generatedAs("CASE WHEN \`deletedAt\` IS NULL THEN <key> ELSE NULL END", { stored: true })) + .unique([...]) on ${r}, or move this table to postgres/sqlite/mssql.`);
|
|
372
373
|
let n = "";
|
|
373
374
|
t.where !== void 0 && t.where.length > 0 && (r === "mysql" || r === "mariadb" ? D(`[voltro:migrate] partial index '${t.name}' on '${e.tableName}': ${r} does not support WHERE clauses on indexes — emitting a full index`) : n = ` WHERE ${t.where}`);
|
|
374
|
-
let i = d(t.name, e.tableName, t.fields, t.kind, t.kindOptions, n);
|
|
375
|
+
let i = d(t.name, e.tableName, t.fields, t.kind, t.kindOptions, n, t.unique === !0);
|
|
375
376
|
i !== "" && a.push(i), t.fields.every((e) => typeof e == "string") && s.add(c(t.fields));
|
|
376
377
|
}
|
|
377
378
|
for (let [t, n] of Object.entries(e.fields)) {
|
|
@@ -660,6 +661,7 @@ BEGIN
|
|
|
660
661
|
});
|
|
661
662
|
let o = e.appliedIndexes ?? [];
|
|
662
663
|
for (let t of o) {
|
|
664
|
+
if (t.unique && t.where) continue;
|
|
663
665
|
let n = t.fields.map((e) => typeof e == "string" ? e : "jsonPath" in e ? `(json:${e.jsonPath.column}->${e.jsonPath.path.join(".")}${e.jsonPath.numeric ? "::num" : ""})` : `(${e.expr})`), i = t.fields.some((e) => typeof e != "string");
|
|
664
666
|
r({
|
|
665
667
|
name: t.name,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/database",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Browser-safe schema DSL, query builder, and cross-dialect migration planner for Voltro — one schema, every SQL backend.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@effect/sql": "^0.51.1",
|
|
41
|
-
"@voltro/logger": "0.
|
|
41
|
+
"@voltro/logger": "0.3.0",
|
|
42
42
|
"typeid-js": "^1.2.0",
|
|
43
43
|
"ulidx": "^2.4.1"
|
|
44
44
|
},
|