@taskcast/postgres 0.3.1 → 1.1.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
@@ -1,4 +1,5 @@
1
1
  export { PostgresLongTermStore } from './long-term.js';
2
+ export { runMigrations, loadMigrationFiles } from './migration-runner.js';
2
3
  import { PostgresLongTermStore } from './long-term.js';
3
4
  export interface PostgresAdapterOptions {
4
5
  url: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAGtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAEtD,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAG5F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAGzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAEtD,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAG5F"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { PostgresLongTermStore } from './long-term.js';
2
+ export { runMigrations, loadMigrationFiles } from './migration-runner.js';
2
3
  import postgres from 'postgres';
3
4
  import { PostgresLongTermStore } from './long-term.js';
4
5
  export function createPostgresAdapter(options) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAEtD,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAOtD,MAAM,UAAU,qBAAqB,CAAC,OAA+B;IACnE,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAA;IAC3E,OAAO,IAAI,qBAAqB,CAAC,GAAG,CAAC,CAAA;AACvC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAEzE,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAOtD,MAAM,UAAU,qBAAqB,CAAC,OAA+B;IACnE,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAA;IAC3E,OAAO,IAAI,qBAAqB,CAAC,GAAG,CAAC,CAAA;AACvC,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type postgres from 'postgres';
2
+ export interface MigrationFile {
3
+ version: number;
4
+ description: string;
5
+ sql: string;
6
+ checksum: Buffer;
7
+ filename: string;
8
+ }
9
+ export interface MigrationResult {
10
+ applied: string[];
11
+ skipped: string[];
12
+ }
13
+ /**
14
+ * Parse a migration filename into version and description.
15
+ *
16
+ * Format: `{version}_{description}.sql`
17
+ * - version: numeric prefix (e.g. "001" → 1)
18
+ * - description: rest of filename with underscores replaced by spaces
19
+ *
20
+ * Returns null if the filename doesn't match the expected format.
21
+ */
22
+ export declare function parseMigrationFilename(filename: string): {
23
+ version: number;
24
+ description: string;
25
+ } | null;
26
+ /**
27
+ * Compute a SHA-384 hash of SQL content, matching sqlx's checksum behavior.
28
+ * Returns a 48-byte Buffer.
29
+ */
30
+ export declare function computeChecksum(sql: string): Buffer;
31
+ /**
32
+ * Read and parse all *.sql migration files from a directory, sorted by version.
33
+ */
34
+ export declare function loadMigrationFiles(migrationsDir: string): MigrationFile[];
35
+ /**
36
+ * Run pending migrations and verify checksums of already-applied ones.
37
+ *
38
+ * This is fully compatible with sqlx's _sqlx_migrations table — the Rust
39
+ * server and the TS server can share the same database and track migrations
40
+ * in the same table.
41
+ */
42
+ export declare function runMigrations(sql: ReturnType<typeof postgres>, migrationsDir: string): Promise<MigrationResult>;
43
+ //# sourceMappingURL=migration-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-runner.d.ts","sourceRoot":"","sources":["../src/migration-runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAA;AAEpC,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAiBxG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,EAAE,CAoBzE;AAaD;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,EAChC,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,eAAe,CAAC,CAkE1B"}
@@ -0,0 +1,122 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync, readdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ /**
5
+ * Parse a migration filename into version and description.
6
+ *
7
+ * Format: `{version}_{description}.sql`
8
+ * - version: numeric prefix (e.g. "001" → 1)
9
+ * - description: rest of filename with underscores replaced by spaces
10
+ *
11
+ * Returns null if the filename doesn't match the expected format.
12
+ */
13
+ export function parseMigrationFilename(filename) {
14
+ if (!filename.endsWith('.sql'))
15
+ return null;
16
+ const withoutExt = filename.slice(0, -4); // remove ".sql"
17
+ const underscoreIdx = withoutExt.indexOf('_');
18
+ if (underscoreIdx === -1)
19
+ return null; // no underscore means no description
20
+ const versionStr = withoutExt.slice(0, underscoreIdx);
21
+ const descriptionRaw = withoutExt.slice(underscoreIdx + 1);
22
+ if (!/^\d+$/.test(versionStr))
23
+ return null;
24
+ if (descriptionRaw.length === 0)
25
+ return null;
26
+ const version = parseInt(versionStr, 10);
27
+ const description = descriptionRaw.replace(/_/g, ' ');
28
+ return { version, description };
29
+ }
30
+ /**
31
+ * Compute a SHA-384 hash of SQL content, matching sqlx's checksum behavior.
32
+ * Returns a 48-byte Buffer.
33
+ */
34
+ export function computeChecksum(sql) {
35
+ return createHash('sha384').update(sql).digest();
36
+ }
37
+ /**
38
+ * Read and parse all *.sql migration files from a directory, sorted by version.
39
+ */
40
+ export function loadMigrationFiles(migrationsDir) {
41
+ const entries = readdirSync(migrationsDir).filter((f) => f.endsWith('.sql')).sort();
42
+ const files = [];
43
+ for (const filename of entries) {
44
+ const parsed = parseMigrationFilename(filename);
45
+ if (!parsed)
46
+ continue;
47
+ const content = readFileSync(join(migrationsDir, filename), 'utf8');
48
+ files.push({
49
+ version: parsed.version,
50
+ description: parsed.description,
51
+ sql: content,
52
+ checksum: computeChecksum(content),
53
+ filename,
54
+ });
55
+ }
56
+ files.sort((a, b) => a.version - b.version);
57
+ return files;
58
+ }
59
+ const CREATE_MIGRATIONS_TABLE = `
60
+ CREATE TABLE IF NOT EXISTS _sqlx_migrations (
61
+ version BIGINT PRIMARY KEY,
62
+ description TEXT NOT NULL,
63
+ installed_on TIMESTAMPTZ NOT NULL DEFAULT now(),
64
+ success BOOLEAN NOT NULL,
65
+ checksum BYTEA NOT NULL,
66
+ execution_time BIGINT NOT NULL
67
+ )
68
+ `;
69
+ /**
70
+ * Run pending migrations and verify checksums of already-applied ones.
71
+ *
72
+ * This is fully compatible with sqlx's _sqlx_migrations table — the Rust
73
+ * server and the TS server can share the same database and track migrations
74
+ * in the same table.
75
+ */
76
+ export async function runMigrations(sql, migrationsDir) {
77
+ // 1. Ensure the migrations table exists
78
+ await sql.unsafe(CREATE_MIGRATIONS_TABLE);
79
+ // 2. Check for dirty migrations (success = false)
80
+ const dirty = await sql.unsafe('SELECT version, description FROM _sqlx_migrations WHERE success = false');
81
+ if (dirty.length > 0) {
82
+ const row = dirty[0];
83
+ throw new Error(`Dirty migration found: version ${row['version']} (${row['description']}). ` +
84
+ 'A previous migration failed. Please fix it manually before running migrations.');
85
+ }
86
+ // 3. Load local files and query applied migrations
87
+ const localFiles = loadMigrationFiles(migrationsDir);
88
+ const appliedRows = await sql.unsafe('SELECT version, checksum FROM _sqlx_migrations ORDER BY version');
89
+ const appliedMap = new Map();
90
+ for (const row of appliedRows) {
91
+ appliedMap.set(Number(row['version']), Buffer.from(row['checksum']));
92
+ }
93
+ const result = { applied: [], skipped: [] };
94
+ // 4. Process each migration file
95
+ for (const file of localFiles) {
96
+ const appliedChecksum = appliedMap.get(file.version);
97
+ if (appliedChecksum) {
98
+ // Already applied — verify checksum
99
+ if (!file.checksum.equals(appliedChecksum)) {
100
+ throw new Error(`Checksum mismatch for migration ${file.filename} (version ${file.version}). ` +
101
+ 'The applied migration differs from the local file.');
102
+ }
103
+ result.skipped.push(file.filename);
104
+ }
105
+ else {
106
+ // Not yet applied — execute it
107
+ const startTime = process.hrtime.bigint();
108
+ await sql.begin(async (tx) => {
109
+ // Insert the migration record first with execution_time = -1 (in-progress marker)
110
+ await tx.unsafe('INSERT INTO _sqlx_migrations (version, description, success, checksum, execution_time) VALUES ($1, $2, TRUE, $3, -1)', [file.version, file.description, file.checksum]);
111
+ // Execute the migration SQL
112
+ await tx.unsafe(file.sql);
113
+ });
114
+ // Update execution_time with actual nanoseconds (outside transaction, matching sqlx behavior)
115
+ const elapsed = process.hrtime.bigint() - startTime;
116
+ await sql.unsafe('UPDATE _sqlx_migrations SET execution_time = $1 WHERE version = $2', [elapsed.toString(), file.version]);
117
+ result.applied.push(file.filename);
118
+ }
119
+ }
120
+ return result;
121
+ }
122
+ //# sourceMappingURL=migration-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-runner.js","sourceRoot":"","sources":["../src/migration-runner.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskcast/postgres",
3
- "version": "0.3.1",
3
+ "version": "1.1.0",
4
4
  "description": "PostgreSQL long-term store adapter for Taskcast.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "postgres": "^3.4.5",
32
- "@taskcast/core": "0.3.1"
32
+ "@taskcast/core": "1.1.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "typescript": "^5.7.0",