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