hazo_env 0.2.0 → 0.4.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.
Files changed (43) hide show
  1. package/CHANGE_LOG.md +47 -0
  2. package/README.md +83 -17
  3. package/SETUP_CHECKLIST.md +32 -0
  4. package/config/hazo_env_config.ini.sample +16 -0
  5. package/dist/cli.js +21 -7
  6. package/dist/doctor.d.ts.map +1 -1
  7. package/dist/doctor.js +168 -3
  8. package/dist/env.server.d.ts +7 -1
  9. package/dist/env.server.d.ts.map +1 -1
  10. package/dist/env.server.js +25 -2
  11. package/dist/index.client.d.ts +6 -2
  12. package/dist/index.client.d.ts.map +1 -1
  13. package/dist/index.client.js +15 -9
  14. package/dist/index.d.ts +5 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +5 -2
  17. package/dist/migrate/db.d.ts +3 -0
  18. package/dist/migrate/db.d.ts.map +1 -1
  19. package/dist/migrate/db.js +6 -1
  20. package/dist/migrate/db.postgrest.d.ts +4 -0
  21. package/dist/migrate/db.postgrest.d.ts.map +1 -0
  22. package/dist/migrate/db.postgrest.js +93 -0
  23. package/dist/migrate/files.d.ts.map +1 -1
  24. package/dist/migrate/files.js +2 -4
  25. package/dist/migrate/progress.d.ts +5 -0
  26. package/dist/migrate/progress.d.ts.map +1 -0
  27. package/dist/migrate/progress.js +24 -0
  28. package/dist/migrate/run.d.ts.map +1 -1
  29. package/dist/migrate/run.js +60 -25
  30. package/dist/migrate/snapshot.d.ts.map +1 -1
  31. package/dist/migrate/snapshot.js +3 -0
  32. package/dist/migrate/verify.d.ts +1 -0
  33. package/dist/migrate/verify.d.ts.map +1 -1
  34. package/dist/migrate/verify.js +120 -30
  35. package/dist/resolve/files.d.ts +6 -0
  36. package/dist/resolve/files.d.ts.map +1 -1
  37. package/dist/resolve/files.js +23 -0
  38. package/dist/resolve/migrate.d.ts +3 -0
  39. package/dist/resolve/migrate.d.ts.map +1 -0
  40. package/dist/resolve/migrate.js +29 -0
  41. package/dist/types/index.d.ts +13 -0
  42. package/dist/types/index.d.ts.map +1 -1
  43. package/package.json +9 -9
@@ -1,7 +1,7 @@
1
1
  // hazo_env/src/index.client.ts — Client-safe entry point
2
2
  import { getCurrentEnv, HazoError } from 'hazo_core/client';
3
3
  /** Normalize raw env string to canonical form */
4
- function normalize(raw) {
4
+ export function normalize(raw) {
5
5
  if (raw === 'development')
6
6
  return 'dev';
7
7
  if (raw === 'production')
@@ -12,16 +12,22 @@ function normalize(raw) {
12
12
  export function getEnv() {
13
13
  return normalize(getCurrentEnv());
14
14
  }
15
+ const DEFAULT_ROLE_MAP = {
16
+ dev: 'development',
17
+ test: 'test',
18
+ staging: 'staging',
19
+ prod: 'production',
20
+ };
21
+ /** Get the role map, merging any build-time injected overrides from __HAZO_ENV_ROLES__ */
22
+ export function getRoleMap() {
23
+ const g = globalThis;
24
+ const injected = g['__HAZO_ENV_ROLES__'];
25
+ return { ...DEFAULT_ROLE_MAP, ...(injected ?? {}) };
26
+ }
15
27
  /** Get the broad role for the current environment */
16
28
  export function getEnvRole(env) {
17
- const e = env ?? getEnv();
18
- if (e === 'dev')
19
- return 'development';
20
- if (e === 'test')
21
- return 'test';
22
- if (e === 'staging')
23
- return 'staging';
24
- return 'production';
29
+ const e = normalize(env ?? getEnv());
30
+ return getRoleMap()[e] ?? 'development';
25
31
  }
26
32
  export function isDev(env) { return getEnvRole(env) === 'development'; }
27
33
  export function isTest(env) { return getEnvRole(env) === 'test'; }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { getEnv, getEnvRole, isDev, isTest, isStaging, isProd } from './index.client.js';
2
- export { getPattern, listEnvs, assertEnv, describeEnv } from './env.server.js';
1
+ export { getEnv, isDev, isTest, isStaging, isProd } from './index.client.js';
2
+ export { getPattern, listEnvs, assertEnv, describeEnv, getEnvRole, getRoleMap } from './env.server.js';
3
3
  export * from './types/index.js';
4
4
  export * from './resolve/secrets.js';
5
5
  export * from './resolve/connect.js';
@@ -8,7 +8,10 @@ export * from './doctor.js';
8
8
  export { runMigration } from './migrate/run.js';
9
9
  export { verifyFiles } from './migrate/verify.js';
10
10
  export { takeSnapshot, restoreSnapshot } from './migrate/snapshot.js';
11
+ export { writeMigrationProgress, readMigrationProgress, clearMigrationProgress } from './migrate/progress.js';
11
12
  export { registerMask } from './mask/registry.js';
13
+ export { loadRuleset, syncRulesetFromIni, parseIniRules } from './mask/ruleset.js';
14
+ export type { MaskRule } from './mask/ruleset.js';
12
15
  export type { MaskTransform } from './types/index.js';
13
16
  export * from './lib/index.js';
14
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEzF,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE/E,cAAc,kBAAkB,CAAC;AAEjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEtE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEvG,cAAc,kBAAkB,CAAC;AAEjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE9G,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACnF,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,cAAc,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // hazo_env/src/index.ts — Server entry point
2
2
  // Pure client helpers re-exported explicitly (getPattern/listEnvs/assertEnv/describeEnv are overridden below)
3
- export { getEnv, getEnvRole, isDev, isTest, isStaging, isProd } from './index.client.js';
3
+ export { getEnv, isDev, isTest, isStaging, isProd } from './index.client.js';
4
4
  // Server-aware overrides
5
- export { getPattern, listEnvs, assertEnv, describeEnv } from './env.server.js';
5
+ export { getPattern, listEnvs, assertEnv, describeEnv, getEnvRole, getRoleMap } from './env.server.js';
6
6
  // Types
7
7
  export * from './types/index.js';
8
8
  // Resolver + secrets
@@ -15,7 +15,10 @@ export * from './doctor.js';
15
15
  export { runMigration } from './migrate/run.js';
16
16
  export { verifyFiles } from './migrate/verify.js';
17
17
  export { takeSnapshot, restoreSnapshot } from './migrate/snapshot.js';
18
+ export { writeMigrationProgress, readMigrationProgress, clearMigrationProgress } from './migrate/progress.js';
18
19
  // Masking registry
19
20
  export { registerMask } from './mask/registry.js';
21
+ // Masking ruleset
22
+ export { loadRuleset, syncRulesetFromIni, parseIniRules } from './mask/ruleset.js';
20
23
  // Lib
21
24
  export * from './lib/index.js';
@@ -1,6 +1,9 @@
1
1
  import type { HazoConnectAdapter } from 'hazo_connect';
2
2
  export interface DbCopyOptions {
3
+ type?: 'sqlite' | 'postgrest';
3
4
  tables?: '*' | string[];
5
+ preserve?: string[];
6
+ pkOverrides?: Record<string, string>;
4
7
  scrubHook?: (tableName: string, row: Record<string, unknown>) => Record<string, unknown>;
5
8
  onProgress?: (msg: string) => void;
6
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/migrate/db.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AA6BD,wBAAsB,MAAM,CAC1B,UAAU,EAAE,kBAAkB,EAC9B,UAAU,EAAE,kBAAkB,EAC9B,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,CA+GvB"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/migrate/db.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC9B,MAAM,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AA6BD,wBAAsB,MAAM,CAC1B,UAAU,EAAE,kBAAkB,EAC9B,UAAU,EAAE,kBAAkB,EAC9B,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,CAqHvB"}
@@ -21,6 +21,11 @@ async function getMigrationNames(adapter) {
21
21
  return rows.map((r) => r.name);
22
22
  }
23
23
  export async function copyDb(srcAdapter, tgtAdapter, opts = {}) {
24
+ // PostgREST path: dispatch to dedicated implementation
25
+ if (opts.type === 'postgrest') {
26
+ const { copyDbPostgrest } = await import('./db.postgrest.js');
27
+ return copyDbPostgrest(srcAdapter, tgtAdapter, opts);
28
+ }
24
29
  const connect = await optional_import('hazo_connect/server');
25
30
  if (!connect) {
26
31
  throw new HazoError({
@@ -29,7 +34,7 @@ export async function copyDb(srcAdapter, tgtAdapter, opts = {}) {
29
34
  message: 'hazo_connect is required for DB migration. Install hazo_connect to continue.',
30
35
  });
31
36
  }
32
- // Schema parity check via _migrations
37
+ // Schema parity check via _migrations (SQLite only — PostgREST apps may not use _migrations)
33
38
  const srcMigrations = await getMigrationNames(srcAdapter);
34
39
  const tgtMigrations = await getMigrationNames(tgtAdapter);
35
40
  if (srcMigrations === null || tgtMigrations === null) {
@@ -0,0 +1,4 @@
1
+ import type { HazoConnectAdapter } from 'hazo_connect';
2
+ import type { DbCopyOptions, DbCopyResult } from './db.js';
3
+ export declare function copyDbPostgrest(srcAdapter: HazoConnectAdapter, tgtAdapter: HazoConnectAdapter, opts?: DbCopyOptions): Promise<DbCopyResult>;
4
+ //# sourceMappingURL=db.postgrest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.postgrest.d.ts","sourceRoot":"","sources":["../../src/migrate/db.postgrest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAM3D,wBAAsB,eAAe,CACnC,UAAU,EAAE,kBAAkB,EAC9B,UAAU,EAAE,kBAAkB,EAC9B,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,CA6GvB"}
@@ -0,0 +1,93 @@
1
+ // hazo_env/src/migrate/db.postgrest.ts — PostgREST-based DB copy (REST-only, no raw SQL)
2
+ import { HazoError, optional_import } from 'hazo_core';
3
+ import { isSecretColumn } from '../lib/secret_columns.js';
4
+ const PAGE_SIZE = 500;
5
+ export async function copyDbPostgrest(srcAdapter, tgtAdapter, opts = {}) {
6
+ const connect = await optional_import('hazo_connect/server');
7
+ if (!connect) {
8
+ throw new HazoError({
9
+ code: 'HAZO_ENV_MISSING_DEPENDENCY',
10
+ pkg: 'hazo_env',
11
+ message: 'hazo_connect is required for DB migration. Install hazo_connect to continue.',
12
+ });
13
+ }
14
+ // Table list is required for PostgREST (no auto-discovery — FK ordering unsafe without explicit list)
15
+ if (!opts.tables || opts.tables === '*') {
16
+ throw new HazoError({
17
+ code: 'HAZO_ENV_MISSING_TABLE_LIST',
18
+ pkg: 'hazo_env',
19
+ message: 'PostgREST migration requires an explicit table list. ' +
20
+ 'Set [migrate] tables = ... in hazo_env_config.ini or pass --tables to the CLI.',
21
+ });
22
+ }
23
+ const orderedTables = opts.tables;
24
+ const preserveSet = new Set(opts.preserve ?? []);
25
+ const pkOverrides = opts.pkOverrides ?? {};
26
+ // Tables to actually process (exclude preserve list)
27
+ const tablesToProcess = orderedTables.filter((t) => !preserveSet.has(t));
28
+ // --- Clear pass: reverse dependency order ---
29
+ opts.onProgress?.('Clearing target tables (reverse order for FK safety)...');
30
+ for (const table of [...tablesToProcess].reverse()) {
31
+ const pk = pkOverrides[table] ?? 'id';
32
+ try {
33
+ // PostgREST match-all DELETE: DELETE /table?pk=not.is.null
34
+ await tgtAdapter.rawQuery(`/${table}?${pk}=not.is.null`, { method: 'DELETE' });
35
+ opts.onProgress?.(`Cleared: ${table}`);
36
+ }
37
+ catch (err) {
38
+ const msg = err instanceof Error ? err.message : String(err);
39
+ opts.onProgress?.(`Warning: could not clear ${table}: ${msg}`);
40
+ }
41
+ }
42
+ let totalRows = 0;
43
+ let totalScrubbed = 0;
44
+ // --- Copy pass: forward dependency order ---
45
+ for (const table of tablesToProcess) {
46
+ opts.onProgress?.(`Copying table: ${table}`);
47
+ const pk = pkOverrides[table] ?? 'id';
48
+ const srcService = connect.createCrudService(srcAdapter, table, { autoId: false });
49
+ const tgtService = connect.createCrudService(tgtAdapter, table, { autoId: false });
50
+ let offset = 0;
51
+ let pageRows = [];
52
+ do {
53
+ pageRows = await srcService.list((qb) =>
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
+ qb.order(pk, 'asc').limit(PAGE_SIZE).offset(offset));
56
+ if (pageRows.length === 0)
57
+ break;
58
+ const batch = [];
59
+ for (let row of pageRows) {
60
+ const cleaned = {};
61
+ let didScrub = false;
62
+ for (const [col, val] of Object.entries(row)) {
63
+ if (isSecretColumn(col)) {
64
+ didScrub = true;
65
+ }
66
+ else {
67
+ cleaned[col] = val;
68
+ }
69
+ }
70
+ let processedRow = cleaned;
71
+ if (opts.scrubHook) {
72
+ const hooked = opts.scrubHook(table, cleaned);
73
+ if (hooked !== cleaned)
74
+ didScrub = true;
75
+ processedRow = hooked;
76
+ }
77
+ if (didScrub)
78
+ totalScrubbed++;
79
+ batch.push(processedRow);
80
+ }
81
+ if (batch.length > 0) {
82
+ await tgtService.insert(batch);
83
+ totalRows += batch.length;
84
+ opts.onProgress?.(` inserted ${batch.length} rows into ${table}`);
85
+ }
86
+ offset += PAGE_SIZE;
87
+ } while (pageRows.length === PAGE_SIZE);
88
+ }
89
+ // Note: PostgREST has no REST endpoint for NOTIFY. Schema reload only needed after DDL.
90
+ // If schema DDL changed since last reload, run: NOTIFY pgrst, 'reload schema' in Postgres.
91
+ opts.onProgress?.("DB copy complete. If schema DDL changed, run NOTIFY pgrst, 'reload schema' in Postgres.");
92
+ return { tables: tablesToProcess.length, rows: totalRows, scrubbed: totalScrubbed };
93
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/migrate/files.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AA4BD,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CA4ChF"}
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/migrate/files.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AA0BD,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CA4ChF"}
@@ -6,11 +6,9 @@
6
6
  // Failures are non-fatal warnings — the file copy still completes.
7
7
  import fs from 'node:fs';
8
8
  import path from 'node:path';
9
- import { resolveFilesConfig } from '../resolve/files.js';
9
+ import { resolveFilesRoot } from '../resolve/files.js';
10
10
  function resolveEnvFilesRoot(env) {
11
- // resolveFilesConfig returns the data root; env-specific files are at <root>/<env>/files
12
- const base = resolveFilesConfig().local.basePath;
13
- return path.join(base, env, 'files');
11
+ return resolveFilesRoot(env);
14
12
  }
15
13
  /**
16
14
  * Attempt to scrub a PDF file in-place using hazo_pdf/mask_pdf.
@@ -0,0 +1,5 @@
1
+ import type { MigrationProgress } from '../types/index.js';
2
+ export declare function writeMigrationProgress(progressDir: string, jobId: string, p: MigrationProgress): void;
3
+ export declare function readMigrationProgress(progressDir: string, jobId: string): MigrationProgress | null;
4
+ export declare function clearMigrationProgress(progressDir: string, jobId: string): void;
5
+ //# sourceMappingURL=progress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/migrate/progress.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAGrG;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAQlG;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAG/E"}
@@ -0,0 +1,24 @@
1
+ // hazo_env/src/migrate/progress.ts — Filesystem-backed migration progress store
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ export function writeMigrationProgress(progressDir, jobId, p) {
5
+ fs.mkdirSync(progressDir, { recursive: true });
6
+ fs.writeFileSync(path.join(progressDir, `${jobId}.json`), JSON.stringify(p), 'utf-8');
7
+ }
8
+ export function readMigrationProgress(progressDir, jobId) {
9
+ const filePath = path.join(progressDir, `${jobId}.json`);
10
+ try {
11
+ const raw = fs.readFileSync(filePath, 'utf-8');
12
+ return JSON.parse(raw);
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ export function clearMigrationProgress(progressDir, jobId) {
19
+ const filePath = path.join(progressDir, `${jobId}.json`);
20
+ try {
21
+ fs.unlinkSync(filePath);
22
+ }
23
+ catch { /* ignore */ }
24
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/migrate/run.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAkC,MAAM,mBAAmB,CAAC;AAgE3G,wBAAsB,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAiJlF"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/migrate/run.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAkC,MAAM,mBAAmB,CAAC;AA0D3G,wBAAsB,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkLlF"}
@@ -6,6 +6,7 @@
6
6
  // See: src/migrate/files.ts scrubPdfFile() and hazo_pdf/src/server/mask.ts
7
7
  import { HazoError, optional_import } from 'hazo_core';
8
8
  import { resolveConnectConfig } from '../resolve/connect.js';
9
+ import { resolveMigrateConfig } from '../resolve/migrate.js';
9
10
  import { resolveFilesConfig } from '../resolve/files.js';
10
11
  import { resolveTransport } from './transport.js';
11
12
  import { takeSnapshot } from './snapshot.js';
@@ -13,13 +14,13 @@ import { copyDb } from './db.js';
13
14
  import { copyFiles } from './files.js';
14
15
  import { verifyFiles } from './verify.js';
15
16
  import { auditMigration } from './audit.js';
16
- // Production roles that require explicit confirmation
17
- const PROD_ROLES = new Set(['prod', 'production']);
18
- function isProdEnv(env) {
19
- return PROD_ROLES.has(env.toLowerCase());
20
- }
17
+ import { writeMigrationProgress } from './progress.js';
18
+ import { getEnvRole } from '../env.server.js';
21
19
  function emitProgress(req, p) {
22
20
  req.onProgress?.(p);
21
+ if (req.jobId && req.progressDir) {
22
+ writeMigrationProgress(req.progressDir, req.jobId, p);
23
+ }
23
24
  }
24
25
  // Resolve the SQLite driver — prefer better-sqlite3 in tests when env var is set
25
26
  function getSqliteDriver() {
@@ -29,9 +30,8 @@ function getSqliteDriver() {
29
30
  }
30
31
  async function buildScrubHook(toEnv, scrubMode, tgtAdapter) {
31
32
  const maskKey = process.env['HAZO_ENV_MASK_KEY'];
32
- const shouldScrub = scrubMode !== 'none' &&
33
- maskKey &&
34
- (toEnv === 'test' || toEnv === 'staging');
33
+ const toRole = getEnvRole(toEnv);
34
+ const shouldScrub = scrubMode !== 'none' && !!maskKey && (toRole === 'test' || toRole === 'staging');
35
35
  if (!shouldScrub)
36
36
  return undefined;
37
37
  const { loadRuleset } = await import('../mask/ruleset.js');
@@ -85,14 +85,15 @@ export async function runMigration(req) {
85
85
  if (req.from === req.to) {
86
86
  throw new HazoError({ code: 'HAZO_ENV_INVALID_REQUEST', pkg: 'hazo_env', message: `Source and target environments must differ (got "${req.from}" for both).` });
87
87
  }
88
- if (isProdEnv(req.to) && !req.allowProdTarget) {
88
+ const toRole = getEnvRole(req.to);
89
+ if (toRole === 'production' && !req.allowProdTarget) {
89
90
  throw new HazoError({
90
91
  code: 'HAZO_ENV_PROD_TARGET_REFUSED',
91
92
  pkg: 'hazo_env',
92
93
  message: `Refusing to migrate to production environment "${req.to}". Pass allowProdTarget:true and a confirmToken to override.`,
93
94
  });
94
95
  }
95
- if (isProdEnv(req.to) && req.allowProdTarget && !req.confirmToken) {
96
+ if (toRole === 'production' && req.allowProdTarget && !req.confirmToken) {
96
97
  throw new HazoError({
97
98
  code: 'HAZO_ENV_PROD_TARGET_REFUSED',
98
99
  pkg: 'hazo_env',
@@ -122,20 +123,43 @@ export async function runMigration(req) {
122
123
  }
123
124
  // Create adapters
124
125
  function makeAdapter(dbConfig, readOnly = false) {
125
- if (dbConfig.type !== 'sqlite' || !dbConfig.sqlite) {
126
- throw new HazoError({
127
- code: 'HAZO_ENV_NOT_IMPLEMENTED',
128
- pkg: 'hazo_env',
129
- message: `Migration is only implemented for SQLite databases. Got type "${dbConfig.type}".`,
126
+ if (dbConfig.type === 'sqlite') {
127
+ if (!dbConfig.sqlite) {
128
+ throw new HazoError({
129
+ code: 'HAZO_ENV_NOT_IMPLEMENTED',
130
+ pkg: 'hazo_env',
131
+ message: 'SQLite config missing database_path.',
132
+ });
133
+ }
134
+ return connect.createHazoConnect({
135
+ type: 'sqlite',
136
+ sqlite: {
137
+ database_path: dbConfig.sqlite.database_path,
138
+ read_only: readOnly,
139
+ driver: getSqliteDriver(),
140
+ },
141
+ });
142
+ }
143
+ if (dbConfig.type === 'postgrest') {
144
+ if (!dbConfig.postgrest) {
145
+ throw new HazoError({
146
+ code: 'HAZO_ENV_NOT_IMPLEMENTED',
147
+ pkg: 'hazo_env',
148
+ message: 'PostgREST config missing base_url/api_key.',
149
+ });
150
+ }
151
+ return connect.createHazoConnect({
152
+ type: 'postgrest',
153
+ postgrest: {
154
+ base_url: dbConfig.postgrest.base_url,
155
+ api_key: dbConfig.postgrest.api_key,
156
+ },
130
157
  });
131
158
  }
132
- return connect.createHazoConnect({
133
- type: 'sqlite',
134
- sqlite: {
135
- database_path: dbConfig.sqlite.database_path,
136
- read_only: readOnly,
137
- driver: getSqliteDriver(),
138
- },
159
+ throw new HazoError({
160
+ code: 'HAZO_ENV_NOT_IMPLEMENTED',
161
+ pkg: 'hazo_env',
162
+ message: `Migration is not implemented for DB type "${dbConfig.type}".`,
139
163
  });
140
164
  }
141
165
  const srcAdapter = makeAdapter(fromDbConfig, true);
@@ -144,12 +168,16 @@ export async function runMigration(req) {
144
168
  emitProgress(req, { phase: 'schema-check', message: 'Checking schema parity', percent: 15 });
145
169
  let dbResult;
146
170
  let filesResult;
171
+ const migrateConfig = resolveMigrateConfig();
147
172
  // Step 5: DB copy
148
173
  if (includeDb) {
149
174
  emitProgress(req, { phase: 'db', message: 'Copying database tables', percent: 20 });
150
175
  const scrubHook = await buildScrubHook(req.to, scrubMode, tgtAdapter);
151
176
  const result = await copyDb(srcAdapter, tgtAdapter, {
152
- tables: req.tables,
177
+ type: fromDbConfig.type,
178
+ tables: req.tables ?? migrateConfig.tables,
179
+ preserve: migrateConfig.preserve,
180
+ pkOverrides: migrateConfig.pkOverrides,
153
181
  scrubHook,
154
182
  onProgress: (msg) => emitProgress(req, { phase: 'db', message: msg }),
155
183
  });
@@ -169,9 +197,16 @@ export async function runMigration(req) {
169
197
  // Step 7: Verify
170
198
  emitProgress(req, { phase: 'verify', message: 'Running post-migration verification', percent: 80 });
171
199
  const dataRoot = resolveFilesConfig().local.basePath;
172
- const verifyReport = await verifyFiles(req.to, dataRoot, { hash: 'sample', checkOrphans: true });
200
+ const verifyReport = await verifyFiles(req.to, dataRoot, { hash: 'sample', checkOrphans: true, adapter: tgtAdapter });
173
201
  if (!verifyReport.ok) {
174
- warnings.push(`Verification found issues: missing=${verifyReport.missing.length}, sizeMismatch=${verifyReport.sizeMismatch.length}`);
202
+ const parts = [];
203
+ if (verifyReport.missing.length)
204
+ parts.push(`missing=${verifyReport.missing.length}`);
205
+ if (verifyReport.sizeMismatch.length)
206
+ parts.push(`sizeMismatch=${verifyReport.sizeMismatch.length}`);
207
+ if (verifyReport.hashMismatch.length)
208
+ parts.push(`hashMismatch=${verifyReport.hashMismatch.length}`);
209
+ warnings.push(`Verify found issues (${parts.join(', ')}) — migration continues; snapshotId=${snapshot.snapshotId}`);
175
210
  }
176
211
  // Step 8: Finalize — audit
177
212
  emitProgress(req, { phase: 'finalize', message: 'Recording audit event', percent: 95 });
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/migrate/snapshot.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,cAAc,CAmBlE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAuB/E"}
1
+ {"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/migrate/snapshot.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,cAAc,CAsBlE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAuB/E"}
@@ -3,6 +3,9 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { HazoError } from 'hazo_core';
5
5
  export function takeSnapshot(toConfig) {
6
+ if (toConfig.type === 'postgrest') {
7
+ return { snapshotId: 'postgrest:no-snapshot', createdAt: new Date().toISOString() };
8
+ }
6
9
  if (toConfig.type !== 'sqlite' || !toConfig.sqlite?.database_path) {
7
10
  throw new HazoError({
8
11
  code: 'HAZO_ENV_NOT_IMPLEMENTED',
@@ -3,6 +3,7 @@ export interface VerifyOptions {
3
3
  hash?: HashMode;
4
4
  samplePct?: number;
5
5
  checkOrphans?: boolean;
6
+ adapter?: import('hazo_connect').HazoConnectAdapter;
6
7
  }
7
8
  export declare function verifyFiles(env: HazoEnv, dataRoot: string, opts?: VerifyOptions): Promise<VerifyReport>;
8
9
  //# sourceMappingURL=verify.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/migrate/verify.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEzE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAMD,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,CAgDvB"}
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/migrate/verify.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEzE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,cAAc,EAAE,kBAAkB,CAAC;CACrD;AAqBD,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,CA8IvB"}