@fjall/util 8.0.0 → 9.0.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.
@@ -9,7 +9,7 @@
9
9
  "package.json",
10
10
  "tsconfig.json"
11
11
  ],
12
- "hash": "44f9288c9bafa6baf6a191987515c94196c3e7b7d96fa9ed9c97d2b6d49044c5",
12
+ "hash": "45c0c260a70c536ef2d05997781c8ccc49866ce559e8d6940f58f9f092c5f250",
13
13
  "files": {
14
14
  "src/async/concurrency.ts": "3c7ebe0222a720f9",
15
15
  "src/async/singleton.ts": "7bcc21b0fb920561",
@@ -81,7 +81,7 @@
81
81
  "src/migration/index.ts": "de586c9d775b3174",
82
82
  "src/migration/pickLatestClickHouseMigration.ts": "a5b4a00ba63522cb",
83
83
  "src/migration/pickLatestPrismaMigration.ts": "cbddc2950f4d4c85",
84
- "src/migration/verifyExpectedSchemaVersion.ts": "f2a9c63023ffb3d2",
84
+ "src/migration/verifyExpectedSchemaVersion.ts": "cd583329d771a047",
85
85
  "src/naming/accountNameCollision.ts": "2dc8b278ff039220",
86
86
  "src/naming/caseConversion.ts": "8b8984f2b58b8ab7",
87
87
  "src/naming/connectedAccountName.ts": "3cd3bc7dba0a2f0b",
@@ -103,9 +103,9 @@
103
103
  "src/securityHelpers.ts": "ee2ab9ffd53119fb",
104
104
  "src/targets.ts": "126280f0a5762e21",
105
105
  "../scripts/minify-dist.mjs": "6b2e4b0df8aec601",
106
- "package.json": "4c2a38493de54289",
106
+ "package.json": "de20bfd9ecd3a130",
107
107
  "tsconfig.json": "db759831ad95f396"
108
108
  },
109
- "outputHash": "5d99d7488716ca9206bb2dab110e4230414d66429c6d03cb3787c27e51ca8e26",
109
+ "outputHash": "d92d1ce1a95c7c760cdd03cefa7cd594c041bb5c0768c96a6806c9d703b6456b",
110
110
  "outputFileCount": 189
111
111
  }
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 94 files minified at 2026-08-09T06:27:36.179Z
1
+ 94 files minified at 2026-08-09T10:16:53.310Z
@@ -1,18 +1,25 @@
1
1
  /**
2
- * Tool-discriminated boot-time gate: verify a relational database's most
3
- * recently applied migration matches the expected version baked into the
4
- * deployed image. Companion to {@link pickLatestPrismaMigration} (which
2
+ * Tool-discriminated boot-time gate: verify a relational database's
3
+ * latest-by-name applied migration matches the expected version baked into
4
+ * the deployed image. Companion to {@link pickLatestPrismaMigration} (which
5
5
  * resolves the expected version FROM disk at synth/runner time); this
6
6
  * helper closes the loop by reading what the DB actually has APPLIED.
7
7
  *
8
+ * "Latest" MUST mean lexicographic max of migration_name, mirroring
9
+ * pickLatestPrismaMigration — NOT most recently finished. Prisma's
10
+ * `migrate deploy` applies a pending migration even when it name-sorts
11
+ * before already-applied ones (parallel branches landing interleaved
12
+ * timestamps), so latest-by-finished_at can be an OLDER name than what the
13
+ * schema actually satisfies, false-failing the gate against a complete DB.
14
+ *
8
15
  * Consumer supplies the driver via the {@link MigrationsSqlClient} shim so
9
16
  * the package stays driver-agnostic (pg, mysql2, postgres.js, Drizzle
10
17
  * session, raw Prisma `$queryRawUnsafe`, …). The shim's single `query(sql)`
11
18
  * surface is intentionally narrower than any real driver — callers wrap.
12
19
  *
13
20
  * Two tool branches today:
14
- * - `tool: "prisma"` reads `_prisma_migrations.migration_name` (most-recent
15
- * `finished_at IS NOT NULL`). Stable across all Prisma DB engines.
21
+ * - `tool: "prisma"` reads `_prisma_migrations.migration_name` (max name
22
+ * among `finished_at IS NOT NULL`). Stable across all Prisma DB engines.
16
23
  * - `tool: "custom"` lets a caller pass a `{ sql, column }` pair when the
17
24
  * migration tool's metadata table doesn't match the Prisma shape (e.g.
18
25
  * Drizzle's `__drizzle_migrations.hash`, Knex's `knex_migrations.name`).
@@ -1 +1 @@
1
- var s=Object.defineProperty;var c=(e,r)=>s(e,"name",{value:r,configurable:!0});import{isSchemaVersionSatisfied as a}from"./compareSchemaVersion.js";const u="SELECT migration_name FROM _prisma_migrations WHERE finished_at IS NOT NULL ORDER BY finished_at DESC LIMIT 1",l="migration_name";async function E(e){if(e.signal?.aborted)throw new Error("verifyExpectedSchemaVersion: aborted by signal before query");let r,n;if(e.tool==="prisma")r=u,n=l;else if(e.tool==="custom"){if(e.customQuery===void 0)throw new Error('verifyExpectedSchemaVersion: tool="custom" requires customQuery');r=e.customQuery.sql,n=e.customQuery.column}else throw new Error(`verifyExpectedSchemaVersion: unknown tool ${String(e.tool)}`);const i=(await e.client.query(r)).rows[0],o=i!==void 0?i[n]:void 0,t=typeof o=="string"?o:null;return{matches:t!==null&&a(e.expected,t),expected:e.expected,actual:t}}c(E,"verifyExpectedSchemaVersion");export{E as verifyExpectedSchemaVersion};
1
+ var a=Object.defineProperty;var c=(e,r)=>a(e,"name",{value:r,configurable:!0});import{isSchemaVersionSatisfied as s}from"./compareSchemaVersion.js";const u="SELECT migration_name FROM _prisma_migrations WHERE finished_at IS NOT NULL ORDER BY migration_name DESC LIMIT 1",m="migration_name";async function E(e){if(e.signal?.aborted)throw new Error("verifyExpectedSchemaVersion: aborted by signal before query");let r,n;if(e.tool==="prisma")r=u,n=m;else if(e.tool==="custom"){if(e.customQuery===void 0)throw new Error('verifyExpectedSchemaVersion: tool="custom" requires customQuery');r=e.customQuery.sql,n=e.customQuery.column}else throw new Error(`verifyExpectedSchemaVersion: unknown tool ${String(e.tool)}`);const i=(await e.client.query(r)).rows[0],o=i!==void 0?i[n]:void 0,t=typeof o=="string"?o:null;return{matches:t!==null&&s(e.expected,t),expected:e.expected,actual:t}}c(E,"verifyExpectedSchemaVersion");export{E as verifyExpectedSchemaVersion};
@@ -1 +1 @@
1
- var d=Object.defineProperty;var i=(t,e)=>d(t,"name",{value:e,configurable:!0});import{describe as c,expect as a,it as s,vi as u}from"vitest";import{verifyExpectedSchemaVersion as o}from"./verifyExpectedSchemaVersion.js";function n(t){const e=[];return{client:{query:u.fn(async l=>(e.push(l),{rows:t}))},queries:e}}i(n,"makeMockClient"),c("verifyExpectedSchemaVersion",()=>{c('tool: "prisma"',()=>{s("returns matches=true when actual === expected",async()=>{const{client:t,queries:e}=n([{migration_name:"20260520000000_add_widgets"}]),r=await o({tool:"prisma",expected:"20260520000000_add_widgets",client:t});a(r).toEqual({matches:!0,expected:"20260520000000_add_widgets",actual:"20260520000000_add_widgets"}),a(e).toHaveLength(1),a(e[0]).toContain("_prisma_migrations"),a(e[0]).toContain("finished_at IS NOT NULL"),a(e[0]).toContain("ORDER BY finished_at DESC LIMIT 1")}),s("returns matches=false with actual when DB trails image",async()=>{const{client:t}=n([{migration_name:"20260519000000_old"}]),e=await o({tool:"prisma",expected:"20260520000000_add_widgets",client:t});a(e).toEqual({matches:!1,expected:"20260520000000_add_widgets",actual:"20260519000000_old"})}),s("returns matches=true when DB is AHEAD (expand-only rollback)",async()=>{const{client:t}=n([{migration_name:"20260521000000_add_more_widgets"}]),e=await o({tool:"prisma",expected:"20260520000000_add_widgets",client:t});a(e).toEqual({matches:!0,expected:"20260520000000_add_widgets",actual:"20260521000000_add_more_widgets"})}),s("returns actual=null on empty result set",async()=>{const{client:t}=n([]),e=await o({tool:"prisma",expected:"20260520000000_add_widgets",client:t});a(e).toEqual({matches:!1,expected:"20260520000000_add_widgets",actual:null})}),s("returns actual=null when row's migration_name is not a string",async()=>{const{client:t}=n([{migration_name:12345}]),e=await o({tool:"prisma",expected:"x",client:t});a(e.actual).toBeNull(),a(e.matches).toBe(!1)})}),c('tool: "custom"',()=>{s("uses customQuery sql and column",async()=>{const{client:t,queries:e}=n([{hash:"deadbeef"}]),r=await o({tool:"custom",expected:"deadbeef",client:t,customQuery:{sql:"SELECT hash FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1",column:"hash"}});a(r.matches).toBe(!0),a(r.actual).toBe("deadbeef"),a(e[0]).toBe("SELECT hash FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1")}),s("stays strict for non-orderable versions even when actual sorts later",async()=>{const{client:t}=n([{hash:"ffffffff"}]),e=await o({tool:"custom",expected:"00000000",client:t,customQuery:{sql:"SELECT hash FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1",column:"hash"}});a(e.matches).toBe(!1),a(e.actual).toBe("ffffffff")}),s("throws when customQuery is omitted",async()=>{const{client:t}=n([]);await a(o({tool:"custom",expected:"x",client:t})).rejects.toThrow(/tool="custom" requires customQuery/)})}),c("abort signal",()=>{s("short-circuits before query when signal is already aborted",async()=>{const{client:t,queries:e}=n([{migration_name:"irrelevant"}]),r=new AbortController;r.abort(),await a(o({tool:"prisma",expected:"x",client:t,signal:r.signal})).rejects.toThrow(/aborted by signal before query/),a(e).toEqual([])})})});
1
+ var d=Object.defineProperty;var c=(e,t)=>d(e,"name",{value:t,configurable:!0});import{describe as r,expect as a,it as o,vi as u}from"vitest";import{verifyExpectedSchemaVersion as n}from"./verifyExpectedSchemaVersion.js";function i(e){const t=[];return{client:{query:u.fn(async l=>(t.push(l),{rows:e}))},queries:t}}c(i,"makeMockClient"),r("verifyExpectedSchemaVersion",()=>{r('tool: "prisma"',()=>{o("returns matches=true when actual === expected",async()=>{const{client:e,queries:t}=i([{migration_name:"20260520000000_add_widgets"}]),s=await n({tool:"prisma",expected:"20260520000000_add_widgets",client:e});a(s).toEqual({matches:!0,expected:"20260520000000_add_widgets",actual:"20260520000000_add_widgets"}),a(t).toHaveLength(1),a(t[0]).toContain("_prisma_migrations"),a(t[0]).toContain("finished_at IS NOT NULL"),a(t[0]).toContain("ORDER BY migration_name DESC LIMIT 1")}),o("orders by migration_name, not finished_at (out-of-order application)",async()=>{const{client:e,queries:t}=i([{migration_name:"20260809140000_drop_admin_plugin_columns"}]),s=await n({tool:"prisma",expected:"20260809140000_drop_admin_plugin_columns",client:e});a(s.matches).toBe(!0),a(t[0]).not.toContain("ORDER BY finished_at"),a(t[0]).toContain("ORDER BY migration_name DESC LIMIT 1")}),o("returns matches=false with actual when DB trails image",async()=>{const{client:e}=i([{migration_name:"20260519000000_old"}]),t=await n({tool:"prisma",expected:"20260520000000_add_widgets",client:e});a(t).toEqual({matches:!1,expected:"20260520000000_add_widgets",actual:"20260519000000_old"})}),o("returns matches=true when DB is AHEAD (expand-only rollback)",async()=>{const{client:e}=i([{migration_name:"20260521000000_add_more_widgets"}]),t=await n({tool:"prisma",expected:"20260520000000_add_widgets",client:e});a(t).toEqual({matches:!0,expected:"20260520000000_add_widgets",actual:"20260521000000_add_more_widgets"})}),o("returns actual=null on empty result set",async()=>{const{client:e}=i([]),t=await n({tool:"prisma",expected:"20260520000000_add_widgets",client:e});a(t).toEqual({matches:!1,expected:"20260520000000_add_widgets",actual:null})}),o("returns actual=null when row's migration_name is not a string",async()=>{const{client:e}=i([{migration_name:12345}]),t=await n({tool:"prisma",expected:"x",client:e});a(t.actual).toBeNull(),a(t.matches).toBe(!1)})}),r('tool: "custom"',()=>{o("uses customQuery sql and column",async()=>{const{client:e,queries:t}=i([{hash:"deadbeef"}]),s=await n({tool:"custom",expected:"deadbeef",client:e,customQuery:{sql:"SELECT hash FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1",column:"hash"}});a(s.matches).toBe(!0),a(s.actual).toBe("deadbeef"),a(t[0]).toBe("SELECT hash FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1")}),o("stays strict for non-orderable versions even when actual sorts later",async()=>{const{client:e}=i([{hash:"ffffffff"}]),t=await n({tool:"custom",expected:"00000000",client:e,customQuery:{sql:"SELECT hash FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1",column:"hash"}});a(t.matches).toBe(!1),a(t.actual).toBe("ffffffff")}),o("throws when customQuery is omitted",async()=>{const{client:e}=i([]);await a(n({tool:"custom",expected:"x",client:e})).rejects.toThrow(/tool="custom" requires customQuery/)})}),r("abort signal",()=>{o("short-circuits before query when signal is already aborted",async()=>{const{client:e,queries:t}=i([{migration_name:"irrelevant"}]),s=new AbortController;s.abort(),await a(n({tool:"prisma",expected:"x",client:e,signal:s.signal})).rejects.toThrow(/aborted by signal before query/),a(t).toEqual([])})})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/util",
3
- "version": "8.0.0",
3
+ "version": "9.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",