@voltro/database 0.67.0 → 0.69.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/dist/index.d.ts CHANGED
@@ -2345,6 +2345,28 @@ export declare interface DataStore {
2345
2345
  * - memory store: linear scan + match-by-conflict-columns
2346
2346
  */
2347
2347
  upsert(table: string, row: Row, options: UpsertOptions): Promise<Row>;
2348
+ /**
2349
+ * `upsert`, plus the one thing it cannot report: whether the row was INSERTED
2350
+ * or UPDATED.
2351
+ *
2352
+ * An app that needs to know does the read itself today — look for the row,
2353
+ * then upsert — which costs a round trip AND opens a window in which the
2354
+ * answer stops being true. It does not have to: the framework's store wrapper
2355
+ * mints the id before the statement, and on the conflict path every dialect
2356
+ * hands back the EXISTING row's id. Two ids that differ ARE the answer, for
2357
+ * free.
2358
+ *
2359
+ * Optional, and provided by the framework's wrapper rather than by a dialect
2360
+ * store — the outcome is decided from what the wrapper already knows, so no
2361
+ * dialect implements anything. It is present on `ctx.store` in a handler; a
2362
+ * hand-written `DataStore` need not have it. The typed helper is
2363
+ * `upsertRowOutcome`.
2364
+ *
2365
+ * `'inserted'` is exact, never a default for "could not tell": where the free
2366
+ * comparison cannot answer (the caller supplied the id, or the table's id is
2367
+ * database-assigned) the destination is read first.
2368
+ */
2369
+ upsertWithOutcome?(table: string, row: Row, options: UpsertOptions): Promise<UpsertResult>;
2348
2370
  /**
2349
2371
  * Insert-or-ignore: INSERT a row, but if it conflicts on
2350
2372
  * `conflictColumns`, leave the existing row UNCHANGED and skip the
@@ -4049,6 +4071,29 @@ export declare type InferViewRow<V> = V extends View<string, infer F> ? InferRow
4049
4071
  */
4050
4072
  export declare const INITIAL_VERSION = 1;
4051
4073
 
4074
+ /**
4075
+ * Insert MANY rows, typed against the table — `insertRow`'s batch, and nothing
4076
+ * else: it forwards to `store.insertMany` unchanged, so the chunking, the
4077
+ * dialect behaviour and the conflict semantics are exactly what the untyped
4078
+ * call already does. What it adds is the one thing the string-keyed form cannot
4079
+ * have: every element is checked against `InferInsertRow<T>`, so a row that
4080
+ * omits a required column, misspells one, or passes a `number` where the column
4081
+ * is a timestamp is a compile error at the call site instead of a dialect error
4082
+ * somewhere inside the batch.
4083
+ *
4084
+ * That last shape is why the batch form needs its own helper rather than a loop
4085
+ * over `insertRow`: a batch is usually MAPPED from something (a parsed import,
4086
+ * an API page), and a mapping is exactly where a `Date`-vs-`number` mismatch
4087
+ * survives review — the loop body's inferred element type is only as good as
4088
+ * whatever produced the array.
4089
+ *
4090
+ * await insertManyRows(ctx.store, roadmapEpicStats, rows)
4091
+ *
4092
+ * An empty array is a legal no-op, so a caller does not have to guard the call
4093
+ * — `store.insertMany` decides what that means, and it means "insert nothing".
4094
+ */
4095
+ export declare const insertManyRows: <T extends AnyTable>(store: TypedInsertManyStore, table: T, rows: ReadonlyArray<InferInsertRow<T>>) => Promise<ReadonlyArray<InferRow<T>>>;
4096
+
4052
4097
  /**
4053
4098
  * Insert a row, typed against the table: the payload must carry every required
4054
4099
  * column (NOT NULL, no default, not auto-filled) or it is a compile error.
@@ -8122,6 +8167,11 @@ export declare interface TxnRecorderPort {
8122
8167
  readonly maxOf: TxnMaxOf;
8123
8168
  }
8124
8169
 
8170
+ /** The minimal store surface `insertManyRows` needs. */
8171
+ export declare interface TypedInsertManyStore {
8172
+ readonly insertMany: (table: string, rows: ReadonlyArray<Row>) => Promise<ReadonlyArray<Row>>;
8173
+ }
8174
+
8125
8175
  /** The minimal store surface `insertRow` needs — satisfied by `ctx.store`. */
8126
8176
  export declare interface TypedInsertStore {
8127
8177
  readonly insert: (table: string, row: Row) => Promise<Row>;
@@ -8134,6 +8184,14 @@ export declare interface TypedUpdateManyStore {
8134
8184
  }) => Promise<number>;
8135
8185
  }
8136
8186
 
8187
+ /** The minimal store surface `upsertRowOutcome` needs. */
8188
+ export declare interface TypedUpsertOutcomeStore {
8189
+ readonly upsertWithOutcome?: (table: string, row: Row, options: UpsertOptions) => Promise<{
8190
+ readonly row: Row;
8191
+ readonly outcome: 'inserted' | 'updated';
8192
+ }>;
8193
+ }
8194
+
8137
8195
  /** The minimal store surface `upsertRow` needs. */
8138
8196
  export declare interface TypedUpsertStore {
8139
8197
  readonly upsert: (table: string, row: Row, options: UpsertOptions) => Promise<Row>;
@@ -8243,6 +8301,13 @@ export declare interface UpsertOptions {
8243
8301
  readonly updateValues?: Readonly<Record<string, unknown>>;
8244
8302
  }
8245
8303
 
8304
+ /** What an `upsertWithOutcome` did, beside the row it wrote. */
8305
+ export declare interface UpsertResult {
8306
+ readonly row: Row;
8307
+ /** `'updated'` means the statement landed on a row that already existed. */
8308
+ readonly outcome: 'inserted' | 'updated';
8309
+ }
8310
+
8246
8311
  /**
8247
8312
  * Upsert a row, typed the same way. `conflictColumns` is constrained to the
8248
8313
  * table's own column names, so a typo'd conflict key is a compile error too.
@@ -8253,6 +8318,34 @@ export declare const upsertRow: <T extends AnyTable>(store: TypedUpsertStore, ta
8253
8318
  readonly updateValues?: Readonly<Partial<Omit<InferRow<T>, "id">>>;
8254
8319
  }) => Promise<InferRow<T>>;
8255
8320
 
8321
+ /**
8322
+ * `upsertRow`, plus whether the row was INSERTED or UPDATED.
8323
+ *
8324
+ * The reason this exists is one an app measured: to report "created" it had to
8325
+ * run a second query before every upsert — a round trip per row, and a window
8326
+ * in which another writer makes the answer wrong. The store already knows: it
8327
+ * mints the id before the statement, and on the conflict path every dialect
8328
+ * returns the EXISTING row's id.
8329
+ *
8330
+ * const { row, outcome } = await upsertRowOutcome(ctx.store, players, player, {
8331
+ * conflictColumns: ['tenantId', 'externalId'],
8332
+ * update: ['name', 'score'],
8333
+ * })
8334
+ * if (outcome === 'inserted') await notifyNewPlayer(row)
8335
+ *
8336
+ * `'inserted'` is never a stand-in for "could not tell". Where the id cannot
8337
+ * decide it — the caller supplied one, or the table's id is database-assigned —
8338
+ * the store reads the destination first and answers exactly.
8339
+ */
8340
+ export declare const upsertRowOutcome: <T extends AnyTable>(store: TypedUpsertOutcomeStore, table: T, row: InferInsertRow<T>, options: {
8341
+ readonly conflictColumns: ReadonlyArray<keyof InferRow<T> & string>;
8342
+ readonly update?: ReadonlyArray<keyof InferRow<T> & string> | ((existing: InferRow<T>) => Readonly<Partial<InferRow<T>>>);
8343
+ readonly updateValues?: Readonly<Partial<Omit<InferRow<T>, "id">>>;
8344
+ }) => Promise<{
8345
+ readonly row: InferRow<T>;
8346
+ readonly outcome: "inserted" | "updated";
8347
+ }>;
8348
+
8256
8349
  /** Conflict patch for stores/callback paths that already have the existing row. */
8257
8350
  export declare const upsertUpdatePatch: (row: Row, existing: Row, options: UpsertOptions) => Row;
8258
8351
 
package/dist/index.js CHANGED
@@ -2670,29 +2670,36 @@ ${e.all.map((e) => {
2670
2670
  reason: "null"
2671
2671
  }));
2672
2672
  return a;
2673
- }, ml = (e, t, n) => e.insert(t.tableName, n), hl = (e, t, n, r) => e.upsert(t.tableName, n, {
2673
+ }, ml = (e, t, n) => e.insert(t.tableName, n), hl = (e, t, n) => e.insertMany(t.tableName, n), gl = (e, t, n, r) => e.upsert(t.tableName, n, {
2674
2674
  conflictColumns: r.conflictColumns,
2675
2675
  ...r.update === void 0 ? {} : { update: r.update },
2676
2676
  ...r.updateValues === void 0 ? {} : { updateValues: r.updateValues }
2677
- }), gl = (e, t, n, r) => e.updateMany(t.tableName, n, { where: r.where }), _l = 512, vl = (e) => {
2677
+ }), _l = async (e, t, n, r) => {
2678
+ if (e.upsertWithOutcome === void 0) throw Error("upsertRowOutcome: this store does not implement `upsertWithOutcome`. It is provided by the framework's store wrapper, so `ctx.store` has it; a hand-written DataStore must add it.");
2679
+ return e.upsertWithOutcome(t.tableName, n, {
2680
+ conflictColumns: r.conflictColumns,
2681
+ ...r.update === void 0 ? {} : { update: r.update },
2682
+ ...r.updateValues === void 0 ? {} : { updateValues: r.updateValues }
2683
+ });
2684
+ }, vl = (e, t, n, r) => e.updateMany(t.tableName, n, { where: r.where }), yl = 512, bl = (e) => {
2678
2685
  e.setMaxListeners(512);
2679
- }, yl = (e, t, n) => ({
2686
+ }, xl = (e, t, n) => ({
2680
2687
  _tag: "PrimaryKeyConflictError",
2681
2688
  table: e,
2682
2689
  key: t,
2683
2690
  cause: n
2684
- }), bl = (e) => typeof e == "object" && !!e && e._tag === "PrimaryKeyConflictError", xl = {
2691
+ }), Sl = (e) => typeof e == "object" && !!e && e._tag === "PrimaryKeyConflictError", Cl = {
2685
2692
  postgres: 5432,
2686
2693
  mysql: 3306,
2687
2694
  mariadb: 3306,
2688
2695
  mssql: 1433
2689
- }, Sl = /* @__PURE__ */ new Set([
2696
+ }, wl = /* @__PURE__ */ new Set([
2690
2697
  "localhost",
2691
2698
  "127.0.0.1",
2692
2699
  "::1",
2693
2700
  "[::1]",
2694
2701
  "0.0.0.0"
2695
- ]), Cl = (e) => {
2702
+ ]), Tl = (e) => {
2696
2703
  let t = e.host, n = e.port, r = e.database;
2697
2704
  if (e.url !== void 0 && e.url !== "") try {
2698
2705
  let i = new URL(e.url);
@@ -2701,29 +2708,29 @@ ${e.all.map((e) => {
2701
2708
  return;
2702
2709
  }
2703
2710
  if (t === void 0 || t === "" || r === void 0 || r === "") return;
2704
- let i = n ?? xl[(e.dialect ?? "").toLowerCase()];
2711
+ let i = n ?? Cl[(e.dialect ?? "").toLowerCase()];
2705
2712
  if (i === void 0 || !Number.isFinite(i)) return;
2706
2713
  let a = t.toLowerCase();
2707
2714
  return {
2708
- host: Sl.has(a) ? "localhost" : a,
2715
+ host: wl.has(a) ? "localhost" : a,
2709
2716
  port: i,
2710
2717
  database: r.toLowerCase()
2711
2718
  };
2712
- }, wl = (e) => {
2713
- let t = Cl(e);
2719
+ }, El = (e) => {
2720
+ let t = Tl(e);
2714
2721
  if (t !== void 0) return qt("sha256").update(`${t.host}\u0000${t.port}\u0000${t.database}`).digest("hex").slice(0, 32);
2715
- }, Tl = (e, t) => e === void 0 || t === void 0 ? "unknown" : e === t ? "same" : "different", El = {
2722
+ }, Dl = (e, t) => e === void 0 || t === void 0 ? "unknown" : e === t ? "same" : "different", Ol = {
2716
2723
  app: 3,
2717
2724
  plugin: 2,
2718
2725
  framework: 1
2719
- }, Dl = /* @__PURE__ */ new Map(), Ol = [], kl = () => [...Ol], Al = (e) => {
2726
+ }, kl = /* @__PURE__ */ new Map(), Al = [], jl = () => [...Al], Ml = (e) => {
2720
2727
  if (!e.table || !e.timeColumn) throw Error("registerRetention: table + timeColumn are required");
2721
2728
  if (!Number.isFinite(e.ttlMs) || e.ttlMs <= 0) throw Error(`registerRetention(${e.table}): ttlMs must be a positive number`);
2722
- let t = e.source ?? "app", n = Dl.get(e.table);
2729
+ let t = e.source ?? "app", n = kl.get(e.table);
2723
2730
  if (n !== void 0) {
2724
2731
  let r = n.source ?? "app", i = n.ttlMs !== e.ttlMs || n.where !== void 0 != (e.where !== void 0);
2725
- if (El[t] < El[r]) {
2726
- i && Ol.push({
2732
+ if (Ol[t] < Ol[r]) {
2733
+ i && Al.push({
2727
2734
  table: e.table,
2728
2735
  kept: {
2729
2736
  source: r,
@@ -2736,7 +2743,7 @@ ${e.all.map((e) => {
2736
2743
  });
2737
2744
  return;
2738
2745
  }
2739
- i && r !== t && Ol.push({
2746
+ i && r !== t && Al.push({
2740
2747
  table: e.table,
2741
2748
  kept: {
2742
2749
  source: t,
@@ -2748,13 +2755,13 @@ ${e.all.map((e) => {
2748
2755
  }
2749
2756
  });
2750
2757
  }
2751
- Dl.set(e.table, {
2758
+ kl.set(e.table, {
2752
2759
  ...e,
2753
2760
  source: t
2754
2761
  });
2755
- }, jl = () => [...Dl.values()], Ml = () => {
2756
- Dl.clear(), Ol.length = 0;
2757
- }, Nl = async (e, t, n, r = 2e4) => {
2762
+ }, Nl = () => [...kl.values()], Pl = () => {
2763
+ kl.clear(), Al.length = 0;
2764
+ }, Fl = async (e, t, n, r = 2e4) => {
2758
2765
  let i = new Date(t - e.ttlMs), a = 0;
2759
2766
  try {
2760
2767
  for (;;) {
@@ -2762,26 +2769,26 @@ ${e.all.map((e) => {
2762
2769
  if (a += t, t < r) break;
2763
2770
  }
2764
2771
  } catch (t) {
2765
- throw k.runSync(A.increment(A.tagged(Fl, "table", e.table))), t;
2772
+ throw k.runSync(A.increment(A.tagged(Ll, "table", e.table))), t;
2766
2773
  }
2767
- return a > 0 && k.runSync(A.incrementBy(A.tagged(Pl, "table", e.table), a)), a;
2768
- }, Pl = A.counter("voltro_retention_swept_rows_total"), Fl = A.counter("voltro_retention_sweep_errors_total"), Il = (e, t) => {
2774
+ return a > 0 && k.runSync(A.incrementBy(A.tagged(Il, "table", e.table), a)), a;
2775
+ }, Il = A.counter("voltro_retention_swept_rows_total"), Ll = A.counter("voltro_retention_sweep_errors_total"), Rl = (e, t) => {
2769
2776
  let n = Number(e ?? String(t));
2770
2777
  return (Number.isFinite(n) && n > 0 ? n : t) * 36e5;
2771
- }, Ll = "_voltro_cdc_offsets", Rl = T(Ll, {
2778
+ }, zl = "_voltro_cdc_offsets", Bl = T(zl, {
2772
2779
  id: D({ prefix: "cdcoff" }),
2773
2780
  replicaId: w(),
2774
2781
  streamName: w(),
2775
2782
  binlogFile: w(),
2776
2783
  binlogPosition: w(),
2777
2784
  updatedAt: E()
2778
- }).index(["replicaId"]), zl = T(Ll, {
2785
+ }).index(["replicaId"]), Vl = T(zl, {
2779
2786
  id: D({ prefix: "cdcoff" }),
2780
2787
  replicaId: w(),
2781
2788
  streamName: w(),
2782
2789
  ctVersion: w(),
2783
2790
  updatedAt: E()
2784
- }).index(["replicaId"]), Bl = "_voltro_data_transfers", Vl = T(Bl, {
2791
+ }).index(["replicaId"]), Hl = "_voltro_data_transfers", Ul = T(Hl, {
2785
2792
  id: D({ prefix: "dimp" }),
2786
2793
  startedAt: E().default("now"),
2787
2794
  finishedAt: E().nullable(),
@@ -2794,13 +2801,13 @@ ${e.all.map((e) => {
2794
2801
  rowsWritten: y().default(0),
2795
2802
  failure: w().nullable(),
2796
2803
  staged: p().nullable()
2797
- }).renamedFrom("_voltro_data_imports").nonReactive(), Hl = (e) => {
2804
+ }).renamedFrom("_voltro_data_imports").nonReactive(), Wl = (e) => {
2798
2805
  let t = Number.isFinite(e.startedAt.getTime()) ? e.startedAt.toISOString() : "an unknown time", n = e.direction === "import" ? e.mode : `${e.direction} ${e.mode}`, r = e.failure === null ? e.finishedAt === null ? `${n} — started and never reported finishing` : e.direction === "backup" || e.direction === "restore" ? `${n} — finished${e.rowsWritten > 0 ? ` (${e.rowsWritten} blob(s))` : ""}` : `${n} — ${e.rowsWritten} row(s) across ${e.tablesWritten} table(s)` : `FAILED ${n} — ${e.failure}`, i = e.staged === null ? "" : e.staged ? " · staged (target untouched until the swap)" : " · NOT staged (target emptied first)";
2799
2806
  return `${t} · ${r} · via ${e.via}${i}` + (e.bundle === null ? "" : ` · from ${e.bundle}`) + (e.sourceFingerprint === null ? "" : ` · source schema ${e.sourceFingerprint}`);
2800
- }, Ul = 255, Wl = (e) => e == null ? "null" : typeof e == "string" || typeof e == "number" || typeof e == "boolean" ? JSON.stringify(e) : Array.isArray(e) ? `[${e.map(Wl).join(",")}]` : typeof e == "object" ? `{${Object.entries(e).filter(([, e]) => e !== void 0).sort(([e], [t]) => e < t ? -1 : +(e > t)).map(([e, t]) => `${JSON.stringify(e)}:${Wl(t)}`).join(",")}}` : JSON.stringify(String(e)), Gl = (e) => {
2807
+ }, Gl = 255, Kl = (e) => e == null ? "null" : typeof e == "string" || typeof e == "number" || typeof e == "boolean" ? JSON.stringify(e) : Array.isArray(e) ? `[${e.map(Kl).join(",")}]` : typeof e == "object" ? `{${Object.entries(e).filter(([, e]) => e !== void 0).sort(([e], [t]) => e < t ? -1 : +(e > t)).map(([e, t]) => `${JSON.stringify(e)}:${Kl(t)}`).join(",")}}` : JSON.stringify(String(e)), ql = (e) => {
2801
2808
  if (e == null) return null;
2802
- let t = typeof e == "string" ? e : Wl(e);
2809
+ let t = typeof e == "string" ? e : Kl(e);
2803
2810
  return t.length > 255 ? t.slice(0, 255) : t;
2804
- }, Kl = "postgres";
2811
+ }, Jl = "postgres";
2805
2812
  //#endregion
2806
- export { ct as AUTO_FILLED_COLUMNS, an as BOUND_ENCRYPTED_PREFIX, cl as BULK_INSERT_LIMITS, Y as BranchNameInvalid, tc as BranchTransitionInvalid, Ll as CDC_OFFSETS_TABLE, _o as CHANGE_CLAIM_TTL_ENV, _l as CHANGE_LISTENER_CEILING, Ze as ColumnBuilder, Bl as DATA_TRANSFERS_TABLE, ot as DEFAULT_ACQUIRE_TIMEOUT_MS, vo as DEFAULT_CHANGE_CLAIM_TTL_HOURS, ys as DEFAULT_EAGER_FALLBACK_WARN_INTERVAL_MS, vt as DEFAULT_SOFT_DROP_RETENTION_DAYS, pr as DEFAULT_SUBJECT_GRAPH_DEPTH, rn as ENCRYPTED_PREFIX, Kl as ENGINE, Br as EagerCardinalityError, xt as FILE_MIGRATION_PATTERN, it as FRAMEWORK_LIVE_TABLES, cn as FieldDecryptionError, Vo as ID_COLUMN_MAX_LENGTH, It as INITIAL_VERSION, Ha as MATCHES_NO_ROWS, wt as MAX_IDENTIFIER_LENGTH, As as MSSQL_TRANSIENT_NUMBERS, Os as MYSQL_TRANSIENT_CODES, go as OccurrenceCounter, ks as POSTGRES_TRANSIENT_STATES, Zc as REPLACE_IN_PROGRESS_TABLE, Ul as SCOPE_KEY_MAX_LENGTH, ae as SELF_REFERENCE_TARGET, js as SQLITE_TRANSIENT_CODES, $c as STAGING_STALE_MINUTES, ft as STAGING_TABLE_PREFIX, Xt as SqlClient, Ms as TURSO_TRANSIENT_MESSAGE, Us as TableStreamError, ei as TenantNamespaceInvalid, $r as TenantNamespaceUnresolved, bc as TenantRegionUnavailable, yc as TenantResidencyUnresolved, Ls as VOLTRO_EXPIRES_MIXIN_ID, zs as VOLTRO_LOCAL_FIRST_MIXIN_ID, Nt as VersionConflict, kt as WARN_IDENTIFIER_LENGTH, Jc as _voltroApiKeysTable, Rl as _voltroCdcOffsetsTable, Vl as _voltroDataTransfersTable, Kc as _voltroIdempotencyTable, qc as _voltroKvTable, Gc as _voltroMigrationPlansTable, Uc as _voltroMigrationsTable, zl as _voltroMssqlCdcOffsetsTable, Qc as _voltroReplaceInProgressTable, Yc as _voltroReplicaObservationsTable, Wc as _voltroSeedsTable, Oa as actorsTable, mc as admitBranch, qe as agedSoftDropSnapshots, ye as allEnumRenames, ur as allRegisteredRelations, te as allRegisteredTables, Ka as and, Rr as applyJsonPatch, se as array, za as arrayContains, Va as arrayHas, Ba as arrayOverlaps, Hr as attachEagerLoads, Co as attributionFields, Do as attributionKey, Pc as auditAllTableIndexes, Nc as auditTableIndexes, Tt as avg, ve as avgOver, Io as beginLocalWrite, de as bigint, Oc as bindResidentStore, p as boolean, Qs as branchNamespaceName, ll as bulkInsertLimitsFor, ee as bytes, xn as causeBranches, lo as changeDelete, po as changeDigest, so as changeInsert, uo as changeSoftDelete, co as changeUpdate, dl as chunkRowsForInsert, Ao as claimPendingAttribution, jn as classifyConstraintViolation, d as clearEnumRenames, zo as clearPendingAttribution, fr as clearRelationsRegistry, wc as clearResidencyConfig, Ml as clearRetentions, De as clearTableRegistry, Jo as clearWriteRecorders, W as coercePredicateForSqlite, ci as coercePredicateValue, li as coercePredicateValues, ba as coerceValueForSqlite, io as collectSubqueries, u as column, Bn as columnSchema, ul as columnsPerRow, Ci as compileEagerJson, zr as compileJsonPatch, L as compilePredicate, ui as compileRawFragment, Si as compileSelect, mo as composeChangeKey, Js as computeEmbedding, wl as connectionIdentity, La as contains, c as count, ce as countDistinct, ie as crdtDoc, he as crdtManagedColumns, jt as crdtManagedColumnsOf, a as crdtText, So as currentWriteAttribution, Qe as databaseHandle, Ce as date, _ as dbEnum, ps as dbMetricTagCacheSizeForTests, gs as dbOperationsInFlight, xe as decimal, l as declareEnumRename, pn as decodeFieldValue, Dr as decodeRowsFromSchema, yn as decryptFieldsOnRead, Vc as defineMigration, St as defineMixin, lt as defineSeed, n as denseRank, br as deriveSubjectGraph, ut as deriveTypeIdPrefix, Wo as derivedRowId, Go as derivedRowIdLength, Me as describeAgedSoftDropSnapshots, Pn as describeDriverError, Fn as describeRowBindings, Hl as describeTransferRun, nn as deserializeArraysOnRead, Fe as drainIdentifierWarnings, Te as dropped, dn as encodeFieldValue, kr as encodeRowForSchema, vn as encryptFieldsForWrite, gn as encryptedColumnsOf, Lo as endLocalWrite, dc as enforceBranchLimits, Ee as ensureTableRegistered, ka as eq, di as escapeLike, ro as evaluatePredicate, Xe as except, $a as exists, Rs as expires, oo as externalChangeEvent, wn as extractDbCause, on as fieldBinding, nt as fireSchemaChangeSeeds, Ge as fireTenantCreateSeeds, zc as formatIndexAuditIssue, xr as formatSubjectGraph, Xc as frameworkTables, rt as generateId, We as generateSnowflake, h as geography, re as geometry, le as getEnumRenames, lr as getRelation, cr as getRelations, Cc as getResidencyConfig, C as getTable, ja as gt, Ma as gte, Qr as hasEagerLoads, qs as hybridSearch, D as id, Fa as inSet, Za as inSubquery, $n as inferForeignKey, ml as insertRow, y as integer, nl as interruptedReplaceRefusal, x as intersect, Oe as interval, sn as isBoundCiphertext, $s as isBranchNamespace, Pr as isCompositeRowKey, Jn as isEmptyRelationsSpec, un as isEncrypted, ln as isFieldDecryptionError, Le as isFileMigration, Mn as isForeignKeyViolation, st as isFrameworkOwnedLiveTable, Vs as isLocalFirst, Hc as isMigrationDefinition, mt as isMysqlFamily, ac as isNeonConnection, Wa as isNotNull, Ua as isNull, bl as isPrimaryKeyConflictError, Nn as isQueryTimeout, mn as isRawEncoding, Ec as isRegionServable, Yn as isRelationsSpec, Ue as isSeedDefinition, ht as isSoftDropSnapshotName, Ye as isSqliteFamily, Ie as isStagingTableName, fe as isTableReactive, Fs as isTransientContention, Pe as isView, S as json, Ya as jsonField, _t as jsonIndex, Fr as jsonPatchAssignments, oe as lag, ue as lead, kl as listRetentionConflicts, jl as listRetentions, Bs as localFirst, Hs as localFirstTables, Na as lt, Pa as lte, Ss as makeEagerFallbackReporter, vc as makeNamespaceBranchExecutor, Zn as many, Qn as manyToMany, ke as materializeFields, f as max, Ve as migration, v as min, pl as missingConflictColumns, fl as missingRequiredColumns, Re as mixin, Aa as neq, nc as nextBranchState, Cl as normaliseTarget, Lr as normalizeJsonPatchValue, Ja as not, eo as notExists, Ia as notInSet, Qa as notInSubquery, pe as numeric, vs as observeDbOp, yo as occurrenceWindowMs, Xn as one, Vr as oneCardinalityMessage, qa as or, b as orderClausesOf, me as paginateBy, At as paginateById, ho as parseChangeKey, Bo as pendingAttributionCount, lc as planBranch, rc as planBranchProvision, ic as planBranchTeardown, uc as planBranchTeardownFor, sc as planNeonBranchProvision, cc as planNeonBranchTeardown, Mt as planVersionedUpdate, $e as pluginRef, r as pluginRefSpecOf, yl as primaryKeyConflictError, fc as provisionBranch, kc as provisionResidentTenant, ai as qualifyTable, i as queryFor, Dt as queryForView, Ne as quoteIdent, vl as raiseChangeListenerCeiling, Se as rank, t as raw, ze as readableByColumns, yt as real, _s as recordDbOp, $o as recordsTable, at as reference, Rn as refuseWrite, wa as registerCoreTables, sr as registerDiscoveredRelations, ko as registerPendingAttribution, or as registerRelations, Al as registerRetention, e as registerTable, Ko as registerWriteRecorder, qn as relations, Ea as requireActors, Be as requireTable, Da as requireTenants, fs as resetDbMetricTagCacheForTests, je as resetSnowflake, Dc as residentPlacement, Qt as resolveActorSnapshot, oc as resolveBranchMechanism, xs as resolveEagerFallbackWarnIntervalMs, Ro as resolveEchoAttribution, g as resolveFullTextIndex, Ke as resolveIdScheme, He as resolveMixinGraph, Tc as resolveTenantHome, ii as resolveTenantNamespace, Il as retentionTtlMsFromEnv, Mr as rowKeyColumns, Nr as rowKeyIdentity, be as rowNumber, Vn as rowSchema, Es as runRetryingTransaction, Ds as runStoreTransaction, xo as runWithWriteAttribution, es as runWriteRecorders, Tl as sameDatabase, ri as sanitizeIdentifierFragment, Gl as scopeKeyOf, pt as selfReference, tn as serializeArraysForWrite, Je as serverOnlyColumns, Sc as setResidencyConfig, Ae as setSchemaChangeSeedHook, Ct as setTenantCreateSeedHook, Cs as settleTransactionExit, Ft as settleVersionedUpdate, et as softDropSnapshotStamp, Ga as spatialPredicate, il as stagingLeftoverReport, rl as stagingLeftovers, tl as stagingNamesOf, el as stagingStaleAfterMs, ol as stampGeneratedId, sl as stampGeneratedIds, Ra as startsWith, zn as storeRefusalOf, Ks as streamTable, we as sum, m as sumOver, Xo as suspendWriteRecorders, Zo as suspendWriteRecordersEffect, Nl as sweepRetention, T as table, pc as teardownBranch, w as text, E as timestamp, Rt as timestampMs, Pt as timestampMsOrNull, ne as union, dt as unionAll, bt as unregisterTable, qo as unregisterWriteRecorder, gl as updateManyRow, hl as upsertRow, jr as upsertUpdatePatch, Ar as upsertUpdateValues, Ot as validateColumnName, _e as validateIndexName, dr as validateRegisteredRelations, rr as validateRelationConfig, s as validateTableName, Et as validateTypeIdPrefix, gt as vector, tt as vectorDistanceToOpclass, Ys as vectorEmbedding, Lt as versionColumnOf, ge as view, o as viewSql, ao as walkLeaves, Tn as wireSafeCause, wo as withCapturedAttribution, Qo as writeRecordersSuspended };
2813
+ export { ct as AUTO_FILLED_COLUMNS, an as BOUND_ENCRYPTED_PREFIX, cl as BULK_INSERT_LIMITS, Y as BranchNameInvalid, tc as BranchTransitionInvalid, zl as CDC_OFFSETS_TABLE, _o as CHANGE_CLAIM_TTL_ENV, yl as CHANGE_LISTENER_CEILING, Ze as ColumnBuilder, Hl as DATA_TRANSFERS_TABLE, ot as DEFAULT_ACQUIRE_TIMEOUT_MS, vo as DEFAULT_CHANGE_CLAIM_TTL_HOURS, ys as DEFAULT_EAGER_FALLBACK_WARN_INTERVAL_MS, vt as DEFAULT_SOFT_DROP_RETENTION_DAYS, pr as DEFAULT_SUBJECT_GRAPH_DEPTH, rn as ENCRYPTED_PREFIX, Jl as ENGINE, Br as EagerCardinalityError, xt as FILE_MIGRATION_PATTERN, it as FRAMEWORK_LIVE_TABLES, cn as FieldDecryptionError, Vo as ID_COLUMN_MAX_LENGTH, It as INITIAL_VERSION, Ha as MATCHES_NO_ROWS, wt as MAX_IDENTIFIER_LENGTH, As as MSSQL_TRANSIENT_NUMBERS, Os as MYSQL_TRANSIENT_CODES, go as OccurrenceCounter, ks as POSTGRES_TRANSIENT_STATES, Zc as REPLACE_IN_PROGRESS_TABLE, Gl as SCOPE_KEY_MAX_LENGTH, ae as SELF_REFERENCE_TARGET, js as SQLITE_TRANSIENT_CODES, $c as STAGING_STALE_MINUTES, ft as STAGING_TABLE_PREFIX, Xt as SqlClient, Ms as TURSO_TRANSIENT_MESSAGE, Us as TableStreamError, ei as TenantNamespaceInvalid, $r as TenantNamespaceUnresolved, bc as TenantRegionUnavailable, yc as TenantResidencyUnresolved, Ls as VOLTRO_EXPIRES_MIXIN_ID, zs as VOLTRO_LOCAL_FIRST_MIXIN_ID, Nt as VersionConflict, kt as WARN_IDENTIFIER_LENGTH, Jc as _voltroApiKeysTable, Bl as _voltroCdcOffsetsTable, Ul as _voltroDataTransfersTable, Kc as _voltroIdempotencyTable, qc as _voltroKvTable, Gc as _voltroMigrationPlansTable, Uc as _voltroMigrationsTable, Vl as _voltroMssqlCdcOffsetsTable, Qc as _voltroReplaceInProgressTable, Yc as _voltroReplicaObservationsTable, Wc as _voltroSeedsTable, Oa as actorsTable, mc as admitBranch, qe as agedSoftDropSnapshots, ye as allEnumRenames, ur as allRegisteredRelations, te as allRegisteredTables, Ka as and, Rr as applyJsonPatch, se as array, za as arrayContains, Va as arrayHas, Ba as arrayOverlaps, Hr as attachEagerLoads, Co as attributionFields, Do as attributionKey, Pc as auditAllTableIndexes, Nc as auditTableIndexes, Tt as avg, ve as avgOver, Io as beginLocalWrite, de as bigint, Oc as bindResidentStore, p as boolean, Qs as branchNamespaceName, ll as bulkInsertLimitsFor, ee as bytes, xn as causeBranches, lo as changeDelete, po as changeDigest, so as changeInsert, uo as changeSoftDelete, co as changeUpdate, dl as chunkRowsForInsert, Ao as claimPendingAttribution, jn as classifyConstraintViolation, d as clearEnumRenames, zo as clearPendingAttribution, fr as clearRelationsRegistry, wc as clearResidencyConfig, Pl as clearRetentions, De as clearTableRegistry, Jo as clearWriteRecorders, W as coercePredicateForSqlite, ci as coercePredicateValue, li as coercePredicateValues, ba as coerceValueForSqlite, io as collectSubqueries, u as column, Bn as columnSchema, ul as columnsPerRow, Ci as compileEagerJson, zr as compileJsonPatch, L as compilePredicate, ui as compileRawFragment, Si as compileSelect, mo as composeChangeKey, Js as computeEmbedding, El as connectionIdentity, La as contains, c as count, ce as countDistinct, ie as crdtDoc, he as crdtManagedColumns, jt as crdtManagedColumnsOf, a as crdtText, So as currentWriteAttribution, Qe as databaseHandle, Ce as date, _ as dbEnum, ps as dbMetricTagCacheSizeForTests, gs as dbOperationsInFlight, xe as decimal, l as declareEnumRename, pn as decodeFieldValue, Dr as decodeRowsFromSchema, yn as decryptFieldsOnRead, Vc as defineMigration, St as defineMixin, lt as defineSeed, n as denseRank, br as deriveSubjectGraph, ut as deriveTypeIdPrefix, Wo as derivedRowId, Go as derivedRowIdLength, Me as describeAgedSoftDropSnapshots, Pn as describeDriverError, Fn as describeRowBindings, Wl as describeTransferRun, nn as deserializeArraysOnRead, Fe as drainIdentifierWarnings, Te as dropped, dn as encodeFieldValue, kr as encodeRowForSchema, vn as encryptFieldsForWrite, gn as encryptedColumnsOf, Lo as endLocalWrite, dc as enforceBranchLimits, Ee as ensureTableRegistered, ka as eq, di as escapeLike, ro as evaluatePredicate, Xe as except, $a as exists, Rs as expires, oo as externalChangeEvent, wn as extractDbCause, on as fieldBinding, nt as fireSchemaChangeSeeds, Ge as fireTenantCreateSeeds, zc as formatIndexAuditIssue, xr as formatSubjectGraph, Xc as frameworkTables, rt as generateId, We as generateSnowflake, h as geography, re as geometry, le as getEnumRenames, lr as getRelation, cr as getRelations, Cc as getResidencyConfig, C as getTable, ja as gt, Ma as gte, Qr as hasEagerLoads, qs as hybridSearch, D as id, Fa as inSet, Za as inSubquery, $n as inferForeignKey, hl as insertManyRows, ml as insertRow, y as integer, nl as interruptedReplaceRefusal, x as intersect, Oe as interval, sn as isBoundCiphertext, $s as isBranchNamespace, Pr as isCompositeRowKey, Jn as isEmptyRelationsSpec, un as isEncrypted, ln as isFieldDecryptionError, Le as isFileMigration, Mn as isForeignKeyViolation, st as isFrameworkOwnedLiveTable, Vs as isLocalFirst, Hc as isMigrationDefinition, mt as isMysqlFamily, ac as isNeonConnection, Wa as isNotNull, Ua as isNull, Sl as isPrimaryKeyConflictError, Nn as isQueryTimeout, mn as isRawEncoding, Ec as isRegionServable, Yn as isRelationsSpec, Ue as isSeedDefinition, ht as isSoftDropSnapshotName, Ye as isSqliteFamily, Ie as isStagingTableName, fe as isTableReactive, Fs as isTransientContention, Pe as isView, S as json, Ya as jsonField, _t as jsonIndex, Fr as jsonPatchAssignments, oe as lag, ue as lead, jl as listRetentionConflicts, Nl as listRetentions, Bs as localFirst, Hs as localFirstTables, Na as lt, Pa as lte, Ss as makeEagerFallbackReporter, vc as makeNamespaceBranchExecutor, Zn as many, Qn as manyToMany, ke as materializeFields, f as max, Ve as migration, v as min, pl as missingConflictColumns, fl as missingRequiredColumns, Re as mixin, Aa as neq, nc as nextBranchState, Tl as normaliseTarget, Lr as normalizeJsonPatchValue, Ja as not, eo as notExists, Ia as notInSet, Qa as notInSubquery, pe as numeric, vs as observeDbOp, yo as occurrenceWindowMs, Xn as one, Vr as oneCardinalityMessage, qa as or, b as orderClausesOf, me as paginateBy, At as paginateById, ho as parseChangeKey, Bo as pendingAttributionCount, lc as planBranch, rc as planBranchProvision, ic as planBranchTeardown, uc as planBranchTeardownFor, sc as planNeonBranchProvision, cc as planNeonBranchTeardown, Mt as planVersionedUpdate, $e as pluginRef, r as pluginRefSpecOf, xl as primaryKeyConflictError, fc as provisionBranch, kc as provisionResidentTenant, ai as qualifyTable, i as queryFor, Dt as queryForView, Ne as quoteIdent, bl as raiseChangeListenerCeiling, Se as rank, t as raw, ze as readableByColumns, yt as real, _s as recordDbOp, $o as recordsTable, at as reference, Rn as refuseWrite, wa as registerCoreTables, sr as registerDiscoveredRelations, ko as registerPendingAttribution, or as registerRelations, Ml as registerRetention, e as registerTable, Ko as registerWriteRecorder, qn as relations, Ea as requireActors, Be as requireTable, Da as requireTenants, fs as resetDbMetricTagCacheForTests, je as resetSnowflake, Dc as residentPlacement, Qt as resolveActorSnapshot, oc as resolveBranchMechanism, xs as resolveEagerFallbackWarnIntervalMs, Ro as resolveEchoAttribution, g as resolveFullTextIndex, Ke as resolveIdScheme, He as resolveMixinGraph, Tc as resolveTenantHome, ii as resolveTenantNamespace, Rl as retentionTtlMsFromEnv, Mr as rowKeyColumns, Nr as rowKeyIdentity, be as rowNumber, Vn as rowSchema, Es as runRetryingTransaction, Ds as runStoreTransaction, xo as runWithWriteAttribution, es as runWriteRecorders, Dl as sameDatabase, ri as sanitizeIdentifierFragment, ql as scopeKeyOf, pt as selfReference, tn as serializeArraysForWrite, Je as serverOnlyColumns, Sc as setResidencyConfig, Ae as setSchemaChangeSeedHook, Ct as setTenantCreateSeedHook, Cs as settleTransactionExit, Ft as settleVersionedUpdate, et as softDropSnapshotStamp, Ga as spatialPredicate, il as stagingLeftoverReport, rl as stagingLeftovers, tl as stagingNamesOf, el as stagingStaleAfterMs, ol as stampGeneratedId, sl as stampGeneratedIds, Ra as startsWith, zn as storeRefusalOf, Ks as streamTable, we as sum, m as sumOver, Xo as suspendWriteRecorders, Zo as suspendWriteRecordersEffect, Fl as sweepRetention, T as table, pc as teardownBranch, w as text, E as timestamp, Rt as timestampMs, Pt as timestampMsOrNull, ne as union, dt as unionAll, bt as unregisterTable, qo as unregisterWriteRecorder, vl as updateManyRow, gl as upsertRow, _l as upsertRowOutcome, jr as upsertUpdatePatch, Ar as upsertUpdateValues, Ot as validateColumnName, _e as validateIndexName, dr as validateRegisteredRelations, rr as validateRelationConfig, s as validateTableName, Et as validateTypeIdPrefix, gt as vector, tt as vectorDistanceToOpclass, Ys as vectorEmbedding, Lt as versionColumnOf, ge as view, o as viewSql, ao as walkLeaves, Tn as wireSafeCause, wo as withCapturedAttribution, Qo as writeRecordersSuspended };
package/dist/sql.d.ts CHANGED
@@ -7,7 +7,7 @@ import { SqlError } from '@effect/sql';
7
7
  import { SqlError as SqlError_2 } from '@effect/sql/SqlError';
8
8
  import { Statement } from '@effect/sql';
9
9
 
10
- export declare const acquireMigrationLock: (sql: SqlClient.SqlClient, scope?: MigrationLockScope) => Effect.Effect<void, SqlError_2>;
10
+ export declare const acquireMigrationLock: (sql: SqlClient.SqlClient, scope?: MigrationLockScope) => Effect.Effect<HeldMigrationLock, SqlError_2>;
11
11
 
12
12
  declare interface AggregateColumn<TResult = number | Date | null> {
13
13
  readonly op: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'count-distinct' | 'window-row-number' | 'window-rank' | 'window-dense-rank' | 'window-lag' | 'window-lead' | 'window-sum-over' | 'window-avg-over' | 'column';
@@ -126,6 +126,14 @@ export declare interface AppliedOp {
126
126
  */
127
127
  export declare const applyNamespacedSchema: (tables: ReadonlyArray<AnyTable>, namespace: string, dialect?: DialectId, options?: SchemaApplyOptions) => Effect.Effect<void, never, SqlClient.SqlClient>;
128
128
 
129
+ /**
130
+ * Apply a plan, holding the migration lock for the whole of it.
131
+ *
132
+ * `acquireUseRelease` rather than a `try/finally` in the body: Effect's own
133
+ * bracket runs its release on success, on failure, on a defect AND on
134
+ * interruption, which is exactly the set a JS `finally` inside `Effect.gen`
135
+ * does not cover.
136
+ */
129
137
  export declare const applyPlan: (sql: SqlClient.SqlClient, plan: MigrationPlan, ctx: ApplyPlanCtx) => Effect.Effect<AppliedMigration, SqlError_2, SqlClient.SqlClient>;
130
138
 
131
139
  export declare interface ApplyPlanCtx {
@@ -1344,6 +1352,22 @@ export declare const formatBranchRehearsal: (report: BranchRehearsalReport) => s
1344
1352
  /** Human report, or `undefined` when the database and the schema agree. */
1345
1353
  export declare const formatReactiveTriggerDrift: (drift: ReactiveTriggerDrift) => string | undefined;
1346
1354
 
1355
+ /**
1356
+ * A lock that is being HELD, and the only way to let it go.
1357
+ *
1358
+ * The reserved connection lives inside this handle: `release` gives the lock
1359
+ * back and closes the reservation, in that order. Returning it as a value
1360
+ * rather than exposing a free `releaseMigrationLock(sql, scope)` is deliberate
1361
+ * — a free release cannot know WHICH connection took the lock, and on every
1362
+ * session-scoped dialect a release on the wrong connection is a no-op that
1363
+ * reports success.
1364
+ */
1365
+ export declare interface HeldMigrationLock {
1366
+ /** Release the lock and return its connection to the pool. Idempotent only
1367
+ * in the sense that the dialect statements are; call it once. */
1368
+ readonly release: Effect.Effect<void, SqlError_2>;
1369
+ }
1370
+
1347
1371
  /**
1348
1372
  * Fully-resolved scheme stored on the `id` column definition AFTER
1349
1373
  * `table()` walks the fields. For `typeid`, the prefix is always
@@ -2555,8 +2579,6 @@ export declare const rehearsedOperation: (planned: PlannedOperation) => Rehearse
2555
2579
  */
2556
2580
  export declare const rehearseMigrationOnBranch: (ops: BranchRehearsalOps) => Promise<BranchRehearsalReport>;
2557
2581
 
2558
- export declare const releaseMigrationLock: (sql: SqlClient.SqlClient, scope?: MigrationLockScope) => Effect.Effect<void, SqlError_2>;
2559
-
2560
2582
  export declare const renderColumnMysql: (col: ColumnSnapshot) => string;
2561
2583
 
2562
2584
  export declare const renderColumnPg: (col: ColumnSnapshot) => string;
@@ -3570,12 +3592,6 @@ declare interface WindowSpec {
3570
3592
  }>;
3571
3593
  }
3572
3594
 
3573
- /**
3574
- * Convenience wrapper: acquire → run `work` → release. Releases even
3575
- * on failure via `Effect.ensuring`, but a process crash mid-work
3576
- * leaves the postgres lock held until that connection closes (which
3577
- * the pooled SqlClient does on Effect scope cleanup).
3578
- */
3579
3595
  export declare const withMigrationLock: <A, E, R = never>(sql: SqlClient.SqlClient, work: Effect.Effect<A, E, R>, scope?: MigrationLockScope) => Effect.Effect<A, E | SqlError_2, R>;
3580
3596
 
3581
3597
  /** Tree of relations to eager-load. */