@objectstack/metadata 4.0.5 → 4.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.
@@ -17,20 +17,19 @@ var __copyProps = (to, from, except, desc) => {
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
- // src/migrations/migrate-env-id-to-project-id.ts
21
- var migrate_env_id_to_project_id_exports = {};
22
- __export(migrate_env_id_to_project_id_exports, {
20
+ // src/migrations/index.ts
21
+ var migrations_exports = {};
22
+ __export(migrations_exports, {
23
+ addSysMetadataOverlayIndex: () => addSysMetadataOverlayIndex,
24
+ dropProjectionTables: () => dropProjectionTables,
23
25
  migrateEnvIdToProjectId: () => migrateEnvIdToProjectId
24
26
  });
25
- module.exports = __toCommonJS(migrate_env_id_to_project_id_exports);
27
+ module.exports = __toCommonJS(migrations_exports);
28
+
29
+ // src/migrations/migrate-env-id-to-project-id.ts
26
30
  var AFFECTED_TABLES = [
27
31
  "sys_metadata",
28
- "sys_metadata_history",
29
- "sys_object",
30
- "sys_view",
31
- "sys_flow",
32
- "sys_agent",
33
- "sys_tool"
32
+ "sys_metadata_history"
34
33
  ];
35
34
  async function migrateEnvIdToProjectId(driver) {
36
35
  const driverAny = driver;
@@ -77,8 +76,81 @@ async function _columnExists(driver, table, column) {
77
76
  return false;
78
77
  }
79
78
  }
79
+
80
+ // src/migrations/drop-projection-tables.ts
81
+ var DEPRECATED_TABLES = [
82
+ "sys_object",
83
+ "sys_view",
84
+ "sys_flow",
85
+ "sys_agent",
86
+ "sys_tool"
87
+ ];
88
+ async function dropProjectionTables(driver) {
89
+ const driverAny = driver;
90
+ if (typeof driverAny.raw !== "function") {
91
+ throw new Error("dropProjectionTables: driver must expose a raw(sql) method");
92
+ }
93
+ const results = [];
94
+ for (const table of DEPRECATED_TABLES) {
95
+ try {
96
+ await driverAny.raw(`DROP TABLE IF EXISTS ${table}`);
97
+ results.push({ table, status: "dropped" });
98
+ } catch (error) {
99
+ results.push({
100
+ table,
101
+ status: "error",
102
+ error: error instanceof Error ? error.message : String(error)
103
+ });
104
+ }
105
+ }
106
+ return results;
107
+ }
108
+
109
+ // src/migrations/add-sys-metadata-overlay-index.ts
110
+ var INDEX_NAME = "idx_sys_metadata_overlay_active";
111
+ var TABLE = "sys_metadata";
112
+ var COLUMNS = "(type, name, organization_id, project_id, scope)";
113
+ var WHERE = "state = 'active'";
114
+ async function addSysMetadataOverlayIndex(driver) {
115
+ const driverAny = driver;
116
+ const exec = async (sql) => {
117
+ if (typeof driverAny.raw === "function") {
118
+ await driverAny.raw(sql);
119
+ } else if (typeof driverAny.execute === "function") {
120
+ await driverAny.execute(sql);
121
+ } else {
122
+ throw new Error("driver has neither raw nor execute");
123
+ }
124
+ };
125
+ const partialSql = `CREATE UNIQUE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS} WHERE ${WHERE}`;
126
+ const fallbackSql = `CREATE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS}`;
127
+ try {
128
+ await exec(partialSql);
129
+ return { index: INDEX_NAME, status: "created" };
130
+ } catch (err) {
131
+ const msg = err instanceof Error ? err.message : String(err);
132
+ if (/partial|where clause|syntax/i.test(msg)) {
133
+ try {
134
+ await exec(fallbackSql);
135
+ return { index: INDEX_NAME, status: "fallback_non_unique" };
136
+ } catch (fallbackErr) {
137
+ return {
138
+ index: INDEX_NAME,
139
+ status: "error",
140
+ error: fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)
141
+ };
142
+ }
143
+ }
144
+ if (/already exists/i.test(msg)) {
145
+ return { index: INDEX_NAME, status: "already_exists" };
146
+ }
147
+ return { index: INDEX_NAME, status: "error", error: msg };
148
+ }
149
+ }
80
150
  // Annotate the CommonJS export names for ESM import in node:
81
151
  0 && (module.exports = {
152
+ addSysMetadataOverlayIndex,
153
+ dropProjectionTables,
82
154
  migrateEnvIdToProjectId
83
155
  });
84
- //# sourceMappingURL=migrate-env-id-to-project-id.cjs.map
156
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/migrations/index.ts","../../src/migrations/migrate-env-id-to-project-id.ts","../../src/migrations/drop-projection-tables.ts","../../src/migrations/add-sys-metadata-overlay-index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/metadata/migrations\n *\n * One-off database migrations for the metadata storage layer.\n */\n\nexport { migrateEnvIdToProjectId, type MigrationResult } from './migrate-env-id-to-project-id.js';\nexport { dropProjectionTables, type DropProjectionResult } from './drop-projection-tables.js';\nexport {\n addSysMetadataOverlayIndex,\n type AddSysMetadataOverlayIndexResult,\n} from './add-sys-metadata-overlay-index.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Migration: env_id → project_id\n *\n * Renames the `env_id` column to `project_id` on the metadata storage tables:\n * - sys_metadata\n * - sys_metadata_history\n *\n * (The per-type projection tables `sys_object` / `sys_view` / `sys_flow` /\n * `sys_agent` / `sys_tool` were removed in 2026-05 along with the projection\n * pipeline — see ADR 0005 addendum. They are intentionally not included.)\n *\n * Safe to run multiple times (idempotent): checks for column existence before\n * attempting to rename. If `project_id` already exists, the step is skipped.\n *\n * Usage:\n * import { migrateEnvIdToProjectId } from '@objectstack/metadata/migrations';\n * await migrateEnvIdToProjectId(driver);\n */\n\nimport type { IDataDriver } from '@objectstack/spec/contracts';\n\nconst AFFECTED_TABLES = [\n 'sys_metadata',\n 'sys_metadata_history',\n] as const;\n\nexport interface MigrationResult {\n table: string;\n status: 'renamed' | 'already_done' | 'table_missing' | 'error';\n error?: string;\n}\n\n/**\n * Rename `env_id` → `project_id` on all metadata tables.\n *\n * @param driver An IDataDriver with access to the target database.\n * Must expose a raw query method: `driver.raw(sql, bindings?)`.\n * @returns Per-table migration results.\n */\nexport async function migrateEnvIdToProjectId(driver: IDataDriver): Promise<MigrationResult[]> {\n const driverAny = driver as any;\n\n if (typeof driverAny.raw !== 'function') {\n throw new Error(\n 'migrateEnvIdToProjectId: driver must expose a .raw(sql, bindings?) method. ' +\n 'SqlDriver (better-sqlite3/knex) and TursoDriver both support this.'\n );\n }\n\n const results: MigrationResult[] = [];\n\n for (const table of AFFECTED_TABLES) {\n try {\n // Detect dialect: SQLite uses PRAGMA, others use information_schema.\n const hasColumn = await _columnExists(driverAny, table, 'env_id');\n const alreadyMigrated = await _columnExists(driverAny, table, 'project_id');\n\n if (alreadyMigrated && !hasColumn) {\n results.push({ table, status: 'already_done' });\n continue;\n }\n\n if (!hasColumn) {\n // Neither column exists — table might not exist yet.\n results.push({ table, status: 'table_missing' });\n continue;\n }\n\n // Perform the rename. SQLite ≥ 3.25.0 supports ALTER TABLE RENAME COLUMN.\n await driverAny.raw(`ALTER TABLE \"${table}\" RENAME COLUMN env_id TO project_id`);\n\n results.push({ table, status: 'renamed' });\n } catch (err: any) {\n results.push({ table, status: 'error', error: err?.message ?? String(err) });\n }\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nasync function _columnExists(driver: any, table: string, column: string): Promise<boolean> {\n try {\n // SQLite: PRAGMA table_info returns rows with `name` column.\n const rows: any[] = await driver.raw(`PRAGMA table_info(\"${table}\")`);\n if (Array.isArray(rows) && rows.length > 0) {\n // knex wraps PRAGMA result; handle both `rows` and `rows[0]` shapes.\n const list: any[] = Array.isArray(rows[0]) ? rows[0] : rows;\n return list.some((r: any) => r?.name === column);\n }\n\n // Fallback for non-SQLite: query information_schema.\n const result: any[] = await driver.raw(\n `SELECT column_name FROM information_schema.columns WHERE table_name = ? AND column_name = ?`,\n [table, column]\n );\n const list: any[] = Array.isArray(result[0]) ? result[0] : result;\n return list.length > 0;\n } catch {\n return false;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Migration: drop deprecated metadata projection tables.\n *\n * In 2026-05 the per-type projection tables (`sys_object` / `sys_view` /\n * `sys_flow` / `sys_agent` / `sys_tool`) and the corresponding\n * `MetadataProjector` were removed (see ADR 0005 addendum). All metadata\n * now lives as JSON inside `sys_metadata` — these projection tables are\n * dead weight on any existing database.\n *\n * This migration drops them if present. It is idempotent and safe to run\n * on databases that never had them (the `DROP TABLE IF EXISTS` is a no-op).\n *\n * Usage:\n * import { dropProjectionTables } from '@objectstack/metadata/migrations';\n * await dropProjectionTables(driver);\n */\n\nimport type { IDataDriver } from '@objectstack/spec/contracts';\n\nconst DEPRECATED_TABLES = [\n 'sys_object',\n 'sys_view',\n 'sys_flow',\n 'sys_agent',\n 'sys_tool',\n] as const;\n\nexport interface DropProjectionResult {\n table: string;\n status: 'dropped' | 'not_present' | 'error';\n error?: string;\n}\n\n/**\n * Drop the deprecated per-type metadata projection tables.\n *\n * @param driver An `IDataDriver` with `driver.raw(sql, bindings?)` access.\n * @returns Per-table results.\n */\nexport async function dropProjectionTables(driver: IDataDriver): Promise<DropProjectionResult[]> {\n const driverAny = driver as any;\n if (typeof driverAny.raw !== 'function') {\n throw new Error('dropProjectionTables: driver must expose a raw(sql) method');\n }\n\n const results: DropProjectionResult[] = [];\n for (const table of DEPRECATED_TABLES) {\n try {\n await driverAny.raw(`DROP TABLE IF EXISTS ${table}`);\n results.push({ table, status: 'dropped' });\n } catch (error) {\n results.push({\n table,\n status: 'error',\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return results;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Migration: ensure overlay-uniqueness index exists on `sys_metadata`.\n *\n * ADR-0005 Phase 1 — Overlay rows must be uniquely keyed by\n * `(type, name, organization_id, project_id, scope)` for active rows only.\n * The previous `(type, name, project_id)` unique constraint pre-dated\n * multi-tenant overlays and would incorrectly reject per-org customizations.\n *\n * Behaviour:\n * - SQLite / Postgres: creates a partial UNIQUE INDEX with `WHERE state = 'active'`.\n * - MySQL (no partial-index support): falls back to a non-unique composite index\n * plus an application-level guard (handled in `protocol.ts saveMetaItem`).\n * - Idempotent — uses `CREATE INDEX IF NOT EXISTS`. Safe to run on every boot.\n * - Best-effort: failures are recorded but never throw, so tenant boot is\n * not blocked on a database that doesn't support partial indexes.\n *\n * Usage:\n * import { addSysMetadataOverlayIndex } from '@objectstack/metadata/migrations';\n * await addSysMetadataOverlayIndex(driver);\n *\n * The `DatabaseLoader.ensureSchema()` invokes this automatically after the\n * `sys_metadata` table is created/synced, so most callers do not need to\n * invoke it directly.\n */\n\nimport type { IDataDriver } from '@objectstack/spec/contracts';\n\nconst INDEX_NAME = 'idx_sys_metadata_overlay_active';\nconst TABLE = 'sys_metadata';\nconst COLUMNS = '(type, name, organization_id, project_id, scope)';\nconst WHERE = \"state = 'active'\";\n\nexport interface AddSysMetadataOverlayIndexResult {\n index: string;\n status: 'created' | 'already_exists' | 'fallback_non_unique' | 'unsupported' | 'error';\n error?: string;\n}\n\n/**\n * Ensure the overlay-uniqueness index exists on `sys_metadata`.\n *\n * @param driver An `IDataDriver` exposing a `raw(sql, bindings?)` method.\n */\nexport async function addSysMetadataOverlayIndex(\n driver: IDataDriver,\n): Promise<AddSysMetadataOverlayIndexResult> {\n const driverAny = driver as any;\n const exec = async (sql: string): Promise<void> => {\n if (typeof driverAny.raw === 'function') {\n await driverAny.raw(sql);\n } else if (typeof driverAny.execute === 'function') {\n await driverAny.execute(sql);\n } else {\n throw new Error('driver has neither raw nor execute');\n }\n };\n\n const partialSql = `CREATE UNIQUE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS} WHERE ${WHERE}`;\n const fallbackSql = `CREATE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS}`;\n\n try {\n await exec(partialSql);\n return { index: INDEX_NAME, status: 'created' };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n\n // Partial-index unsupported (typically MySQL): fall back to a plain composite index.\n if (/partial|where clause|syntax/i.test(msg)) {\n try {\n await exec(fallbackSql);\n return { index: INDEX_NAME, status: 'fallback_non_unique' };\n } catch (fallbackErr) {\n return {\n index: INDEX_NAME,\n status: 'error',\n error:\n fallbackErr instanceof Error\n ? fallbackErr.message\n : String(fallbackErr),\n };\n }\n }\n\n if (/already exists/i.test(msg)) {\n return { index: INDEX_NAME, status: 'already_exists' };\n }\n\n return { index: INDEX_NAME, status: 'error', error: msg };\n }\n}\n"],"mappings":"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","names":["list"]}
@@ -0,0 +1,103 @@
1
+ import { IDataDriver } from '@objectstack/spec/contracts';
2
+
3
+ /**
4
+ * Migration: env_id → project_id
5
+ *
6
+ * Renames the `env_id` column to `project_id` on the metadata storage tables:
7
+ * - sys_metadata
8
+ * - sys_metadata_history
9
+ *
10
+ * (The per-type projection tables `sys_object` / `sys_view` / `sys_flow` /
11
+ * `sys_agent` / `sys_tool` were removed in 2026-05 along with the projection
12
+ * pipeline — see ADR 0005 addendum. They are intentionally not included.)
13
+ *
14
+ * Safe to run multiple times (idempotent): checks for column existence before
15
+ * attempting to rename. If `project_id` already exists, the step is skipped.
16
+ *
17
+ * Usage:
18
+ * import { migrateEnvIdToProjectId } from '@objectstack/metadata/migrations';
19
+ * await migrateEnvIdToProjectId(driver);
20
+ */
21
+
22
+ interface MigrationResult {
23
+ table: string;
24
+ status: 'renamed' | 'already_done' | 'table_missing' | 'error';
25
+ error?: string;
26
+ }
27
+ /**
28
+ * Rename `env_id` → `project_id` on all metadata tables.
29
+ *
30
+ * @param driver An IDataDriver with access to the target database.
31
+ * Must expose a raw query method: `driver.raw(sql, bindings?)`.
32
+ * @returns Per-table migration results.
33
+ */
34
+ declare function migrateEnvIdToProjectId(driver: IDataDriver): Promise<MigrationResult[]>;
35
+
36
+ /**
37
+ * Migration: drop deprecated metadata projection tables.
38
+ *
39
+ * In 2026-05 the per-type projection tables (`sys_object` / `sys_view` /
40
+ * `sys_flow` / `sys_agent` / `sys_tool`) and the corresponding
41
+ * `MetadataProjector` were removed (see ADR 0005 addendum). All metadata
42
+ * now lives as JSON inside `sys_metadata` — these projection tables are
43
+ * dead weight on any existing database.
44
+ *
45
+ * This migration drops them if present. It is idempotent and safe to run
46
+ * on databases that never had them (the `DROP TABLE IF EXISTS` is a no-op).
47
+ *
48
+ * Usage:
49
+ * import { dropProjectionTables } from '@objectstack/metadata/migrations';
50
+ * await dropProjectionTables(driver);
51
+ */
52
+
53
+ interface DropProjectionResult {
54
+ table: string;
55
+ status: 'dropped' | 'not_present' | 'error';
56
+ error?: string;
57
+ }
58
+ /**
59
+ * Drop the deprecated per-type metadata projection tables.
60
+ *
61
+ * @param driver An `IDataDriver` with `driver.raw(sql, bindings?)` access.
62
+ * @returns Per-table results.
63
+ */
64
+ declare function dropProjectionTables(driver: IDataDriver): Promise<DropProjectionResult[]>;
65
+
66
+ /**
67
+ * Migration: ensure overlay-uniqueness index exists on `sys_metadata`.
68
+ *
69
+ * ADR-0005 Phase 1 — Overlay rows must be uniquely keyed by
70
+ * `(type, name, organization_id, project_id, scope)` for active rows only.
71
+ * The previous `(type, name, project_id)` unique constraint pre-dated
72
+ * multi-tenant overlays and would incorrectly reject per-org customizations.
73
+ *
74
+ * Behaviour:
75
+ * - SQLite / Postgres: creates a partial UNIQUE INDEX with `WHERE state = 'active'`.
76
+ * - MySQL (no partial-index support): falls back to a non-unique composite index
77
+ * plus an application-level guard (handled in `protocol.ts saveMetaItem`).
78
+ * - Idempotent — uses `CREATE INDEX IF NOT EXISTS`. Safe to run on every boot.
79
+ * - Best-effort: failures are recorded but never throw, so tenant boot is
80
+ * not blocked on a database that doesn't support partial indexes.
81
+ *
82
+ * Usage:
83
+ * import { addSysMetadataOverlayIndex } from '@objectstack/metadata/migrations';
84
+ * await addSysMetadataOverlayIndex(driver);
85
+ *
86
+ * The `DatabaseLoader.ensureSchema()` invokes this automatically after the
87
+ * `sys_metadata` table is created/synced, so most callers do not need to
88
+ * invoke it directly.
89
+ */
90
+
91
+ interface AddSysMetadataOverlayIndexResult {
92
+ index: string;
93
+ status: 'created' | 'already_exists' | 'fallback_non_unique' | 'unsupported' | 'error';
94
+ error?: string;
95
+ }
96
+ /**
97
+ * Ensure the overlay-uniqueness index exists on `sys_metadata`.
98
+ *
99
+ * @param driver An `IDataDriver` exposing a `raw(sql, bindings?)` method.
100
+ */
101
+ declare function addSysMetadataOverlayIndex(driver: IDataDriver): Promise<AddSysMetadataOverlayIndexResult>;
102
+
103
+ export { type AddSysMetadataOverlayIndexResult, type DropProjectionResult, type MigrationResult, addSysMetadataOverlayIndex, dropProjectionTables, migrateEnvIdToProjectId };
@@ -0,0 +1,103 @@
1
+ import { IDataDriver } from '@objectstack/spec/contracts';
2
+
3
+ /**
4
+ * Migration: env_id → project_id
5
+ *
6
+ * Renames the `env_id` column to `project_id` on the metadata storage tables:
7
+ * - sys_metadata
8
+ * - sys_metadata_history
9
+ *
10
+ * (The per-type projection tables `sys_object` / `sys_view` / `sys_flow` /
11
+ * `sys_agent` / `sys_tool` were removed in 2026-05 along with the projection
12
+ * pipeline — see ADR 0005 addendum. They are intentionally not included.)
13
+ *
14
+ * Safe to run multiple times (idempotent): checks for column existence before
15
+ * attempting to rename. If `project_id` already exists, the step is skipped.
16
+ *
17
+ * Usage:
18
+ * import { migrateEnvIdToProjectId } from '@objectstack/metadata/migrations';
19
+ * await migrateEnvIdToProjectId(driver);
20
+ */
21
+
22
+ interface MigrationResult {
23
+ table: string;
24
+ status: 'renamed' | 'already_done' | 'table_missing' | 'error';
25
+ error?: string;
26
+ }
27
+ /**
28
+ * Rename `env_id` → `project_id` on all metadata tables.
29
+ *
30
+ * @param driver An IDataDriver with access to the target database.
31
+ * Must expose a raw query method: `driver.raw(sql, bindings?)`.
32
+ * @returns Per-table migration results.
33
+ */
34
+ declare function migrateEnvIdToProjectId(driver: IDataDriver): Promise<MigrationResult[]>;
35
+
36
+ /**
37
+ * Migration: drop deprecated metadata projection tables.
38
+ *
39
+ * In 2026-05 the per-type projection tables (`sys_object` / `sys_view` /
40
+ * `sys_flow` / `sys_agent` / `sys_tool`) and the corresponding
41
+ * `MetadataProjector` were removed (see ADR 0005 addendum). All metadata
42
+ * now lives as JSON inside `sys_metadata` — these projection tables are
43
+ * dead weight on any existing database.
44
+ *
45
+ * This migration drops them if present. It is idempotent and safe to run
46
+ * on databases that never had them (the `DROP TABLE IF EXISTS` is a no-op).
47
+ *
48
+ * Usage:
49
+ * import { dropProjectionTables } from '@objectstack/metadata/migrations';
50
+ * await dropProjectionTables(driver);
51
+ */
52
+
53
+ interface DropProjectionResult {
54
+ table: string;
55
+ status: 'dropped' | 'not_present' | 'error';
56
+ error?: string;
57
+ }
58
+ /**
59
+ * Drop the deprecated per-type metadata projection tables.
60
+ *
61
+ * @param driver An `IDataDriver` with `driver.raw(sql, bindings?)` access.
62
+ * @returns Per-table results.
63
+ */
64
+ declare function dropProjectionTables(driver: IDataDriver): Promise<DropProjectionResult[]>;
65
+
66
+ /**
67
+ * Migration: ensure overlay-uniqueness index exists on `sys_metadata`.
68
+ *
69
+ * ADR-0005 Phase 1 — Overlay rows must be uniquely keyed by
70
+ * `(type, name, organization_id, project_id, scope)` for active rows only.
71
+ * The previous `(type, name, project_id)` unique constraint pre-dated
72
+ * multi-tenant overlays and would incorrectly reject per-org customizations.
73
+ *
74
+ * Behaviour:
75
+ * - SQLite / Postgres: creates a partial UNIQUE INDEX with `WHERE state = 'active'`.
76
+ * - MySQL (no partial-index support): falls back to a non-unique composite index
77
+ * plus an application-level guard (handled in `protocol.ts saveMetaItem`).
78
+ * - Idempotent — uses `CREATE INDEX IF NOT EXISTS`. Safe to run on every boot.
79
+ * - Best-effort: failures are recorded but never throw, so tenant boot is
80
+ * not blocked on a database that doesn't support partial indexes.
81
+ *
82
+ * Usage:
83
+ * import { addSysMetadataOverlayIndex } from '@objectstack/metadata/migrations';
84
+ * await addSysMetadataOverlayIndex(driver);
85
+ *
86
+ * The `DatabaseLoader.ensureSchema()` invokes this automatically after the
87
+ * `sys_metadata` table is created/synced, so most callers do not need to
88
+ * invoke it directly.
89
+ */
90
+
91
+ interface AddSysMetadataOverlayIndexResult {
92
+ index: string;
93
+ status: 'created' | 'already_exists' | 'fallback_non_unique' | 'unsupported' | 'error';
94
+ error?: string;
95
+ }
96
+ /**
97
+ * Ensure the overlay-uniqueness index exists on `sys_metadata`.
98
+ *
99
+ * @param driver An `IDataDriver` exposing a `raw(sql, bindings?)` method.
100
+ */
101
+ declare function addSysMetadataOverlayIndex(driver: IDataDriver): Promise<AddSysMetadataOverlayIndexResult>;
102
+
103
+ export { type AddSysMetadataOverlayIndexResult, type DropProjectionResult, type MigrationResult, addSysMetadataOverlayIndex, dropProjectionTables, migrateEnvIdToProjectId };
@@ -0,0 +1,127 @@
1
+ // src/migrations/migrate-env-id-to-project-id.ts
2
+ var AFFECTED_TABLES = [
3
+ "sys_metadata",
4
+ "sys_metadata_history"
5
+ ];
6
+ async function migrateEnvIdToProjectId(driver) {
7
+ const driverAny = driver;
8
+ if (typeof driverAny.raw !== "function") {
9
+ throw new Error(
10
+ "migrateEnvIdToProjectId: driver must expose a .raw(sql, bindings?) method. SqlDriver (better-sqlite3/knex) and TursoDriver both support this."
11
+ );
12
+ }
13
+ const results = [];
14
+ for (const table of AFFECTED_TABLES) {
15
+ try {
16
+ const hasColumn = await _columnExists(driverAny, table, "env_id");
17
+ const alreadyMigrated = await _columnExists(driverAny, table, "project_id");
18
+ if (alreadyMigrated && !hasColumn) {
19
+ results.push({ table, status: "already_done" });
20
+ continue;
21
+ }
22
+ if (!hasColumn) {
23
+ results.push({ table, status: "table_missing" });
24
+ continue;
25
+ }
26
+ await driverAny.raw(`ALTER TABLE "${table}" RENAME COLUMN env_id TO project_id`);
27
+ results.push({ table, status: "renamed" });
28
+ } catch (err) {
29
+ results.push({ table, status: "error", error: err?.message ?? String(err) });
30
+ }
31
+ }
32
+ return results;
33
+ }
34
+ async function _columnExists(driver, table, column) {
35
+ try {
36
+ const rows = await driver.raw(`PRAGMA table_info("${table}")`);
37
+ if (Array.isArray(rows) && rows.length > 0) {
38
+ const list2 = Array.isArray(rows[0]) ? rows[0] : rows;
39
+ return list2.some((r) => r?.name === column);
40
+ }
41
+ const result = await driver.raw(
42
+ `SELECT column_name FROM information_schema.columns WHERE table_name = ? AND column_name = ?`,
43
+ [table, column]
44
+ );
45
+ const list = Array.isArray(result[0]) ? result[0] : result;
46
+ return list.length > 0;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ // src/migrations/drop-projection-tables.ts
53
+ var DEPRECATED_TABLES = [
54
+ "sys_object",
55
+ "sys_view",
56
+ "sys_flow",
57
+ "sys_agent",
58
+ "sys_tool"
59
+ ];
60
+ async function dropProjectionTables(driver) {
61
+ const driverAny = driver;
62
+ if (typeof driverAny.raw !== "function") {
63
+ throw new Error("dropProjectionTables: driver must expose a raw(sql) method");
64
+ }
65
+ const results = [];
66
+ for (const table of DEPRECATED_TABLES) {
67
+ try {
68
+ await driverAny.raw(`DROP TABLE IF EXISTS ${table}`);
69
+ results.push({ table, status: "dropped" });
70
+ } catch (error) {
71
+ results.push({
72
+ table,
73
+ status: "error",
74
+ error: error instanceof Error ? error.message : String(error)
75
+ });
76
+ }
77
+ }
78
+ return results;
79
+ }
80
+
81
+ // src/migrations/add-sys-metadata-overlay-index.ts
82
+ var INDEX_NAME = "idx_sys_metadata_overlay_active";
83
+ var TABLE = "sys_metadata";
84
+ var COLUMNS = "(type, name, organization_id, project_id, scope)";
85
+ var WHERE = "state = 'active'";
86
+ async function addSysMetadataOverlayIndex(driver) {
87
+ const driverAny = driver;
88
+ const exec = async (sql) => {
89
+ if (typeof driverAny.raw === "function") {
90
+ await driverAny.raw(sql);
91
+ } else if (typeof driverAny.execute === "function") {
92
+ await driverAny.execute(sql);
93
+ } else {
94
+ throw new Error("driver has neither raw nor execute");
95
+ }
96
+ };
97
+ const partialSql = `CREATE UNIQUE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS} WHERE ${WHERE}`;
98
+ const fallbackSql = `CREATE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS}`;
99
+ try {
100
+ await exec(partialSql);
101
+ return { index: INDEX_NAME, status: "created" };
102
+ } catch (err) {
103
+ const msg = err instanceof Error ? err.message : String(err);
104
+ if (/partial|where clause|syntax/i.test(msg)) {
105
+ try {
106
+ await exec(fallbackSql);
107
+ return { index: INDEX_NAME, status: "fallback_non_unique" };
108
+ } catch (fallbackErr) {
109
+ return {
110
+ index: INDEX_NAME,
111
+ status: "error",
112
+ error: fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)
113
+ };
114
+ }
115
+ }
116
+ if (/already exists/i.test(msg)) {
117
+ return { index: INDEX_NAME, status: "already_exists" };
118
+ }
119
+ return { index: INDEX_NAME, status: "error", error: msg };
120
+ }
121
+ }
122
+ export {
123
+ addSysMetadataOverlayIndex,
124
+ dropProjectionTables,
125
+ migrateEnvIdToProjectId
126
+ };
127
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/migrations/migrate-env-id-to-project-id.ts","../../src/migrations/drop-projection-tables.ts","../../src/migrations/add-sys-metadata-overlay-index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Migration: env_id → project_id\n *\n * Renames the `env_id` column to `project_id` on the metadata storage tables:\n * - sys_metadata\n * - sys_metadata_history\n *\n * (The per-type projection tables `sys_object` / `sys_view` / `sys_flow` /\n * `sys_agent` / `sys_tool` were removed in 2026-05 along with the projection\n * pipeline — see ADR 0005 addendum. They are intentionally not included.)\n *\n * Safe to run multiple times (idempotent): checks for column existence before\n * attempting to rename. If `project_id` already exists, the step is skipped.\n *\n * Usage:\n * import { migrateEnvIdToProjectId } from '@objectstack/metadata/migrations';\n * await migrateEnvIdToProjectId(driver);\n */\n\nimport type { IDataDriver } from '@objectstack/spec/contracts';\n\nconst AFFECTED_TABLES = [\n 'sys_metadata',\n 'sys_metadata_history',\n] as const;\n\nexport interface MigrationResult {\n table: string;\n status: 'renamed' | 'already_done' | 'table_missing' | 'error';\n error?: string;\n}\n\n/**\n * Rename `env_id` → `project_id` on all metadata tables.\n *\n * @param driver An IDataDriver with access to the target database.\n * Must expose a raw query method: `driver.raw(sql, bindings?)`.\n * @returns Per-table migration results.\n */\nexport async function migrateEnvIdToProjectId(driver: IDataDriver): Promise<MigrationResult[]> {\n const driverAny = driver as any;\n\n if (typeof driverAny.raw !== 'function') {\n throw new Error(\n 'migrateEnvIdToProjectId: driver must expose a .raw(sql, bindings?) method. ' +\n 'SqlDriver (better-sqlite3/knex) and TursoDriver both support this.'\n );\n }\n\n const results: MigrationResult[] = [];\n\n for (const table of AFFECTED_TABLES) {\n try {\n // Detect dialect: SQLite uses PRAGMA, others use information_schema.\n const hasColumn = await _columnExists(driverAny, table, 'env_id');\n const alreadyMigrated = await _columnExists(driverAny, table, 'project_id');\n\n if (alreadyMigrated && !hasColumn) {\n results.push({ table, status: 'already_done' });\n continue;\n }\n\n if (!hasColumn) {\n // Neither column exists — table might not exist yet.\n results.push({ table, status: 'table_missing' });\n continue;\n }\n\n // Perform the rename. SQLite ≥ 3.25.0 supports ALTER TABLE RENAME COLUMN.\n await driverAny.raw(`ALTER TABLE \"${table}\" RENAME COLUMN env_id TO project_id`);\n\n results.push({ table, status: 'renamed' });\n } catch (err: any) {\n results.push({ table, status: 'error', error: err?.message ?? String(err) });\n }\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nasync function _columnExists(driver: any, table: string, column: string): Promise<boolean> {\n try {\n // SQLite: PRAGMA table_info returns rows with `name` column.\n const rows: any[] = await driver.raw(`PRAGMA table_info(\"${table}\")`);\n if (Array.isArray(rows) && rows.length > 0) {\n // knex wraps PRAGMA result; handle both `rows` and `rows[0]` shapes.\n const list: any[] = Array.isArray(rows[0]) ? rows[0] : rows;\n return list.some((r: any) => r?.name === column);\n }\n\n // Fallback for non-SQLite: query information_schema.\n const result: any[] = await driver.raw(\n `SELECT column_name FROM information_schema.columns WHERE table_name = ? AND column_name = ?`,\n [table, column]\n );\n const list: any[] = Array.isArray(result[0]) ? result[0] : result;\n return list.length > 0;\n } catch {\n return false;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Migration: drop deprecated metadata projection tables.\n *\n * In 2026-05 the per-type projection tables (`sys_object` / `sys_view` /\n * `sys_flow` / `sys_agent` / `sys_tool`) and the corresponding\n * `MetadataProjector` were removed (see ADR 0005 addendum). All metadata\n * now lives as JSON inside `sys_metadata` — these projection tables are\n * dead weight on any existing database.\n *\n * This migration drops them if present. It is idempotent and safe to run\n * on databases that never had them (the `DROP TABLE IF EXISTS` is a no-op).\n *\n * Usage:\n * import { dropProjectionTables } from '@objectstack/metadata/migrations';\n * await dropProjectionTables(driver);\n */\n\nimport type { IDataDriver } from '@objectstack/spec/contracts';\n\nconst DEPRECATED_TABLES = [\n 'sys_object',\n 'sys_view',\n 'sys_flow',\n 'sys_agent',\n 'sys_tool',\n] as const;\n\nexport interface DropProjectionResult {\n table: string;\n status: 'dropped' | 'not_present' | 'error';\n error?: string;\n}\n\n/**\n * Drop the deprecated per-type metadata projection tables.\n *\n * @param driver An `IDataDriver` with `driver.raw(sql, bindings?)` access.\n * @returns Per-table results.\n */\nexport async function dropProjectionTables(driver: IDataDriver): Promise<DropProjectionResult[]> {\n const driverAny = driver as any;\n if (typeof driverAny.raw !== 'function') {\n throw new Error('dropProjectionTables: driver must expose a raw(sql) method');\n }\n\n const results: DropProjectionResult[] = [];\n for (const table of DEPRECATED_TABLES) {\n try {\n await driverAny.raw(`DROP TABLE IF EXISTS ${table}`);\n results.push({ table, status: 'dropped' });\n } catch (error) {\n results.push({\n table,\n status: 'error',\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n return results;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Migration: ensure overlay-uniqueness index exists on `sys_metadata`.\n *\n * ADR-0005 Phase 1 — Overlay rows must be uniquely keyed by\n * `(type, name, organization_id, project_id, scope)` for active rows only.\n * The previous `(type, name, project_id)` unique constraint pre-dated\n * multi-tenant overlays and would incorrectly reject per-org customizations.\n *\n * Behaviour:\n * - SQLite / Postgres: creates a partial UNIQUE INDEX with `WHERE state = 'active'`.\n * - MySQL (no partial-index support): falls back to a non-unique composite index\n * plus an application-level guard (handled in `protocol.ts saveMetaItem`).\n * - Idempotent — uses `CREATE INDEX IF NOT EXISTS`. Safe to run on every boot.\n * - Best-effort: failures are recorded but never throw, so tenant boot is\n * not blocked on a database that doesn't support partial indexes.\n *\n * Usage:\n * import { addSysMetadataOverlayIndex } from '@objectstack/metadata/migrations';\n * await addSysMetadataOverlayIndex(driver);\n *\n * The `DatabaseLoader.ensureSchema()` invokes this automatically after the\n * `sys_metadata` table is created/synced, so most callers do not need to\n * invoke it directly.\n */\n\nimport type { IDataDriver } from '@objectstack/spec/contracts';\n\nconst INDEX_NAME = 'idx_sys_metadata_overlay_active';\nconst TABLE = 'sys_metadata';\nconst COLUMNS = '(type, name, organization_id, project_id, scope)';\nconst WHERE = \"state = 'active'\";\n\nexport interface AddSysMetadataOverlayIndexResult {\n index: string;\n status: 'created' | 'already_exists' | 'fallback_non_unique' | 'unsupported' | 'error';\n error?: string;\n}\n\n/**\n * Ensure the overlay-uniqueness index exists on `sys_metadata`.\n *\n * @param driver An `IDataDriver` exposing a `raw(sql, bindings?)` method.\n */\nexport async function addSysMetadataOverlayIndex(\n driver: IDataDriver,\n): Promise<AddSysMetadataOverlayIndexResult> {\n const driverAny = driver as any;\n const exec = async (sql: string): Promise<void> => {\n if (typeof driverAny.raw === 'function') {\n await driverAny.raw(sql);\n } else if (typeof driverAny.execute === 'function') {\n await driverAny.execute(sql);\n } else {\n throw new Error('driver has neither raw nor execute');\n }\n };\n\n const partialSql = `CREATE UNIQUE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS} WHERE ${WHERE}`;\n const fallbackSql = `CREATE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS}`;\n\n try {\n await exec(partialSql);\n return { index: INDEX_NAME, status: 'created' };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n\n // Partial-index unsupported (typically MySQL): fall back to a plain composite index.\n if (/partial|where clause|syntax/i.test(msg)) {\n try {\n await exec(fallbackSql);\n return { index: INDEX_NAME, status: 'fallback_non_unique' };\n } catch (fallbackErr) {\n return {\n index: INDEX_NAME,\n status: 'error',\n error:\n fallbackErr instanceof Error\n ? fallbackErr.message\n : String(fallbackErr),\n };\n }\n }\n\n if (/already exists/i.test(msg)) {\n return { index: INDEX_NAME, status: 'already_exists' };\n }\n\n return { index: INDEX_NAME, status: 'error', error: msg };\n }\n}\n"],"mappings":"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","names":["list"]}