@objectstack/metadata 4.0.5 → 4.1.1
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.js
CHANGED
|
@@ -275,307 +275,113 @@ function generateDiffSummary(diff) {
|
|
|
275
275
|
return summary.join(", ");
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
// src/
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
this.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
278
|
+
// src/utils/lru-cache.ts
|
|
279
|
+
var LRUCache = class {
|
|
280
|
+
constructor(options = {}) {
|
|
281
|
+
this.map = /* @__PURE__ */ new Map();
|
|
282
|
+
this.hits = 0;
|
|
283
|
+
this.misses = 0;
|
|
284
|
+
this.maxSize = options.maxSize && options.maxSize > 0 ? options.maxSize : 0;
|
|
285
|
+
this.ttl = options.ttl && options.ttl > 0 ? options.ttl : 0;
|
|
286
|
+
}
|
|
287
|
+
get(key) {
|
|
288
|
+
const entry = this.map.get(key);
|
|
289
|
+
if (!entry) {
|
|
290
|
+
this.misses++;
|
|
291
|
+
return void 0;
|
|
292
|
+
}
|
|
293
|
+
if (entry.expiresAt !== 0 && entry.expiresAt <= Date.now()) {
|
|
294
|
+
this.map.delete(key);
|
|
295
|
+
this.misses++;
|
|
296
|
+
return void 0;
|
|
297
|
+
}
|
|
298
|
+
this.map.delete(key);
|
|
299
|
+
this.map.set(key, entry);
|
|
300
|
+
this.hits++;
|
|
301
|
+
return entry.value;
|
|
302
|
+
}
|
|
303
|
+
set(key, value) {
|
|
304
|
+
if (this.map.has(key)) {
|
|
305
|
+
this.map.delete(key);
|
|
306
|
+
} else if (this.maxSize > 0 && this.map.size >= this.maxSize) {
|
|
307
|
+
const oldest = this.map.keys().next();
|
|
308
|
+
if (!oldest.done) this.map.delete(oldest.value);
|
|
309
|
+
}
|
|
310
|
+
this.map.set(key, {
|
|
311
|
+
value,
|
|
312
|
+
expiresAt: this.ttl > 0 ? Date.now() + this.ttl : 0
|
|
313
|
+
});
|
|
300
314
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
*/
|
|
304
|
-
async project(type, name, data) {
|
|
305
|
-
const targetTable = this.typeTableMap[type];
|
|
306
|
-
if (!targetTable) {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
const projectedData = this.transformToProjection(type, name, data);
|
|
310
|
-
if (!projectedData) {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
try {
|
|
314
|
-
const projId = this.scope.projectId ?? null;
|
|
315
|
-
const existing = await this._findOne(targetTable, {
|
|
316
|
-
where: { name, project_id: projId }
|
|
317
|
-
});
|
|
318
|
-
if (existing) {
|
|
319
|
-
await this._update(targetTable, existing.id, projectedData);
|
|
320
|
-
} else {
|
|
321
|
-
const id = this.generateId();
|
|
322
|
-
await this._create(targetTable, {
|
|
323
|
-
id,
|
|
324
|
-
...projectedData
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
} catch (error) {
|
|
328
|
-
console.error(`Failed to project ${type}/${name} to ${targetTable}:`, error);
|
|
329
|
-
}
|
|
315
|
+
has(key) {
|
|
316
|
+
return this.get(key) !== void 0;
|
|
330
317
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
*/
|
|
334
|
-
async deleteProjection(type, name) {
|
|
335
|
-
const targetTable = this.typeTableMap[type];
|
|
336
|
-
if (!targetTable) {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
try {
|
|
340
|
-
const projId = this.scope.projectId ?? null;
|
|
341
|
-
const existing = await this._findOne(targetTable, {
|
|
342
|
-
where: { name, project_id: projId }
|
|
343
|
-
});
|
|
344
|
-
if (existing) {
|
|
345
|
-
await this._delete(targetTable, existing.id);
|
|
346
|
-
}
|
|
347
|
-
} catch (error) {
|
|
348
|
-
console.error(`Failed to delete projection ${type}/${name} from ${targetTable}:`, error);
|
|
349
|
-
}
|
|
318
|
+
delete(key) {
|
|
319
|
+
return this.map.delete(key);
|
|
350
320
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
*/
|
|
354
|
-
transformToProjection(type, name, data) {
|
|
355
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
356
|
-
switch (type) {
|
|
357
|
-
case "object":
|
|
358
|
-
return this.projectObject(name, data, now);
|
|
359
|
-
case "view":
|
|
360
|
-
return this.projectView(name, data, now);
|
|
361
|
-
case "agent":
|
|
362
|
-
return this.projectAgent(name, data, now);
|
|
363
|
-
case "tool":
|
|
364
|
-
return this.projectTool(name, data, now);
|
|
365
|
-
case "flow":
|
|
366
|
-
return this.projectFlow(name, data, now);
|
|
367
|
-
default:
|
|
368
|
-
return null;
|
|
369
|
-
}
|
|
321
|
+
clear() {
|
|
322
|
+
this.map.clear();
|
|
370
323
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
*/
|
|
374
|
-
projectObject(name, data, now) {
|
|
375
|
-
return {
|
|
376
|
-
name,
|
|
377
|
-
project_id: this.scope.projectId ?? null,
|
|
378
|
-
label: data.label || name,
|
|
379
|
-
plural_label: data.pluralLabel || data.label || name,
|
|
380
|
-
description: data.description || "",
|
|
381
|
-
icon: data.icon || "database",
|
|
382
|
-
namespace: data.namespace || "default",
|
|
383
|
-
tags: Array.isArray(data.tags) ? data.tags.join(",") : data.tags || "",
|
|
384
|
-
active: data.active !== false,
|
|
385
|
-
is_system: data.isSystem || false,
|
|
386
|
-
abstract: data.abstract || false,
|
|
387
|
-
datasource: data.datasource || "default",
|
|
388
|
-
table_name: data.name ? StorageNameMapping.resolveTableName({ name: data.name }) : name,
|
|
389
|
-
// Serialize complex structures as JSON
|
|
390
|
-
fields_json: data.fields ? JSON.stringify(data.fields) : null,
|
|
391
|
-
indexes_json: data.indexes ? JSON.stringify(data.indexes) : null,
|
|
392
|
-
validations_json: data.validations ? JSON.stringify(data.validations) : null,
|
|
393
|
-
state_machines_json: data.stateMachines ? JSON.stringify(data.stateMachines) : null,
|
|
394
|
-
capabilities_json: data.enable ? JSON.stringify(data.enable) : null,
|
|
395
|
-
// Denormalized fields
|
|
396
|
-
field_count: data.fields ? Object.keys(data.fields).length : 0,
|
|
397
|
-
display_name_field: data.displayNameField || null,
|
|
398
|
-
title_format: data.titleFormat || null,
|
|
399
|
-
compact_layout: Array.isArray(data.compactLayout) ? data.compactLayout.join(",") : data.compactLayout || null,
|
|
400
|
-
// Capabilities (denormalized for easier querying)
|
|
401
|
-
track_history: data.enable?.trackHistory || false,
|
|
402
|
-
searchable: data.enable?.searchable !== false,
|
|
403
|
-
api_enabled: data.enable?.apiEnabled !== false,
|
|
404
|
-
files: data.enable?.files || false,
|
|
405
|
-
feeds: data.enable?.feeds || false,
|
|
406
|
-
activities: data.enable?.activities || false,
|
|
407
|
-
trash: data.enable?.trash !== false,
|
|
408
|
-
mru: data.enable?.mru !== false,
|
|
409
|
-
clone: data.enable?.clone !== false,
|
|
410
|
-
// Package management
|
|
411
|
-
package_id: data.packageId || null,
|
|
412
|
-
managed_by: data.managedBy || "user",
|
|
413
|
-
// Audit
|
|
414
|
-
created_by: data.createdBy || null,
|
|
415
|
-
created_at: data.createdAt || now,
|
|
416
|
-
updated_by: data.updatedBy || null,
|
|
417
|
-
updated_at: now
|
|
418
|
-
};
|
|
324
|
+
get size() {
|
|
325
|
+
return this.map.size;
|
|
419
326
|
}
|
|
420
|
-
/**
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
projectView(name, data, now) {
|
|
327
|
+
/** Diagnostic counters — useful for `metrics` endpoints. */
|
|
328
|
+
stats() {
|
|
329
|
+
const total = this.hits + this.misses;
|
|
424
330
|
return {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
object_name: data.object || "",
|
|
430
|
-
view_type: data.type || "grid",
|
|
431
|
-
// Serialize configurations as JSON
|
|
432
|
-
columns_json: data.columns ? JSON.stringify(data.columns) : null,
|
|
433
|
-
filters_json: data.filters ? JSON.stringify(data.filters) : null,
|
|
434
|
-
sort_json: data.sort ? JSON.stringify(data.sort) : null,
|
|
435
|
-
config_json: data.config ? JSON.stringify(data.config) : null,
|
|
436
|
-
// Display options
|
|
437
|
-
page_size: data.pageSize || 25,
|
|
438
|
-
show_search: data.showSearch !== false,
|
|
439
|
-
show_filters: data.showFilters !== false,
|
|
440
|
-
// Classification
|
|
441
|
-
namespace: data.namespace || "default",
|
|
442
|
-
// Package management
|
|
443
|
-
package_id: data.packageId || null,
|
|
444
|
-
managed_by: data.managedBy || "user",
|
|
445
|
-
// Audit
|
|
446
|
-
created_by: data.createdBy || null,
|
|
447
|
-
created_at: data.createdAt || now,
|
|
448
|
-
updated_by: data.updatedBy || null,
|
|
449
|
-
updated_at: now
|
|
331
|
+
size: this.map.size,
|
|
332
|
+
hits: this.hits,
|
|
333
|
+
misses: this.misses,
|
|
334
|
+
hitRate: total === 0 ? 0 : this.hits / total
|
|
450
335
|
};
|
|
451
336
|
}
|
|
452
|
-
/**
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
return {
|
|
457
|
-
name,
|
|
458
|
-
project_id: this.scope.projectId ?? null,
|
|
459
|
-
label: data.label || name,
|
|
460
|
-
description: data.description || "",
|
|
461
|
-
agent_type: data.type || "conversational",
|
|
462
|
-
// Model configuration
|
|
463
|
-
model: data.model || null,
|
|
464
|
-
temperature: data.temperature ?? 0.7,
|
|
465
|
-
max_tokens: data.maxTokens || null,
|
|
466
|
-
top_p: data.topP || null,
|
|
467
|
-
// System prompt
|
|
468
|
-
system_prompt: data.systemPrompt || null,
|
|
469
|
-
// Tools and skills as JSON
|
|
470
|
-
tools_json: data.tools ? JSON.stringify(data.tools) : null,
|
|
471
|
-
skills_json: data.skills ? JSON.stringify(data.skills) : null,
|
|
472
|
-
// Memory
|
|
473
|
-
memory_enabled: data.memoryEnabled || false,
|
|
474
|
-
memory_window: data.memoryWindow || 10,
|
|
475
|
-
// Classification
|
|
476
|
-
namespace: data.namespace || "default",
|
|
477
|
-
// Package management
|
|
478
|
-
package_id: data.packageId || null,
|
|
479
|
-
managed_by: data.managedBy || "user",
|
|
480
|
-
// Audit
|
|
481
|
-
created_by: data.createdBy || null,
|
|
482
|
-
created_at: data.createdAt || now,
|
|
483
|
-
updated_by: data.updatedBy || null,
|
|
484
|
-
updated_at: now
|
|
485
|
-
};
|
|
337
|
+
/** Resets hit/miss counters without dropping cached entries. */
|
|
338
|
+
resetStats() {
|
|
339
|
+
this.hits = 0;
|
|
340
|
+
this.misses = 0;
|
|
486
341
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
edges_json: data.edges ? JSON.stringify(data.edges) : null,
|
|
524
|
-
variables_json: data.variables ? JSON.stringify(data.variables) : null,
|
|
525
|
-
// Trigger configuration
|
|
526
|
-
trigger_type: data.triggerType || null,
|
|
527
|
-
trigger_object: data.triggerObject || null,
|
|
528
|
-
// Status
|
|
529
|
-
active: data.active || false,
|
|
530
|
-
// Classification
|
|
531
|
-
namespace: data.namespace || "default",
|
|
532
|
-
// Package management
|
|
533
|
-
package_id: data.packageId || null,
|
|
534
|
-
managed_by: data.managedBy || "user",
|
|
535
|
-
// Audit
|
|
536
|
-
created_by: data.createdBy || null,
|
|
537
|
-
created_at: data.createdAt || now,
|
|
538
|
-
updated_by: data.updatedBy || null,
|
|
539
|
-
updated_at: now
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
// ==========================================
|
|
543
|
-
// Internal CRUD helpers (driver vs engine)
|
|
544
|
-
// ==========================================
|
|
545
|
-
async _findOne(table, query) {
|
|
546
|
-
if (this.engine) {
|
|
547
|
-
return this.engine.findOne(table, query);
|
|
548
|
-
}
|
|
549
|
-
return this.driver.findOne(table, { object: table, ...query });
|
|
550
|
-
}
|
|
551
|
-
async _create(table, data) {
|
|
552
|
-
if (this.engine) {
|
|
553
|
-
return this.engine.insert(table, data);
|
|
554
|
-
}
|
|
555
|
-
return this.driver.create(table, data);
|
|
556
|
-
}
|
|
557
|
-
async _update(table, id, data) {
|
|
558
|
-
if (this.engine) {
|
|
559
|
-
return this.engine.update(table, { id, ...data });
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// src/migrations/add-sys-metadata-overlay-index.ts
|
|
345
|
+
var INDEX_NAME = "idx_sys_metadata_overlay_active";
|
|
346
|
+
var TABLE = "sys_metadata";
|
|
347
|
+
var COLUMNS = "(type, name, organization_id, project_id, scope)";
|
|
348
|
+
var WHERE = "state = 'active'";
|
|
349
|
+
async function addSysMetadataOverlayIndex(driver) {
|
|
350
|
+
const driverAny = driver;
|
|
351
|
+
const exec = async (sql) => {
|
|
352
|
+
if (typeof driverAny.raw === "function") {
|
|
353
|
+
await driverAny.raw(sql);
|
|
354
|
+
} else if (typeof driverAny.execute === "function") {
|
|
355
|
+
await driverAny.execute(sql);
|
|
356
|
+
} else {
|
|
357
|
+
throw new Error("driver has neither raw nor execute");
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
const partialSql = `CREATE UNIQUE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS} WHERE ${WHERE}`;
|
|
361
|
+
const fallbackSql = `CREATE INDEX IF NOT EXISTS ${INDEX_NAME} ON ${TABLE} ${COLUMNS}`;
|
|
362
|
+
try {
|
|
363
|
+
await exec(partialSql);
|
|
364
|
+
return { index: INDEX_NAME, status: "created" };
|
|
365
|
+
} catch (err) {
|
|
366
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
367
|
+
if (/partial|where clause|syntax/i.test(msg)) {
|
|
368
|
+
try {
|
|
369
|
+
await exec(fallbackSql);
|
|
370
|
+
return { index: INDEX_NAME, status: "fallback_non_unique" };
|
|
371
|
+
} catch (fallbackErr) {
|
|
372
|
+
return {
|
|
373
|
+
index: INDEX_NAME,
|
|
374
|
+
status: "error",
|
|
375
|
+
error: fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)
|
|
376
|
+
};
|
|
377
|
+
}
|
|
560
378
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
async _delete(table, id) {
|
|
564
|
-
if (this.engine) {
|
|
565
|
-
return this.engine.delete(table, { where: { id } });
|
|
379
|
+
if (/already exists/i.test(msg)) {
|
|
380
|
+
return { index: INDEX_NAME, status: "already_exists" };
|
|
566
381
|
}
|
|
567
|
-
return
|
|
382
|
+
return { index: INDEX_NAME, status: "error", error: msg };
|
|
568
383
|
}
|
|
569
|
-
|
|
570
|
-
* Generate a simple unique ID
|
|
571
|
-
*/
|
|
572
|
-
generateId() {
|
|
573
|
-
if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") {
|
|
574
|
-
return globalThis.crypto.randomUUID();
|
|
575
|
-
}
|
|
576
|
-
return `proj_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
577
|
-
}
|
|
578
|
-
};
|
|
384
|
+
}
|
|
579
385
|
|
|
580
386
|
// src/loaders/database-loader.ts
|
|
581
387
|
var DatabaseLoader = class {
|
|
@@ -602,17 +408,56 @@ var DatabaseLoader = class {
|
|
|
602
408
|
this.organizationId = options.organizationId;
|
|
603
409
|
this.projectId = options.projectId;
|
|
604
410
|
this.trackHistory = options.trackHistory !== false;
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
411
|
+
const cacheOpts = options.cache;
|
|
412
|
+
const cacheEnabled = cacheOpts?.enabled !== false;
|
|
413
|
+
if (cacheEnabled) {
|
|
414
|
+
const lruOpts = {
|
|
415
|
+
maxSize: cacheOpts?.maxSize ?? 500,
|
|
416
|
+
ttl: cacheOpts?.ttl ?? 6e4
|
|
417
|
+
};
|
|
418
|
+
this.loadCache = new LRUCache(lruOpts);
|
|
419
|
+
this.loadManyCache = new LRUCache(lruOpts);
|
|
420
|
+
this.listCache = new LRUCache(lruOpts);
|
|
421
|
+
this.statCache = new LRUCache(lruOpts);
|
|
613
422
|
}
|
|
614
423
|
}
|
|
615
424
|
// ==========================================
|
|
425
|
+
// Cache helpers
|
|
426
|
+
// ==========================================
|
|
427
|
+
cacheKey(type, name) {
|
|
428
|
+
return `${type}::${name}`;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Invalidate all cached entries for a specific (type, name) pair plus
|
|
432
|
+
* the type-level aggregates (`loadMany`, `list`). Called from every write
|
|
433
|
+
* path (`save`, `delete`, `registerRollback`).
|
|
434
|
+
*/
|
|
435
|
+
invalidate(type, name) {
|
|
436
|
+
if (!this.loadCache) return;
|
|
437
|
+
const key = this.cacheKey(type, name);
|
|
438
|
+
this.loadCache.delete(key);
|
|
439
|
+
this.statCache?.delete(key);
|
|
440
|
+
this.loadManyCache?.delete(type);
|
|
441
|
+
this.listCache?.delete(type);
|
|
442
|
+
}
|
|
443
|
+
/** Drop the entire cache — useful after bulk imports or schema changes. */
|
|
444
|
+
invalidateAll() {
|
|
445
|
+
this.loadCache?.clear();
|
|
446
|
+
this.loadManyCache?.clear();
|
|
447
|
+
this.listCache?.clear();
|
|
448
|
+
this.statCache?.clear();
|
|
449
|
+
}
|
|
450
|
+
/** Diagnostic: aggregated cache statistics for `metrics` endpoints. */
|
|
451
|
+
getCacheStats() {
|
|
452
|
+
return {
|
|
453
|
+
enabled: this.loadCache !== void 0,
|
|
454
|
+
load: this.loadCache?.stats() ?? null,
|
|
455
|
+
loadMany: this.loadManyCache?.stats() ?? null,
|
|
456
|
+
list: this.listCache?.stats() ?? null,
|
|
457
|
+
stat: this.statCache?.stats() ?? null
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
// ==========================================
|
|
616
461
|
// Internal CRUD helpers (driver vs engine)
|
|
617
462
|
// ==========================================
|
|
618
463
|
async _find(table, query) {
|
|
@@ -660,6 +505,23 @@ var DatabaseLoader = class {
|
|
|
660
505
|
if (this.schemaReady) return;
|
|
661
506
|
if (this.engine) {
|
|
662
507
|
this.schemaReady = true;
|
|
508
|
+
try {
|
|
509
|
+
const engineAny = this.engine;
|
|
510
|
+
let driver = engineAny?.driver ?? engineAny?.getDriver?.();
|
|
511
|
+
if (!driver && engineAny?.drivers instanceof Map) {
|
|
512
|
+
for (const candidate of engineAny.drivers.values()) {
|
|
513
|
+
const c = candidate;
|
|
514
|
+
if (c && (typeof c.raw === "function" || typeof c.execute === "function")) {
|
|
515
|
+
driver = candidate;
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (driver) {
|
|
521
|
+
await addSysMetadataOverlayIndex(driver);
|
|
522
|
+
}
|
|
523
|
+
} catch {
|
|
524
|
+
}
|
|
663
525
|
return;
|
|
664
526
|
}
|
|
665
527
|
try {
|
|
@@ -668,6 +530,10 @@ var DatabaseLoader = class {
|
|
|
668
530
|
name: this.tableName
|
|
669
531
|
});
|
|
670
532
|
this.schemaReady = true;
|
|
533
|
+
try {
|
|
534
|
+
await addSysMetadataOverlayIndex(this.driver);
|
|
535
|
+
} catch {
|
|
536
|
+
}
|
|
671
537
|
} catch {
|
|
672
538
|
this.schemaReady = true;
|
|
673
539
|
}
|
|
@@ -812,11 +678,24 @@ var DatabaseLoader = class {
|
|
|
812
678
|
async load(type, name, _options) {
|
|
813
679
|
const startTime = Date.now();
|
|
814
680
|
await this.ensureSchema();
|
|
681
|
+
const key = this.cacheKey(type, name);
|
|
682
|
+
if (this.loadCache) {
|
|
683
|
+
const cached = this.loadCache.get(key);
|
|
684
|
+
if (cached !== void 0) {
|
|
685
|
+
return {
|
|
686
|
+
data: cached,
|
|
687
|
+
source: "database",
|
|
688
|
+
format: "json",
|
|
689
|
+
loadTime: Date.now() - startTime
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
815
693
|
try {
|
|
816
694
|
const row = await this._findOne(this.tableName, {
|
|
817
695
|
where: this.baseFilter(type, name)
|
|
818
696
|
});
|
|
819
697
|
if (!row) {
|
|
698
|
+
this.loadCache?.set(key, null);
|
|
820
699
|
return {
|
|
821
700
|
data: null,
|
|
822
701
|
loadTime: Date.now() - startTime
|
|
@@ -824,6 +703,7 @@ var DatabaseLoader = class {
|
|
|
824
703
|
}
|
|
825
704
|
const data = this.rowToData(row);
|
|
826
705
|
const record = this.rowToRecord(row);
|
|
706
|
+
this.loadCache?.set(key, data);
|
|
827
707
|
return {
|
|
828
708
|
data,
|
|
829
709
|
source: "database",
|
|
@@ -840,17 +720,27 @@ var DatabaseLoader = class {
|
|
|
840
720
|
}
|
|
841
721
|
async loadMany(type, _options) {
|
|
842
722
|
await this.ensureSchema();
|
|
723
|
+
if (this.loadManyCache) {
|
|
724
|
+
const cached = this.loadManyCache.get(type);
|
|
725
|
+
if (cached !== void 0) return cached;
|
|
726
|
+
}
|
|
843
727
|
try {
|
|
844
728
|
const rows = await this._find(this.tableName, {
|
|
845
729
|
where: this.baseFilter(type)
|
|
846
730
|
});
|
|
847
|
-
|
|
731
|
+
const result = rows.map((row) => this.rowToData(row)).filter((data) => data !== null);
|
|
732
|
+
this.loadManyCache?.set(type, result);
|
|
733
|
+
return result;
|
|
848
734
|
} catch {
|
|
849
735
|
return [];
|
|
850
736
|
}
|
|
851
737
|
}
|
|
852
738
|
async exists(type, name) {
|
|
853
739
|
await this.ensureSchema();
|
|
740
|
+
if (this.loadCache) {
|
|
741
|
+
const cached = this.loadCache.get(this.cacheKey(type, name));
|
|
742
|
+
if (cached !== void 0) return cached !== null;
|
|
743
|
+
}
|
|
854
744
|
try {
|
|
855
745
|
const count = await this._count(this.tableName, {
|
|
856
746
|
where: this.baseFilter(type, name)
|
|
@@ -862,31 +752,47 @@ var DatabaseLoader = class {
|
|
|
862
752
|
}
|
|
863
753
|
async stat(type, name) {
|
|
864
754
|
await this.ensureSchema();
|
|
755
|
+
const key = this.cacheKey(type, name);
|
|
756
|
+
if (this.statCache) {
|
|
757
|
+
const cached = this.statCache.get(key);
|
|
758
|
+
if (cached !== void 0) return cached;
|
|
759
|
+
}
|
|
865
760
|
try {
|
|
866
761
|
const row = await this._findOne(this.tableName, {
|
|
867
762
|
where: this.baseFilter(type, name)
|
|
868
763
|
});
|
|
869
|
-
if (!row)
|
|
764
|
+
if (!row) {
|
|
765
|
+
this.statCache?.set(key, null);
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
870
768
|
const record = this.rowToRecord(row);
|
|
871
769
|
const metadataStr = typeof row.metadata === "string" ? row.metadata : JSON.stringify(row.metadata);
|
|
872
|
-
|
|
770
|
+
const stats = {
|
|
873
771
|
size: metadataStr.length,
|
|
874
772
|
mtime: record.updatedAt ?? record.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
875
773
|
format: "json",
|
|
876
774
|
etag: record.checksum
|
|
877
775
|
};
|
|
776
|
+
this.statCache?.set(key, stats);
|
|
777
|
+
return stats;
|
|
878
778
|
} catch {
|
|
879
779
|
return null;
|
|
880
780
|
}
|
|
881
781
|
}
|
|
882
782
|
async list(type) {
|
|
883
783
|
await this.ensureSchema();
|
|
784
|
+
if (this.listCache) {
|
|
785
|
+
const cached = this.listCache.get(type);
|
|
786
|
+
if (cached !== void 0) return cached;
|
|
787
|
+
}
|
|
884
788
|
try {
|
|
885
789
|
const rows = await this._find(this.tableName, {
|
|
886
790
|
where: this.baseFilter(type),
|
|
887
791
|
fields: ["name"]
|
|
888
792
|
});
|
|
889
|
-
|
|
793
|
+
const names = rows.map((row) => row.name).filter((name) => typeof name === "string");
|
|
794
|
+
this.listCache?.set(type, names);
|
|
795
|
+
return names;
|
|
890
796
|
} catch {
|
|
891
797
|
return [];
|
|
892
798
|
}
|
|
@@ -1026,6 +932,7 @@ var DatabaseLoader = class {
|
|
|
1026
932
|
updated_at: now,
|
|
1027
933
|
state: "active"
|
|
1028
934
|
});
|
|
935
|
+
this.invalidate(type, name);
|
|
1029
936
|
await this.createHistoryRecord(
|
|
1030
937
|
existing.id,
|
|
1031
938
|
type,
|
|
@@ -1051,6 +958,7 @@ var DatabaseLoader = class {
|
|
|
1051
958
|
if (existing) {
|
|
1052
959
|
const previousChecksum = existing.checksum;
|
|
1053
960
|
if (newChecksum === previousChecksum) {
|
|
961
|
+
this.loadCache?.set(this.cacheKey(type, name), data);
|
|
1054
962
|
return {
|
|
1055
963
|
success: true,
|
|
1056
964
|
path: `datasource://${this.tableName}/${type}/${name}`,
|
|
@@ -1066,6 +974,7 @@ var DatabaseLoader = class {
|
|
|
1066
974
|
updated_at: now,
|
|
1067
975
|
state: "active"
|
|
1068
976
|
});
|
|
977
|
+
this.invalidate(type, name);
|
|
1069
978
|
await this.createHistoryRecord(
|
|
1070
979
|
existing.id,
|
|
1071
980
|
type,
|
|
@@ -1075,9 +984,6 @@ var DatabaseLoader = class {
|
|
|
1075
984
|
"update",
|
|
1076
985
|
previousChecksum
|
|
1077
986
|
);
|
|
1078
|
-
if (this.projector) {
|
|
1079
|
-
await this.projector.project(type, name, data);
|
|
1080
|
-
}
|
|
1081
987
|
return {
|
|
1082
988
|
success: true,
|
|
1083
989
|
path: `datasource://${this.tableName}/${type}/${name}`,
|
|
@@ -1103,6 +1009,7 @@ var DatabaseLoader = class {
|
|
|
1103
1009
|
created_at: now,
|
|
1104
1010
|
updated_at: now
|
|
1105
1011
|
});
|
|
1012
|
+
this.invalidate(type, name);
|
|
1106
1013
|
await this.createHistoryRecord(
|
|
1107
1014
|
id,
|
|
1108
1015
|
type,
|
|
@@ -1111,9 +1018,6 @@ var DatabaseLoader = class {
|
|
|
1111
1018
|
data,
|
|
1112
1019
|
"create"
|
|
1113
1020
|
);
|
|
1114
|
-
if (this.projector) {
|
|
1115
|
-
await this.projector.project(type, name, data);
|
|
1116
|
-
}
|
|
1117
1021
|
return {
|
|
1118
1022
|
success: true,
|
|
1119
1023
|
path: `datasource://${this.tableName}/${type}/${name}`,
|
|
@@ -1139,9 +1043,7 @@ var DatabaseLoader = class {
|
|
|
1139
1043
|
return;
|
|
1140
1044
|
}
|
|
1141
1045
|
await this._delete(this.tableName, existing.id);
|
|
1142
|
-
|
|
1143
|
-
await this.projector.deleteProjection(type, name);
|
|
1144
|
-
}
|
|
1046
|
+
this.invalidate(type, name);
|
|
1145
1047
|
}
|
|
1146
1048
|
};
|
|
1147
1049
|
function generateId() {
|
|
@@ -1152,7 +1054,7 @@ function generateId() {
|
|
|
1152
1054
|
}
|
|
1153
1055
|
|
|
1154
1056
|
// src/metadata-manager.ts
|
|
1155
|
-
var
|
|
1057
|
+
var _MetadataManager = class _MetadataManager {
|
|
1156
1058
|
constructor(config) {
|
|
1157
1059
|
this.loaders = /* @__PURE__ */ new Map();
|
|
1158
1060
|
this.watchCallbacks = /* @__PURE__ */ new Map();
|
|
@@ -1164,6 +1066,18 @@ var MetadataManager = class {
|
|
|
1164
1066
|
this.typeRegistry = [];
|
|
1165
1067
|
// Dependency tracking: "type:name" -> dependencies
|
|
1166
1068
|
this.dependencies = /* @__PURE__ */ new Map();
|
|
1069
|
+
// Short-lived cache for list() results. Built primarily to break the
|
|
1070
|
+
// deadlock that occurs when security/permission middleware calls
|
|
1071
|
+
// `list('permission')` from inside a user-initiated DB transaction: the
|
|
1072
|
+
// DatabaseLoader's `engine.find('sys_metadata', ...)` would then try to
|
|
1073
|
+
// acquire a fresh knex connection while the transaction is still holding
|
|
1074
|
+
// SQLite's single connection — knex waits the full `acquireConnectionTimeout`
|
|
1075
|
+
// (60s) before returning []. The cache absorbs the repeated lookups so the
|
|
1076
|
+
// loader is only hit once per TTL window.
|
|
1077
|
+
//
|
|
1078
|
+
// Invalidated on every `register()` / `unregister()` to keep CRUD writes
|
|
1079
|
+
// visible to subsequent reads.
|
|
1080
|
+
this.listCache = /* @__PURE__ */ new Map();
|
|
1167
1081
|
this.config = config;
|
|
1168
1082
|
this.logger = createLogger({ level: "info", format: "pretty" });
|
|
1169
1083
|
this.serializers = /* @__PURE__ */ new Map();
|
|
@@ -1214,7 +1128,8 @@ var MetadataManager = class {
|
|
|
1214
1128
|
driver,
|
|
1215
1129
|
tableName,
|
|
1216
1130
|
organizationId,
|
|
1217
|
-
projectId
|
|
1131
|
+
projectId,
|
|
1132
|
+
cache: this.config.cache?.databaseLoader
|
|
1218
1133
|
});
|
|
1219
1134
|
this.registerLoader(dbLoader);
|
|
1220
1135
|
this.logger.info("DatabaseLoader configured", { datasource: this.config.datasource, tableName });
|
|
@@ -1242,7 +1157,8 @@ var MetadataManager = class {
|
|
|
1242
1157
|
engine,
|
|
1243
1158
|
tableName,
|
|
1244
1159
|
organizationId,
|
|
1245
|
-
projectId
|
|
1160
|
+
projectId,
|
|
1161
|
+
cache: this.config.cache?.databaseLoader
|
|
1246
1162
|
});
|
|
1247
1163
|
this.registerLoader(dbLoader);
|
|
1248
1164
|
this.logger.info("DatabaseLoader configured via DataEngine", { tableName });
|
|
@@ -1274,10 +1190,19 @@ var MetadataManager = class {
|
|
|
1274
1190
|
* should not be written to during runtime registration.
|
|
1275
1191
|
*/
|
|
1276
1192
|
async register(type, name, data) {
|
|
1193
|
+
if (this.config.persistence?.writable === false) {
|
|
1194
|
+
const msg = `MetadataManager is read-only (persistence.writable=false); refusing to register ${type}/${name}`;
|
|
1195
|
+
if (this.config.validation?.throwOnError) {
|
|
1196
|
+
throw new Error(msg);
|
|
1197
|
+
}
|
|
1198
|
+
this.logger.warn(msg);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1277
1201
|
if (!this.registry.has(type)) {
|
|
1278
1202
|
this.registry.set(type, /* @__PURE__ */ new Map());
|
|
1279
1203
|
}
|
|
1280
1204
|
this.registry.get(type).set(name, data);
|
|
1205
|
+
this.invalidateListCache(type);
|
|
1281
1206
|
for (const loader of this.loaders.values()) {
|
|
1282
1207
|
if (loader.save && loader.contract.protocol === "datasource:" && loader.contract.capabilities.write) {
|
|
1283
1208
|
await loader.save(type, name, data);
|
|
@@ -1319,6 +1244,10 @@ var MetadataManager = class {
|
|
|
1319
1244
|
* List all metadata items of a given type
|
|
1320
1245
|
*/
|
|
1321
1246
|
async list(type) {
|
|
1247
|
+
const cached = this.listCache.get(type);
|
|
1248
|
+
if (cached && Date.now() - cached.ts < _MetadataManager.LIST_CACHE_TTL_MS) {
|
|
1249
|
+
return cached.items;
|
|
1250
|
+
}
|
|
1322
1251
|
const items = /* @__PURE__ */ new Map();
|
|
1323
1252
|
const typeStore = this.registry.get(type);
|
|
1324
1253
|
if (typeStore) {
|
|
@@ -1339,7 +1268,16 @@ var MetadataManager = class {
|
|
|
1339
1268
|
this.logger.warn(`Loader ${loader.contract.name} failed to loadMany ${type}`, { error: e });
|
|
1340
1269
|
}
|
|
1341
1270
|
}
|
|
1342
|
-
|
|
1271
|
+
const result = Array.from(items.values());
|
|
1272
|
+
this.cacheListResult(type, result);
|
|
1273
|
+
return result;
|
|
1274
|
+
}
|
|
1275
|
+
cacheListResult(type, items) {
|
|
1276
|
+
this.listCache.set(type, { ts: Date.now(), items });
|
|
1277
|
+
}
|
|
1278
|
+
/** Internal helper: drop the cached `list()` result for a type. */
|
|
1279
|
+
invalidateListCache(type) {
|
|
1280
|
+
this.listCache.delete(type);
|
|
1343
1281
|
}
|
|
1344
1282
|
/**
|
|
1345
1283
|
* Unregister/remove a metadata item by type and name.
|
|
@@ -1353,6 +1291,7 @@ var MetadataManager = class {
|
|
|
1353
1291
|
this.registry.delete(type);
|
|
1354
1292
|
}
|
|
1355
1293
|
}
|
|
1294
|
+
this.invalidateListCache(type);
|
|
1356
1295
|
for (const loader of this.loaders.values()) {
|
|
1357
1296
|
if (loader.contract.protocol !== "datasource:" || !loader.contract.capabilities.write) continue;
|
|
1358
1297
|
if (typeof loader.delete === "function") {
|
|
@@ -1767,6 +1706,14 @@ var MetadataManager = class {
|
|
|
1767
1706
|
* Save/update an overlay for a metadata item
|
|
1768
1707
|
*/
|
|
1769
1708
|
async saveOverlay(overlay) {
|
|
1709
|
+
if (this.config.persistence?.overlayWritable === false) {
|
|
1710
|
+
const msg = `MetadataManager overlays are read-only (persistence.overlayWritable=false); refusing to save overlay for ${overlay.baseType}/${overlay.baseName}`;
|
|
1711
|
+
if (this.config.validation?.throwOnError) {
|
|
1712
|
+
throw new Error(msg);
|
|
1713
|
+
}
|
|
1714
|
+
this.logger.warn(msg);
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1770
1717
|
const key = this.overlayKey(overlay.baseType, overlay.baseName, overlay.scope);
|
|
1771
1718
|
this.overlays.set(key, overlay);
|
|
1772
1719
|
}
|
|
@@ -2236,6 +2183,8 @@ var MetadataManager = class {
|
|
|
2236
2183
|
};
|
|
2237
2184
|
}
|
|
2238
2185
|
};
|
|
2186
|
+
_MetadataManager.LIST_CACHE_TTL_MS = 3e4;
|
|
2187
|
+
var MetadataManager = _MetadataManager;
|
|
2239
2188
|
|
|
2240
2189
|
// src/plugin.ts
|
|
2241
2190
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -2727,13 +2676,13 @@ var MemoryLoader = class {
|
|
|
2727
2676
|
// src/plugin.ts
|
|
2728
2677
|
import { DEFAULT_METADATA_TYPE_REGISTRY } from "@objectstack/spec/kernel";
|
|
2729
2678
|
import {
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
SysObject,
|
|
2733
|
-
SysTool,
|
|
2734
|
-
SysView
|
|
2679
|
+
SysMetadataObject as SysMetadataObject2,
|
|
2680
|
+
SysMetadataHistoryObject as SysMetadataHistoryObject2
|
|
2735
2681
|
} from "@objectstack/platform-objects/metadata";
|
|
2736
|
-
var queryableMetadataObjects = [
|
|
2682
|
+
var queryableMetadataObjects = [
|
|
2683
|
+
SysMetadataObject2,
|
|
2684
|
+
SysMetadataHistoryObject2
|
|
2685
|
+
];
|
|
2737
2686
|
var ARTIFACT_FIELD_TO_TYPE = {
|
|
2738
2687
|
objects: "object",
|
|
2739
2688
|
objectExtensions: "object_extension",
|
|
@@ -2801,13 +2750,35 @@ var MetadataPlugin = class {
|
|
|
2801
2750
|
};
|
|
2802
2751
|
this.start = async (ctx) => {
|
|
2803
2752
|
const src = this.options.artifactSource;
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2753
|
+
const mode = this.options.config?.bootstrap ?? "eager";
|
|
2754
|
+
ctx.logger.info("[MetadataPlugin] Bootstrapping metadata", {
|
|
2755
|
+
bootstrap: mode,
|
|
2756
|
+
artifactSource: src?.mode ?? "none"
|
|
2757
|
+
});
|
|
2758
|
+
if (mode === "artifact-only") {
|
|
2759
|
+
if (src?.mode === "local-file") {
|
|
2760
|
+
await this._loadFromLocalFile(ctx, src.path, src.fetchTimeoutMs);
|
|
2761
|
+
} else if (src?.mode === "artifact-api") {
|
|
2762
|
+
await this._loadFromArtifactApi(ctx, src);
|
|
2763
|
+
} else {
|
|
2764
|
+
throw new Error("[MetadataPlugin] bootstrap=artifact-only requires options.artifactSource to be set");
|
|
2765
|
+
}
|
|
2766
|
+
} else if (mode === "lazy") {
|
|
2767
|
+
if (src?.mode === "local-file") {
|
|
2768
|
+
await this._loadFromLocalFile(ctx, src.path, src.fetchTimeoutMs);
|
|
2769
|
+
} else if (src?.mode === "artifact-api") {
|
|
2770
|
+
await this._loadFromArtifactApi(ctx, src);
|
|
2771
|
+
} else {
|
|
2772
|
+
ctx.logger.info("[MetadataPlugin] lazy bootstrap \u2014 skipping filesystem priming; metadata loads on demand");
|
|
2773
|
+
}
|
|
2809
2774
|
} else {
|
|
2810
|
-
|
|
2775
|
+
if (src?.mode === "local-file") {
|
|
2776
|
+
await this._loadFromLocalFile(ctx, src.path, src.fetchTimeoutMs);
|
|
2777
|
+
} else if (src?.mode === "artifact-api") {
|
|
2778
|
+
await this._loadFromArtifactApi(ctx, src);
|
|
2779
|
+
} else {
|
|
2780
|
+
await this._loadFromFileSystem(ctx);
|
|
2781
|
+
}
|
|
2811
2782
|
}
|
|
2812
2783
|
try {
|
|
2813
2784
|
const realtimeService = ctx.getService("realtime");
|
|
@@ -2826,45 +2797,46 @@ var MetadataPlugin = class {
|
|
|
2826
2797
|
...options
|
|
2827
2798
|
};
|
|
2828
2799
|
const rootDir = this.options.rootDir || process.cwd();
|
|
2800
|
+
const bootstrapMode = this.options.config?.bootstrap ?? "eager";
|
|
2801
|
+
const effectiveWatch = bootstrapMode === "artifact-only" ? false : this.options.watch ?? true;
|
|
2829
2802
|
this.manager = new NodeMetadataManager({
|
|
2830
2803
|
rootDir,
|
|
2831
|
-
watch:
|
|
2804
|
+
watch: effectiveWatch,
|
|
2832
2805
|
formats: ["yaml", "json", "typescript", "javascript"]
|
|
2833
2806
|
});
|
|
2834
2807
|
this.manager.setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY);
|
|
2835
2808
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
);
|
|
2842
|
-
|
|
2809
|
+
/**
|
|
2810
|
+
* Fetch JSON content from a URL with configurable timeout.
|
|
2811
|
+
*/
|
|
2812
|
+
async _fetchJson(url, fetchTimeoutMs, token) {
|
|
2813
|
+
const envTimeout = Number(process.env.OS_ARTIFACT_FETCH_TIMEOUT_MS);
|
|
2814
|
+
const timeoutMs = fetchTimeoutMs ?? (Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : void 0) ?? 6e4;
|
|
2815
|
+
const controller = new AbortController();
|
|
2816
|
+
const timer = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
2843
2817
|
try {
|
|
2844
|
-
|
|
2845
|
-
if (
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
redirect: "follow",
|
|
2851
|
-
signal: controller.signal,
|
|
2852
|
-
headers: { Accept: "application/json, */*;q=0.5" }
|
|
2853
|
-
});
|
|
2854
|
-
if (!res.ok) {
|
|
2855
|
-
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
2856
|
-
}
|
|
2857
|
-
content = await res.text();
|
|
2858
|
-
} finally {
|
|
2859
|
-
clearTimeout(timer);
|
|
2860
|
-
}
|
|
2861
|
-
} else {
|
|
2862
|
-
content = await readFile2(filePath, "utf8");
|
|
2863
|
-
}
|
|
2864
|
-
raw = JSON.parse(content);
|
|
2818
|
+
const headers = { Accept: "application/json, */*;q=0.5" };
|
|
2819
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
2820
|
+
const res = await fetch(url, { redirect: "follow", signal: controller.signal, headers });
|
|
2821
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
2822
|
+
const content = await res.text();
|
|
2823
|
+
return JSON.parse(content);
|
|
2865
2824
|
} catch (e) {
|
|
2866
|
-
|
|
2825
|
+
if (e?.name === "AbortError") {
|
|
2826
|
+
throw new Error(
|
|
2827
|
+
`fetch timed out after ${timeoutMs}ms \u2014 set artifactSource.fetchTimeoutMs or OS_ARTIFACT_FETCH_TIMEOUT_MS to extend it (0 disables)`
|
|
2828
|
+
);
|
|
2829
|
+
}
|
|
2830
|
+
throw e;
|
|
2831
|
+
} finally {
|
|
2832
|
+
if (timer) clearTimeout(timer);
|
|
2867
2833
|
}
|
|
2834
|
+
}
|
|
2835
|
+
/**
|
|
2836
|
+
* Parse raw artifact JSON (envelope or bare definition) and register all
|
|
2837
|
+
* metadata items into the MetadataManager.
|
|
2838
|
+
*/
|
|
2839
|
+
async _parseAndRegisterArtifact(ctx, raw, label) {
|
|
2868
2840
|
const { ProjectArtifactSchema } = await import("@objectstack/spec/cloud");
|
|
2869
2841
|
const { ObjectStackDefinitionSchema } = await import("@objectstack/spec");
|
|
2870
2842
|
let metadata;
|
|
@@ -2872,6 +2844,9 @@ var MetadataPlugin = class {
|
|
|
2872
2844
|
if (obj?.schemaVersion && obj?.commitId && obj?.metadata !== void 0) {
|
|
2873
2845
|
const artifact = ProjectArtifactSchema.parse(obj);
|
|
2874
2846
|
metadata = artifact.metadata;
|
|
2847
|
+
} else if (obj?.success && obj?.data?.metadata) {
|
|
2848
|
+
const artifact = ProjectArtifactSchema.parse(obj.data);
|
|
2849
|
+
metadata = artifact.metadata;
|
|
2875
2850
|
} else {
|
|
2876
2851
|
const def = ObjectStackDefinitionSchema.parse(obj);
|
|
2877
2852
|
const canonical = JSON.stringify(def, Object.keys(def).sort());
|
|
@@ -2904,10 +2879,51 @@ var MetadataPlugin = class {
|
|
|
2904
2879
|
}
|
|
2905
2880
|
}
|
|
2906
2881
|
this.manager.registerLoader(memLoader);
|
|
2907
|
-
ctx.logger.info("[MetadataPlugin] Artifact metadata loaded", {
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2882
|
+
ctx.logger.info("[MetadataPlugin] Artifact metadata loaded", { source: label, totalRegistered });
|
|
2883
|
+
return totalRegistered;
|
|
2884
|
+
}
|
|
2885
|
+
async _loadFromLocalFile(ctx, filePath, fetchTimeoutMs) {
|
|
2886
|
+
const isUrl = /^https?:\/\//i.test(filePath);
|
|
2887
|
+
ctx.logger.info(
|
|
2888
|
+
`[MetadataPlugin] Loading metadata from ${isUrl ? "remote URL" : "local artifact file"}`,
|
|
2889
|
+
{ path: filePath }
|
|
2890
|
+
);
|
|
2891
|
+
let raw;
|
|
2892
|
+
try {
|
|
2893
|
+
if (isUrl) {
|
|
2894
|
+
raw = await this._fetchJson(filePath, fetchTimeoutMs);
|
|
2895
|
+
} else {
|
|
2896
|
+
const content = await readFile2(filePath, "utf8");
|
|
2897
|
+
raw = JSON.parse(content);
|
|
2898
|
+
}
|
|
2899
|
+
} catch (e) {
|
|
2900
|
+
throw new Error(`[MetadataPlugin] Cannot read artifact ${isUrl ? "URL" : "file"} at "${filePath}": ${e.message}`);
|
|
2901
|
+
}
|
|
2902
|
+
await this._parseAndRegisterArtifact(ctx, raw, filePath);
|
|
2903
|
+
}
|
|
2904
|
+
/**
|
|
2905
|
+
* P2: Load metadata from the cloud artifact API endpoint.
|
|
2906
|
+
*/
|
|
2907
|
+
async _loadFromArtifactApi(ctx, src) {
|
|
2908
|
+
const projectId = this.options.projectId;
|
|
2909
|
+
if (!projectId) {
|
|
2910
|
+
throw new Error("[MetadataPlugin] artifact-api source requires options.projectId to be set");
|
|
2911
|
+
}
|
|
2912
|
+
let artifactUrl = src.url.replace(/\/+$/, "");
|
|
2913
|
+
if (!/\/api\/v\d+\/cloud\/projects\//i.test(artifactUrl)) {
|
|
2914
|
+
artifactUrl = `${artifactUrl}/api/v1/cloud/projects/${projectId}/artifact`;
|
|
2915
|
+
}
|
|
2916
|
+
if (src.commitId) {
|
|
2917
|
+
artifactUrl += `${artifactUrl.includes("?") ? "&" : "?"}commit=${encodeURIComponent(src.commitId)}`;
|
|
2918
|
+
}
|
|
2919
|
+
ctx.logger.info("[MetadataPlugin] Loading metadata from artifact API", { url: artifactUrl });
|
|
2920
|
+
let raw;
|
|
2921
|
+
try {
|
|
2922
|
+
raw = await this._fetchJson(artifactUrl, src.fetchTimeoutMs, src.token);
|
|
2923
|
+
} catch (e) {
|
|
2924
|
+
throw new Error(`[MetadataPlugin] Cannot load artifact from API "${artifactUrl}": ${e.message}`);
|
|
2925
|
+
}
|
|
2926
|
+
await this._parseAndRegisterArtifact(ctx, raw, artifactUrl);
|
|
2911
2927
|
}
|
|
2912
2928
|
async _loadFromFileSystem(ctx) {
|
|
2913
2929
|
ctx.logger.info("Loading metadata from file system...");
|
|
@@ -3038,7 +3054,7 @@ var RemoteLoader = class {
|
|
|
3038
3054
|
};
|
|
3039
3055
|
|
|
3040
3056
|
// src/index.ts
|
|
3041
|
-
import { SysMetadataObject as
|
|
3057
|
+
import { SysMetadataObject as SysMetadataObject3, SysMetadataHistoryObject as SysMetadataHistoryObject3 } from "@objectstack/platform-objects/metadata";
|
|
3042
3058
|
|
|
3043
3059
|
// src/routes/history-routes.ts
|
|
3044
3060
|
function registerMetadataHistoryRoutes(app, metadataService) {
|
|
@@ -3430,12 +3446,11 @@ export {
|
|
|
3430
3446
|
MemoryLoader,
|
|
3431
3447
|
MetadataManager,
|
|
3432
3448
|
MetadataPlugin,
|
|
3433
|
-
MetadataProjector,
|
|
3434
3449
|
migration_exports as Migration,
|
|
3435
3450
|
NodeMetadataManager,
|
|
3436
3451
|
RemoteLoader,
|
|
3437
|
-
|
|
3438
|
-
|
|
3452
|
+
SysMetadataHistoryObject3 as SysMetadataHistoryObject,
|
|
3453
|
+
SysMetadataObject3 as SysMetadataObject,
|
|
3439
3454
|
TypeScriptSerializer,
|
|
3440
3455
|
YAMLSerializer,
|
|
3441
3456
|
calculateChecksum,
|