@objectstack/metadata 5.1.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +157 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -9
- package/dist/index.d.ts +31 -9
- package/dist/index.js +157 -24
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.cjs +58 -3
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +45 -3
- package/dist/migrations/index.d.ts +45 -3
- package/dist/migrations/index.js +56 -2
- package/dist/migrations/index.js.map +1 -1
- package/dist/node.cjs +157 -24
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +157 -24
- package/dist/node.js.map +1 -1
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -546,7 +546,7 @@ var LRUCache = class {
|
|
|
546
546
|
// src/migrations/add-sys-metadata-overlay-index.ts
|
|
547
547
|
var INDEX_NAME = "idx_sys_metadata_overlay_active";
|
|
548
548
|
var TABLE = "sys_metadata";
|
|
549
|
-
var COLUMNS = "(type, name, organization_id,
|
|
549
|
+
var COLUMNS = "(type, name, organization_id, environment_id, scope)";
|
|
550
550
|
var WHERE = "state = 'active'";
|
|
551
551
|
async function addSysMetadataOverlayIndex(driver) {
|
|
552
552
|
const driverAny = driver;
|
|
@@ -585,6 +585,59 @@ async function addSysMetadataOverlayIndex(driver) {
|
|
|
585
585
|
}
|
|
586
586
|
}
|
|
587
587
|
|
|
588
|
+
// src/migrations/migrate-project-id-to-environment-id.ts
|
|
589
|
+
var AFFECTED_TABLES = [
|
|
590
|
+
"sys_metadata",
|
|
591
|
+
"sys_metadata_history"
|
|
592
|
+
];
|
|
593
|
+
async function migrateProjectIdToEnvironmentId(driver) {
|
|
594
|
+
const driverAny = driver;
|
|
595
|
+
if (typeof driverAny.raw !== "function") {
|
|
596
|
+
throw new Error(
|
|
597
|
+
"migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. SqlDriver (better-sqlite3/knex) and TursoDriver both support this."
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
const results = [];
|
|
601
|
+
for (const table of AFFECTED_TABLES) {
|
|
602
|
+
try {
|
|
603
|
+
const hasColumn = await _columnExists(driverAny, table, "project_id");
|
|
604
|
+
const alreadyMigrated = await _columnExists(driverAny, table, "environment_id");
|
|
605
|
+
if (alreadyMigrated && !hasColumn) {
|
|
606
|
+
results.push({ table, status: "already_done" });
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (!hasColumn) {
|
|
610
|
+
results.push({ table, status: "table_missing" });
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
await driverAny.raw(
|
|
614
|
+
`ALTER TABLE "${table}" RENAME COLUMN project_id TO environment_id`
|
|
615
|
+
);
|
|
616
|
+
results.push({ table, status: "renamed" });
|
|
617
|
+
} catch (err) {
|
|
618
|
+
results.push({ table, status: "error", error: err?.message ?? String(err) });
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return results;
|
|
622
|
+
}
|
|
623
|
+
async function _columnExists(driver, table, column) {
|
|
624
|
+
try {
|
|
625
|
+
const rows = await driver.raw(`PRAGMA table_info("${table}")`);
|
|
626
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
627
|
+
const list2 = Array.isArray(rows[0]) ? rows[0] : rows;
|
|
628
|
+
return list2.some((r) => r?.name === column);
|
|
629
|
+
}
|
|
630
|
+
const result = await driver.raw(
|
|
631
|
+
`SELECT column_name FROM information_schema.columns WHERE table_name = ? AND column_name = ?`,
|
|
632
|
+
[table, column]
|
|
633
|
+
);
|
|
634
|
+
const list = Array.isArray(result[0]) ? result[0] : result;
|
|
635
|
+
return list.length > 0;
|
|
636
|
+
} catch {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
588
641
|
// src/loaders/database-loader.ts
|
|
589
642
|
var DatabaseLoader = class {
|
|
590
643
|
constructor(options) {
|
|
@@ -608,7 +661,7 @@ var DatabaseLoader = class {
|
|
|
608
661
|
this.tableName = options.tableName ?? "sys_metadata";
|
|
609
662
|
this.historyTableName = options.historyTableName ?? "sys_metadata_history";
|
|
610
663
|
this.organizationId = options.organizationId;
|
|
611
|
-
void options.
|
|
664
|
+
void options.environmentId;
|
|
612
665
|
this.trackHistory = options.trackHistory !== false;
|
|
613
666
|
const cacheOpts = options.cache;
|
|
614
667
|
const cacheEnabled = cacheOpts?.enabled !== false;
|
|
@@ -740,6 +793,7 @@ var DatabaseLoader = class {
|
|
|
740
793
|
}
|
|
741
794
|
}
|
|
742
795
|
if (driver) {
|
|
796
|
+
await migrateProjectIdToEnvironmentId(driver).catch(() => void 0);
|
|
743
797
|
await addSysMetadataOverlayIndex(driver);
|
|
744
798
|
}
|
|
745
799
|
} catch {
|
|
@@ -752,6 +806,10 @@ var DatabaseLoader = class {
|
|
|
752
806
|
name: this.tableName
|
|
753
807
|
});
|
|
754
808
|
this.schemaReady = true;
|
|
809
|
+
try {
|
|
810
|
+
await migrateProjectIdToEnvironmentId(this.driver);
|
|
811
|
+
} catch {
|
|
812
|
+
}
|
|
755
813
|
try {
|
|
756
814
|
await addSysMetadataOverlayIndex(this.driver);
|
|
757
815
|
} catch {
|
|
@@ -782,7 +840,7 @@ var DatabaseLoader = class {
|
|
|
782
840
|
}
|
|
783
841
|
/**
|
|
784
842
|
* Build base filter conditions for queries.
|
|
785
|
-
* Filters by organizationId when configured. `
|
|
843
|
+
* Filters by organizationId when configured. `environmentId` is accepted
|
|
786
844
|
* for back-compat but no longer constrains the query — see
|
|
787
845
|
* ADR-0008 §0 (branch/project removal).
|
|
788
846
|
*/
|
|
@@ -881,7 +939,7 @@ var DatabaseLoader = class {
|
|
|
881
939
|
owner: row.owner,
|
|
882
940
|
state: row.state ?? "active",
|
|
883
941
|
organizationId: row.organization_id,
|
|
884
|
-
|
|
942
|
+
environmentId: row.environment_id,
|
|
885
943
|
version: row.version ?? 1,
|
|
886
944
|
checksum: row.checksum,
|
|
887
945
|
source: row.source,
|
|
@@ -1315,13 +1373,13 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1315
1373
|
*
|
|
1316
1374
|
* @param driver - An IDataDriver instance for database operations
|
|
1317
1375
|
* @param organizationId - Organization ID for multi-tenant isolation
|
|
1318
|
-
* @param
|
|
1376
|
+
* @param environmentId - Project ID (undefined = platform-global)
|
|
1319
1377
|
*/
|
|
1320
|
-
setDatabaseDriver(driver, organizationId,
|
|
1321
|
-
if (
|
|
1378
|
+
setDatabaseDriver(driver, organizationId, environmentId) {
|
|
1379
|
+
if (environmentId !== void 0) {
|
|
1322
1380
|
this.logger.info("Project kernel \u2014 skipping DatabaseLoader for sys_metadata (control-plane only)", {
|
|
1323
1381
|
organizationId,
|
|
1324
|
-
|
|
1382
|
+
environmentId
|
|
1325
1383
|
});
|
|
1326
1384
|
return;
|
|
1327
1385
|
}
|
|
@@ -1330,7 +1388,7 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1330
1388
|
driver,
|
|
1331
1389
|
tableName,
|
|
1332
1390
|
organizationId,
|
|
1333
|
-
|
|
1391
|
+
environmentId,
|
|
1334
1392
|
cache: this.config.cache?.databaseLoader
|
|
1335
1393
|
});
|
|
1336
1394
|
this.registerLoader(dbLoader);
|
|
@@ -1344,13 +1402,13 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1344
1402
|
*
|
|
1345
1403
|
* @param engine - An IDataEngine instance (typically the ObjectQL service)
|
|
1346
1404
|
* @param organizationId - Organization ID for multi-tenant isolation
|
|
1347
|
-
* @param
|
|
1405
|
+
* @param environmentId - Project ID (undefined = platform-global)
|
|
1348
1406
|
*/
|
|
1349
|
-
setDataEngine(engine, organizationId,
|
|
1350
|
-
if (
|
|
1407
|
+
setDataEngine(engine, organizationId, environmentId) {
|
|
1408
|
+
if (environmentId !== void 0) {
|
|
1351
1409
|
this.logger.info("Project kernel \u2014 skipping DatabaseLoader for sys_metadata (control-plane only)", {
|
|
1352
1410
|
organizationId,
|
|
1353
|
-
|
|
1411
|
+
environmentId
|
|
1354
1412
|
});
|
|
1355
1413
|
return;
|
|
1356
1414
|
}
|
|
@@ -1359,7 +1417,7 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1359
1417
|
engine,
|
|
1360
1418
|
tableName,
|
|
1361
1419
|
organizationId,
|
|
1362
|
-
|
|
1420
|
+
environmentId,
|
|
1363
1421
|
cache: this.config.cache?.databaseLoader
|
|
1364
1422
|
});
|
|
1365
1423
|
this.registerLoader(dbLoader);
|
|
@@ -2392,6 +2450,23 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2392
2450
|
this.notifyWatchers(type, legacyEvent);
|
|
2393
2451
|
}
|
|
2394
2452
|
notifyWatchers(type, event) {
|
|
2453
|
+
this.notifyWatchersLocal(type, event);
|
|
2454
|
+
if (this.clusterPubSub) {
|
|
2455
|
+
const payload = {
|
|
2456
|
+
originNode: this.clusterNodeId,
|
|
2457
|
+
type,
|
|
2458
|
+
event
|
|
2459
|
+
};
|
|
2460
|
+
const key = `${type}:${event.name ?? ""}`;
|
|
2461
|
+
void this.clusterPubSub.publish(_MetadataManager.CLUSTER_CHANNEL, payload, { partitionKey: key }).catch((err) => {
|
|
2462
|
+
this.logger.error("Cluster metadata publish failed", void 0, {
|
|
2463
|
+
type,
|
|
2464
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2465
|
+
});
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
notifyWatchersLocal(type, event) {
|
|
2395
2470
|
const callbacks = this.watchCallbacks.get(type);
|
|
2396
2471
|
if (!callbacks) return;
|
|
2397
2472
|
for (const callback of callbacks) {
|
|
@@ -2405,6 +2480,63 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2405
2480
|
}
|
|
2406
2481
|
}
|
|
2407
2482
|
}
|
|
2483
|
+
/**
|
|
2484
|
+
* Attach a cluster pub/sub transport so metadata-change events fan
|
|
2485
|
+
* out to peer nodes and remote events replay into local watchers.
|
|
2486
|
+
*
|
|
2487
|
+
* The bridge plugin in @objectstack/service-cluster calls this once
|
|
2488
|
+
* per kernel boot after both cluster and metadata services are
|
|
2489
|
+
* registered. Passing the same MetadataManager twice no-ops; passing
|
|
2490
|
+
* a different transport replaces the prior subscription.
|
|
2491
|
+
*
|
|
2492
|
+
* Pass `nodeId` matching the local cluster's nodeId so loopback
|
|
2493
|
+
* suppression works.
|
|
2494
|
+
*
|
|
2495
|
+
* @returns disposer that unsubscribes from cluster events.
|
|
2496
|
+
*/
|
|
2497
|
+
attachClusterPubSub(pubsub, nodeId) {
|
|
2498
|
+
if (this.clusterPubSub === pubsub && this.clusterNodeId === nodeId) {
|
|
2499
|
+
return () => this.detachClusterPubSub();
|
|
2500
|
+
}
|
|
2501
|
+
this.detachClusterPubSub();
|
|
2502
|
+
this.clusterPubSub = pubsub;
|
|
2503
|
+
this.clusterNodeId = nodeId;
|
|
2504
|
+
this.clusterUnsubscribe = pubsub.subscribe(
|
|
2505
|
+
_MetadataManager.CLUSTER_CHANNEL,
|
|
2506
|
+
(msg) => {
|
|
2507
|
+
const p = msg.payload;
|
|
2508
|
+
if (p?.originNode && p.originNode === this.clusterNodeId) return;
|
|
2509
|
+
if (!p?.type || !p.event) return;
|
|
2510
|
+
setImmediate(() => {
|
|
2511
|
+
try {
|
|
2512
|
+
this.notifyWatchersLocal(p.type, p.event);
|
|
2513
|
+
} catch (err) {
|
|
2514
|
+
this.logger.error("Cluster remote replay failed", void 0, {
|
|
2515
|
+
type: p.type,
|
|
2516
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
);
|
|
2522
|
+
this.logger.info("MetadataManager attached to cluster pubsub", {
|
|
2523
|
+
nodeId,
|
|
2524
|
+
channel: _MetadataManager.CLUSTER_CHANNEL
|
|
2525
|
+
});
|
|
2526
|
+
return () => this.detachClusterPubSub();
|
|
2527
|
+
}
|
|
2528
|
+
/** Tear down cluster wiring. Safe to call multiple times. */
|
|
2529
|
+
detachClusterPubSub() {
|
|
2530
|
+
if (this.clusterUnsubscribe) {
|
|
2531
|
+
try {
|
|
2532
|
+
this.clusterUnsubscribe();
|
|
2533
|
+
} catch {
|
|
2534
|
+
}
|
|
2535
|
+
this.clusterUnsubscribe = void 0;
|
|
2536
|
+
}
|
|
2537
|
+
this.clusterPubSub = void 0;
|
|
2538
|
+
this.clusterNodeId = void 0;
|
|
2539
|
+
}
|
|
2408
2540
|
// ==========================================
|
|
2409
2541
|
// Version History & Rollback
|
|
2410
2542
|
// ==========================================
|
|
@@ -2505,6 +2637,7 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2505
2637
|
}
|
|
2506
2638
|
};
|
|
2507
2639
|
_MetadataManager.LIST_CACHE_TTL_MS = 3e4;
|
|
2640
|
+
_MetadataManager.CLUSTER_CHANNEL = "metadata.changed";
|
|
2508
2641
|
var MetadataManager = _MetadataManager;
|
|
2509
2642
|
|
|
2510
2643
|
// src/plugin.ts
|
|
@@ -3283,24 +3416,24 @@ var MetadataPlugin = class {
|
|
|
3283
3416
|
* metadata items into the MetadataManager.
|
|
3284
3417
|
*/
|
|
3285
3418
|
async _parseAndRegisterArtifact(ctx, raw, label) {
|
|
3286
|
-
const {
|
|
3419
|
+
const { EnvironmentArtifactSchema } = await import("@objectstack/spec/cloud");
|
|
3287
3420
|
const { ObjectStackDefinitionSchema } = await import("@objectstack/spec");
|
|
3288
3421
|
let metadata;
|
|
3289
3422
|
const obj = raw;
|
|
3290
3423
|
if (obj?.schemaVersion && obj?.commitId && obj?.metadata !== void 0) {
|
|
3291
|
-
const artifact =
|
|
3424
|
+
const artifact = EnvironmentArtifactSchema.parse(obj);
|
|
3292
3425
|
metadata = artifact.metadata;
|
|
3293
3426
|
} else if (obj?.success && obj?.data?.metadata) {
|
|
3294
|
-
const artifact =
|
|
3427
|
+
const artifact = EnvironmentArtifactSchema.parse(obj.data);
|
|
3295
3428
|
metadata = artifact.metadata;
|
|
3296
3429
|
} else {
|
|
3297
3430
|
const def = ObjectStackDefinitionSchema.parse(obj);
|
|
3298
3431
|
const canonical = JSON.stringify(def, Object.keys(def).sort());
|
|
3299
3432
|
const checksum = (0, import_node_crypto2.createHash)("sha256").update(canonical).digest("hex");
|
|
3300
|
-
const
|
|
3301
|
-
|
|
3433
|
+
const environmentId = this.options.environmentId ?? "proj_local";
|
|
3434
|
+
EnvironmentArtifactSchema.parse({
|
|
3302
3435
|
schemaVersion: "0.1",
|
|
3303
|
-
|
|
3436
|
+
environmentId,
|
|
3304
3437
|
commitId: "local-dev",
|
|
3305
3438
|
checksum,
|
|
3306
3439
|
metadata: def
|
|
@@ -3356,13 +3489,13 @@ var MetadataPlugin = class {
|
|
|
3356
3489
|
* P2: Load metadata from the cloud artifact API endpoint.
|
|
3357
3490
|
*/
|
|
3358
3491
|
async _loadFromArtifactApi(ctx, src) {
|
|
3359
|
-
const
|
|
3360
|
-
if (!
|
|
3361
|
-
throw new Error("[MetadataPlugin] artifact-api source requires options.
|
|
3492
|
+
const environmentId = this.options.environmentId;
|
|
3493
|
+
if (!environmentId) {
|
|
3494
|
+
throw new Error("[MetadataPlugin] artifact-api source requires options.environmentId to be set");
|
|
3362
3495
|
}
|
|
3363
3496
|
let artifactUrl = src.url.replace(/\/+$/, "");
|
|
3364
3497
|
if (!/\/api\/v\d+\/cloud\/projects\//i.test(artifactUrl)) {
|
|
3365
|
-
artifactUrl = `${artifactUrl}/api/v1/cloud/
|
|
3498
|
+
artifactUrl = `${artifactUrl}/api/v1/cloud/environments/${environmentId}/artifact`;
|
|
3366
3499
|
}
|
|
3367
3500
|
if (src.commitId) {
|
|
3368
3501
|
artifactUrl += `${artifactUrl.includes("?") ? "&" : "?"}commit=${encodeURIComponent(src.commitId)}`;
|