@fjall/util 0.99.4 → 0.102.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/.minified CHANGED
@@ -1 +1 @@
1
- 53 files minified at 2026-05-22T23:32:43.200Z
1
+ 57 files minified at 2026-05-23T08:39:30.171Z
package/dist/index.d.ts CHANGED
@@ -16,4 +16,4 @@ export { findInfrastructurePaths, findBoundaryPath, isInfrastructureFile, type M
16
16
  export { inferContainerFromCandidates } from "./inferContainerFromCandidates.js";
17
17
  export { RESERVED_APP_NAMES, type ReservedAppName, isReservedAppName } from "./reservedAppNames.js";
18
18
  export { deriveContentHashTag } from "./deriveContentHashTag.js";
19
- export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV, PRISMA_MIGRATION_DIR_RE } from "./migration/constants.js";
19
+ export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV, EXPECTED_CH_SCHEMA_VERSION_ENV, PRISMA_MIGRATION_DIR_RE, CLICKHOUSE_MIGRATION_SKIP_RE } from "./migration/constants.js";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{DNS_APEX as o,getDomainExportNames as t}from"./domainExports.js";import{toPascalCase as E,toKebab as i,toValidDatabaseName as n,toScreamingSnake as m,capitalise as s,getSafeZoneName as p}from"./caseConversion.js";import{normaliseError as S,getErrorMessage as N,hasErrorCode as R,getErrorCode as g,getErrorStack as _,formatErrorString as A}from"./errorUtils.js";import{singleton as T}from"./singleton.js";import{DANGEROUS_ENV_VARS as l,filterDangerousEnvVars as C,maskSensitiveOutput as O,parseShellArgs as I}from"./securityHelpers.js";import{sleep as P}from"./sleep.js";import{STANDARD_ENVIRONMENTS as D,STRUCTURAL_ENVIRONMENTS as M,isValidEnvironment as c,ENVIRONMENT_LABELS as v,getEnvironmentLabel as b}from"./environments.js";import{RESOURCE_CATEGORIES as L,categoriseResource as U,getExpectedDuration as F,getFriendlyResourceType as G}from"./resourceCategorisation.js";import{parseGitRemoteUrl as X}from"./gitRemoteParser.js";import{abbreviateRegion as y}from"./regions.js";import{SCOPE_VALUES as K}from"./tokenScopes.js";import{deriveRegionsFromOrgConfig as j,deriveTargets as q,deriveAllTargets as w,findTarget as z,generateTargetName as J}from"./targets.js";import{buildAppConfigPath as W}from"./appPath.js";import{findInfrastructurePaths as $,findBoundaryPath as ee,isInfrastructureFile as re}from"./findInfrastructurePaths.js";import{inferContainerFromCandidates as te}from"./inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as Ee,isReservedAppName as ie}from"./reservedAppNames.js";import{deriveContentHashTag as me}from"./deriveContentHashTag.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as pe,EXPECTED_SCHEMA_VERSION_ENV as fe,EXPECTED_SCHEMA_VERSION_TOOL_ENV as Se,PRISMA_MIGRATION_DIR_RE as Ne}from"./migration/constants.js";export{l as DANGEROUS_ENV_VARS,o as DNS_APEX,v as ENVIRONMENT_LABELS,fe as EXPECTED_SCHEMA_VERSION_ENV,Se as EXPECTED_SCHEMA_VERSION_TOOL_ENV,pe as MIGRATION_SNAPSHOT_NAME_PREFIX,Ne as PRISMA_MIGRATION_DIR_RE,Ee as RESERVED_APP_NAMES,L as RESOURCE_CATEGORIES,K as SCOPE_VALUES,D as STANDARD_ENVIRONMENTS,M as STRUCTURAL_ENVIRONMENTS,y as abbreviateRegion,W as buildAppConfigPath,s as capitalise,U as categoriseResource,w as deriveAllTargets,me as deriveContentHashTag,j as deriveRegionsFromOrgConfig,q as deriveTargets,C as filterDangerousEnvVars,ee as findBoundaryPath,$ as findInfrastructurePaths,z as findTarget,A as formatErrorString,J as generateTargetName,t as getDomainExportNames,b as getEnvironmentLabel,g as getErrorCode,N as getErrorMessage,_ as getErrorStack,F as getExpectedDuration,G as getFriendlyResourceType,p as getSafeZoneName,R as hasErrorCode,te as inferContainerFromCandidates,re as isInfrastructureFile,ie as isReservedAppName,c as isValidEnvironment,O as maskSensitiveOutput,S as normaliseError,X as parseGitRemoteUrl,I as parseShellArgs,T as singleton,P as sleep,i as toKebab,E as toPascalCase,m as toScreamingSnake,n as toValidDatabaseName};
1
+ import{DNS_APEX as o,getDomainExportNames as t}from"./domainExports.js";import{toPascalCase as a,toKebab as i,toValidDatabaseName as n,toScreamingSnake as m,capitalise as s,getSafeZoneName as S}from"./caseConversion.js";import{normaliseError as N,getErrorMessage as R,hasErrorCode as _,getErrorCode as f,getErrorStack as g,formatErrorString as A}from"./errorUtils.js";import{singleton as T}from"./singleton.js";import{DANGEROUS_ENV_VARS as I,filterDangerousEnvVars as O,maskSensitiveOutput as d,parseShellArgs as l}from"./securityHelpers.js";import{sleep as P}from"./sleep.js";import{STANDARD_ENVIRONMENTS as M,STRUCTURAL_ENVIRONMENTS as u,isValidEnvironment as c,ENVIRONMENT_LABELS as v,getEnvironmentLabel as b}from"./environments.js";import{RESOURCE_CATEGORIES as L,categoriseResource as U,getExpectedDuration as h,getFriendlyResourceType as G}from"./resourceCategorisation.js";import{parseGitRemoteUrl as X}from"./gitRemoteParser.js";import{abbreviateRegion as y}from"./regions.js";import{SCOPE_VALUES as B}from"./tokenScopes.js";import{deriveRegionsFromOrgConfig as j,deriveTargets as q,deriveAllTargets as w,findTarget as z,generateTargetName as J}from"./targets.js";import{buildAppConfigPath as W}from"./appPath.js";import{findInfrastructurePaths as $,findBoundaryPath as ee,isInfrastructureFile as re}from"./findInfrastructurePaths.js";import{inferContainerFromCandidates as te}from"./inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as ae,isReservedAppName as ie}from"./reservedAppNames.js";import{deriveContentHashTag as me}from"./deriveContentHashTag.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as Se,EXPECTED_SCHEMA_VERSION_ENV as pe,EXPECTED_SCHEMA_VERSION_TOOL_ENV as Ne,EXPECTED_CH_SCHEMA_VERSION_ENV as Re,PRISMA_MIGRATION_DIR_RE as _e,CLICKHOUSE_MIGRATION_SKIP_RE as fe}from"./migration/constants.js";export{fe as CLICKHOUSE_MIGRATION_SKIP_RE,I as DANGEROUS_ENV_VARS,o as DNS_APEX,v as ENVIRONMENT_LABELS,Re as EXPECTED_CH_SCHEMA_VERSION_ENV,pe as EXPECTED_SCHEMA_VERSION_ENV,Ne as EXPECTED_SCHEMA_VERSION_TOOL_ENV,Se as MIGRATION_SNAPSHOT_NAME_PREFIX,_e as PRISMA_MIGRATION_DIR_RE,ae as RESERVED_APP_NAMES,L as RESOURCE_CATEGORIES,B as SCOPE_VALUES,M as STANDARD_ENVIRONMENTS,u as STRUCTURAL_ENVIRONMENTS,y as abbreviateRegion,W as buildAppConfigPath,s as capitalise,U as categoriseResource,w as deriveAllTargets,me as deriveContentHashTag,j as deriveRegionsFromOrgConfig,q as deriveTargets,O as filterDangerousEnvVars,ee as findBoundaryPath,$ as findInfrastructurePaths,z as findTarget,A as formatErrorString,J as generateTargetName,t as getDomainExportNames,b as getEnvironmentLabel,f as getErrorCode,R as getErrorMessage,g as getErrorStack,h as getExpectedDuration,G as getFriendlyResourceType,S as getSafeZoneName,_ as hasErrorCode,te as inferContainerFromCandidates,re as isInfrastructureFile,ie as isReservedAppName,c as isValidEnvironment,d as maskSensitiveOutput,N as normaliseError,X as parseGitRemoteUrl,l as parseShellArgs,T as singleton,P as sleep,i as toKebab,a as toPascalCase,m as toScreamingSnake,n as toValidDatabaseName};
@@ -27,8 +27,23 @@ export declare const EXPECTED_SCHEMA_VERSION_ENV: "EXPECTED_SCHEMA_VERSION";
27
27
  * runtime gate can dispatch to the matching consumer-side resolver.
28
28
  */
29
29
  export declare const EXPECTED_SCHEMA_VERSION_TOOL_ENV: "EXPECTED_SCHEMA_VERSION_TOOL";
30
+ /**
31
+ * Container env var name for the ClickHouse schema-version fail-fast gate.
32
+ * Sibling to `EXPECTED_SCHEMA_VERSION` (Postgres). Both gates can fire on
33
+ * one service when its `connections:` spans a relational DB AND a CH DB
34
+ * each declaring `migrations:`.
35
+ */
36
+ export declare const EXPECTED_CH_SCHEMA_VERSION_ENV: "EXPECTED_CH_SCHEMA_VERSION";
30
37
  /**
31
38
  * Prisma migration directory pattern: 14-digit timestamp + underscore prefix.
32
39
  * Sortable alphanumerically because the timestamp is fixed-width.
33
40
  */
34
41
  export declare const PRISMA_MIGRATION_DIR_RE: RegExp;
42
+ /**
43
+ * Files in a ClickHouse migrations dir that `runSqlMigrations` skips by
44
+ * default (dev-only SQL). Shared with `pickLatestClickHouseMigration` so the
45
+ * synth-time gate and the runtime applier agree on which file is "latest" —
46
+ * drift would inject an `EXPECTED_CH_SCHEMA_VERSION` the runner can never
47
+ * record, hard-failing every boot.
48
+ */
49
+ export declare const CLICKHOUSE_MIGRATION_SKIP_RE: RegExp;
@@ -1 +1 @@
1
- const E="fjall-premigrate",_="EXPECTED_SCHEMA_VERSION",I="EXPECTED_SCHEMA_VERSION_TOOL",O=/^\d{14}_/;export{_ as EXPECTED_SCHEMA_VERSION_ENV,I as EXPECTED_SCHEMA_VERSION_TOOL_ENV,E as MIGRATION_SNAPSHOT_NAME_PREFIX,O as PRISMA_MIGRATION_DIR_RE};
1
+ const E="fjall-premigrate",_="EXPECTED_SCHEMA_VERSION",I="EXPECTED_SCHEMA_VERSION_TOOL",S="EXPECTED_CH_SCHEMA_VERSION",C=/^\d{14}_/,O=/\.dev\.sql$/;export{O as CLICKHOUSE_MIGRATION_SKIP_RE,S as EXPECTED_CH_SCHEMA_VERSION_ENV,_ as EXPECTED_SCHEMA_VERSION_ENV,I as EXPECTED_SCHEMA_VERSION_TOOL_ENV,E as MIGRATION_SNAPSHOT_NAME_PREFIX,C as PRISMA_MIGRATION_DIR_RE};
@@ -1,3 +1,5 @@
1
- export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV, PRISMA_MIGRATION_DIR_RE } from "./constants.js";
1
+ export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV, EXPECTED_CH_SCHEMA_VERSION_ENV, PRISMA_MIGRATION_DIR_RE, CLICKHOUSE_MIGRATION_SKIP_RE } from "./constants.js";
2
2
  export { pickLatestPrismaMigration } from "./pickLatestPrismaMigration.js";
3
+ export { pickLatestClickHouseMigration } from "./pickLatestClickHouseMigration.js";
4
+ export { type MigrationsSqlClient, type VerifyExpectedSchemaVersionOpts, type VerifyExpectedSchemaVersionResult, verifyExpectedSchemaVersion } from "./verifyExpectedSchemaVersion.js";
3
5
  export { CLICKHOUSE_MANAGED_USERS_ENV, MANAGED_USER_NAME_PATTERN, userPasswordEnvName, ManagedUserNameSchema, ManagedUserNamesSchema, type ManagedUserName, type ManagedUserNames } from "./clickhouseSqlUsers.js";
@@ -1 +1 @@
1
- import{MIGRATION_SNAPSHOT_NAME_PREFIX as N,EXPECTED_SCHEMA_VERSION_ENV as e,EXPECTED_SCHEMA_VERSION_TOOL_ENV as a,PRISMA_MIGRATION_DIR_RE as A}from"./constants.js";import{pickLatestPrismaMigration as r}from"./pickLatestPrismaMigration.js";import{CLICKHOUSE_MANAGED_USERS_ENV as R,MANAGED_USER_NAME_PATTERN as I,userPasswordEnvName as m,ManagedUserNameSchema as o,ManagedUserNamesSchema as s}from"./clickhouseSqlUsers.js";export{R as CLICKHOUSE_MANAGED_USERS_ENV,e as EXPECTED_SCHEMA_VERSION_ENV,a as EXPECTED_SCHEMA_VERSION_TOOL_ENV,I as MANAGED_USER_NAME_PATTERN,N as MIGRATION_SNAPSHOT_NAME_PREFIX,o as ManagedUserNameSchema,s as ManagedUserNamesSchema,A as PRISMA_MIGRATION_DIR_RE,r as pickLatestPrismaMigration,m as userPasswordEnvName};
1
+ import{MIGRATION_SNAPSHOT_NAME_PREFIX as e,EXPECTED_SCHEMA_VERSION_ENV as r,EXPECTED_SCHEMA_VERSION_TOOL_ENV as N,EXPECTED_CH_SCHEMA_VERSION_ENV as S,PRISMA_MIGRATION_DIR_RE as a,CLICKHOUSE_MIGRATION_SKIP_RE as o}from"./constants.js";import{pickLatestPrismaMigration as I}from"./pickLatestPrismaMigration.js";import{pickLatestClickHouseMigration as R}from"./pickLatestClickHouseMigration.js";import{verifyExpectedSchemaVersion as t}from"./verifyExpectedSchemaVersion.js";import{CLICKHOUSE_MANAGED_USERS_ENV as s,MANAGED_USER_NAME_PATTERN as O,userPasswordEnvName as i,ManagedUserNameSchema as P,ManagedUserNamesSchema as T}from"./clickhouseSqlUsers.js";export{s as CLICKHOUSE_MANAGED_USERS_ENV,o as CLICKHOUSE_MIGRATION_SKIP_RE,S as EXPECTED_CH_SCHEMA_VERSION_ENV,r as EXPECTED_SCHEMA_VERSION_ENV,N as EXPECTED_SCHEMA_VERSION_TOOL_ENV,O as MANAGED_USER_NAME_PATTERN,e as MIGRATION_SNAPSHOT_NAME_PREFIX,P as ManagedUserNameSchema,T as ManagedUserNamesSchema,a as PRISMA_MIGRATION_DIR_RE,R as pickLatestClickHouseMigration,I as pickLatestPrismaMigration,i as userPasswordEnvName,t as verifyExpectedSchemaVersion};
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Returns the lexicographically-latest ClickHouse migration filename under
3
+ * `dir` (e.g. `"011-issue-brief-citations.sql"`). Matches the file selection
4
+ * `runSqlMigrations` performs at runtime so the synth-time gate value equals
5
+ * the value the runner records in `_schema_migrations.ch_version`.
6
+ *
7
+ * Synchronous I/O — safe at both CDK synth time (the new use site) and inside
8
+ * the migration runner script (the existing applier). Throws when the
9
+ * directory contains no `.sql` files (after stripping `.dev.sql`).
10
+ */
11
+ export declare function pickLatestClickHouseMigration(dir: string): string;
@@ -0,0 +1 @@
1
+ import{readdirSync as o}from"node:fs";import{CLICKHOUSE_MIGRATION_SKIP_RE as n}from"./constants.js";function f(t){const i=o(t,{withFileTypes:!0}).filter(e=>e.isFile()).map(e=>e.name).filter(e=>e.endsWith(".sql")).filter(e=>!n.test(e)).sort(),r=i[i.length-1];if(r===void 0)throw new Error(`No ClickHouse migration files found under ${t} (looked for *.sql excluding *.dev.sql)`);return r}export{f as pickLatestClickHouseMigration};
@@ -0,0 +1 @@
1
+ import{mkdtempSync as c,mkdirSync as a,rmSync as m,writeFileSync as i}from"node:fs";import{tmpdir as f}from"node:os";import{join as s}from"node:path";import{afterEach as h,beforeEach as p,describe as n,expect as t,it as o}from"vitest";import{CLICKHOUSE_MIGRATION_SKIP_RE as l}from"./constants.js";import{pickLatestClickHouseMigration as r}from"./pickLatestClickHouseMigration.js";n("pickLatestClickHouseMigration",()=>{let e;p(()=>{e=c(s(f(),"fjall-ch-test-"))}),h(()=>{m(e,{recursive:!0,force:!0})}),o("returns the lexicographically-latest *.sql filename",()=>{i(s(e,"001-schema.sql"),""),i(s(e,"010-issue-briefs.sql"),""),i(s(e,"005-optimisation-mvs.sql"),""),t(r(e)).toBe("010-issue-briefs.sql")}),o("skips *.dev.sql so synth and runner agree on 'latest'",()=>{i(s(e,"001-schema.sql"),""),i(s(e,"002-permissions.sql"),""),i(s(e,"999-local-only.dev.sql"),""),t(r(e)).toBe("002-permissions.sql")}),o("ignores non-SQL files (shell backfills, configs)",()=>{i(s(e,"001-schema.sql"),""),i(s(e,"009-mv-ttl-backfill.sh"),""),i(s(e,"users.xml"),""),i(s(e,"config.xml"),""),t(r(e)).toBe("001-schema.sql")}),o("ignores subdirectories that look like SQL",()=>{i(s(e,"001-schema.sql"),""),a(s(e,"999-not-a-file.sql")),t(r(e)).toBe("001-schema.sql")}),o("throws when the directory contains no eligible SQL files",()=>{i(s(e,"999-only.dev.sql"),""),i(s(e,"config.xml"),""),t(()=>r(e)).toThrow(/No ClickHouse migration files found/)}),o("throws on an empty directory",()=>{t(()=>r(e)).toThrow(/No ClickHouse migration files found/)})}),n("CLICKHOUSE_MIGRATION_SKIP_RE",()=>{o("matches *.dev.sql",()=>{t(l.test("001-users.dev.sql")).toBe(!0)}),o("rejects bare *.sql",()=>{t(l.test("001-schema.sql")).toBe(!1)}),o("regex identity pin \u2014 drift here decouples synth gate from runner applier",()=>{t(l.source).toBe(String.raw`\.dev\.sql$`)})});
@@ -0,0 +1,57 @@
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
5
+ * resolves the expected version FROM disk at synth/runner time); this
6
+ * helper closes the loop by reading what the DB actually has APPLIED.
7
+ *
8
+ * Consumer supplies the driver via the {@link MigrationsSqlClient} shim so
9
+ * the package stays driver-agnostic (pg, mysql2, postgres.js, Drizzle
10
+ * session, raw Prisma `$queryRawUnsafe`, …). The shim's single `query(sql)`
11
+ * surface is intentionally narrower than any real driver — callers wrap.
12
+ *
13
+ * 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.
16
+ * - `tool: "custom"` lets a caller pass a `{ sql, column }` pair when the
17
+ * migration tool's metadata table doesn't match the Prisma shape (e.g.
18
+ * Drizzle's `__drizzle_migrations.hash`, Knex's `knex_migrations.name`).
19
+ *
20
+ * Returns `{ matches, expected, actual }` rather than throwing on mismatch:
21
+ * the boot gate's caller owns the exit-code / log shape so the helper can
22
+ * be reused by integration tests, dashboards, and CLI checks alike.
23
+ */
24
+ /**
25
+ * Minimal SQL-driver shim. Compatible with `pg.Client.query`, `mysql2.query`,
26
+ * `postgres()(sql)`, and Drizzle's `db.session.execute()` via a thin adapter.
27
+ */
28
+ export interface MigrationsSqlClient {
29
+ query(sql: string): Promise<{
30
+ rows: ReadonlyArray<Record<string, unknown>>;
31
+ }>;
32
+ }
33
+ export interface VerifyExpectedSchemaVersionOpts {
34
+ /** Migration tool whose metadata table to inspect. */
35
+ tool: "prisma" | "custom";
36
+ /** Expected version (typically read from an env var baked into the image). */
37
+ expected: string;
38
+ /** SQL driver shim. Consumer owns connection lifecycle. */
39
+ client: MigrationsSqlClient;
40
+ /** Abort signal honoured before the query is issued. */
41
+ signal?: AbortSignal;
42
+ /**
43
+ * Required when `tool: "custom"`. `sql` MUST return a single row whose
44
+ * `column` holds the latest applied migration's name/hash.
45
+ */
46
+ customQuery?: {
47
+ sql: string;
48
+ column: string;
49
+ };
50
+ }
51
+ export interface VerifyExpectedSchemaVersionResult {
52
+ matches: boolean;
53
+ expected: string;
54
+ /** `null` when no migrations have been applied (empty result set). */
55
+ actual: string | null;
56
+ }
57
+ export declare function verifyExpectedSchemaVersion(opts: VerifyExpectedSchemaVersionOpts): Promise<VerifyExpectedSchemaVersionResult>;
@@ -0,0 +1 @@
1
+ const c="SELECT migration_name FROM _prisma_migrations WHERE finished_at IS NOT NULL ORDER BY finished_at DESC LIMIT 1",s="migration_name";async function a(e){if(e.signal?.aborted)throw new Error("verifyExpectedSchemaVersion: aborted by signal before query");let r,n;if(e.tool==="prisma")r=c,n=s;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 t=(await e.client.query(r)).rows[0],o=t!==void 0?t[n]:void 0,i=typeof o=="string"?o:null;return{matches:i===e.expected,expected:e.expected,actual:i}}export{a as verifyExpectedSchemaVersion};
@@ -0,0 +1 @@
1
+ import{describe as r,expect as a,it as o,vi as l}from"vitest";import{verifyExpectedSchemaVersion as i}from"./verifyExpectedSchemaVersion.js";function n(t){const e=[];return{client:{query:l.fn(async c=>(e.push(c),{rows:t}))},queries:e}}r("verifyExpectedSchemaVersion",()=>{r('tool: "prisma"',()=>{o("returns matches=true when actual === expected",async()=>{const{client:t,queries:e}=n([{migration_name:"20260520000000_add_widgets"}]),s=await i({tool:"prisma",expected:"20260520000000_add_widgets",client:t});a(s).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")}),o("returns matches=false with actual when DB trails image",async()=>{const{client:t}=n([{migration_name:"20260519000000_old"}]),e=await i({tool:"prisma",expected:"20260520000000_add_widgets",client:t});a(e).toEqual({matches:!1,expected:"20260520000000_add_widgets",actual:"20260519000000_old"})}),o("returns actual=null on empty result set",async()=>{const{client:t}=n([]),e=await i({tool:"prisma",expected:"20260520000000_add_widgets",client:t});a(e).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:t}=n([{migration_name:12345}]),e=await i({tool:"prisma",expected:"x",client:t});a(e.actual).toBeNull(),a(e.matches).toBe(!1)})}),r('tool: "custom"',()=>{o("uses customQuery sql and column",async()=>{const{client:t,queries:e}=n([{hash:"deadbeef"}]),s=await i({tool:"custom",expected:"deadbeef",client:t,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(e[0]).toBe("SELECT hash FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1")}),o("throws when customQuery is omitted",async()=>{const{client:t}=n([]);await a(i({tool:"custom",expected:"x",client:t})).rejects.toThrow(/tool="custom" requires customQuery/)})}),r("abort signal",()=>{o("short-circuits before query when signal is already aborted",async()=>{const{client:t,queries:e}=n([{migration_name:"irrelevant"}]),s=new AbortController;s.abort(),await a(i({tool:"prisma",expected:"x",client:t,signal:s.signal})).rejects.toThrow(/aborted by signal before query/),a(e).toEqual([])})})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/util",
3
- "version": "0.99.4",
3
+ "version": "0.102.0",
4
4
  "description": "Common utility methods",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -117,5 +117,5 @@
117
117
  "engines": {
118
118
  "node": ">=22.0.0"
119
119
  },
120
- "gitHead": "4d52f0059ebb467b4a63603ed397dae36811d735"
120
+ "gitHead": "04a4f13181c261cc063786eae527fa82c90a610e"
121
121
  }