@objectstack/metadata 4.0.4 → 4.0.5
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 +30 -10
- package/dist/index.cjs +770 -528
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +173 -6151
- package/dist/index.d.ts +173 -6151
- package/dist/index.js +773 -526
- package/dist/index.js.map +1 -1
- package/dist/migrations/migrate-env-id-to-project-id.cjs +84 -0
- package/dist/migrations/migrate-env-id-to-project-id.cjs.map +1 -0
- package/dist/migrations/migrate-env-id-to-project-id.d.cts +37 -0
- package/dist/migrations/migrate-env-id-to-project-id.d.ts +37 -0
- package/dist/migrations/migrate-env-id-to-project-id.js +59 -0
- package/dist/migrations/migrate-env-id-to-project-id.js.map +1 -0
- package/dist/node.cjs +770 -528
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +2 -3
- package/dist/node.d.ts +2 -3
- package/dist/node.js +773 -526
- package/dist/node.js.map +1 -1
- package/package.json +28 -8
package/dist/node.js
CHANGED
|
@@ -174,277 +174,8 @@ export default metadata;
|
|
|
174
174
|
}
|
|
175
175
|
};
|
|
176
176
|
|
|
177
|
-
// src/
|
|
178
|
-
import {
|
|
179
|
-
var SysMetadataObject = ObjectSchema.create({
|
|
180
|
-
namespace: "sys",
|
|
181
|
-
name: "metadata",
|
|
182
|
-
label: "System Metadata",
|
|
183
|
-
pluralLabel: "System Metadata",
|
|
184
|
-
icon: "settings",
|
|
185
|
-
isSystem: true,
|
|
186
|
-
description: "Stores platform and user-scope metadata records (objects, views, flows, etc.)",
|
|
187
|
-
fields: {
|
|
188
|
-
/** Primary Key (UUID) */
|
|
189
|
-
id: Field.text({
|
|
190
|
-
label: "ID",
|
|
191
|
-
required: true,
|
|
192
|
-
readonly: true
|
|
193
|
-
}),
|
|
194
|
-
/** Machine name — unique identifier used in code references */
|
|
195
|
-
name: Field.text({
|
|
196
|
-
label: "Name",
|
|
197
|
-
required: true,
|
|
198
|
-
searchable: true,
|
|
199
|
-
maxLength: 255
|
|
200
|
-
}),
|
|
201
|
-
/** Metadata type (e.g. "object", "view", "flow") */
|
|
202
|
-
type: Field.text({
|
|
203
|
-
label: "Metadata Type",
|
|
204
|
-
required: true,
|
|
205
|
-
searchable: true,
|
|
206
|
-
maxLength: 100
|
|
207
|
-
}),
|
|
208
|
-
/** Namespace / module grouping (e.g. "crm", "core") */
|
|
209
|
-
namespace: Field.text({
|
|
210
|
-
label: "Namespace",
|
|
211
|
-
required: false,
|
|
212
|
-
defaultValue: "default",
|
|
213
|
-
maxLength: 100
|
|
214
|
-
}),
|
|
215
|
-
/** Package that owns/delivered this metadata */
|
|
216
|
-
package_id: Field.text({
|
|
217
|
-
label: "Package ID",
|
|
218
|
-
required: false,
|
|
219
|
-
maxLength: 255
|
|
220
|
-
}),
|
|
221
|
-
/** Who manages this record: package, platform, or user */
|
|
222
|
-
managed_by: Field.select(["package", "platform", "user"], {
|
|
223
|
-
label: "Managed By",
|
|
224
|
-
required: false
|
|
225
|
-
}),
|
|
226
|
-
/** Scope: system (code), platform (admin DB), user (personal DB) */
|
|
227
|
-
scope: Field.select(["system", "platform", "user"], {
|
|
228
|
-
label: "Scope",
|
|
229
|
-
required: true,
|
|
230
|
-
defaultValue: "platform"
|
|
231
|
-
}),
|
|
232
|
-
/** JSON payload — the actual metadata configuration */
|
|
233
|
-
metadata: Field.textarea({
|
|
234
|
-
label: "Metadata",
|
|
235
|
-
required: true,
|
|
236
|
-
description: "JSON-serialized metadata payload"
|
|
237
|
-
}),
|
|
238
|
-
/** Parent metadata name for extension/override */
|
|
239
|
-
extends: Field.text({
|
|
240
|
-
label: "Extends",
|
|
241
|
-
required: false,
|
|
242
|
-
maxLength: 255
|
|
243
|
-
}),
|
|
244
|
-
/** Merge strategy when extending parent metadata */
|
|
245
|
-
strategy: Field.select(["merge", "replace"], {
|
|
246
|
-
label: "Strategy",
|
|
247
|
-
required: false,
|
|
248
|
-
defaultValue: "merge"
|
|
249
|
-
}),
|
|
250
|
-
/** Owner user ID (for user-scope items) */
|
|
251
|
-
owner: Field.text({
|
|
252
|
-
label: "Owner",
|
|
253
|
-
required: false,
|
|
254
|
-
maxLength: 255
|
|
255
|
-
}),
|
|
256
|
-
/** Lifecycle state */
|
|
257
|
-
state: Field.select(["draft", "active", "archived", "deprecated"], {
|
|
258
|
-
label: "State",
|
|
259
|
-
required: false,
|
|
260
|
-
defaultValue: "active"
|
|
261
|
-
}),
|
|
262
|
-
/** Tenant ID for multi-tenant isolation */
|
|
263
|
-
tenant_id: Field.text({
|
|
264
|
-
label: "Tenant ID",
|
|
265
|
-
required: false,
|
|
266
|
-
maxLength: 255
|
|
267
|
-
}),
|
|
268
|
-
/** Version number for optimistic concurrency */
|
|
269
|
-
version: Field.number({
|
|
270
|
-
label: "Version",
|
|
271
|
-
required: false,
|
|
272
|
-
defaultValue: 1
|
|
273
|
-
}),
|
|
274
|
-
/** Content checksum for change detection */
|
|
275
|
-
checksum: Field.text({
|
|
276
|
-
label: "Checksum",
|
|
277
|
-
required: false,
|
|
278
|
-
maxLength: 64
|
|
279
|
-
}),
|
|
280
|
-
/** Origin of this metadata record */
|
|
281
|
-
source: Field.select(["filesystem", "database", "api", "migration"], {
|
|
282
|
-
label: "Source",
|
|
283
|
-
required: false
|
|
284
|
-
}),
|
|
285
|
-
/** Classification tags (JSON array) */
|
|
286
|
-
tags: Field.textarea({
|
|
287
|
-
label: "Tags",
|
|
288
|
-
required: false,
|
|
289
|
-
description: "JSON-serialized array of classification tags"
|
|
290
|
-
}),
|
|
291
|
-
/** Audit fields */
|
|
292
|
-
created_by: Field.text({
|
|
293
|
-
label: "Created By",
|
|
294
|
-
required: false,
|
|
295
|
-
readonly: true,
|
|
296
|
-
maxLength: 255
|
|
297
|
-
}),
|
|
298
|
-
created_at: Field.datetime({
|
|
299
|
-
label: "Created At",
|
|
300
|
-
required: false,
|
|
301
|
-
readonly: true
|
|
302
|
-
}),
|
|
303
|
-
updated_by: Field.text({
|
|
304
|
-
label: "Updated By",
|
|
305
|
-
required: false,
|
|
306
|
-
maxLength: 255
|
|
307
|
-
}),
|
|
308
|
-
updated_at: Field.datetime({
|
|
309
|
-
label: "Updated At",
|
|
310
|
-
required: false
|
|
311
|
-
})
|
|
312
|
-
},
|
|
313
|
-
indexes: [
|
|
314
|
-
{ fields: ["type", "name"], unique: true },
|
|
315
|
-
{ fields: ["type", "scope"] },
|
|
316
|
-
{ fields: ["tenant_id"] },
|
|
317
|
-
{ fields: ["state"] },
|
|
318
|
-
{ fields: ["namespace"] }
|
|
319
|
-
],
|
|
320
|
-
enable: {
|
|
321
|
-
trackHistory: true,
|
|
322
|
-
searchable: false,
|
|
323
|
-
apiEnabled: true,
|
|
324
|
-
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
325
|
-
trash: false
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// src/objects/sys-metadata-history.object.ts
|
|
330
|
-
import { ObjectSchema as ObjectSchema2, Field as Field2 } from "@objectstack/spec/data";
|
|
331
|
-
var SysMetadataHistoryObject = ObjectSchema2.create({
|
|
332
|
-
namespace: "sys",
|
|
333
|
-
name: "metadata_history",
|
|
334
|
-
label: "Metadata History",
|
|
335
|
-
pluralLabel: "Metadata History",
|
|
336
|
-
icon: "history",
|
|
337
|
-
isSystem: true,
|
|
338
|
-
description: "Version history and audit trail for metadata changes",
|
|
339
|
-
fields: {
|
|
340
|
-
/** Primary Key (UUID) */
|
|
341
|
-
id: Field2.text({
|
|
342
|
-
label: "ID",
|
|
343
|
-
required: true,
|
|
344
|
-
readonly: true
|
|
345
|
-
}),
|
|
346
|
-
/** Foreign key to sys_metadata.id */
|
|
347
|
-
metadata_id: Field2.text({
|
|
348
|
-
label: "Metadata ID",
|
|
349
|
-
required: true,
|
|
350
|
-
readonly: true,
|
|
351
|
-
maxLength: 255
|
|
352
|
-
}),
|
|
353
|
-
/** Machine name (denormalized for easier querying) */
|
|
354
|
-
name: Field2.text({
|
|
355
|
-
label: "Name",
|
|
356
|
-
required: true,
|
|
357
|
-
searchable: true,
|
|
358
|
-
readonly: true,
|
|
359
|
-
maxLength: 255
|
|
360
|
-
}),
|
|
361
|
-
/** Metadata type (denormalized for easier querying) */
|
|
362
|
-
type: Field2.text({
|
|
363
|
-
label: "Metadata Type",
|
|
364
|
-
required: true,
|
|
365
|
-
searchable: true,
|
|
366
|
-
readonly: true,
|
|
367
|
-
maxLength: 100
|
|
368
|
-
}),
|
|
369
|
-
/** Version number at this snapshot */
|
|
370
|
-
version: Field2.number({
|
|
371
|
-
label: "Version",
|
|
372
|
-
required: true,
|
|
373
|
-
readonly: true
|
|
374
|
-
}),
|
|
375
|
-
/** Type of operation that created this history entry */
|
|
376
|
-
operation_type: Field2.select(["create", "update", "publish", "revert", "delete"], {
|
|
377
|
-
label: "Operation Type",
|
|
378
|
-
required: true,
|
|
379
|
-
readonly: true
|
|
380
|
-
}),
|
|
381
|
-
/** Historical metadata snapshot (JSON payload) */
|
|
382
|
-
metadata: Field2.textarea({
|
|
383
|
-
label: "Metadata",
|
|
384
|
-
required: true,
|
|
385
|
-
readonly: true,
|
|
386
|
-
description: "JSON-serialized metadata snapshot at this version"
|
|
387
|
-
}),
|
|
388
|
-
/** SHA-256 checksum of metadata content */
|
|
389
|
-
checksum: Field2.text({
|
|
390
|
-
label: "Checksum",
|
|
391
|
-
required: true,
|
|
392
|
-
readonly: true,
|
|
393
|
-
maxLength: 64
|
|
394
|
-
}),
|
|
395
|
-
/** Checksum of the previous version */
|
|
396
|
-
previous_checksum: Field2.text({
|
|
397
|
-
label: "Previous Checksum",
|
|
398
|
-
required: false,
|
|
399
|
-
readonly: true,
|
|
400
|
-
maxLength: 64
|
|
401
|
-
}),
|
|
402
|
-
/** Human-readable description of changes */
|
|
403
|
-
change_note: Field2.textarea({
|
|
404
|
-
label: "Change Note",
|
|
405
|
-
required: false,
|
|
406
|
-
readonly: true,
|
|
407
|
-
description: "Description of what changed in this version"
|
|
408
|
-
}),
|
|
409
|
-
/** Tenant ID for multi-tenant isolation */
|
|
410
|
-
tenant_id: Field2.text({
|
|
411
|
-
label: "Tenant ID",
|
|
412
|
-
required: false,
|
|
413
|
-
readonly: true,
|
|
414
|
-
maxLength: 255
|
|
415
|
-
}),
|
|
416
|
-
/** User who made this change */
|
|
417
|
-
recorded_by: Field2.text({
|
|
418
|
-
label: "Recorded By",
|
|
419
|
-
required: false,
|
|
420
|
-
readonly: true,
|
|
421
|
-
maxLength: 255
|
|
422
|
-
}),
|
|
423
|
-
/** When was this version recorded */
|
|
424
|
-
recorded_at: Field2.datetime({
|
|
425
|
-
label: "Recorded At",
|
|
426
|
-
required: true,
|
|
427
|
-
readonly: true
|
|
428
|
-
})
|
|
429
|
-
},
|
|
430
|
-
indexes: [
|
|
431
|
-
{ fields: ["metadata_id", "version"], unique: true },
|
|
432
|
-
{ fields: ["metadata_id", "recorded_at"] },
|
|
433
|
-
{ fields: ["type", "name"] },
|
|
434
|
-
{ fields: ["recorded_at"] },
|
|
435
|
-
{ fields: ["operation_type"] },
|
|
436
|
-
{ fields: ["tenant_id"] }
|
|
437
|
-
],
|
|
438
|
-
enable: {
|
|
439
|
-
trackHistory: false,
|
|
440
|
-
// Don't track history of history records
|
|
441
|
-
searchable: false,
|
|
442
|
-
apiEnabled: true,
|
|
443
|
-
apiMethods: ["get", "list"],
|
|
444
|
-
// Read-only via API
|
|
445
|
-
trash: false
|
|
446
|
-
}
|
|
447
|
-
});
|
|
177
|
+
// src/loaders/database-loader.ts
|
|
178
|
+
import { SysMetadataObject, SysMetadataHistoryObject } from "@objectstack/platform-objects/metadata";
|
|
448
179
|
|
|
449
180
|
// src/utils/metadata-history-utils.ts
|
|
450
181
|
async function calculateChecksum(metadata) {
|
|
@@ -544,6 +275,308 @@ function generateDiffSummary(diff) {
|
|
|
544
275
|
return summary.join(", ");
|
|
545
276
|
}
|
|
546
277
|
|
|
278
|
+
// src/projection/metadata-projector.ts
|
|
279
|
+
import { StorageNameMapping } from "@objectstack/spec/system";
|
|
280
|
+
var MetadataProjector = class {
|
|
281
|
+
constructor(options) {
|
|
282
|
+
// Map of metadata types to their target table names
|
|
283
|
+
this.typeTableMap = {
|
|
284
|
+
object: "sys_object",
|
|
285
|
+
view: "sys_view",
|
|
286
|
+
agent: "sys_agent",
|
|
287
|
+
tool: "sys_tool",
|
|
288
|
+
flow: "sys_flow"
|
|
289
|
+
// Add more as needed: dashboard, app, action, workflow, etc.
|
|
290
|
+
};
|
|
291
|
+
if (!options.driver && !options.engine) {
|
|
292
|
+
throw new Error("MetadataProjector requires either a driver or engine");
|
|
293
|
+
}
|
|
294
|
+
this.driver = options.driver;
|
|
295
|
+
this.engine = options.engine;
|
|
296
|
+
this.scope = {
|
|
297
|
+
organizationId: options.organizationId,
|
|
298
|
+
projectId: options.projectId
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Project metadata to type-specific table
|
|
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
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Delete projection from type-specific table
|
|
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
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Transform metadata into projection record
|
|
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
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Project object metadata to sys_object
|
|
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
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Project view metadata to sys_view
|
|
422
|
+
*/
|
|
423
|
+
projectView(name, data, now) {
|
|
424
|
+
return {
|
|
425
|
+
name,
|
|
426
|
+
project_id: this.scope.projectId ?? null,
|
|
427
|
+
label: data.label || name,
|
|
428
|
+
description: data.description || "",
|
|
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
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Project agent metadata to sys_agent
|
|
454
|
+
*/
|
|
455
|
+
projectAgent(name, data, now) {
|
|
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
|
+
};
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Project tool metadata to sys_tool
|
|
489
|
+
*/
|
|
490
|
+
projectTool(name, data, now) {
|
|
491
|
+
return {
|
|
492
|
+
name,
|
|
493
|
+
project_id: this.scope.projectId ?? null,
|
|
494
|
+
label: data.label || name,
|
|
495
|
+
description: data.description || "",
|
|
496
|
+
// Parameters and implementation
|
|
497
|
+
parameters_json: data.parameters ? JSON.stringify(data.parameters) : null,
|
|
498
|
+
handler_code: data.handler || null,
|
|
499
|
+
// Classification
|
|
500
|
+
namespace: data.namespace || "default",
|
|
501
|
+
// Package management
|
|
502
|
+
package_id: data.packageId || null,
|
|
503
|
+
managed_by: data.managedBy || "user",
|
|
504
|
+
// Audit
|
|
505
|
+
created_by: data.createdBy || null,
|
|
506
|
+
created_at: data.createdAt || now,
|
|
507
|
+
updated_by: data.updatedBy || null,
|
|
508
|
+
updated_at: now
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Project flow metadata to sys_flow
|
|
513
|
+
*/
|
|
514
|
+
projectFlow(name, data, now) {
|
|
515
|
+
return {
|
|
516
|
+
name,
|
|
517
|
+
project_id: this.scope.projectId ?? null,
|
|
518
|
+
label: data.label || name,
|
|
519
|
+
description: data.description || "",
|
|
520
|
+
flow_type: data.type || "autolaunched",
|
|
521
|
+
// Flow definition
|
|
522
|
+
nodes_json: data.nodes ? JSON.stringify(data.nodes) : null,
|
|
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 });
|
|
560
|
+
}
|
|
561
|
+
return this.driver.update(table, id, data);
|
|
562
|
+
}
|
|
563
|
+
async _delete(table, id) {
|
|
564
|
+
if (this.engine) {
|
|
565
|
+
return this.engine.delete(table, { where: { id } });
|
|
566
|
+
}
|
|
567
|
+
return this.driver.delete(table, id);
|
|
568
|
+
}
|
|
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
|
+
};
|
|
579
|
+
|
|
547
580
|
// src/loaders/database-loader.ts
|
|
548
581
|
var DatabaseLoader = class {
|
|
549
582
|
constructor(options) {
|
|
@@ -559,11 +592,64 @@ var DatabaseLoader = class {
|
|
|
559
592
|
};
|
|
560
593
|
this.schemaReady = false;
|
|
561
594
|
this.historySchemaReady = false;
|
|
595
|
+
if (!options.driver && !options.engine) {
|
|
596
|
+
throw new Error("DatabaseLoader requires either a driver or engine");
|
|
597
|
+
}
|
|
562
598
|
this.driver = options.driver;
|
|
599
|
+
this.engine = options.engine;
|
|
563
600
|
this.tableName = options.tableName ?? "sys_metadata";
|
|
564
601
|
this.historyTableName = options.historyTableName ?? "sys_metadata_history";
|
|
565
|
-
this.
|
|
602
|
+
this.organizationId = options.organizationId;
|
|
603
|
+
this.projectId = options.projectId;
|
|
566
604
|
this.trackHistory = options.trackHistory !== false;
|
|
605
|
+
this.enableProjection = options.enableProjection !== false;
|
|
606
|
+
if (this.enableProjection) {
|
|
607
|
+
this.projector = new MetadataProjector({
|
|
608
|
+
driver: this.driver,
|
|
609
|
+
engine: this.engine,
|
|
610
|
+
organizationId: this.organizationId,
|
|
611
|
+
projectId: this.projectId
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
// ==========================================
|
|
616
|
+
// Internal CRUD helpers (driver vs engine)
|
|
617
|
+
// ==========================================
|
|
618
|
+
async _find(table, query) {
|
|
619
|
+
if (this.engine) {
|
|
620
|
+
return this.engine.find(table, query);
|
|
621
|
+
}
|
|
622
|
+
return this.driver.find(table, { object: table, ...query });
|
|
623
|
+
}
|
|
624
|
+
async _findOne(table, query) {
|
|
625
|
+
if (this.engine) {
|
|
626
|
+
return this.engine.findOne(table, query);
|
|
627
|
+
}
|
|
628
|
+
return this.driver.findOne(table, { object: table, ...query });
|
|
629
|
+
}
|
|
630
|
+
async _count(table, query) {
|
|
631
|
+
if (this.engine) {
|
|
632
|
+
return this.engine.count(table, query);
|
|
633
|
+
}
|
|
634
|
+
return this.driver.count(table, { object: table, ...query });
|
|
635
|
+
}
|
|
636
|
+
async _create(table, data) {
|
|
637
|
+
if (this.engine) {
|
|
638
|
+
return this.engine.insert(table, data);
|
|
639
|
+
}
|
|
640
|
+
return this.driver.create(table, data);
|
|
641
|
+
}
|
|
642
|
+
async _update(table, id, data) {
|
|
643
|
+
if (this.engine) {
|
|
644
|
+
return this.engine.update(table, { id, ...data });
|
|
645
|
+
}
|
|
646
|
+
return this.driver.update(table, id, data);
|
|
647
|
+
}
|
|
648
|
+
async _delete(table, id) {
|
|
649
|
+
if (this.engine) {
|
|
650
|
+
return this.engine.delete(table, { where: { id } });
|
|
651
|
+
}
|
|
652
|
+
return this.driver.delete(table, id);
|
|
567
653
|
}
|
|
568
654
|
/**
|
|
569
655
|
* Ensure the metadata table exists.
|
|
@@ -572,6 +658,10 @@ var DatabaseLoader = class {
|
|
|
572
658
|
*/
|
|
573
659
|
async ensureSchema() {
|
|
574
660
|
if (this.schemaReady) return;
|
|
661
|
+
if (this.engine) {
|
|
662
|
+
this.schemaReady = true;
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
575
665
|
try {
|
|
576
666
|
await this.driver.syncSchema(this.tableName, {
|
|
577
667
|
...SysMetadataObject,
|
|
@@ -588,6 +678,10 @@ var DatabaseLoader = class {
|
|
|
588
678
|
*/
|
|
589
679
|
async ensureHistorySchema() {
|
|
590
680
|
if (!this.trackHistory || this.historySchemaReady) return;
|
|
681
|
+
if (this.engine) {
|
|
682
|
+
this.historySchemaReady = true;
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
591
685
|
try {
|
|
592
686
|
await this.driver.syncSchema(this.historyTableName, {
|
|
593
687
|
...SysMetadataHistoryObject,
|
|
@@ -600,16 +694,18 @@ var DatabaseLoader = class {
|
|
|
600
694
|
}
|
|
601
695
|
/**
|
|
602
696
|
* Build base filter conditions for queries.
|
|
603
|
-
*
|
|
697
|
+
* Filters by organizationId when configured; project_id when projectId is set,
|
|
698
|
+
* or null (platform-global) when not set.
|
|
604
699
|
*/
|
|
605
700
|
baseFilter(type, name) {
|
|
606
701
|
const filter = { type };
|
|
607
702
|
if (name !== void 0) {
|
|
608
703
|
filter.name = name;
|
|
609
704
|
}
|
|
610
|
-
if (this.
|
|
611
|
-
filter.
|
|
705
|
+
if (this.organizationId) {
|
|
706
|
+
filter.organization_id = this.organizationId;
|
|
612
707
|
}
|
|
708
|
+
filter.project_id = this.projectId ?? null;
|
|
613
709
|
return filter;
|
|
614
710
|
}
|
|
615
711
|
/**
|
|
@@ -648,10 +744,11 @@ var DatabaseLoader = class {
|
|
|
648
744
|
changeNote,
|
|
649
745
|
recordedBy,
|
|
650
746
|
recordedAt: now,
|
|
651
|
-
...this.
|
|
747
|
+
...this.organizationId ? { organizationId: this.organizationId } : {},
|
|
748
|
+
...this.projectId !== void 0 ? { projectId: this.projectId } : {}
|
|
652
749
|
};
|
|
653
750
|
try {
|
|
654
|
-
await this.
|
|
751
|
+
await this._create(this.historyTableName, {
|
|
655
752
|
id: historyRecord.id,
|
|
656
753
|
metadata_id: historyRecord.metadataId,
|
|
657
754
|
name: historyRecord.name,
|
|
@@ -664,7 +761,8 @@ var DatabaseLoader = class {
|
|
|
664
761
|
change_note: historyRecord.changeNote,
|
|
665
762
|
recorded_by: historyRecord.recordedBy,
|
|
666
763
|
recorded_at: historyRecord.recordedAt,
|
|
667
|
-
...this.
|
|
764
|
+
...this.organizationId ? { organization_id: this.organizationId } : {},
|
|
765
|
+
...this.projectId !== void 0 ? { project_id: this.projectId } : {}
|
|
668
766
|
});
|
|
669
767
|
} catch (error) {
|
|
670
768
|
console.error(`Failed to create history record for ${type}/${name}:`, error);
|
|
@@ -696,7 +794,8 @@ var DatabaseLoader = class {
|
|
|
696
794
|
strategy: row.strategy ?? "merge",
|
|
697
795
|
owner: row.owner,
|
|
698
796
|
state: row.state ?? "active",
|
|
699
|
-
|
|
797
|
+
organizationId: row.organization_id,
|
|
798
|
+
projectId: row.project_id,
|
|
700
799
|
version: row.version ?? 1,
|
|
701
800
|
checksum: row.checksum,
|
|
702
801
|
source: row.source,
|
|
@@ -714,8 +813,7 @@ var DatabaseLoader = class {
|
|
|
714
813
|
const startTime = Date.now();
|
|
715
814
|
await this.ensureSchema();
|
|
716
815
|
try {
|
|
717
|
-
const row = await this.
|
|
718
|
-
object: this.tableName,
|
|
816
|
+
const row = await this._findOne(this.tableName, {
|
|
719
817
|
where: this.baseFilter(type, name)
|
|
720
818
|
});
|
|
721
819
|
if (!row) {
|
|
@@ -743,8 +841,7 @@ var DatabaseLoader = class {
|
|
|
743
841
|
async loadMany(type, _options) {
|
|
744
842
|
await this.ensureSchema();
|
|
745
843
|
try {
|
|
746
|
-
const rows = await this.
|
|
747
|
-
object: this.tableName,
|
|
844
|
+
const rows = await this._find(this.tableName, {
|
|
748
845
|
where: this.baseFilter(type)
|
|
749
846
|
});
|
|
750
847
|
return rows.map((row) => this.rowToData(row)).filter((data) => data !== null);
|
|
@@ -755,8 +852,7 @@ var DatabaseLoader = class {
|
|
|
755
852
|
async exists(type, name) {
|
|
756
853
|
await this.ensureSchema();
|
|
757
854
|
try {
|
|
758
|
-
const count = await this.
|
|
759
|
-
object: this.tableName,
|
|
855
|
+
const count = await this._count(this.tableName, {
|
|
760
856
|
where: this.baseFilter(type, name)
|
|
761
857
|
});
|
|
762
858
|
return count > 0;
|
|
@@ -767,8 +863,7 @@ var DatabaseLoader = class {
|
|
|
767
863
|
async stat(type, name) {
|
|
768
864
|
await this.ensureSchema();
|
|
769
865
|
try {
|
|
770
|
-
const row = await this.
|
|
771
|
-
object: this.tableName,
|
|
866
|
+
const row = await this._findOne(this.tableName, {
|
|
772
867
|
where: this.baseFilter(type, name)
|
|
773
868
|
});
|
|
774
869
|
if (!row) return null;
|
|
@@ -787,8 +882,7 @@ var DatabaseLoader = class {
|
|
|
787
882
|
async list(type) {
|
|
788
883
|
await this.ensureSchema();
|
|
789
884
|
try {
|
|
790
|
-
const rows = await this.
|
|
791
|
-
object: this.tableName,
|
|
885
|
+
const rows = await this._find(this.tableName, {
|
|
792
886
|
where: this.baseFilter(type),
|
|
793
887
|
fields: ["name"]
|
|
794
888
|
});
|
|
@@ -804,8 +898,7 @@ var DatabaseLoader = class {
|
|
|
804
898
|
async getHistoryRecord(type, name, version) {
|
|
805
899
|
if (!this.trackHistory) return null;
|
|
806
900
|
await this.ensureHistorySchema();
|
|
807
|
-
const metadataRow = await this.
|
|
808
|
-
object: this.tableName,
|
|
901
|
+
const metadataRow = await this._findOne(this.tableName, {
|
|
809
902
|
where: this.baseFilter(type, name)
|
|
810
903
|
});
|
|
811
904
|
if (!metadataRow) return null;
|
|
@@ -813,11 +906,11 @@ var DatabaseLoader = class {
|
|
|
813
906
|
metadata_id: metadataRow.id,
|
|
814
907
|
version
|
|
815
908
|
};
|
|
816
|
-
if (this.
|
|
817
|
-
filter.
|
|
909
|
+
if (this.organizationId) {
|
|
910
|
+
filter.organization_id = this.organizationId;
|
|
818
911
|
}
|
|
819
|
-
|
|
820
|
-
|
|
912
|
+
filter.project_id = this.projectId ?? null;
|
|
913
|
+
const row = await this._findOne(this.historyTableName, {
|
|
821
914
|
where: filter
|
|
822
915
|
});
|
|
823
916
|
if (!row) return null;
|
|
@@ -832,11 +925,80 @@ var DatabaseLoader = class {
|
|
|
832
925
|
checksum: row.checksum,
|
|
833
926
|
previousChecksum: row.previous_checksum,
|
|
834
927
|
changeNote: row.change_note,
|
|
835
|
-
|
|
928
|
+
organizationId: row.organization_id,
|
|
929
|
+
projectId: row.project_id,
|
|
836
930
|
recordedBy: row.recorded_by,
|
|
837
931
|
recordedAt: row.recorded_at
|
|
838
932
|
};
|
|
839
933
|
}
|
|
934
|
+
/**
|
|
935
|
+
* Query history records with pagination and filtering.
|
|
936
|
+
* Encapsulates history table queries so MetadataManager doesn't need
|
|
937
|
+
* direct driver access.
|
|
938
|
+
*/
|
|
939
|
+
async queryHistory(type, name, options) {
|
|
940
|
+
if (!this.trackHistory) {
|
|
941
|
+
return { records: [], total: 0, hasMore: false };
|
|
942
|
+
}
|
|
943
|
+
await this.ensureSchema();
|
|
944
|
+
await this.ensureHistorySchema();
|
|
945
|
+
const filter = { type, name };
|
|
946
|
+
if (this.organizationId) filter.organization_id = this.organizationId;
|
|
947
|
+
filter.project_id = this.projectId ?? null;
|
|
948
|
+
const metadataRecord = await this._findOne(this.tableName, { where: filter });
|
|
949
|
+
if (!metadataRecord) {
|
|
950
|
+
return { records: [], total: 0, hasMore: false };
|
|
951
|
+
}
|
|
952
|
+
const historyFilter = {
|
|
953
|
+
metadata_id: metadataRecord.id
|
|
954
|
+
};
|
|
955
|
+
if (this.organizationId) historyFilter.organization_id = this.organizationId;
|
|
956
|
+
historyFilter.project_id = this.projectId ?? null;
|
|
957
|
+
if (options?.operationType) historyFilter.operation_type = options.operationType;
|
|
958
|
+
if (options?.since) historyFilter.recorded_at = { $gte: options.since };
|
|
959
|
+
if (options?.until) {
|
|
960
|
+
if (historyFilter.recorded_at) {
|
|
961
|
+
historyFilter.recorded_at.$lte = options.until;
|
|
962
|
+
} else {
|
|
963
|
+
historyFilter.recorded_at = { $lte: options.until };
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
const limit = options?.limit ?? 50;
|
|
967
|
+
const offset = options?.offset ?? 0;
|
|
968
|
+
const historyRecords = await this._find(this.historyTableName, {
|
|
969
|
+
where: historyFilter,
|
|
970
|
+
orderBy: [
|
|
971
|
+
{ field: "recorded_at", order: "desc" },
|
|
972
|
+
{ field: "version", order: "desc" }
|
|
973
|
+
],
|
|
974
|
+
limit: limit + 1,
|
|
975
|
+
offset
|
|
976
|
+
});
|
|
977
|
+
const hasMore = historyRecords.length > limit;
|
|
978
|
+
const records = historyRecords.slice(0, limit);
|
|
979
|
+
const total = await this._count(this.historyTableName, { where: historyFilter });
|
|
980
|
+
const includeMetadata = options?.includeMetadata !== false;
|
|
981
|
+
const result = records.map((row) => {
|
|
982
|
+
const parsedMetadata = typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata;
|
|
983
|
+
return {
|
|
984
|
+
id: row.id,
|
|
985
|
+
metadataId: row.metadata_id,
|
|
986
|
+
name: row.name,
|
|
987
|
+
type: row.type,
|
|
988
|
+
version: row.version,
|
|
989
|
+
operationType: row.operation_type,
|
|
990
|
+
metadata: includeMetadata ? parsedMetadata : null,
|
|
991
|
+
checksum: row.checksum,
|
|
992
|
+
previousChecksum: row.previous_checksum,
|
|
993
|
+
changeNote: row.change_note,
|
|
994
|
+
organizationId: row.organization_id,
|
|
995
|
+
projectId: row.project_id,
|
|
996
|
+
recordedBy: row.recorded_by,
|
|
997
|
+
recordedAt: row.recorded_at
|
|
998
|
+
};
|
|
999
|
+
});
|
|
1000
|
+
return { records: result, total, hasMore };
|
|
1001
|
+
}
|
|
840
1002
|
/**
|
|
841
1003
|
* Perform a rollback: persist `restoredData` as the new current state and record a
|
|
842
1004
|
* single 'revert' history entry (instead of the usual 'update' entry that `save()`
|
|
@@ -849,8 +1011,7 @@ var DatabaseLoader = class {
|
|
|
849
1011
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
850
1012
|
const metadataJson = JSON.stringify(restoredData);
|
|
851
1013
|
const newChecksum = await calculateChecksum(restoredData);
|
|
852
|
-
const existing = await this.
|
|
853
|
-
object: this.tableName,
|
|
1014
|
+
const existing = await this._findOne(this.tableName, {
|
|
854
1015
|
where: this.baseFilter(type, name)
|
|
855
1016
|
});
|
|
856
1017
|
if (!existing) {
|
|
@@ -858,7 +1019,7 @@ var DatabaseLoader = class {
|
|
|
858
1019
|
}
|
|
859
1020
|
const previousChecksum = existing.checksum;
|
|
860
1021
|
const newVersion = (existing.version ?? 0) + 1;
|
|
861
|
-
await this.
|
|
1022
|
+
await this._update(this.tableName, existing.id, {
|
|
862
1023
|
metadata: metadataJson,
|
|
863
1024
|
version: newVersion,
|
|
864
1025
|
checksum: newChecksum,
|
|
@@ -884,8 +1045,7 @@ var DatabaseLoader = class {
|
|
|
884
1045
|
const metadataJson = JSON.stringify(data);
|
|
885
1046
|
const newChecksum = await calculateChecksum(data);
|
|
886
1047
|
try {
|
|
887
|
-
const existing = await this.
|
|
888
|
-
object: this.tableName,
|
|
1048
|
+
const existing = await this._findOne(this.tableName, {
|
|
889
1049
|
where: this.baseFilter(type, name)
|
|
890
1050
|
});
|
|
891
1051
|
if (existing) {
|
|
@@ -899,7 +1059,7 @@ var DatabaseLoader = class {
|
|
|
899
1059
|
};
|
|
900
1060
|
}
|
|
901
1061
|
const version = (existing.version ?? 0) + 1;
|
|
902
|
-
await this.
|
|
1062
|
+
await this._update(this.tableName, existing.id, {
|
|
903
1063
|
metadata: metadataJson,
|
|
904
1064
|
version,
|
|
905
1065
|
checksum: newChecksum,
|
|
@@ -915,6 +1075,9 @@ var DatabaseLoader = class {
|
|
|
915
1075
|
"update",
|
|
916
1076
|
previousChecksum
|
|
917
1077
|
);
|
|
1078
|
+
if (this.projector) {
|
|
1079
|
+
await this.projector.project(type, name, data);
|
|
1080
|
+
}
|
|
918
1081
|
return {
|
|
919
1082
|
success: true,
|
|
920
1083
|
path: `datasource://${this.tableName}/${type}/${name}`,
|
|
@@ -923,7 +1086,7 @@ var DatabaseLoader = class {
|
|
|
923
1086
|
};
|
|
924
1087
|
} else {
|
|
925
1088
|
const id = generateId();
|
|
926
|
-
await this.
|
|
1089
|
+
await this._create(this.tableName, {
|
|
927
1090
|
id,
|
|
928
1091
|
name,
|
|
929
1092
|
type,
|
|
@@ -935,7 +1098,8 @@ var DatabaseLoader = class {
|
|
|
935
1098
|
state: "active",
|
|
936
1099
|
version: 1,
|
|
937
1100
|
source: "database",
|
|
938
|
-
...this.
|
|
1101
|
+
...this.organizationId ? { organization_id: this.organizationId } : {},
|
|
1102
|
+
...this.projectId !== void 0 ? { project_id: this.projectId } : { project_id: null },
|
|
939
1103
|
created_at: now,
|
|
940
1104
|
updated_at: now
|
|
941
1105
|
});
|
|
@@ -947,6 +1111,9 @@ var DatabaseLoader = class {
|
|
|
947
1111
|
data,
|
|
948
1112
|
"create"
|
|
949
1113
|
);
|
|
1114
|
+
if (this.projector) {
|
|
1115
|
+
await this.projector.project(type, name, data);
|
|
1116
|
+
}
|
|
950
1117
|
return {
|
|
951
1118
|
success: true,
|
|
952
1119
|
path: `datasource://${this.tableName}/${type}/${name}`,
|
|
@@ -965,14 +1132,16 @@ var DatabaseLoader = class {
|
|
|
965
1132
|
*/
|
|
966
1133
|
async delete(type, name) {
|
|
967
1134
|
await this.ensureSchema();
|
|
968
|
-
const existing = await this.
|
|
969
|
-
object: this.tableName,
|
|
1135
|
+
const existing = await this._findOne(this.tableName, {
|
|
970
1136
|
where: this.baseFilter(type, name)
|
|
971
1137
|
});
|
|
972
1138
|
if (!existing) {
|
|
973
1139
|
return;
|
|
974
1140
|
}
|
|
975
|
-
await this.
|
|
1141
|
+
await this._delete(this.tableName, existing.id);
|
|
1142
|
+
if (this.projector) {
|
|
1143
|
+
await this.projector.deleteProjection(type, name);
|
|
1144
|
+
}
|
|
976
1145
|
}
|
|
977
1146
|
};
|
|
978
1147
|
function generateId() {
|
|
@@ -1029,16 +1198,55 @@ var MetadataManager = class {
|
|
|
1029
1198
|
* Can be called at any time to enable database storage (e.g. after kernel resolves the driver).
|
|
1030
1199
|
*
|
|
1031
1200
|
* @param driver - An IDataDriver instance for database operations
|
|
1032
|
-
|
|
1033
|
-
|
|
1201
|
+
* @param organizationId - Organization ID for multi-tenant isolation
|
|
1202
|
+
* @param projectId - Project ID (undefined = platform-global)
|
|
1203
|
+
*/
|
|
1204
|
+
setDatabaseDriver(driver, organizationId, projectId) {
|
|
1205
|
+
if (projectId !== void 0) {
|
|
1206
|
+
this.logger.info("Project kernel \u2014 skipping DatabaseLoader for sys_metadata (control-plane only)", {
|
|
1207
|
+
organizationId,
|
|
1208
|
+
projectId
|
|
1209
|
+
});
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1034
1212
|
const tableName = this.config.tableName ?? "sys_metadata";
|
|
1035
1213
|
const dbLoader = new DatabaseLoader({
|
|
1036
1214
|
driver,
|
|
1037
|
-
tableName
|
|
1215
|
+
tableName,
|
|
1216
|
+
organizationId,
|
|
1217
|
+
projectId
|
|
1038
1218
|
});
|
|
1039
1219
|
this.registerLoader(dbLoader);
|
|
1040
1220
|
this.logger.info("DatabaseLoader configured", { datasource: this.config.datasource, tableName });
|
|
1041
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Configure and register a DatabaseLoader backed by an IDataEngine (ObjectQL).
|
|
1224
|
+
* The engine handles datasource routing automatically — sys_metadata will
|
|
1225
|
+
* be routed to the correct driver via the standard namespace mapping.
|
|
1226
|
+
* No manual driver resolution needed.
|
|
1227
|
+
*
|
|
1228
|
+
* @param engine - An IDataEngine instance (typically the ObjectQL service)
|
|
1229
|
+
* @param organizationId - Organization ID for multi-tenant isolation
|
|
1230
|
+
* @param projectId - Project ID (undefined = platform-global)
|
|
1231
|
+
*/
|
|
1232
|
+
setDataEngine(engine, organizationId, projectId) {
|
|
1233
|
+
if (projectId !== void 0) {
|
|
1234
|
+
this.logger.info("Project kernel \u2014 skipping DatabaseLoader for sys_metadata (control-plane only)", {
|
|
1235
|
+
organizationId,
|
|
1236
|
+
projectId
|
|
1237
|
+
});
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const tableName = this.config.tableName ?? "sys_metadata";
|
|
1241
|
+
const dbLoader = new DatabaseLoader({
|
|
1242
|
+
engine,
|
|
1243
|
+
tableName,
|
|
1244
|
+
organizationId,
|
|
1245
|
+
projectId
|
|
1246
|
+
});
|
|
1247
|
+
this.registerLoader(dbLoader);
|
|
1248
|
+
this.logger.info("DatabaseLoader configured via DataEngine", { tableName });
|
|
1249
|
+
}
|
|
1042
1250
|
/**
|
|
1043
1251
|
* Set the realtime service for publishing metadata change events.
|
|
1044
1252
|
* Should be called after kernel resolves the realtime service.
|
|
@@ -1952,84 +2160,14 @@ var MetadataManager = class {
|
|
|
1952
2160
|
if (!dbLoader) {
|
|
1953
2161
|
throw new Error("History tracking requires a database loader to be configured");
|
|
1954
2162
|
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
}
|
|
1963
|
-
const metadataRecord = await driver.findOne(tableName, {
|
|
1964
|
-
object: tableName,
|
|
1965
|
-
where: filter
|
|
1966
|
-
});
|
|
1967
|
-
if (!metadataRecord) {
|
|
1968
|
-
return {
|
|
1969
|
-
records: [],
|
|
1970
|
-
total: 0,
|
|
1971
|
-
hasMore: false
|
|
1972
|
-
};
|
|
1973
|
-
}
|
|
1974
|
-
const historyFilter = {
|
|
1975
|
-
metadata_id: metadataRecord.id
|
|
1976
|
-
};
|
|
1977
|
-
if (tenantId) {
|
|
1978
|
-
historyFilter.tenant_id = tenantId;
|
|
1979
|
-
}
|
|
1980
|
-
if (options?.operationType) {
|
|
1981
|
-
historyFilter.operation_type = options.operationType;
|
|
1982
|
-
}
|
|
1983
|
-
if (options?.since) {
|
|
1984
|
-
historyFilter.recorded_at = { $gte: options.since };
|
|
1985
|
-
}
|
|
1986
|
-
if (options?.until) {
|
|
1987
|
-
if (historyFilter.recorded_at) {
|
|
1988
|
-
historyFilter.recorded_at.$lte = options.until;
|
|
1989
|
-
} else {
|
|
1990
|
-
historyFilter.recorded_at = { $lte: options.until };
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
const limit = options?.limit ?? 50;
|
|
1994
|
-
const offset = options?.offset ?? 0;
|
|
1995
|
-
const historyRecords = await driver.find(historyTableName, {
|
|
1996
|
-
object: historyTableName,
|
|
1997
|
-
where: historyFilter,
|
|
1998
|
-
orderBy: [{ field: "recorded_at", order: "desc" }],
|
|
1999
|
-
limit: limit + 1,
|
|
2000
|
-
// Fetch one extra to determine hasMore
|
|
2001
|
-
offset
|
|
2002
|
-
});
|
|
2003
|
-
const hasMore = historyRecords.length > limit;
|
|
2004
|
-
const records = historyRecords.slice(0, limit);
|
|
2005
|
-
const total = await driver.count(historyTableName, {
|
|
2006
|
-
object: historyTableName,
|
|
2007
|
-
where: historyFilter
|
|
2008
|
-
});
|
|
2009
|
-
const includeMetadata = options?.includeMetadata !== false;
|
|
2010
|
-
const historyResult = records.map((row) => {
|
|
2011
|
-
const parsedMetadata = typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata;
|
|
2012
|
-
return {
|
|
2013
|
-
id: row.id,
|
|
2014
|
-
metadataId: row.metadata_id,
|
|
2015
|
-
name: row.name,
|
|
2016
|
-
type: row.type,
|
|
2017
|
-
version: row.version,
|
|
2018
|
-
operationType: row.operation_type,
|
|
2019
|
-
metadata: includeMetadata ? parsedMetadata : null,
|
|
2020
|
-
checksum: row.checksum,
|
|
2021
|
-
previousChecksum: row.previous_checksum,
|
|
2022
|
-
changeNote: row.change_note,
|
|
2023
|
-
tenantId: row.tenant_id,
|
|
2024
|
-
recordedBy: row.recorded_by,
|
|
2025
|
-
recordedAt: row.recorded_at
|
|
2026
|
-
};
|
|
2163
|
+
return dbLoader.queryHistory(type, name, {
|
|
2164
|
+
operationType: options?.operationType,
|
|
2165
|
+
since: options?.since,
|
|
2166
|
+
until: options?.until,
|
|
2167
|
+
limit: options?.limit,
|
|
2168
|
+
offset: options?.offset,
|
|
2169
|
+
includeMetadata: options?.includeMetadata
|
|
2027
2170
|
});
|
|
2028
|
-
return {
|
|
2029
|
-
records: historyResult,
|
|
2030
|
-
total,
|
|
2031
|
-
hasMore
|
|
2032
|
-
};
|
|
2033
2171
|
}
|
|
2034
2172
|
/**
|
|
2035
2173
|
* Rollback a metadata item to a specific version.
|
|
@@ -2099,6 +2237,10 @@ var MetadataManager = class {
|
|
|
2099
2237
|
}
|
|
2100
2238
|
};
|
|
2101
2239
|
|
|
2240
|
+
// src/plugin.ts
|
|
2241
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
2242
|
+
import { createHash as createHash2 } from "crypto";
|
|
2243
|
+
|
|
2102
2244
|
// src/node-metadata-manager.ts
|
|
2103
2245
|
import * as path2 from "path";
|
|
2104
2246
|
import { watch as chokidarWatch } from "chokidar";
|
|
@@ -2215,7 +2357,7 @@ var FilesystemLoader = class {
|
|
|
2215
2357
|
);
|
|
2216
2358
|
for (const pattern of globPatterns) {
|
|
2217
2359
|
const files = await glob(pattern, {
|
|
2218
|
-
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*"],
|
|
2360
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/*[*]*"],
|
|
2219
2361
|
nodir: true
|
|
2220
2362
|
});
|
|
2221
2363
|
for (const file of files) {
|
|
@@ -2504,124 +2646,6 @@ var NodeMetadataManager = class extends MetadataManager {
|
|
|
2504
2646
|
}
|
|
2505
2647
|
};
|
|
2506
2648
|
|
|
2507
|
-
// src/plugin.ts
|
|
2508
|
-
import { DEFAULT_METADATA_TYPE_REGISTRY } from "@objectstack/spec/kernel";
|
|
2509
|
-
var MetadataPlugin = class {
|
|
2510
|
-
constructor(options = {}) {
|
|
2511
|
-
this.name = "com.objectstack.metadata";
|
|
2512
|
-
this.type = "standard";
|
|
2513
|
-
this.version = "1.0.0";
|
|
2514
|
-
this.init = async (ctx) => {
|
|
2515
|
-
ctx.logger.info("Initializing Metadata Manager", {
|
|
2516
|
-
root: this.options.rootDir || process.cwd(),
|
|
2517
|
-
watch: this.options.watch
|
|
2518
|
-
});
|
|
2519
|
-
ctx.registerService("metadata", this.manager);
|
|
2520
|
-
console.log("[MetadataPlugin] Registered metadata service, has getRegisteredTypes:", typeof this.manager.getRegisteredTypes);
|
|
2521
|
-
try {
|
|
2522
|
-
ctx.getService("manifest").register({
|
|
2523
|
-
id: "com.objectstack.metadata",
|
|
2524
|
-
name: "Metadata",
|
|
2525
|
-
version: "1.0.0",
|
|
2526
|
-
type: "plugin",
|
|
2527
|
-
namespace: "sys",
|
|
2528
|
-
objects: [SysMetadataObject]
|
|
2529
|
-
});
|
|
2530
|
-
} catch {
|
|
2531
|
-
}
|
|
2532
|
-
ctx.logger.info("MetadataPlugin providing metadata service (primary mode)", {
|
|
2533
|
-
mode: "file-system",
|
|
2534
|
-
features: ["watch", "persistence", "multi-format", "query", "overlay", "type-registry"]
|
|
2535
|
-
});
|
|
2536
|
-
};
|
|
2537
|
-
this.start = async (ctx) => {
|
|
2538
|
-
ctx.logger.info("Loading metadata from file system...");
|
|
2539
|
-
const sortedTypes = [...DEFAULT_METADATA_TYPE_REGISTRY].sort((a, b) => a.loadOrder - b.loadOrder);
|
|
2540
|
-
let totalLoaded = 0;
|
|
2541
|
-
for (const entry of sortedTypes) {
|
|
2542
|
-
try {
|
|
2543
|
-
const items = await this.manager.loadMany(entry.type, {
|
|
2544
|
-
recursive: true
|
|
2545
|
-
});
|
|
2546
|
-
if (items.length > 0) {
|
|
2547
|
-
for (const item of items) {
|
|
2548
|
-
const meta = item;
|
|
2549
|
-
if (meta?.name) {
|
|
2550
|
-
await this.manager.register(entry.type, meta.name, item);
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
ctx.logger.info(`Loaded ${items.length} ${entry.type} from file system`);
|
|
2554
|
-
totalLoaded += items.length;
|
|
2555
|
-
}
|
|
2556
|
-
} catch (e) {
|
|
2557
|
-
ctx.logger.debug(`No ${entry.type} metadata found`, { error: e.message });
|
|
2558
|
-
}
|
|
2559
|
-
}
|
|
2560
|
-
ctx.logger.info("Metadata loading complete", {
|
|
2561
|
-
totalItems: totalLoaded,
|
|
2562
|
-
registeredTypes: sortedTypes.length
|
|
2563
|
-
});
|
|
2564
|
-
let driverBridged = false;
|
|
2565
|
-
try {
|
|
2566
|
-
const ql = ctx.getService("objectql");
|
|
2567
|
-
if (ql) {
|
|
2568
|
-
const tableName = this.manager["config"]?.tableName ?? "sys_metadata";
|
|
2569
|
-
const driver = ql.getDriverForObject?.(tableName);
|
|
2570
|
-
if (driver) {
|
|
2571
|
-
ctx.logger.info("[MetadataPlugin] Bridging driver to MetadataManager via ObjectQL routing", {
|
|
2572
|
-
tableName,
|
|
2573
|
-
driver: driver.name
|
|
2574
|
-
});
|
|
2575
|
-
this.manager.setDatabaseDriver(driver);
|
|
2576
|
-
driverBridged = true;
|
|
2577
|
-
} else {
|
|
2578
|
-
ctx.logger.debug("[MetadataPlugin] ObjectQL could not resolve driver for metadata table", { tableName });
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
} catch {
|
|
2582
|
-
}
|
|
2583
|
-
if (!driverBridged) {
|
|
2584
|
-
try {
|
|
2585
|
-
const services = ctx.getServices();
|
|
2586
|
-
for (const [serviceName, service] of services) {
|
|
2587
|
-
if (serviceName.startsWith("driver.") && service) {
|
|
2588
|
-
ctx.logger.info("[MetadataPlugin] Bridging driver to MetadataManager (fallback: first driver)", {
|
|
2589
|
-
driverService: serviceName
|
|
2590
|
-
});
|
|
2591
|
-
this.manager.setDatabaseDriver(service);
|
|
2592
|
-
break;
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
} catch (e) {
|
|
2596
|
-
ctx.logger.debug("[MetadataPlugin] No driver service found", { error: e.message });
|
|
2597
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
try {
|
|
2600
|
-
const realtimeService = ctx.getService("realtime");
|
|
2601
|
-
if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
|
|
2602
|
-
ctx.logger.info("[MetadataPlugin] Bridging realtime service to MetadataManager for event publishing");
|
|
2603
|
-
this.manager.setRealtimeService(realtimeService);
|
|
2604
|
-
}
|
|
2605
|
-
} catch (e) {
|
|
2606
|
-
ctx.logger.debug("[MetadataPlugin] No realtime service found \u2014 metadata events will not be published", {
|
|
2607
|
-
error: e.message
|
|
2608
|
-
});
|
|
2609
|
-
}
|
|
2610
|
-
};
|
|
2611
|
-
this.options = {
|
|
2612
|
-
watch: true,
|
|
2613
|
-
...options
|
|
2614
|
-
};
|
|
2615
|
-
const rootDir = this.options.rootDir || process.cwd();
|
|
2616
|
-
this.manager = new NodeMetadataManager({
|
|
2617
|
-
rootDir,
|
|
2618
|
-
watch: this.options.watch ?? true,
|
|
2619
|
-
formats: ["yaml", "json", "typescript", "javascript"]
|
|
2620
|
-
});
|
|
2621
|
-
this.manager.setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY);
|
|
2622
|
-
}
|
|
2623
|
-
};
|
|
2624
|
-
|
|
2625
2649
|
// src/loaders/memory-loader.ts
|
|
2626
2650
|
var MemoryLoader = class {
|
|
2627
2651
|
constructor() {
|
|
@@ -2700,6 +2724,222 @@ var MemoryLoader = class {
|
|
|
2700
2724
|
}
|
|
2701
2725
|
};
|
|
2702
2726
|
|
|
2727
|
+
// src/plugin.ts
|
|
2728
|
+
import { DEFAULT_METADATA_TYPE_REGISTRY } from "@objectstack/spec/kernel";
|
|
2729
|
+
import {
|
|
2730
|
+
SysAgent,
|
|
2731
|
+
SysFlow,
|
|
2732
|
+
SysObject,
|
|
2733
|
+
SysTool,
|
|
2734
|
+
SysView
|
|
2735
|
+
} from "@objectstack/platform-objects/metadata";
|
|
2736
|
+
var queryableMetadataObjects = [SysObject, SysView, SysFlow, SysAgent, SysTool];
|
|
2737
|
+
var ARTIFACT_FIELD_TO_TYPE = {
|
|
2738
|
+
objects: "object",
|
|
2739
|
+
objectExtensions: "object_extension",
|
|
2740
|
+
apps: "app",
|
|
2741
|
+
views: "view",
|
|
2742
|
+
pages: "page",
|
|
2743
|
+
dashboards: "dashboard",
|
|
2744
|
+
reports: "report",
|
|
2745
|
+
actions: "action",
|
|
2746
|
+
themes: "theme",
|
|
2747
|
+
workflows: "workflow",
|
|
2748
|
+
approvals: "approval",
|
|
2749
|
+
flows: "flow",
|
|
2750
|
+
roles: "role",
|
|
2751
|
+
permissions: "permission",
|
|
2752
|
+
sharingRules: "sharing_rule",
|
|
2753
|
+
policies: "policy",
|
|
2754
|
+
apis: "api",
|
|
2755
|
+
webhooks: "webhook",
|
|
2756
|
+
agents: "agent",
|
|
2757
|
+
skills: "skill",
|
|
2758
|
+
ragPipelines: "rag_pipeline",
|
|
2759
|
+
hooks: "hook",
|
|
2760
|
+
mappings: "mapping",
|
|
2761
|
+
analyticsCubes: "analytics_cube",
|
|
2762
|
+
connectors: "connector",
|
|
2763
|
+
data: "dataset"
|
|
2764
|
+
};
|
|
2765
|
+
var MetadataPlugin = class {
|
|
2766
|
+
constructor(options = {}) {
|
|
2767
|
+
this.name = "com.objectstack.metadata";
|
|
2768
|
+
this.type = "standard";
|
|
2769
|
+
this.version = "1.0.0";
|
|
2770
|
+
this.init = async (ctx) => {
|
|
2771
|
+
ctx.logger.info("Initializing Metadata Manager", {
|
|
2772
|
+
root: this.options.rootDir || process.cwd(),
|
|
2773
|
+
watch: this.options.watch,
|
|
2774
|
+
artifactSource: this.options.artifactSource?.mode
|
|
2775
|
+
});
|
|
2776
|
+
ctx.registerService("metadata", this.manager);
|
|
2777
|
+
console.log("[MetadataPlugin] Registered metadata service, has getRegisteredTypes:", typeof this.manager.getRegisteredTypes);
|
|
2778
|
+
const registerSysObjects = this.options.registerSystemObjects !== false;
|
|
2779
|
+
if (registerSysObjects) {
|
|
2780
|
+
try {
|
|
2781
|
+
const manifestService = ctx.getService("manifest");
|
|
2782
|
+
manifestService.register({
|
|
2783
|
+
id: "com.objectstack.metadata-objects",
|
|
2784
|
+
name: "Metadata Platform Objects",
|
|
2785
|
+
version: "1.0.0",
|
|
2786
|
+
type: "plugin",
|
|
2787
|
+
scope: "system",
|
|
2788
|
+
defaultDatasource: "cloud",
|
|
2789
|
+
objects: queryableMetadataObjects
|
|
2790
|
+
});
|
|
2791
|
+
ctx.logger.info("Registered system metadata objects", {
|
|
2792
|
+
queryable: queryableMetadataObjects.map((object) => object.name)
|
|
2793
|
+
});
|
|
2794
|
+
} catch {
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
ctx.logger.info("MetadataPlugin providing metadata service (primary mode)", {
|
|
2798
|
+
mode: this.options.artifactSource?.mode ?? "file-system",
|
|
2799
|
+
features: ["watch", "multi-format", "query", "overlay", "type-registry"]
|
|
2800
|
+
});
|
|
2801
|
+
};
|
|
2802
|
+
this.start = async (ctx) => {
|
|
2803
|
+
const src = this.options.artifactSource;
|
|
2804
|
+
if (src?.mode === "local-file") {
|
|
2805
|
+
await this._loadFromLocalFile(ctx, src.path);
|
|
2806
|
+
} else if (src?.mode === "artifact-api") {
|
|
2807
|
+
ctx.logger.warn("[MetadataPlugin] artifact-api source is not yet implemented; falling back to file-system scan");
|
|
2808
|
+
await this._loadFromFileSystem(ctx);
|
|
2809
|
+
} else {
|
|
2810
|
+
await this._loadFromFileSystem(ctx);
|
|
2811
|
+
}
|
|
2812
|
+
try {
|
|
2813
|
+
const realtimeService = ctx.getService("realtime");
|
|
2814
|
+
if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
|
|
2815
|
+
ctx.logger.info("[MetadataPlugin] Bridging realtime service to MetadataManager for event publishing");
|
|
2816
|
+
this.manager.setRealtimeService(realtimeService);
|
|
2817
|
+
}
|
|
2818
|
+
} catch (e) {
|
|
2819
|
+
ctx.logger.debug("[MetadataPlugin] No realtime service found \u2014 metadata events will not be published", {
|
|
2820
|
+
error: e.message
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
this.options = {
|
|
2825
|
+
watch: true,
|
|
2826
|
+
...options
|
|
2827
|
+
};
|
|
2828
|
+
const rootDir = this.options.rootDir || process.cwd();
|
|
2829
|
+
this.manager = new NodeMetadataManager({
|
|
2830
|
+
rootDir,
|
|
2831
|
+
watch: this.options.watch ?? true,
|
|
2832
|
+
formats: ["yaml", "json", "typescript", "javascript"]
|
|
2833
|
+
});
|
|
2834
|
+
this.manager.setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY);
|
|
2835
|
+
}
|
|
2836
|
+
async _loadFromLocalFile(ctx, filePath) {
|
|
2837
|
+
const isUrl = /^https?:\/\//i.test(filePath);
|
|
2838
|
+
ctx.logger.info(
|
|
2839
|
+
`[MetadataPlugin] Loading metadata from ${isUrl ? "remote URL" : "local artifact file"}`,
|
|
2840
|
+
{ path: filePath }
|
|
2841
|
+
);
|
|
2842
|
+
let raw;
|
|
2843
|
+
try {
|
|
2844
|
+
let content;
|
|
2845
|
+
if (isUrl) {
|
|
2846
|
+
const controller = new AbortController();
|
|
2847
|
+
const timer = setTimeout(() => controller.abort(), 15e3);
|
|
2848
|
+
try {
|
|
2849
|
+
const res = await fetch(filePath, {
|
|
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);
|
|
2865
|
+
} catch (e) {
|
|
2866
|
+
throw new Error(`[MetadataPlugin] Cannot read artifact ${isUrl ? "URL" : "file"} at "${filePath}": ${e.message}`);
|
|
2867
|
+
}
|
|
2868
|
+
const { ProjectArtifactSchema } = await import("@objectstack/spec/cloud");
|
|
2869
|
+
const { ObjectStackDefinitionSchema } = await import("@objectstack/spec");
|
|
2870
|
+
let metadata;
|
|
2871
|
+
const obj = raw;
|
|
2872
|
+
if (obj?.schemaVersion && obj?.commitId && obj?.metadata !== void 0) {
|
|
2873
|
+
const artifact = ProjectArtifactSchema.parse(obj);
|
|
2874
|
+
metadata = artifact.metadata;
|
|
2875
|
+
} else {
|
|
2876
|
+
const def = ObjectStackDefinitionSchema.parse(obj);
|
|
2877
|
+
const canonical = JSON.stringify(def, Object.keys(def).sort());
|
|
2878
|
+
const checksum = createHash2("sha256").update(canonical).digest("hex");
|
|
2879
|
+
const projectId = this.options.projectId ?? "proj_local";
|
|
2880
|
+
ProjectArtifactSchema.parse({
|
|
2881
|
+
schemaVersion: "0.1",
|
|
2882
|
+
projectId,
|
|
2883
|
+
commitId: "local-dev",
|
|
2884
|
+
checksum,
|
|
2885
|
+
metadata: def
|
|
2886
|
+
});
|
|
2887
|
+
metadata = def;
|
|
2888
|
+
}
|
|
2889
|
+
const memLoader = new MemoryLoader();
|
|
2890
|
+
const manifestPackageId = metadata?.manifest?.id ?? metadata?.id ?? void 0;
|
|
2891
|
+
let totalRegistered = 0;
|
|
2892
|
+
for (const [field, metaType] of Object.entries(ARTIFACT_FIELD_TO_TYPE)) {
|
|
2893
|
+
const items = metadata[field];
|
|
2894
|
+
if (!Array.isArray(items) || items.length === 0) continue;
|
|
2895
|
+
for (const item of items) {
|
|
2896
|
+
const name = item?.name;
|
|
2897
|
+
if (!name) continue;
|
|
2898
|
+
if (manifestPackageId && item._packageId === void 0) {
|
|
2899
|
+
item._packageId = manifestPackageId;
|
|
2900
|
+
}
|
|
2901
|
+
await memLoader.save(metaType, name, item);
|
|
2902
|
+
await this.manager.register(metaType, name, item);
|
|
2903
|
+
totalRegistered++;
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
this.manager.registerLoader(memLoader);
|
|
2907
|
+
ctx.logger.info("[MetadataPlugin] Artifact metadata loaded", {
|
|
2908
|
+
path: filePath,
|
|
2909
|
+
totalRegistered
|
|
2910
|
+
});
|
|
2911
|
+
}
|
|
2912
|
+
async _loadFromFileSystem(ctx) {
|
|
2913
|
+
ctx.logger.info("Loading metadata from file system...");
|
|
2914
|
+
const sortedTypes = [...DEFAULT_METADATA_TYPE_REGISTRY].sort((a, b) => a.loadOrder - b.loadOrder);
|
|
2915
|
+
let totalLoaded = 0;
|
|
2916
|
+
for (const entry of sortedTypes) {
|
|
2917
|
+
try {
|
|
2918
|
+
const items = await this.manager.loadMany(entry.type, {
|
|
2919
|
+
recursive: true,
|
|
2920
|
+
patterns: entry.filePatterns
|
|
2921
|
+
});
|
|
2922
|
+
if (items.length > 0) {
|
|
2923
|
+
for (const item of items) {
|
|
2924
|
+
const meta = item;
|
|
2925
|
+
if (meta?.name) {
|
|
2926
|
+
await this.manager.register(entry.type, meta.name, item);
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
ctx.logger.info(`Loaded ${items.length} ${entry.type} from file system`);
|
|
2930
|
+
totalLoaded += items.length;
|
|
2931
|
+
}
|
|
2932
|
+
} catch (e) {
|
|
2933
|
+
ctx.logger.debug(`No ${entry.type} metadata found`, { error: e.message });
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
ctx.logger.info("Metadata loading complete", {
|
|
2937
|
+
totalItems: totalLoaded,
|
|
2938
|
+
registeredTypes: sortedTypes.length
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
};
|
|
2942
|
+
|
|
2703
2943
|
// src/loaders/remote-loader.ts
|
|
2704
2944
|
var RemoteLoader = class {
|
|
2705
2945
|
constructor(baseUrl, authToken) {
|
|
@@ -2797,6 +3037,9 @@ var RemoteLoader = class {
|
|
|
2797
3037
|
}
|
|
2798
3038
|
};
|
|
2799
3039
|
|
|
3040
|
+
// src/index.ts
|
|
3041
|
+
import { SysMetadataObject as SysMetadataObject2, SysMetadataHistoryObject as SysMetadataHistoryObject2 } from "@objectstack/platform-objects/metadata";
|
|
3042
|
+
|
|
2800
3043
|
// src/routes/history-routes.ts
|
|
2801
3044
|
function registerMetadataHistoryRoutes(app, metadataService) {
|
|
2802
3045
|
app.get("/api/v1/metadata/:type/:name/history", async (c) => {
|
|
@@ -2952,7 +3195,8 @@ var HistoryCleanupManager = class {
|
|
|
2952
3195
|
async runCleanup() {
|
|
2953
3196
|
const driver = this.dbLoader.driver;
|
|
2954
3197
|
const historyTableName = this.dbLoader.historyTableName;
|
|
2955
|
-
const
|
|
3198
|
+
const organizationId = this.dbLoader.organizationId;
|
|
3199
|
+
const projectId = this.dbLoader.projectId;
|
|
2956
3200
|
let deleted = 0;
|
|
2957
3201
|
let errors = 0;
|
|
2958
3202
|
try {
|
|
@@ -2963,8 +3207,11 @@ var HistoryCleanupManager = class {
|
|
|
2963
3207
|
const filter = {
|
|
2964
3208
|
recorded_at: { $lt: cutoffISO }
|
|
2965
3209
|
};
|
|
2966
|
-
if (
|
|
2967
|
-
filter.
|
|
3210
|
+
if (organizationId) {
|
|
3211
|
+
filter.organization_id = organizationId;
|
|
3212
|
+
}
|
|
3213
|
+
if (projectId !== void 0) {
|
|
3214
|
+
filter.project_id = projectId;
|
|
2968
3215
|
}
|
|
2969
3216
|
try {
|
|
2970
3217
|
const result = await this.bulkDeleteByFilter(driver, historyTableName, filter);
|
|
@@ -2976,9 +3223,12 @@ var HistoryCleanupManager = class {
|
|
|
2976
3223
|
}
|
|
2977
3224
|
if (this.policy.maxVersions) {
|
|
2978
3225
|
try {
|
|
3226
|
+
const baseWhere = {};
|
|
3227
|
+
if (organizationId) baseWhere.organization_id = organizationId;
|
|
3228
|
+
if (projectId !== void 0) baseWhere.project_id = projectId;
|
|
2979
3229
|
const metadataIds = await driver.find(historyTableName, {
|
|
2980
3230
|
object: historyTableName,
|
|
2981
|
-
where:
|
|
3231
|
+
where: baseWhere,
|
|
2982
3232
|
fields: ["metadata_id"]
|
|
2983
3233
|
});
|
|
2984
3234
|
const uniqueIds = /* @__PURE__ */ new Set();
|
|
@@ -2988,10 +3238,7 @@ var HistoryCleanupManager = class {
|
|
|
2988
3238
|
}
|
|
2989
3239
|
}
|
|
2990
3240
|
for (const metadataId of uniqueIds) {
|
|
2991
|
-
const filter = { metadata_id: metadataId };
|
|
2992
|
-
if (tenantId) {
|
|
2993
|
-
filter.tenant_id = tenantId;
|
|
2994
|
-
}
|
|
3241
|
+
const filter = { metadata_id: metadataId, ...baseWhere };
|
|
2995
3242
|
try {
|
|
2996
3243
|
const historyRecords = await driver.find(historyTableName, {
|
|
2997
3244
|
object: historyTableName,
|
|
@@ -3065,20 +3312,22 @@ var HistoryCleanupManager = class {
|
|
|
3065
3312
|
async getCleanupStats() {
|
|
3066
3313
|
const driver = this.dbLoader.driver;
|
|
3067
3314
|
const historyTableName = this.dbLoader.historyTableName;
|
|
3068
|
-
const
|
|
3315
|
+
const organizationId = this.dbLoader.organizationId;
|
|
3316
|
+
const projectId = this.dbLoader.projectId;
|
|
3069
3317
|
let recordsByAge = 0;
|
|
3070
3318
|
let recordsByCount = 0;
|
|
3071
3319
|
try {
|
|
3320
|
+
const baseWhere = {};
|
|
3321
|
+
if (organizationId) baseWhere.organization_id = organizationId;
|
|
3322
|
+
if (projectId !== void 0) baseWhere.project_id = projectId;
|
|
3072
3323
|
if (this.policy.maxAgeDays) {
|
|
3073
3324
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
3074
3325
|
cutoffDate.setDate(cutoffDate.getDate() - this.policy.maxAgeDays);
|
|
3075
3326
|
const cutoffISO = cutoffDate.toISOString();
|
|
3076
3327
|
const filter = {
|
|
3077
|
-
recorded_at: { $lt: cutoffISO }
|
|
3328
|
+
recorded_at: { $lt: cutoffISO },
|
|
3329
|
+
...baseWhere
|
|
3078
3330
|
};
|
|
3079
|
-
if (tenantId) {
|
|
3080
|
-
filter.tenant_id = tenantId;
|
|
3081
|
-
}
|
|
3082
3331
|
recordsByAge = await driver.count(historyTableName, {
|
|
3083
3332
|
object: historyTableName,
|
|
3084
3333
|
where: filter
|
|
@@ -3087,7 +3336,7 @@ var HistoryCleanupManager = class {
|
|
|
3087
3336
|
if (this.policy.maxVersions) {
|
|
3088
3337
|
const metadataIds = await driver.find(historyTableName, {
|
|
3089
3338
|
object: historyTableName,
|
|
3090
|
-
where:
|
|
3339
|
+
where: baseWhere,
|
|
3091
3340
|
fields: ["metadata_id"]
|
|
3092
3341
|
});
|
|
3093
3342
|
const uniqueIds = /* @__PURE__ */ new Set();
|
|
@@ -3097,10 +3346,7 @@ var HistoryCleanupManager = class {
|
|
|
3097
3346
|
}
|
|
3098
3347
|
}
|
|
3099
3348
|
for (const metadataId of uniqueIds) {
|
|
3100
|
-
const filter = { metadata_id: metadataId };
|
|
3101
|
-
if (tenantId) {
|
|
3102
|
-
filter.tenant_id = tenantId;
|
|
3103
|
-
}
|
|
3349
|
+
const filter = { metadata_id: metadataId, ...baseWhere };
|
|
3104
3350
|
const count = await driver.count(historyTableName, {
|
|
3105
3351
|
object: historyTableName,
|
|
3106
3352
|
where: filter
|
|
@@ -3184,11 +3430,12 @@ export {
|
|
|
3184
3430
|
MemoryLoader,
|
|
3185
3431
|
MetadataManager,
|
|
3186
3432
|
MetadataPlugin,
|
|
3433
|
+
MetadataProjector,
|
|
3187
3434
|
migration_exports as Migration,
|
|
3188
3435
|
NodeMetadataManager,
|
|
3189
3436
|
RemoteLoader,
|
|
3190
|
-
SysMetadataHistoryObject,
|
|
3191
|
-
SysMetadataObject,
|
|
3437
|
+
SysMetadataHistoryObject2 as SysMetadataHistoryObject,
|
|
3438
|
+
SysMetadataObject2 as SysMetadataObject,
|
|
3192
3439
|
TypeScriptSerializer,
|
|
3193
3440
|
YAMLSerializer,
|
|
3194
3441
|
calculateChecksum,
|