@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.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as System from '@objectstack/spec/system';
|
|
2
2
|
import { MetadataFormat, MetadataLoaderContract, MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataSaveOptions, MetadataSaveResult, MetadataWatchEvent, MetadataManagerConfig, PackagePublishResult, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataDiffResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy } from '@objectstack/spec/system';
|
|
3
3
|
export { MetadataCollectionInfo, MetadataDiffResult, MetadataExportOptions, MetadataFormat, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataImportOptions, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataManagerConfig, MetadataSaveOptions, MetadataSaveResult, MetadataStats, MetadataWatchEvent } from '@objectstack/spec/system';
|
|
4
|
-
import { IMetadataService, IDataDriver, IDataEngine, IRealtimeService, MetadataWatchCallback, MetadataWatchHandle, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, ISchemaDriver } from '@objectstack/spec/contracts';
|
|
4
|
+
import { IMetadataService, IDataDriver, IDataEngine, IRealtimeService, MetadataWatchCallback, MetadataWatchHandle, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, IPubSub, ISchemaDriver } from '@objectstack/spec/contracts';
|
|
5
5
|
export { IMetadataService, MetadataImportResult, MetadataTypeInfo, MetadataWatchCallback, MetadataWatchHandle } from '@objectstack/spec/contracts';
|
|
6
6
|
import { MetadataTypeRegistryEntry, MetadataQuery, MetadataQueryResult, MetadataBulkResult, MetadataOverlay, MetadataValidationResult, MetadataDependency, MetadataPluginConfig } from '@objectstack/spec/kernel';
|
|
7
7
|
export { MetadataBulkResult, MetadataDependency, MetadataPluginConfig, MetadataPluginManifest, MetadataQuery, MetadataQueryResult, MetadataType, MetadataTypeRegistryEntry, MetadataValidationResult } from '@objectstack/spec/kernel';
|
|
@@ -161,6 +161,10 @@ declare class MetadataManager implements IMetadataService {
|
|
|
161
161
|
private listCache;
|
|
162
162
|
private static readonly LIST_CACHE_TTL_MS;
|
|
163
163
|
private realtimeService?;
|
|
164
|
+
private clusterPubSub?;
|
|
165
|
+
private clusterNodeId?;
|
|
166
|
+
private clusterUnsubscribe?;
|
|
167
|
+
private static readonly CLUSTER_CHANNEL;
|
|
164
168
|
protected repository?: MetadataRepository;
|
|
165
169
|
private repoWatchIter?;
|
|
166
170
|
private repoWatchClosed;
|
|
@@ -175,9 +179,9 @@ declare class MetadataManager implements IMetadataService {
|
|
|
175
179
|
*
|
|
176
180
|
* @param driver - An IDataDriver instance for database operations
|
|
177
181
|
* @param organizationId - Organization ID for multi-tenant isolation
|
|
178
|
-
* @param
|
|
182
|
+
* @param environmentId - Project ID (undefined = platform-global)
|
|
179
183
|
*/
|
|
180
|
-
setDatabaseDriver(driver: IDataDriver, organizationId?: string,
|
|
184
|
+
setDatabaseDriver(driver: IDataDriver, organizationId?: string, environmentId?: string): void;
|
|
181
185
|
/**
|
|
182
186
|
* Configure and register a DatabaseLoader backed by an IDataEngine (ObjectQL).
|
|
183
187
|
* The engine handles datasource routing automatically — sys_metadata will
|
|
@@ -186,9 +190,9 @@ declare class MetadataManager implements IMetadataService {
|
|
|
186
190
|
*
|
|
187
191
|
* @param engine - An IDataEngine instance (typically the ObjectQL service)
|
|
188
192
|
* @param organizationId - Organization ID for multi-tenant isolation
|
|
189
|
-
* @param
|
|
193
|
+
* @param environmentId - Project ID (undefined = platform-global)
|
|
190
194
|
*/
|
|
191
|
-
setDataEngine(engine: IDataEngine, organizationId?: string,
|
|
195
|
+
setDataEngine(engine: IDataEngine, organizationId?: string, environmentId?: string): void;
|
|
192
196
|
/**
|
|
193
197
|
* Set the realtime service for publishing metadata change events.
|
|
194
198
|
* Should be called after kernel resolves the realtime service.
|
|
@@ -434,6 +438,24 @@ declare class MetadataManager implements IMetadataService {
|
|
|
434
438
|
/** Translate a repo event to the legacy MetadataWatchEvent + invalidate caches. */
|
|
435
439
|
private applyRepoEvent;
|
|
436
440
|
protected notifyWatchers(type: string, event: MetadataWatchEvent): void;
|
|
441
|
+
private notifyWatchersLocal;
|
|
442
|
+
/**
|
|
443
|
+
* Attach a cluster pub/sub transport so metadata-change events fan
|
|
444
|
+
* out to peer nodes and remote events replay into local watchers.
|
|
445
|
+
*
|
|
446
|
+
* The bridge plugin in @objectstack/service-cluster calls this once
|
|
447
|
+
* per kernel boot after both cluster and metadata services are
|
|
448
|
+
* registered. Passing the same MetadataManager twice no-ops; passing
|
|
449
|
+
* a different transport replaces the prior subscription.
|
|
450
|
+
*
|
|
451
|
+
* Pass `nodeId` matching the local cluster's nodeId so loopback
|
|
452
|
+
* suppression works.
|
|
453
|
+
*
|
|
454
|
+
* @returns disposer that unsubscribes from cluster events.
|
|
455
|
+
*/
|
|
456
|
+
attachClusterPubSub(pubsub: IPubSub, nodeId: string): () => void;
|
|
457
|
+
/** Tear down cluster wiring. Safe to call multiple times. */
|
|
458
|
+
detachClusterPubSub(): void;
|
|
437
459
|
/**
|
|
438
460
|
* Get the database loader for history operations.
|
|
439
461
|
* Returns undefined if no database loader is configured.
|
|
@@ -489,7 +511,7 @@ interface MetadataPluginOptions {
|
|
|
489
511
|
/** Organization ID for metadata-scoped consumers; MetadataPlugin itself does not persist runtime metadata. */
|
|
490
512
|
organizationId?: string;
|
|
491
513
|
/** Project ID used by local artifact envelopes and metadata-scoped consumers. */
|
|
492
|
-
|
|
514
|
+
environmentId?: string;
|
|
493
515
|
/**
|
|
494
516
|
* When set, MetadataPlugin loads metadata from an artifact instead of scanning
|
|
495
517
|
* the filesystem. Only `local-file` is implemented now; `artifact-api` is
|
|
@@ -690,10 +712,10 @@ interface DatabaseLoaderOptions {
|
|
|
690
712
|
* @deprecated since ADR-0008 §0 amendment (branch/project removal).
|
|
691
713
|
* The metadata layer is keyed by organization only. This option is
|
|
692
714
|
* accepted for back-compat but ignored — writes do not set
|
|
693
|
-
* `
|
|
715
|
+
* `environment_id` and filters do not constrain on it. Will be removed
|
|
694
716
|
* in the next major release.
|
|
695
717
|
*/
|
|
696
|
-
|
|
718
|
+
environmentId?: string;
|
|
697
719
|
/** Enable history tracking (default: true) */
|
|
698
720
|
trackHistory?: boolean;
|
|
699
721
|
/**
|
|
@@ -772,7 +794,7 @@ declare class DatabaseLoader implements MetadataLoader {
|
|
|
772
794
|
private ensureHistorySchema;
|
|
773
795
|
/**
|
|
774
796
|
* Build base filter conditions for queries.
|
|
775
|
-
* Filters by organizationId when configured. `
|
|
797
|
+
* Filters by organizationId when configured. `environmentId` is accepted
|
|
776
798
|
* for back-compat but no longer constrains the query — see
|
|
777
799
|
* ADR-0008 §0 (branch/project removal).
|
|
778
800
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as System from '@objectstack/spec/system';
|
|
2
2
|
import { MetadataFormat, MetadataLoaderContract, MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataSaveOptions, MetadataSaveResult, MetadataWatchEvent, MetadataManagerConfig, PackagePublishResult, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataDiffResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy } from '@objectstack/spec/system';
|
|
3
3
|
export { MetadataCollectionInfo, MetadataDiffResult, MetadataExportOptions, MetadataFormat, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataImportOptions, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataManagerConfig, MetadataSaveOptions, MetadataSaveResult, MetadataStats, MetadataWatchEvent } from '@objectstack/spec/system';
|
|
4
|
-
import { IMetadataService, IDataDriver, IDataEngine, IRealtimeService, MetadataWatchCallback, MetadataWatchHandle, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, ISchemaDriver } from '@objectstack/spec/contracts';
|
|
4
|
+
import { IMetadataService, IDataDriver, IDataEngine, IRealtimeService, MetadataWatchCallback, MetadataWatchHandle, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, IPubSub, ISchemaDriver } from '@objectstack/spec/contracts';
|
|
5
5
|
export { IMetadataService, MetadataImportResult, MetadataTypeInfo, MetadataWatchCallback, MetadataWatchHandle } from '@objectstack/spec/contracts';
|
|
6
6
|
import { MetadataTypeRegistryEntry, MetadataQuery, MetadataQueryResult, MetadataBulkResult, MetadataOverlay, MetadataValidationResult, MetadataDependency, MetadataPluginConfig } from '@objectstack/spec/kernel';
|
|
7
7
|
export { MetadataBulkResult, MetadataDependency, MetadataPluginConfig, MetadataPluginManifest, MetadataQuery, MetadataQueryResult, MetadataType, MetadataTypeRegistryEntry, MetadataValidationResult } from '@objectstack/spec/kernel';
|
|
@@ -161,6 +161,10 @@ declare class MetadataManager implements IMetadataService {
|
|
|
161
161
|
private listCache;
|
|
162
162
|
private static readonly LIST_CACHE_TTL_MS;
|
|
163
163
|
private realtimeService?;
|
|
164
|
+
private clusterPubSub?;
|
|
165
|
+
private clusterNodeId?;
|
|
166
|
+
private clusterUnsubscribe?;
|
|
167
|
+
private static readonly CLUSTER_CHANNEL;
|
|
164
168
|
protected repository?: MetadataRepository;
|
|
165
169
|
private repoWatchIter?;
|
|
166
170
|
private repoWatchClosed;
|
|
@@ -175,9 +179,9 @@ declare class MetadataManager implements IMetadataService {
|
|
|
175
179
|
*
|
|
176
180
|
* @param driver - An IDataDriver instance for database operations
|
|
177
181
|
* @param organizationId - Organization ID for multi-tenant isolation
|
|
178
|
-
* @param
|
|
182
|
+
* @param environmentId - Project ID (undefined = platform-global)
|
|
179
183
|
*/
|
|
180
|
-
setDatabaseDriver(driver: IDataDriver, organizationId?: string,
|
|
184
|
+
setDatabaseDriver(driver: IDataDriver, organizationId?: string, environmentId?: string): void;
|
|
181
185
|
/**
|
|
182
186
|
* Configure and register a DatabaseLoader backed by an IDataEngine (ObjectQL).
|
|
183
187
|
* The engine handles datasource routing automatically — sys_metadata will
|
|
@@ -186,9 +190,9 @@ declare class MetadataManager implements IMetadataService {
|
|
|
186
190
|
*
|
|
187
191
|
* @param engine - An IDataEngine instance (typically the ObjectQL service)
|
|
188
192
|
* @param organizationId - Organization ID for multi-tenant isolation
|
|
189
|
-
* @param
|
|
193
|
+
* @param environmentId - Project ID (undefined = platform-global)
|
|
190
194
|
*/
|
|
191
|
-
setDataEngine(engine: IDataEngine, organizationId?: string,
|
|
195
|
+
setDataEngine(engine: IDataEngine, organizationId?: string, environmentId?: string): void;
|
|
192
196
|
/**
|
|
193
197
|
* Set the realtime service for publishing metadata change events.
|
|
194
198
|
* Should be called after kernel resolves the realtime service.
|
|
@@ -434,6 +438,24 @@ declare class MetadataManager implements IMetadataService {
|
|
|
434
438
|
/** Translate a repo event to the legacy MetadataWatchEvent + invalidate caches. */
|
|
435
439
|
private applyRepoEvent;
|
|
436
440
|
protected notifyWatchers(type: string, event: MetadataWatchEvent): void;
|
|
441
|
+
private notifyWatchersLocal;
|
|
442
|
+
/**
|
|
443
|
+
* Attach a cluster pub/sub transport so metadata-change events fan
|
|
444
|
+
* out to peer nodes and remote events replay into local watchers.
|
|
445
|
+
*
|
|
446
|
+
* The bridge plugin in @objectstack/service-cluster calls this once
|
|
447
|
+
* per kernel boot after both cluster and metadata services are
|
|
448
|
+
* registered. Passing the same MetadataManager twice no-ops; passing
|
|
449
|
+
* a different transport replaces the prior subscription.
|
|
450
|
+
*
|
|
451
|
+
* Pass `nodeId` matching the local cluster's nodeId so loopback
|
|
452
|
+
* suppression works.
|
|
453
|
+
*
|
|
454
|
+
* @returns disposer that unsubscribes from cluster events.
|
|
455
|
+
*/
|
|
456
|
+
attachClusterPubSub(pubsub: IPubSub, nodeId: string): () => void;
|
|
457
|
+
/** Tear down cluster wiring. Safe to call multiple times. */
|
|
458
|
+
detachClusterPubSub(): void;
|
|
437
459
|
/**
|
|
438
460
|
* Get the database loader for history operations.
|
|
439
461
|
* Returns undefined if no database loader is configured.
|
|
@@ -489,7 +511,7 @@ interface MetadataPluginOptions {
|
|
|
489
511
|
/** Organization ID for metadata-scoped consumers; MetadataPlugin itself does not persist runtime metadata. */
|
|
490
512
|
organizationId?: string;
|
|
491
513
|
/** Project ID used by local artifact envelopes and metadata-scoped consumers. */
|
|
492
|
-
|
|
514
|
+
environmentId?: string;
|
|
493
515
|
/**
|
|
494
516
|
* When set, MetadataPlugin loads metadata from an artifact instead of scanning
|
|
495
517
|
* the filesystem. Only `local-file` is implemented now; `artifact-api` is
|
|
@@ -690,10 +712,10 @@ interface DatabaseLoaderOptions {
|
|
|
690
712
|
* @deprecated since ADR-0008 §0 amendment (branch/project removal).
|
|
691
713
|
* The metadata layer is keyed by organization only. This option is
|
|
692
714
|
* accepted for back-compat but ignored — writes do not set
|
|
693
|
-
* `
|
|
715
|
+
* `environment_id` and filters do not constrain on it. Will be removed
|
|
694
716
|
* in the next major release.
|
|
695
717
|
*/
|
|
696
|
-
|
|
718
|
+
environmentId?: string;
|
|
697
719
|
/** Enable history tracking (default: true) */
|
|
698
720
|
trackHistory?: boolean;
|
|
699
721
|
/**
|
|
@@ -772,7 +794,7 @@ declare class DatabaseLoader implements MetadataLoader {
|
|
|
772
794
|
private ensureHistorySchema;
|
|
773
795
|
/**
|
|
774
796
|
* Build base filter conditions for queries.
|
|
775
|
-
* Filters by organizationId when configured. `
|
|
797
|
+
* Filters by organizationId when configured. `environmentId` is accepted
|
|
776
798
|
* for back-compat but no longer constrains the query — see
|
|
777
799
|
* ADR-0008 §0 (branch/project removal).
|
|
778
800
|
*/
|
package/dist/index.js
CHANGED
|
@@ -502,7 +502,7 @@ var LRUCache = class {
|
|
|
502
502
|
// src/migrations/add-sys-metadata-overlay-index.ts
|
|
503
503
|
var INDEX_NAME = "idx_sys_metadata_overlay_active";
|
|
504
504
|
var TABLE = "sys_metadata";
|
|
505
|
-
var COLUMNS = "(type, name, organization_id,
|
|
505
|
+
var COLUMNS = "(type, name, organization_id, environment_id, scope)";
|
|
506
506
|
var WHERE = "state = 'active'";
|
|
507
507
|
async function addSysMetadataOverlayIndex(driver) {
|
|
508
508
|
const driverAny = driver;
|
|
@@ -541,6 +541,59 @@ async function addSysMetadataOverlayIndex(driver) {
|
|
|
541
541
|
}
|
|
542
542
|
}
|
|
543
543
|
|
|
544
|
+
// src/migrations/migrate-project-id-to-environment-id.ts
|
|
545
|
+
var AFFECTED_TABLES = [
|
|
546
|
+
"sys_metadata",
|
|
547
|
+
"sys_metadata_history"
|
|
548
|
+
];
|
|
549
|
+
async function migrateProjectIdToEnvironmentId(driver) {
|
|
550
|
+
const driverAny = driver;
|
|
551
|
+
if (typeof driverAny.raw !== "function") {
|
|
552
|
+
throw new Error(
|
|
553
|
+
"migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. SqlDriver (better-sqlite3/knex) and TursoDriver both support this."
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
const results = [];
|
|
557
|
+
for (const table of AFFECTED_TABLES) {
|
|
558
|
+
try {
|
|
559
|
+
const hasColumn = await _columnExists(driverAny, table, "project_id");
|
|
560
|
+
const alreadyMigrated = await _columnExists(driverAny, table, "environment_id");
|
|
561
|
+
if (alreadyMigrated && !hasColumn) {
|
|
562
|
+
results.push({ table, status: "already_done" });
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (!hasColumn) {
|
|
566
|
+
results.push({ table, status: "table_missing" });
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
await driverAny.raw(
|
|
570
|
+
`ALTER TABLE "${table}" RENAME COLUMN project_id TO environment_id`
|
|
571
|
+
);
|
|
572
|
+
results.push({ table, status: "renamed" });
|
|
573
|
+
} catch (err) {
|
|
574
|
+
results.push({ table, status: "error", error: err?.message ?? String(err) });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return results;
|
|
578
|
+
}
|
|
579
|
+
async function _columnExists(driver, table, column) {
|
|
580
|
+
try {
|
|
581
|
+
const rows = await driver.raw(`PRAGMA table_info("${table}")`);
|
|
582
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
583
|
+
const list2 = Array.isArray(rows[0]) ? rows[0] : rows;
|
|
584
|
+
return list2.some((r) => r?.name === column);
|
|
585
|
+
}
|
|
586
|
+
const result = await driver.raw(
|
|
587
|
+
`SELECT column_name FROM information_schema.columns WHERE table_name = ? AND column_name = ?`,
|
|
588
|
+
[table, column]
|
|
589
|
+
);
|
|
590
|
+
const list = Array.isArray(result[0]) ? result[0] : result;
|
|
591
|
+
return list.length > 0;
|
|
592
|
+
} catch {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
544
597
|
// src/loaders/database-loader.ts
|
|
545
598
|
var DatabaseLoader = class {
|
|
546
599
|
constructor(options) {
|
|
@@ -564,7 +617,7 @@ var DatabaseLoader = class {
|
|
|
564
617
|
this.tableName = options.tableName ?? "sys_metadata";
|
|
565
618
|
this.historyTableName = options.historyTableName ?? "sys_metadata_history";
|
|
566
619
|
this.organizationId = options.organizationId;
|
|
567
|
-
void options.
|
|
620
|
+
void options.environmentId;
|
|
568
621
|
this.trackHistory = options.trackHistory !== false;
|
|
569
622
|
const cacheOpts = options.cache;
|
|
570
623
|
const cacheEnabled = cacheOpts?.enabled !== false;
|
|
@@ -696,6 +749,7 @@ var DatabaseLoader = class {
|
|
|
696
749
|
}
|
|
697
750
|
}
|
|
698
751
|
if (driver) {
|
|
752
|
+
await migrateProjectIdToEnvironmentId(driver).catch(() => void 0);
|
|
699
753
|
await addSysMetadataOverlayIndex(driver);
|
|
700
754
|
}
|
|
701
755
|
} catch {
|
|
@@ -708,6 +762,10 @@ var DatabaseLoader = class {
|
|
|
708
762
|
name: this.tableName
|
|
709
763
|
});
|
|
710
764
|
this.schemaReady = true;
|
|
765
|
+
try {
|
|
766
|
+
await migrateProjectIdToEnvironmentId(this.driver);
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
711
769
|
try {
|
|
712
770
|
await addSysMetadataOverlayIndex(this.driver);
|
|
713
771
|
} catch {
|
|
@@ -738,7 +796,7 @@ var DatabaseLoader = class {
|
|
|
738
796
|
}
|
|
739
797
|
/**
|
|
740
798
|
* Build base filter conditions for queries.
|
|
741
|
-
* Filters by organizationId when configured. `
|
|
799
|
+
* Filters by organizationId when configured. `environmentId` is accepted
|
|
742
800
|
* for back-compat but no longer constrains the query — see
|
|
743
801
|
* ADR-0008 §0 (branch/project removal).
|
|
744
802
|
*/
|
|
@@ -837,7 +895,7 @@ var DatabaseLoader = class {
|
|
|
837
895
|
owner: row.owner,
|
|
838
896
|
state: row.state ?? "active",
|
|
839
897
|
organizationId: row.organization_id,
|
|
840
|
-
|
|
898
|
+
environmentId: row.environment_id,
|
|
841
899
|
version: row.version ?? 1,
|
|
842
900
|
checksum: row.checksum,
|
|
843
901
|
source: row.source,
|
|
@@ -1271,13 +1329,13 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1271
1329
|
*
|
|
1272
1330
|
* @param driver - An IDataDriver instance for database operations
|
|
1273
1331
|
* @param organizationId - Organization ID for multi-tenant isolation
|
|
1274
|
-
* @param
|
|
1332
|
+
* @param environmentId - Project ID (undefined = platform-global)
|
|
1275
1333
|
*/
|
|
1276
|
-
setDatabaseDriver(driver, organizationId,
|
|
1277
|
-
if (
|
|
1334
|
+
setDatabaseDriver(driver, organizationId, environmentId) {
|
|
1335
|
+
if (environmentId !== void 0) {
|
|
1278
1336
|
this.logger.info("Project kernel \u2014 skipping DatabaseLoader for sys_metadata (control-plane only)", {
|
|
1279
1337
|
organizationId,
|
|
1280
|
-
|
|
1338
|
+
environmentId
|
|
1281
1339
|
});
|
|
1282
1340
|
return;
|
|
1283
1341
|
}
|
|
@@ -1286,7 +1344,7 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1286
1344
|
driver,
|
|
1287
1345
|
tableName,
|
|
1288
1346
|
organizationId,
|
|
1289
|
-
|
|
1347
|
+
environmentId,
|
|
1290
1348
|
cache: this.config.cache?.databaseLoader
|
|
1291
1349
|
});
|
|
1292
1350
|
this.registerLoader(dbLoader);
|
|
@@ -1300,13 +1358,13 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1300
1358
|
*
|
|
1301
1359
|
* @param engine - An IDataEngine instance (typically the ObjectQL service)
|
|
1302
1360
|
* @param organizationId - Organization ID for multi-tenant isolation
|
|
1303
|
-
* @param
|
|
1361
|
+
* @param environmentId - Project ID (undefined = platform-global)
|
|
1304
1362
|
*/
|
|
1305
|
-
setDataEngine(engine, organizationId,
|
|
1306
|
-
if (
|
|
1363
|
+
setDataEngine(engine, organizationId, environmentId) {
|
|
1364
|
+
if (environmentId !== void 0) {
|
|
1307
1365
|
this.logger.info("Project kernel \u2014 skipping DatabaseLoader for sys_metadata (control-plane only)", {
|
|
1308
1366
|
organizationId,
|
|
1309
|
-
|
|
1367
|
+
environmentId
|
|
1310
1368
|
});
|
|
1311
1369
|
return;
|
|
1312
1370
|
}
|
|
@@ -1315,7 +1373,7 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1315
1373
|
engine,
|
|
1316
1374
|
tableName,
|
|
1317
1375
|
organizationId,
|
|
1318
|
-
|
|
1376
|
+
environmentId,
|
|
1319
1377
|
cache: this.config.cache?.databaseLoader
|
|
1320
1378
|
});
|
|
1321
1379
|
this.registerLoader(dbLoader);
|
|
@@ -2348,6 +2406,23 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2348
2406
|
this.notifyWatchers(type, legacyEvent);
|
|
2349
2407
|
}
|
|
2350
2408
|
notifyWatchers(type, event) {
|
|
2409
|
+
this.notifyWatchersLocal(type, event);
|
|
2410
|
+
if (this.clusterPubSub) {
|
|
2411
|
+
const payload = {
|
|
2412
|
+
originNode: this.clusterNodeId,
|
|
2413
|
+
type,
|
|
2414
|
+
event
|
|
2415
|
+
};
|
|
2416
|
+
const key = `${type}:${event.name ?? ""}`;
|
|
2417
|
+
void this.clusterPubSub.publish(_MetadataManager.CLUSTER_CHANNEL, payload, { partitionKey: key }).catch((err) => {
|
|
2418
|
+
this.logger.error("Cluster metadata publish failed", void 0, {
|
|
2419
|
+
type,
|
|
2420
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2421
|
+
});
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
notifyWatchersLocal(type, event) {
|
|
2351
2426
|
const callbacks = this.watchCallbacks.get(type);
|
|
2352
2427
|
if (!callbacks) return;
|
|
2353
2428
|
for (const callback of callbacks) {
|
|
@@ -2361,6 +2436,63 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2361
2436
|
}
|
|
2362
2437
|
}
|
|
2363
2438
|
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Attach a cluster pub/sub transport so metadata-change events fan
|
|
2441
|
+
* out to peer nodes and remote events replay into local watchers.
|
|
2442
|
+
*
|
|
2443
|
+
* The bridge plugin in @objectstack/service-cluster calls this once
|
|
2444
|
+
* per kernel boot after both cluster and metadata services are
|
|
2445
|
+
* registered. Passing the same MetadataManager twice no-ops; passing
|
|
2446
|
+
* a different transport replaces the prior subscription.
|
|
2447
|
+
*
|
|
2448
|
+
* Pass `nodeId` matching the local cluster's nodeId so loopback
|
|
2449
|
+
* suppression works.
|
|
2450
|
+
*
|
|
2451
|
+
* @returns disposer that unsubscribes from cluster events.
|
|
2452
|
+
*/
|
|
2453
|
+
attachClusterPubSub(pubsub, nodeId) {
|
|
2454
|
+
if (this.clusterPubSub === pubsub && this.clusterNodeId === nodeId) {
|
|
2455
|
+
return () => this.detachClusterPubSub();
|
|
2456
|
+
}
|
|
2457
|
+
this.detachClusterPubSub();
|
|
2458
|
+
this.clusterPubSub = pubsub;
|
|
2459
|
+
this.clusterNodeId = nodeId;
|
|
2460
|
+
this.clusterUnsubscribe = pubsub.subscribe(
|
|
2461
|
+
_MetadataManager.CLUSTER_CHANNEL,
|
|
2462
|
+
(msg) => {
|
|
2463
|
+
const p = msg.payload;
|
|
2464
|
+
if (p?.originNode && p.originNode === this.clusterNodeId) return;
|
|
2465
|
+
if (!p?.type || !p.event) return;
|
|
2466
|
+
setImmediate(() => {
|
|
2467
|
+
try {
|
|
2468
|
+
this.notifyWatchersLocal(p.type, p.event);
|
|
2469
|
+
} catch (err) {
|
|
2470
|
+
this.logger.error("Cluster remote replay failed", void 0, {
|
|
2471
|
+
type: p.type,
|
|
2472
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
});
|
|
2476
|
+
}
|
|
2477
|
+
);
|
|
2478
|
+
this.logger.info("MetadataManager attached to cluster pubsub", {
|
|
2479
|
+
nodeId,
|
|
2480
|
+
channel: _MetadataManager.CLUSTER_CHANNEL
|
|
2481
|
+
});
|
|
2482
|
+
return () => this.detachClusterPubSub();
|
|
2483
|
+
}
|
|
2484
|
+
/** Tear down cluster wiring. Safe to call multiple times. */
|
|
2485
|
+
detachClusterPubSub() {
|
|
2486
|
+
if (this.clusterUnsubscribe) {
|
|
2487
|
+
try {
|
|
2488
|
+
this.clusterUnsubscribe();
|
|
2489
|
+
} catch {
|
|
2490
|
+
}
|
|
2491
|
+
this.clusterUnsubscribe = void 0;
|
|
2492
|
+
}
|
|
2493
|
+
this.clusterPubSub = void 0;
|
|
2494
|
+
this.clusterNodeId = void 0;
|
|
2495
|
+
}
|
|
2364
2496
|
// ==========================================
|
|
2365
2497
|
// Version History & Rollback
|
|
2366
2498
|
// ==========================================
|
|
@@ -2461,6 +2593,7 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2461
2593
|
}
|
|
2462
2594
|
};
|
|
2463
2595
|
_MetadataManager.LIST_CACHE_TTL_MS = 3e4;
|
|
2596
|
+
_MetadataManager.CLUSTER_CHANNEL = "metadata.changed";
|
|
2464
2597
|
var MetadataManager = _MetadataManager;
|
|
2465
2598
|
|
|
2466
2599
|
// src/plugin.ts
|
|
@@ -3242,24 +3375,24 @@ var MetadataPlugin = class {
|
|
|
3242
3375
|
* metadata items into the MetadataManager.
|
|
3243
3376
|
*/
|
|
3244
3377
|
async _parseAndRegisterArtifact(ctx, raw, label) {
|
|
3245
|
-
const {
|
|
3378
|
+
const { EnvironmentArtifactSchema } = await import("@objectstack/spec/cloud");
|
|
3246
3379
|
const { ObjectStackDefinitionSchema } = await import("@objectstack/spec");
|
|
3247
3380
|
let metadata;
|
|
3248
3381
|
const obj = raw;
|
|
3249
3382
|
if (obj?.schemaVersion && obj?.commitId && obj?.metadata !== void 0) {
|
|
3250
|
-
const artifact =
|
|
3383
|
+
const artifact = EnvironmentArtifactSchema.parse(obj);
|
|
3251
3384
|
metadata = artifact.metadata;
|
|
3252
3385
|
} else if (obj?.success && obj?.data?.metadata) {
|
|
3253
|
-
const artifact =
|
|
3386
|
+
const artifact = EnvironmentArtifactSchema.parse(obj.data);
|
|
3254
3387
|
metadata = artifact.metadata;
|
|
3255
3388
|
} else {
|
|
3256
3389
|
const def = ObjectStackDefinitionSchema.parse(obj);
|
|
3257
3390
|
const canonical = JSON.stringify(def, Object.keys(def).sort());
|
|
3258
3391
|
const checksum = createHash2("sha256").update(canonical).digest("hex");
|
|
3259
|
-
const
|
|
3260
|
-
|
|
3392
|
+
const environmentId = this.options.environmentId ?? "proj_local";
|
|
3393
|
+
EnvironmentArtifactSchema.parse({
|
|
3261
3394
|
schemaVersion: "0.1",
|
|
3262
|
-
|
|
3395
|
+
environmentId,
|
|
3263
3396
|
commitId: "local-dev",
|
|
3264
3397
|
checksum,
|
|
3265
3398
|
metadata: def
|
|
@@ -3315,13 +3448,13 @@ var MetadataPlugin = class {
|
|
|
3315
3448
|
* P2: Load metadata from the cloud artifact API endpoint.
|
|
3316
3449
|
*/
|
|
3317
3450
|
async _loadFromArtifactApi(ctx, src) {
|
|
3318
|
-
const
|
|
3319
|
-
if (!
|
|
3320
|
-
throw new Error("[MetadataPlugin] artifact-api source requires options.
|
|
3451
|
+
const environmentId = this.options.environmentId;
|
|
3452
|
+
if (!environmentId) {
|
|
3453
|
+
throw new Error("[MetadataPlugin] artifact-api source requires options.environmentId to be set");
|
|
3321
3454
|
}
|
|
3322
3455
|
let artifactUrl = src.url.replace(/\/+$/, "");
|
|
3323
3456
|
if (!/\/api\/v\d+\/cloud\/projects\//i.test(artifactUrl)) {
|
|
3324
|
-
artifactUrl = `${artifactUrl}/api/v1/cloud/
|
|
3457
|
+
artifactUrl = `${artifactUrl}/api/v1/cloud/environments/${environmentId}/artifact`;
|
|
3325
3458
|
}
|
|
3326
3459
|
if (src.commitId) {
|
|
3327
3460
|
artifactUrl += `${artifactUrl.includes("?") ? "&" : "?"}commit=${encodeURIComponent(src.commitId)}`;
|