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