@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.
- package/README.md +69 -2
- package/dist/index.cjs +374 -357
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -85
- package/dist/index.d.ts +120 -85
- package/dist/index.js +379 -364
- package/dist/index.js.map +1 -1
- package/dist/migrations/{migrate-env-id-to-project-id.cjs → index.cjs} +83 -11
- package/dist/migrations/index.cjs.map +1 -0
- package/dist/migrations/index.d.cts +103 -0
- package/dist/migrations/index.d.ts +103 -0
- package/dist/migrations/index.js +127 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/node.cjs +374 -357
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +379 -364
- package/dist/node.js.map +1 -1
- package/package.json +10 -10
- package/dist/migrations/migrate-env-id-to-project-id.cjs.map +0 -1
- package/dist/migrations/migrate-env-id-to-project-id.d.cts +0 -37
- package/dist/migrations/migrate-env-id-to-project-id.d.ts +0 -37
- package/dist/migrations/migrate-env-id-to-project-id.js +0 -59
- package/dist/migrations/migrate-env-id-to-project-id.js.map +0 -1
package/dist/node.cjs
CHANGED
|
@@ -37,7 +37,6 @@ __export(node_exports, {
|
|
|
37
37
|
MemoryLoader: () => MemoryLoader,
|
|
38
38
|
MetadataManager: () => MetadataManager,
|
|
39
39
|
MetadataPlugin: () => MetadataPlugin,
|
|
40
|
-
MetadataProjector: () => MetadataProjector,
|
|
41
40
|
Migration: () => migration_exports,
|
|
42
41
|
NodeMetadataManager: () => NodeMetadataManager,
|
|
43
42
|
RemoteLoader: () => RemoteLoader,
|
|
@@ -323,307 +322,113 @@ function generateDiffSummary(diff) {
|
|
|
323
322
|
return summary.join(", ");
|
|
324
323
|
}
|
|
325
324
|
|
|
326
|
-
// src/
|
|
327
|
-
var
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
this.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
325
|
+
// src/utils/lru-cache.ts
|
|
326
|
+
var LRUCache = class {
|
|
327
|
+
constructor(options = {}) {
|
|
328
|
+
this.map = /* @__PURE__ */ new Map();
|
|
329
|
+
this.hits = 0;
|
|
330
|
+
this.misses = 0;
|
|
331
|
+
this.maxSize = options.maxSize && options.maxSize > 0 ? options.maxSize : 0;
|
|
332
|
+
this.ttl = options.ttl && options.ttl > 0 ? options.ttl : 0;
|
|
333
|
+
}
|
|
334
|
+
get(key) {
|
|
335
|
+
const entry = this.map.get(key);
|
|
336
|
+
if (!entry) {
|
|
337
|
+
this.misses++;
|
|
338
|
+
return void 0;
|
|
339
|
+
}
|
|
340
|
+
if (entry.expiresAt !== 0 && entry.expiresAt <= Date.now()) {
|
|
341
|
+
this.map.delete(key);
|
|
342
|
+
this.misses++;
|
|
343
|
+
return void 0;
|
|
344
|
+
}
|
|
345
|
+
this.map.delete(key);
|
|
346
|
+
this.map.set(key, entry);
|
|
347
|
+
this.hits++;
|
|
348
|
+
return entry.value;
|
|
349
|
+
}
|
|
350
|
+
set(key, value) {
|
|
351
|
+
if (this.map.has(key)) {
|
|
352
|
+
this.map.delete(key);
|
|
353
|
+
} else if (this.maxSize > 0 && this.map.size >= this.maxSize) {
|
|
354
|
+
const oldest = this.map.keys().next();
|
|
355
|
+
if (!oldest.done) this.map.delete(oldest.value);
|
|
356
|
+
}
|
|
357
|
+
this.map.set(key, {
|
|
358
|
+
value,
|
|
359
|
+
expiresAt: this.ttl > 0 ? Date.now() + this.ttl : 0
|
|
360
|
+
});
|
|
348
361
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
*/
|
|
352
|
-
async project(type, name, data) {
|
|
353
|
-
const targetTable = this.typeTableMap[type];
|
|
354
|
-
if (!targetTable) {
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
const projectedData = this.transformToProjection(type, name, data);
|
|
358
|
-
if (!projectedData) {
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
try {
|
|
362
|
-
const projId = this.scope.projectId ?? null;
|
|
363
|
-
const existing = await this._findOne(targetTable, {
|
|
364
|
-
where: { name, project_id: projId }
|
|
365
|
-
});
|
|
366
|
-
if (existing) {
|
|
367
|
-
await this._update(targetTable, existing.id, projectedData);
|
|
368
|
-
} else {
|
|
369
|
-
const id = this.generateId();
|
|
370
|
-
await this._create(targetTable, {
|
|
371
|
-
id,
|
|
372
|
-
...projectedData
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
} catch (error) {
|
|
376
|
-
console.error(`Failed to project ${type}/${name} to ${targetTable}:`, error);
|
|
377
|
-
}
|
|
362
|
+
has(key) {
|
|
363
|
+
return this.get(key) !== void 0;
|
|
378
364
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
*/
|
|
382
|
-
async deleteProjection(type, name) {
|
|
383
|
-
const targetTable = this.typeTableMap[type];
|
|
384
|
-
if (!targetTable) {
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
try {
|
|
388
|
-
const projId = this.scope.projectId ?? null;
|
|
389
|
-
const existing = await this._findOne(targetTable, {
|
|
390
|
-
where: { name, project_id: projId }
|
|
391
|
-
});
|
|
392
|
-
if (existing) {
|
|
393
|
-
await this._delete(targetTable, existing.id);
|
|
394
|
-
}
|
|
395
|
-
} catch (error) {
|
|
396
|
-
console.error(`Failed to delete projection ${type}/${name} from ${targetTable}:`, error);
|
|
397
|
-
}
|
|
365
|
+
delete(key) {
|
|
366
|
+
return this.map.delete(key);
|
|
398
367
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
*/
|
|
402
|
-
transformToProjection(type, name, data) {
|
|
403
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
404
|
-
switch (type) {
|
|
405
|
-
case "object":
|
|
406
|
-
return this.projectObject(name, data, now);
|
|
407
|
-
case "view":
|
|
408
|
-
return this.projectView(name, data, now);
|
|
409
|
-
case "agent":
|
|
410
|
-
return this.projectAgent(name, data, now);
|
|
411
|
-
case "tool":
|
|
412
|
-
return this.projectTool(name, data, now);
|
|
413
|
-
case "flow":
|
|
414
|
-
return this.projectFlow(name, data, now);
|
|
415
|
-
default:
|
|
416
|
-
return null;
|
|
417
|
-
}
|
|
368
|
+
clear() {
|
|
369
|
+
this.map.clear();
|
|
418
370
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
*/
|
|
422
|
-
projectObject(name, data, now) {
|
|
423
|
-
return {
|
|
424
|
-
name,
|
|
425
|
-
project_id: this.scope.projectId ?? null,
|
|
426
|
-
label: data.label || name,
|
|
427
|
-
plural_label: data.pluralLabel || data.label || name,
|
|
428
|
-
description: data.description || "",
|
|
429
|
-
icon: data.icon || "database",
|
|
430
|
-
namespace: data.namespace || "default",
|
|
431
|
-
tags: Array.isArray(data.tags) ? data.tags.join(",") : data.tags || "",
|
|
432
|
-
active: data.active !== false,
|
|
433
|
-
is_system: data.isSystem || false,
|
|
434
|
-
abstract: data.abstract || false,
|
|
435
|
-
datasource: data.datasource || "default",
|
|
436
|
-
table_name: data.name ? import_system.StorageNameMapping.resolveTableName({ name: data.name }) : name,
|
|
437
|
-
// Serialize complex structures as JSON
|
|
438
|
-
fields_json: data.fields ? JSON.stringify(data.fields) : null,
|
|
439
|
-
indexes_json: data.indexes ? JSON.stringify(data.indexes) : null,
|
|
440
|
-
validations_json: data.validations ? JSON.stringify(data.validations) : null,
|
|
441
|
-
state_machines_json: data.stateMachines ? JSON.stringify(data.stateMachines) : null,
|
|
442
|
-
capabilities_json: data.enable ? JSON.stringify(data.enable) : null,
|
|
443
|
-
// Denormalized fields
|
|
444
|
-
field_count: data.fields ? Object.keys(data.fields).length : 0,
|
|
445
|
-
display_name_field: data.displayNameField || null,
|
|
446
|
-
title_format: data.titleFormat || null,
|
|
447
|
-
compact_layout: Array.isArray(data.compactLayout) ? data.compactLayout.join(",") : data.compactLayout || null,
|
|
448
|
-
// Capabilities (denormalized for easier querying)
|
|
449
|
-
track_history: data.enable?.trackHistory || false,
|
|
450
|
-
searchable: data.enable?.searchable !== false,
|
|
451
|
-
api_enabled: data.enable?.apiEnabled !== false,
|
|
452
|
-
files: data.enable?.files || false,
|
|
453
|
-
feeds: data.enable?.feeds || false,
|
|
454
|
-
activities: data.enable?.activities || false,
|
|
455
|
-
trash: data.enable?.trash !== false,
|
|
456
|
-
mru: data.enable?.mru !== false,
|
|
457
|
-
clone: data.enable?.clone !== false,
|
|
458
|
-
// Package management
|
|
459
|
-
package_id: data.packageId || null,
|
|
460
|
-
managed_by: data.managedBy || "user",
|
|
461
|
-
// Audit
|
|
462
|
-
created_by: data.createdBy || null,
|
|
463
|
-
created_at: data.createdAt || now,
|
|
464
|
-
updated_by: data.updatedBy || null,
|
|
465
|
-
updated_at: now
|
|
466
|
-
};
|
|
371
|
+
get size() {
|
|
372
|
+
return this.map.size;
|
|
467
373
|
}
|
|
468
|
-
/**
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
projectView(name, data, now) {
|
|
374
|
+
/** Diagnostic counters — useful for `metrics` endpoints. */
|
|
375
|
+
stats() {
|
|
376
|
+
const total = this.hits + this.misses;
|
|
472
377
|
return {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
object_name: data.object || "",
|
|
478
|
-
view_type: data.type || "grid",
|
|
479
|
-
// Serialize configurations as JSON
|
|
480
|
-
columns_json: data.columns ? JSON.stringify(data.columns) : null,
|
|
481
|
-
filters_json: data.filters ? JSON.stringify(data.filters) : null,
|
|
482
|
-
sort_json: data.sort ? JSON.stringify(data.sort) : null,
|
|
483
|
-
config_json: data.config ? JSON.stringify(data.config) : null,
|
|
484
|
-
// Display options
|
|
485
|
-
page_size: data.pageSize || 25,
|
|
486
|
-
show_search: data.showSearch !== false,
|
|
487
|
-
show_filters: data.showFilters !== false,
|
|
488
|
-
// Classification
|
|
489
|
-
namespace: data.namespace || "default",
|
|
490
|
-
// Package management
|
|
491
|
-
package_id: data.packageId || null,
|
|
492
|
-
managed_by: data.managedBy || "user",
|
|
493
|
-
// Audit
|
|
494
|
-
created_by: data.createdBy || null,
|
|
495
|
-
created_at: data.createdAt || now,
|
|
496
|
-
updated_by: data.updatedBy || null,
|
|
497
|
-
updated_at: now
|
|
378
|
+
size: this.map.size,
|
|
379
|
+
hits: this.hits,
|
|
380
|
+
misses: this.misses,
|
|
381
|
+
hitRate: total === 0 ? 0 : this.hits / total
|
|
498
382
|
};
|
|
499
383
|
}
|
|
500
|
-
/**
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
return {
|
|
505
|
-
name,
|
|
506
|
-
project_id: this.scope.projectId ?? null,
|
|
507
|
-
label: data.label || name,
|
|
508
|
-
description: data.description || "",
|
|
509
|
-
agent_type: data.type || "conversational",
|
|
510
|
-
// Model configuration
|
|
511
|
-
model: data.model || null,
|
|
512
|
-
temperature: data.temperature ?? 0.7,
|
|
513
|
-
max_tokens: data.maxTokens || null,
|
|
514
|
-
top_p: data.topP || null,
|
|
515
|
-
// System prompt
|
|
516
|
-
system_prompt: data.systemPrompt || null,
|
|
517
|
-
// Tools and skills as JSON
|
|
518
|
-
tools_json: data.tools ? JSON.stringify(data.tools) : null,
|
|
519
|
-
skills_json: data.skills ? JSON.stringify(data.skills) : null,
|
|
520
|
-
// Memory
|
|
521
|
-
memory_enabled: data.memoryEnabled || false,
|
|
522
|
-
memory_window: data.memoryWindow || 10,
|
|
523
|
-
// Classification
|
|
524
|
-
namespace: data.namespace || "default",
|
|
525
|
-
// Package management
|
|
526
|
-
package_id: data.packageId || null,
|
|
527
|
-
managed_by: data.managedBy || "user",
|
|
528
|
-
// Audit
|
|
529
|
-
created_by: data.createdBy || null,
|
|
530
|
-
created_at: data.createdAt || now,
|
|
531
|
-
updated_by: data.updatedBy || null,
|
|
532
|
-
updated_at: now
|
|
533
|
-
};
|
|
384
|
+
/** Resets hit/miss counters without dropping cached entries. */
|
|
385
|
+
resetStats() {
|
|
386
|
+
this.hits = 0;
|
|
387
|
+
this.misses = 0;
|
|
534
388
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
edges_json: data.edges ? JSON.stringify(data.edges) : null,
|
|
572
|
-
variables_json: data.variables ? JSON.stringify(data.variables) : null,
|
|
573
|
-
// Trigger configuration
|
|
574
|
-
trigger_type: data.triggerType || null,
|
|
575
|
-
trigger_object: data.triggerObject || null,
|
|
576
|
-
// Status
|
|
577
|
-
active: data.active || false,
|
|
578
|
-
// Classification
|
|
579
|
-
namespace: data.namespace || "default",
|
|
580
|
-
// Package management
|
|
581
|
-
package_id: data.packageId || null,
|
|
582
|
-
managed_by: data.managedBy || "user",
|
|
583
|
-
// Audit
|
|
584
|
-
created_by: data.createdBy || null,
|
|
585
|
-
created_at: data.createdAt || now,
|
|
586
|
-
updated_by: data.updatedBy || null,
|
|
587
|
-
updated_at: now
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
// ==========================================
|
|
591
|
-
// Internal CRUD helpers (driver vs engine)
|
|
592
|
-
// ==========================================
|
|
593
|
-
async _findOne(table, query) {
|
|
594
|
-
if (this.engine) {
|
|
595
|
-
return this.engine.findOne(table, query);
|
|
596
|
-
}
|
|
597
|
-
return this.driver.findOne(table, { object: table, ...query });
|
|
598
|
-
}
|
|
599
|
-
async _create(table, data) {
|
|
600
|
-
if (this.engine) {
|
|
601
|
-
return this.engine.insert(table, data);
|
|
602
|
-
}
|
|
603
|
-
return this.driver.create(table, data);
|
|
604
|
-
}
|
|
605
|
-
async _update(table, id, data) {
|
|
606
|
-
if (this.engine) {
|
|
607
|
-
return this.engine.update(table, { id, ...data });
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// src/migrations/add-sys-metadata-overlay-index.ts
|
|
392
|
+
var INDEX_NAME = "idx_sys_metadata_overlay_active";
|
|
393
|
+
var TABLE = "sys_metadata";
|
|
394
|
+
var COLUMNS = "(type, name, organization_id, project_id, scope)";
|
|
395
|
+
var WHERE = "state = 'active'";
|
|
396
|
+
async function addSysMetadataOverlayIndex(driver) {
|
|
397
|
+
const driverAny = driver;
|
|
398
|
+
const exec = async (sql) => {
|
|
399
|
+
if (typeof driverAny.raw === "function") {
|
|
400
|
+
await driverAny.raw(sql);
|
|
401
|
+
} else if (typeof driverAny.execute === "function") {
|
|
402
|
+
await driverAny.execute(sql);
|
|
403
|
+
} else {
|
|
404
|
+
throw new Error("driver has neither raw nor execute");
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
const partialSql = `CREATE UNIQUE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS} WHERE ${WHERE}`;
|
|
408
|
+
const fallbackSql = `CREATE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS}`;
|
|
409
|
+
try {
|
|
410
|
+
await exec(partialSql);
|
|
411
|
+
return { index: INDEX_NAME, status: "created" };
|
|
412
|
+
} catch (err) {
|
|
413
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
414
|
+
if (/partial|where clause|syntax/i.test(msg)) {
|
|
415
|
+
try {
|
|
416
|
+
await exec(fallbackSql);
|
|
417
|
+
return { index: INDEX_NAME, status: "fallback_non_unique" };
|
|
418
|
+
} catch (fallbackErr) {
|
|
419
|
+
return {
|
|
420
|
+
index: INDEX_NAME,
|
|
421
|
+
status: "error",
|
|
422
|
+
error: fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)
|
|
423
|
+
};
|
|
424
|
+
}
|
|
608
425
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
async _delete(table, id) {
|
|
612
|
-
if (this.engine) {
|
|
613
|
-
return this.engine.delete(table, { where: { id } });
|
|
426
|
+
if (/already exists/i.test(msg)) {
|
|
427
|
+
return { index: INDEX_NAME, status: "already_exists" };
|
|
614
428
|
}
|
|
615
|
-
return
|
|
429
|
+
return { index: INDEX_NAME, status: "error", error: msg };
|
|
616
430
|
}
|
|
617
|
-
|
|
618
|
-
* Generate a simple unique ID
|
|
619
|
-
*/
|
|
620
|
-
generateId() {
|
|
621
|
-
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") {
|
|
622
|
-
return globalThis.crypto.randomUUID();
|
|
623
|
-
}
|
|
624
|
-
return `proj_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
625
|
-
}
|
|
626
|
-
};
|
|
431
|
+
}
|
|
627
432
|
|
|
628
433
|
// src/loaders/database-loader.ts
|
|
629
434
|
var DatabaseLoader = class {
|
|
@@ -650,17 +455,56 @@ var DatabaseLoader = class {
|
|
|
650
455
|
this.organizationId = options.organizationId;
|
|
651
456
|
this.projectId = options.projectId;
|
|
652
457
|
this.trackHistory = options.trackHistory !== false;
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
458
|
+
const cacheOpts = options.cache;
|
|
459
|
+
const cacheEnabled = cacheOpts?.enabled !== false;
|
|
460
|
+
if (cacheEnabled) {
|
|
461
|
+
const lruOpts = {
|
|
462
|
+
maxSize: cacheOpts?.maxSize ?? 500,
|
|
463
|
+
ttl: cacheOpts?.ttl ?? 6e4
|
|
464
|
+
};
|
|
465
|
+
this.loadCache = new LRUCache(lruOpts);
|
|
466
|
+
this.loadManyCache = new LRUCache(lruOpts);
|
|
467
|
+
this.listCache = new LRUCache(lruOpts);
|
|
468
|
+
this.statCache = new LRUCache(lruOpts);
|
|
661
469
|
}
|
|
662
470
|
}
|
|
663
471
|
// ==========================================
|
|
472
|
+
// Cache helpers
|
|
473
|
+
// ==========================================
|
|
474
|
+
cacheKey(type, name) {
|
|
475
|
+
return `${type}::${name}`;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Invalidate all cached entries for a specific (type, name) pair plus
|
|
479
|
+
* the type-level aggregates (`loadMany`, `list`). Called from every write
|
|
480
|
+
* path (`save`, `delete`, `registerRollback`).
|
|
481
|
+
*/
|
|
482
|
+
invalidate(type, name) {
|
|
483
|
+
if (!this.loadCache) return;
|
|
484
|
+
const key = this.cacheKey(type, name);
|
|
485
|
+
this.loadCache.delete(key);
|
|
486
|
+
this.statCache?.delete(key);
|
|
487
|
+
this.loadManyCache?.delete(type);
|
|
488
|
+
this.listCache?.delete(type);
|
|
489
|
+
}
|
|
490
|
+
/** Drop the entire cache — useful after bulk imports or schema changes. */
|
|
491
|
+
invalidateAll() {
|
|
492
|
+
this.loadCache?.clear();
|
|
493
|
+
this.loadManyCache?.clear();
|
|
494
|
+
this.listCache?.clear();
|
|
495
|
+
this.statCache?.clear();
|
|
496
|
+
}
|
|
497
|
+
/** Diagnostic: aggregated cache statistics for `metrics` endpoints. */
|
|
498
|
+
getCacheStats() {
|
|
499
|
+
return {
|
|
500
|
+
enabled: this.loadCache !== void 0,
|
|
501
|
+
load: this.loadCache?.stats() ?? null,
|
|
502
|
+
loadMany: this.loadManyCache?.stats() ?? null,
|
|
503
|
+
list: this.listCache?.stats() ?? null,
|
|
504
|
+
stat: this.statCache?.stats() ?? null
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
// ==========================================
|
|
664
508
|
// Internal CRUD helpers (driver vs engine)
|
|
665
509
|
// ==========================================
|
|
666
510
|
async _find(table, query) {
|
|
@@ -708,6 +552,23 @@ var DatabaseLoader = class {
|
|
|
708
552
|
if (this.schemaReady) return;
|
|
709
553
|
if (this.engine) {
|
|
710
554
|
this.schemaReady = true;
|
|
555
|
+
try {
|
|
556
|
+
const engineAny = this.engine;
|
|
557
|
+
let driver = engineAny?.driver ?? engineAny?.getDriver?.();
|
|
558
|
+
if (!driver && engineAny?.drivers instanceof Map) {
|
|
559
|
+
for (const candidate of engineAny.drivers.values()) {
|
|
560
|
+
const c = candidate;
|
|
561
|
+
if (c && (typeof c.raw === "function" || typeof c.execute === "function")) {
|
|
562
|
+
driver = candidate;
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (driver) {
|
|
568
|
+
await addSysMetadataOverlayIndex(driver);
|
|
569
|
+
}
|
|
570
|
+
} catch {
|
|
571
|
+
}
|
|
711
572
|
return;
|
|
712
573
|
}
|
|
713
574
|
try {
|
|
@@ -716,6 +577,10 @@ var DatabaseLoader = class {
|
|
|
716
577
|
name: this.tableName
|
|
717
578
|
});
|
|
718
579
|
this.schemaReady = true;
|
|
580
|
+
try {
|
|
581
|
+
await addSysMetadataOverlayIndex(this.driver);
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
719
584
|
} catch {
|
|
720
585
|
this.schemaReady = true;
|
|
721
586
|
}
|
|
@@ -860,11 +725,24 @@ var DatabaseLoader = class {
|
|
|
860
725
|
async load(type, name, _options) {
|
|
861
726
|
const startTime = Date.now();
|
|
862
727
|
await this.ensureSchema();
|
|
728
|
+
const key = this.cacheKey(type, name);
|
|
729
|
+
if (this.loadCache) {
|
|
730
|
+
const cached = this.loadCache.get(key);
|
|
731
|
+
if (cached !== void 0) {
|
|
732
|
+
return {
|
|
733
|
+
data: cached,
|
|
734
|
+
source: "database",
|
|
735
|
+
format: "json",
|
|
736
|
+
loadTime: Date.now() - startTime
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
863
740
|
try {
|
|
864
741
|
const row = await this._findOne(this.tableName, {
|
|
865
742
|
where: this.baseFilter(type, name)
|
|
866
743
|
});
|
|
867
744
|
if (!row) {
|
|
745
|
+
this.loadCache?.set(key, null);
|
|
868
746
|
return {
|
|
869
747
|
data: null,
|
|
870
748
|
loadTime: Date.now() - startTime
|
|
@@ -872,6 +750,7 @@ var DatabaseLoader = class {
|
|
|
872
750
|
}
|
|
873
751
|
const data = this.rowToData(row);
|
|
874
752
|
const record = this.rowToRecord(row);
|
|
753
|
+
this.loadCache?.set(key, data);
|
|
875
754
|
return {
|
|
876
755
|
data,
|
|
877
756
|
source: "database",
|
|
@@ -888,17 +767,27 @@ var DatabaseLoader = class {
|
|
|
888
767
|
}
|
|
889
768
|
async loadMany(type, _options) {
|
|
890
769
|
await this.ensureSchema();
|
|
770
|
+
if (this.loadManyCache) {
|
|
771
|
+
const cached = this.loadManyCache.get(type);
|
|
772
|
+
if (cached !== void 0) return cached;
|
|
773
|
+
}
|
|
891
774
|
try {
|
|
892
775
|
const rows = await this._find(this.tableName, {
|
|
893
776
|
where: this.baseFilter(type)
|
|
894
777
|
});
|
|
895
|
-
|
|
778
|
+
const result = rows.map((row) => this.rowToData(row)).filter((data) => data !== null);
|
|
779
|
+
this.loadManyCache?.set(type, result);
|
|
780
|
+
return result;
|
|
896
781
|
} catch {
|
|
897
782
|
return [];
|
|
898
783
|
}
|
|
899
784
|
}
|
|
900
785
|
async exists(type, name) {
|
|
901
786
|
await this.ensureSchema();
|
|
787
|
+
if (this.loadCache) {
|
|
788
|
+
const cached = this.loadCache.get(this.cacheKey(type, name));
|
|
789
|
+
if (cached !== void 0) return cached !== null;
|
|
790
|
+
}
|
|
902
791
|
try {
|
|
903
792
|
const count = await this._count(this.tableName, {
|
|
904
793
|
where: this.baseFilter(type, name)
|
|
@@ -910,31 +799,47 @@ var DatabaseLoader = class {
|
|
|
910
799
|
}
|
|
911
800
|
async stat(type, name) {
|
|
912
801
|
await this.ensureSchema();
|
|
802
|
+
const key = this.cacheKey(type, name);
|
|
803
|
+
if (this.statCache) {
|
|
804
|
+
const cached = this.statCache.get(key);
|
|
805
|
+
if (cached !== void 0) return cached;
|
|
806
|
+
}
|
|
913
807
|
try {
|
|
914
808
|
const row = await this._findOne(this.tableName, {
|
|
915
809
|
where: this.baseFilter(type, name)
|
|
916
810
|
});
|
|
917
|
-
if (!row)
|
|
811
|
+
if (!row) {
|
|
812
|
+
this.statCache?.set(key, null);
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
918
815
|
const record = this.rowToRecord(row);
|
|
919
816
|
const metadataStr = typeof row.metadata === "string" ? row.metadata : JSON.stringify(row.metadata);
|
|
920
|
-
|
|
817
|
+
const stats = {
|
|
921
818
|
size: metadataStr.length,
|
|
922
819
|
mtime: record.updatedAt ?? record.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
923
820
|
format: "json",
|
|
924
821
|
etag: record.checksum
|
|
925
822
|
};
|
|
823
|
+
this.statCache?.set(key, stats);
|
|
824
|
+
return stats;
|
|
926
825
|
} catch {
|
|
927
826
|
return null;
|
|
928
827
|
}
|
|
929
828
|
}
|
|
930
829
|
async list(type) {
|
|
931
830
|
await this.ensureSchema();
|
|
831
|
+
if (this.listCache) {
|
|
832
|
+
const cached = this.listCache.get(type);
|
|
833
|
+
if (cached !== void 0) return cached;
|
|
834
|
+
}
|
|
932
835
|
try {
|
|
933
836
|
const rows = await this._find(this.tableName, {
|
|
934
837
|
where: this.baseFilter(type),
|
|
935
838
|
fields: ["name"]
|
|
936
839
|
});
|
|
937
|
-
|
|
840
|
+
const names = rows.map((row) => row.name).filter((name) => typeof name === "string");
|
|
841
|
+
this.listCache?.set(type, names);
|
|
842
|
+
return names;
|
|
938
843
|
} catch {
|
|
939
844
|
return [];
|
|
940
845
|
}
|
|
@@ -1074,6 +979,7 @@ var DatabaseLoader = class {
|
|
|
1074
979
|
updated_at: now,
|
|
1075
980
|
state: "active"
|
|
1076
981
|
});
|
|
982
|
+
this.invalidate(type, name);
|
|
1077
983
|
await this.createHistoryRecord(
|
|
1078
984
|
existing.id,
|
|
1079
985
|
type,
|
|
@@ -1099,6 +1005,7 @@ var DatabaseLoader = class {
|
|
|
1099
1005
|
if (existing) {
|
|
1100
1006
|
const previousChecksum = existing.checksum;
|
|
1101
1007
|
if (newChecksum === previousChecksum) {
|
|
1008
|
+
this.loadCache?.set(this.cacheKey(type, name), data);
|
|
1102
1009
|
return {
|
|
1103
1010
|
success: true,
|
|
1104
1011
|
path: `datasource://${this.tableName}/${type}/${name}`,
|
|
@@ -1114,6 +1021,7 @@ var DatabaseLoader = class {
|
|
|
1114
1021
|
updated_at: now,
|
|
1115
1022
|
state: "active"
|
|
1116
1023
|
});
|
|
1024
|
+
this.invalidate(type, name);
|
|
1117
1025
|
await this.createHistoryRecord(
|
|
1118
1026
|
existing.id,
|
|
1119
1027
|
type,
|
|
@@ -1123,9 +1031,6 @@ var DatabaseLoader = class {
|
|
|
1123
1031
|
"update",
|
|
1124
1032
|
previousChecksum
|
|
1125
1033
|
);
|
|
1126
|
-
if (this.projector) {
|
|
1127
|
-
await this.projector.project(type, name, data);
|
|
1128
|
-
}
|
|
1129
1034
|
return {
|
|
1130
1035
|
success: true,
|
|
1131
1036
|
path: `datasource://${this.tableName}/${type}/${name}`,
|
|
@@ -1151,6 +1056,7 @@ var DatabaseLoader = class {
|
|
|
1151
1056
|
created_at: now,
|
|
1152
1057
|
updated_at: now
|
|
1153
1058
|
});
|
|
1059
|
+
this.invalidate(type, name);
|
|
1154
1060
|
await this.createHistoryRecord(
|
|
1155
1061
|
id,
|
|
1156
1062
|
type,
|
|
@@ -1159,9 +1065,6 @@ var DatabaseLoader = class {
|
|
|
1159
1065
|
data,
|
|
1160
1066
|
"create"
|
|
1161
1067
|
);
|
|
1162
|
-
if (this.projector) {
|
|
1163
|
-
await this.projector.project(type, name, data);
|
|
1164
|
-
}
|
|
1165
1068
|
return {
|
|
1166
1069
|
success: true,
|
|
1167
1070
|
path: `datasource://${this.tableName}/${type}/${name}`,
|
|
@@ -1187,9 +1090,7 @@ var DatabaseLoader = class {
|
|
|
1187
1090
|
return;
|
|
1188
1091
|
}
|
|
1189
1092
|
await this._delete(this.tableName, existing.id);
|
|
1190
|
-
|
|
1191
|
-
await this.projector.deleteProjection(type, name);
|
|
1192
|
-
}
|
|
1093
|
+
this.invalidate(type, name);
|
|
1193
1094
|
}
|
|
1194
1095
|
};
|
|
1195
1096
|
function generateId() {
|
|
@@ -1200,7 +1101,7 @@ function generateId() {
|
|
|
1200
1101
|
}
|
|
1201
1102
|
|
|
1202
1103
|
// src/metadata-manager.ts
|
|
1203
|
-
var
|
|
1104
|
+
var _MetadataManager = class _MetadataManager {
|
|
1204
1105
|
constructor(config) {
|
|
1205
1106
|
this.loaders = /* @__PURE__ */ new Map();
|
|
1206
1107
|
this.watchCallbacks = /* @__PURE__ */ new Map();
|
|
@@ -1212,6 +1113,18 @@ var MetadataManager = class {
|
|
|
1212
1113
|
this.typeRegistry = [];
|
|
1213
1114
|
// Dependency tracking: "type:name" -> dependencies
|
|
1214
1115
|
this.dependencies = /* @__PURE__ */ new Map();
|
|
1116
|
+
// Short-lived cache for list() results. Built primarily to break the
|
|
1117
|
+
// deadlock that occurs when security/permission middleware calls
|
|
1118
|
+
// `list('permission')` from inside a user-initiated DB transaction: the
|
|
1119
|
+
// DatabaseLoader's `engine.find('sys_metadata', ...)` would then try to
|
|
1120
|
+
// acquire a fresh knex connection while the transaction is still holding
|
|
1121
|
+
// SQLite's single connection — knex waits the full `acquireConnectionTimeout`
|
|
1122
|
+
// (60s) before returning []. The cache absorbs the repeated lookups so the
|
|
1123
|
+
// loader is only hit once per TTL window.
|
|
1124
|
+
//
|
|
1125
|
+
// Invalidated on every `register()` / `unregister()` to keep CRUD writes
|
|
1126
|
+
// visible to subsequent reads.
|
|
1127
|
+
this.listCache = /* @__PURE__ */ new Map();
|
|
1215
1128
|
this.config = config;
|
|
1216
1129
|
this.logger = (0, import_core.createLogger)({ level: "info", format: "pretty" });
|
|
1217
1130
|
this.serializers = /* @__PURE__ */ new Map();
|
|
@@ -1262,7 +1175,8 @@ var MetadataManager = class {
|
|
|
1262
1175
|
driver,
|
|
1263
1176
|
tableName,
|
|
1264
1177
|
organizationId,
|
|
1265
|
-
projectId
|
|
1178
|
+
projectId,
|
|
1179
|
+
cache: this.config.cache?.databaseLoader
|
|
1266
1180
|
});
|
|
1267
1181
|
this.registerLoader(dbLoader);
|
|
1268
1182
|
this.logger.info("DatabaseLoader configured", { datasource: this.config.datasource, tableName });
|
|
@@ -1290,7 +1204,8 @@ var MetadataManager = class {
|
|
|
1290
1204
|
engine,
|
|
1291
1205
|
tableName,
|
|
1292
1206
|
organizationId,
|
|
1293
|
-
projectId
|
|
1207
|
+
projectId,
|
|
1208
|
+
cache: this.config.cache?.databaseLoader
|
|
1294
1209
|
});
|
|
1295
1210
|
this.registerLoader(dbLoader);
|
|
1296
1211
|
this.logger.info("DatabaseLoader configured via DataEngine", { tableName });
|
|
@@ -1322,10 +1237,19 @@ var MetadataManager = class {
|
|
|
1322
1237
|
* should not be written to during runtime registration.
|
|
1323
1238
|
*/
|
|
1324
1239
|
async register(type, name, data) {
|
|
1240
|
+
if (this.config.persistence?.writable === false) {
|
|
1241
|
+
const msg = `MetadataManager is read-only (persistence.writable=false); refusing to register ${type}/${name}`;
|
|
1242
|
+
if (this.config.validation?.throwOnError) {
|
|
1243
|
+
throw new Error(msg);
|
|
1244
|
+
}
|
|
1245
|
+
this.logger.warn(msg);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1325
1248
|
if (!this.registry.has(type)) {
|
|
1326
1249
|
this.registry.set(type, /* @__PURE__ */ new Map());
|
|
1327
1250
|
}
|
|
1328
1251
|
this.registry.get(type).set(name, data);
|
|
1252
|
+
this.invalidateListCache(type);
|
|
1329
1253
|
for (const loader of this.loaders.values()) {
|
|
1330
1254
|
if (loader.save && loader.contract.protocol === "datasource:" && loader.contract.capabilities.write) {
|
|
1331
1255
|
await loader.save(type, name, data);
|
|
@@ -1367,6 +1291,10 @@ var MetadataManager = class {
|
|
|
1367
1291
|
* List all metadata items of a given type
|
|
1368
1292
|
*/
|
|
1369
1293
|
async list(type) {
|
|
1294
|
+
const cached = this.listCache.get(type);
|
|
1295
|
+
if (cached && Date.now() - cached.ts < _MetadataManager.LIST_CACHE_TTL_MS) {
|
|
1296
|
+
return cached.items;
|
|
1297
|
+
}
|
|
1370
1298
|
const items = /* @__PURE__ */ new Map();
|
|
1371
1299
|
const typeStore = this.registry.get(type);
|
|
1372
1300
|
if (typeStore) {
|
|
@@ -1387,7 +1315,16 @@ var MetadataManager = class {
|
|
|
1387
1315
|
this.logger.warn(`Loader ${loader.contract.name} failed to loadMany ${type}`, { error: e });
|
|
1388
1316
|
}
|
|
1389
1317
|
}
|
|
1390
|
-
|
|
1318
|
+
const result = Array.from(items.values());
|
|
1319
|
+
this.cacheListResult(type, result);
|
|
1320
|
+
return result;
|
|
1321
|
+
}
|
|
1322
|
+
cacheListResult(type, items) {
|
|
1323
|
+
this.listCache.set(type, { ts: Date.now(), items });
|
|
1324
|
+
}
|
|
1325
|
+
/** Internal helper: drop the cached `list()` result for a type. */
|
|
1326
|
+
invalidateListCache(type) {
|
|
1327
|
+
this.listCache.delete(type);
|
|
1391
1328
|
}
|
|
1392
1329
|
/**
|
|
1393
1330
|
* Unregister/remove a metadata item by type and name.
|
|
@@ -1401,6 +1338,7 @@ var MetadataManager = class {
|
|
|
1401
1338
|
this.registry.delete(type);
|
|
1402
1339
|
}
|
|
1403
1340
|
}
|
|
1341
|
+
this.invalidateListCache(type);
|
|
1404
1342
|
for (const loader of this.loaders.values()) {
|
|
1405
1343
|
if (loader.contract.protocol !== "datasource:" || !loader.contract.capabilities.write) continue;
|
|
1406
1344
|
if (typeof loader.delete === "function") {
|
|
@@ -1815,6 +1753,14 @@ var MetadataManager = class {
|
|
|
1815
1753
|
* Save/update an overlay for a metadata item
|
|
1816
1754
|
*/
|
|
1817
1755
|
async saveOverlay(overlay) {
|
|
1756
|
+
if (this.config.persistence?.overlayWritable === false) {
|
|
1757
|
+
const msg = `MetadataManager overlays are read-only (persistence.overlayWritable=false); refusing to save overlay for ${overlay.baseType}/${overlay.baseName}`;
|
|
1758
|
+
if (this.config.validation?.throwOnError) {
|
|
1759
|
+
throw new Error(msg);
|
|
1760
|
+
}
|
|
1761
|
+
this.logger.warn(msg);
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1818
1764
|
const key = this.overlayKey(overlay.baseType, overlay.baseName, overlay.scope);
|
|
1819
1765
|
this.overlays.set(key, overlay);
|
|
1820
1766
|
}
|
|
@@ -2284,6 +2230,8 @@ var MetadataManager = class {
|
|
|
2284
2230
|
};
|
|
2285
2231
|
}
|
|
2286
2232
|
};
|
|
2233
|
+
_MetadataManager.LIST_CACHE_TTL_MS = 3e4;
|
|
2234
|
+
var MetadataManager = _MetadataManager;
|
|
2287
2235
|
|
|
2288
2236
|
// src/plugin.ts
|
|
2289
2237
|
var import_promises = require("fs/promises");
|
|
@@ -2775,7 +2723,10 @@ var MemoryLoader = class {
|
|
|
2775
2723
|
// src/plugin.ts
|
|
2776
2724
|
var import_kernel = require("@objectstack/spec/kernel");
|
|
2777
2725
|
var import_metadata2 = require("@objectstack/platform-objects/metadata");
|
|
2778
|
-
var queryableMetadataObjects = [
|
|
2726
|
+
var queryableMetadataObjects = [
|
|
2727
|
+
import_metadata2.SysMetadataObject,
|
|
2728
|
+
import_metadata2.SysMetadataHistoryObject
|
|
2729
|
+
];
|
|
2779
2730
|
var ARTIFACT_FIELD_TO_TYPE = {
|
|
2780
2731
|
objects: "object",
|
|
2781
2732
|
objectExtensions: "object_extension",
|
|
@@ -2843,13 +2794,35 @@ var MetadataPlugin = class {
|
|
|
2843
2794
|
};
|
|
2844
2795
|
this.start = async (ctx) => {
|
|
2845
2796
|
const src = this.options.artifactSource;
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2797
|
+
const mode = this.options.config?.bootstrap ?? "eager";
|
|
2798
|
+
ctx.logger.info("[MetadataPlugin] Bootstrapping metadata", {
|
|
2799
|
+
bootstrap: mode,
|
|
2800
|
+
artifactSource: src?.mode ?? "none"
|
|
2801
|
+
});
|
|
2802
|
+
if (mode === "artifact-only") {
|
|
2803
|
+
if (src?.mode === "local-file") {
|
|
2804
|
+
await this._loadFromLocalFile(ctx, src.path, src.fetchTimeoutMs);
|
|
2805
|
+
} else if (src?.mode === "artifact-api") {
|
|
2806
|
+
await this._loadFromArtifactApi(ctx, src);
|
|
2807
|
+
} else {
|
|
2808
|
+
throw new Error("[MetadataPlugin] bootstrap=artifact-only requires options.artifactSource to be set");
|
|
2809
|
+
}
|
|
2810
|
+
} else if (mode === "lazy") {
|
|
2811
|
+
if (src?.mode === "local-file") {
|
|
2812
|
+
await this._loadFromLocalFile(ctx, src.path, src.fetchTimeoutMs);
|
|
2813
|
+
} else if (src?.mode === "artifact-api") {
|
|
2814
|
+
await this._loadFromArtifactApi(ctx, src);
|
|
2815
|
+
} else {
|
|
2816
|
+
ctx.logger.info("[MetadataPlugin] lazy bootstrap \u2014 skipping filesystem priming; metadata loads on demand");
|
|
2817
|
+
}
|
|
2851
2818
|
} else {
|
|
2852
|
-
|
|
2819
|
+
if (src?.mode === "local-file") {
|
|
2820
|
+
await this._loadFromLocalFile(ctx, src.path, src.fetchTimeoutMs);
|
|
2821
|
+
} else if (src?.mode === "artifact-api") {
|
|
2822
|
+
await this._loadFromArtifactApi(ctx, src);
|
|
2823
|
+
} else {
|
|
2824
|
+
await this._loadFromFileSystem(ctx);
|
|
2825
|
+
}
|
|
2853
2826
|
}
|
|
2854
2827
|
try {
|
|
2855
2828
|
const realtimeService = ctx.getService("realtime");
|
|
@@ -2868,45 +2841,46 @@ var MetadataPlugin = class {
|
|
|
2868
2841
|
...options
|
|
2869
2842
|
};
|
|
2870
2843
|
const rootDir = this.options.rootDir || process.cwd();
|
|
2844
|
+
const bootstrapMode = this.options.config?.bootstrap ?? "eager";
|
|
2845
|
+
const effectiveWatch = bootstrapMode === "artifact-only" ? false : this.options.watch ?? true;
|
|
2871
2846
|
this.manager = new NodeMetadataManager({
|
|
2872
2847
|
rootDir,
|
|
2873
|
-
watch:
|
|
2848
|
+
watch: effectiveWatch,
|
|
2874
2849
|
formats: ["yaml", "json", "typescript", "javascript"]
|
|
2875
2850
|
});
|
|
2876
2851
|
this.manager.setTypeRegistry(import_kernel.DEFAULT_METADATA_TYPE_REGISTRY);
|
|
2877
2852
|
}
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
);
|
|
2884
|
-
|
|
2853
|
+
/**
|
|
2854
|
+
* Fetch JSON content from a URL with configurable timeout.
|
|
2855
|
+
*/
|
|
2856
|
+
async _fetchJson(url, fetchTimeoutMs, token) {
|
|
2857
|
+
const envTimeout = Number(process.env.OS_ARTIFACT_FETCH_TIMEOUT_MS);
|
|
2858
|
+
const timeoutMs = fetchTimeoutMs ?? (Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : void 0) ?? 6e4;
|
|
2859
|
+
const controller = new AbortController();
|
|
2860
|
+
const timer = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
2885
2861
|
try {
|
|
2886
|
-
|
|
2887
|
-
if (
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
redirect: "follow",
|
|
2893
|
-
signal: controller.signal,
|
|
2894
|
-
headers: { Accept: "application/json, */*;q=0.5" }
|
|
2895
|
-
});
|
|
2896
|
-
if (!res.ok) {
|
|
2897
|
-
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
2898
|
-
}
|
|
2899
|
-
content = await res.text();
|
|
2900
|
-
} finally {
|
|
2901
|
-
clearTimeout(timer);
|
|
2902
|
-
}
|
|
2903
|
-
} else {
|
|
2904
|
-
content = await (0, import_promises.readFile)(filePath, "utf8");
|
|
2905
|
-
}
|
|
2906
|
-
raw = JSON.parse(content);
|
|
2862
|
+
const headers = { Accept: "application/json, */*;q=0.5" };
|
|
2863
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
2864
|
+
const res = await fetch(url, { redirect: "follow", signal: controller.signal, headers });
|
|
2865
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
2866
|
+
const content = await res.text();
|
|
2867
|
+
return JSON.parse(content);
|
|
2907
2868
|
} catch (e) {
|
|
2908
|
-
|
|
2869
|
+
if (e?.name === "AbortError") {
|
|
2870
|
+
throw new Error(
|
|
2871
|
+
`fetch timed out after ${timeoutMs}ms \u2014 set artifactSource.fetchTimeoutMs or OS_ARTIFACT_FETCH_TIMEOUT_MS to extend it (0 disables)`
|
|
2872
|
+
);
|
|
2873
|
+
}
|
|
2874
|
+
throw e;
|
|
2875
|
+
} finally {
|
|
2876
|
+
if (timer) clearTimeout(timer);
|
|
2909
2877
|
}
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Parse raw artifact JSON (envelope or bare definition) and register all
|
|
2881
|
+
* metadata items into the MetadataManager.
|
|
2882
|
+
*/
|
|
2883
|
+
async _parseAndRegisterArtifact(ctx, raw, label) {
|
|
2910
2884
|
const { ProjectArtifactSchema } = await import("@objectstack/spec/cloud");
|
|
2911
2885
|
const { ObjectStackDefinitionSchema } = await import("@objectstack/spec");
|
|
2912
2886
|
let metadata;
|
|
@@ -2914,6 +2888,9 @@ var MetadataPlugin = class {
|
|
|
2914
2888
|
if (obj?.schemaVersion && obj?.commitId && obj?.metadata !== void 0) {
|
|
2915
2889
|
const artifact = ProjectArtifactSchema.parse(obj);
|
|
2916
2890
|
metadata = artifact.metadata;
|
|
2891
|
+
} else if (obj?.success && obj?.data?.metadata) {
|
|
2892
|
+
const artifact = ProjectArtifactSchema.parse(obj.data);
|
|
2893
|
+
metadata = artifact.metadata;
|
|
2917
2894
|
} else {
|
|
2918
2895
|
const def = ObjectStackDefinitionSchema.parse(obj);
|
|
2919
2896
|
const canonical = JSON.stringify(def, Object.keys(def).sort());
|
|
@@ -2946,10 +2923,51 @@ var MetadataPlugin = class {
|
|
|
2946
2923
|
}
|
|
2947
2924
|
}
|
|
2948
2925
|
this.manager.registerLoader(memLoader);
|
|
2949
|
-
ctx.logger.info("[MetadataPlugin] Artifact metadata loaded", {
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2926
|
+
ctx.logger.info("[MetadataPlugin] Artifact metadata loaded", { source: label, totalRegistered });
|
|
2927
|
+
return totalRegistered;
|
|
2928
|
+
}
|
|
2929
|
+
async _loadFromLocalFile(ctx, filePath, fetchTimeoutMs) {
|
|
2930
|
+
const isUrl = /^https?:\/\//i.test(filePath);
|
|
2931
|
+
ctx.logger.info(
|
|
2932
|
+
`[MetadataPlugin] Loading metadata from ${isUrl ? "remote URL" : "local artifact file"}`,
|
|
2933
|
+
{ path: filePath }
|
|
2934
|
+
);
|
|
2935
|
+
let raw;
|
|
2936
|
+
try {
|
|
2937
|
+
if (isUrl) {
|
|
2938
|
+
raw = await this._fetchJson(filePath, fetchTimeoutMs);
|
|
2939
|
+
} else {
|
|
2940
|
+
const content = await (0, import_promises.readFile)(filePath, "utf8");
|
|
2941
|
+
raw = JSON.parse(content);
|
|
2942
|
+
}
|
|
2943
|
+
} catch (e) {
|
|
2944
|
+
throw new Error(`[MetadataPlugin] Cannot read artifact ${isUrl ? "URL" : "file"} at "${filePath}": ${e.message}`);
|
|
2945
|
+
}
|
|
2946
|
+
await this._parseAndRegisterArtifact(ctx, raw, filePath);
|
|
2947
|
+
}
|
|
2948
|
+
/**
|
|
2949
|
+
* P2: Load metadata from the cloud artifact API endpoint.
|
|
2950
|
+
*/
|
|
2951
|
+
async _loadFromArtifactApi(ctx, src) {
|
|
2952
|
+
const projectId = this.options.projectId;
|
|
2953
|
+
if (!projectId) {
|
|
2954
|
+
throw new Error("[MetadataPlugin] artifact-api source requires options.projectId to be set");
|
|
2955
|
+
}
|
|
2956
|
+
let artifactUrl = src.url.replace(/\/+$/, "");
|
|
2957
|
+
if (!/\/api\/v\d+\/cloud\/projects\//i.test(artifactUrl)) {
|
|
2958
|
+
artifactUrl = `${artifactUrl}/api/v1/cloud/projects/${projectId}/artifact`;
|
|
2959
|
+
}
|
|
2960
|
+
if (src.commitId) {
|
|
2961
|
+
artifactUrl += `${artifactUrl.includes("?") ? "&" : "?"}commit=${encodeURIComponent(src.commitId)}`;
|
|
2962
|
+
}
|
|
2963
|
+
ctx.logger.info("[MetadataPlugin] Loading metadata from artifact API", { url: artifactUrl });
|
|
2964
|
+
let raw;
|
|
2965
|
+
try {
|
|
2966
|
+
raw = await this._fetchJson(artifactUrl, src.fetchTimeoutMs, src.token);
|
|
2967
|
+
} catch (e) {
|
|
2968
|
+
throw new Error(`[MetadataPlugin] Cannot load artifact from API "${artifactUrl}": ${e.message}`);
|
|
2969
|
+
}
|
|
2970
|
+
await this._parseAndRegisterArtifact(ctx, raw, artifactUrl);
|
|
2953
2971
|
}
|
|
2954
2972
|
async _loadFromFileSystem(ctx) {
|
|
2955
2973
|
ctx.logger.info("Loading metadata from file system...");
|
|
@@ -3473,7 +3491,6 @@ var MigrationExecutor = class {
|
|
|
3473
3491
|
MemoryLoader,
|
|
3474
3492
|
MetadataManager,
|
|
3475
3493
|
MetadataPlugin,
|
|
3476
|
-
MetadataProjector,
|
|
3477
3494
|
Migration,
|
|
3478
3495
|
NodeMetadataManager,
|
|
3479
3496
|
RemoteLoader,
|