@objectstack/metadata 4.0.3 → 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 -509
- 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 -507
- 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 -509
- 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 -507
- package/dist/node.js.map +1 -1
- package/package.json +28 -8
package/dist/index.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
|
|
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
|
|
1966
2170
|
});
|
|
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
|
-
};
|
|
2027
|
-
});
|
|
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,105 +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
|
-
try {
|
|
2565
|
-
const services = ctx.getServices();
|
|
2566
|
-
for (const [serviceName, service] of services) {
|
|
2567
|
-
if (serviceName.startsWith("driver.") && service) {
|
|
2568
|
-
ctx.logger.info("[MetadataPlugin] Bridging driver to MetadataManager for database-backed persistence", {
|
|
2569
|
-
driverService: serviceName
|
|
2570
|
-
});
|
|
2571
|
-
this.manager.setDatabaseDriver(service);
|
|
2572
|
-
break;
|
|
2573
|
-
}
|
|
2574
|
-
}
|
|
2575
|
-
} catch (e) {
|
|
2576
|
-
ctx.logger.debug("[MetadataPlugin] No driver service found \u2014 database metadata persistence not available", {
|
|
2577
|
-
error: e.message
|
|
2578
|
-
});
|
|
2579
|
-
}
|
|
2580
|
-
try {
|
|
2581
|
-
const realtimeService = ctx.getService("realtime");
|
|
2582
|
-
if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
|
|
2583
|
-
ctx.logger.info("[MetadataPlugin] Bridging realtime service to MetadataManager for event publishing");
|
|
2584
|
-
this.manager.setRealtimeService(realtimeService);
|
|
2585
|
-
}
|
|
2586
|
-
} catch (e) {
|
|
2587
|
-
ctx.logger.debug("[MetadataPlugin] No realtime service found \u2014 metadata events will not be published", {
|
|
2588
|
-
error: e.message
|
|
2589
|
-
});
|
|
2590
|
-
}
|
|
2591
|
-
};
|
|
2592
|
-
this.options = {
|
|
2593
|
-
watch: true,
|
|
2594
|
-
...options
|
|
2595
|
-
};
|
|
2596
|
-
const rootDir = this.options.rootDir || process.cwd();
|
|
2597
|
-
this.manager = new NodeMetadataManager({
|
|
2598
|
-
rootDir,
|
|
2599
|
-
watch: this.options.watch ?? true,
|
|
2600
|
-
formats: ["yaml", "json", "typescript", "javascript"]
|
|
2601
|
-
});
|
|
2602
|
-
this.manager.setTypeRegistry(DEFAULT_METADATA_TYPE_REGISTRY);
|
|
2603
|
-
}
|
|
2604
|
-
};
|
|
2605
|
-
|
|
2606
2649
|
// src/loaders/memory-loader.ts
|
|
2607
2650
|
var MemoryLoader = class {
|
|
2608
2651
|
constructor() {
|
|
@@ -2681,6 +2724,222 @@ var MemoryLoader = class {
|
|
|
2681
2724
|
}
|
|
2682
2725
|
};
|
|
2683
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
|
+
|
|
2684
2943
|
// src/loaders/remote-loader.ts
|
|
2685
2944
|
var RemoteLoader = class {
|
|
2686
2945
|
constructor(baseUrl, authToken) {
|
|
@@ -2778,6 +3037,9 @@ var RemoteLoader = class {
|
|
|
2778
3037
|
}
|
|
2779
3038
|
};
|
|
2780
3039
|
|
|
3040
|
+
// src/index.ts
|
|
3041
|
+
import { SysMetadataObject as SysMetadataObject2, SysMetadataHistoryObject as SysMetadataHistoryObject2 } from "@objectstack/platform-objects/metadata";
|
|
3042
|
+
|
|
2781
3043
|
// src/routes/history-routes.ts
|
|
2782
3044
|
function registerMetadataHistoryRoutes(app, metadataService) {
|
|
2783
3045
|
app.get("/api/v1/metadata/:type/:name/history", async (c) => {
|
|
@@ -2933,7 +3195,8 @@ var HistoryCleanupManager = class {
|
|
|
2933
3195
|
async runCleanup() {
|
|
2934
3196
|
const driver = this.dbLoader.driver;
|
|
2935
3197
|
const historyTableName = this.dbLoader.historyTableName;
|
|
2936
|
-
const
|
|
3198
|
+
const organizationId = this.dbLoader.organizationId;
|
|
3199
|
+
const projectId = this.dbLoader.projectId;
|
|
2937
3200
|
let deleted = 0;
|
|
2938
3201
|
let errors = 0;
|
|
2939
3202
|
try {
|
|
@@ -2944,8 +3207,11 @@ var HistoryCleanupManager = class {
|
|
|
2944
3207
|
const filter = {
|
|
2945
3208
|
recorded_at: { $lt: cutoffISO }
|
|
2946
3209
|
};
|
|
2947
|
-
if (
|
|
2948
|
-
filter.
|
|
3210
|
+
if (organizationId) {
|
|
3211
|
+
filter.organization_id = organizationId;
|
|
3212
|
+
}
|
|
3213
|
+
if (projectId !== void 0) {
|
|
3214
|
+
filter.project_id = projectId;
|
|
2949
3215
|
}
|
|
2950
3216
|
try {
|
|
2951
3217
|
const result = await this.bulkDeleteByFilter(driver, historyTableName, filter);
|
|
@@ -2957,9 +3223,12 @@ var HistoryCleanupManager = class {
|
|
|
2957
3223
|
}
|
|
2958
3224
|
if (this.policy.maxVersions) {
|
|
2959
3225
|
try {
|
|
3226
|
+
const baseWhere = {};
|
|
3227
|
+
if (organizationId) baseWhere.organization_id = organizationId;
|
|
3228
|
+
if (projectId !== void 0) baseWhere.project_id = projectId;
|
|
2960
3229
|
const metadataIds = await driver.find(historyTableName, {
|
|
2961
3230
|
object: historyTableName,
|
|
2962
|
-
where:
|
|
3231
|
+
where: baseWhere,
|
|
2963
3232
|
fields: ["metadata_id"]
|
|
2964
3233
|
});
|
|
2965
3234
|
const uniqueIds = /* @__PURE__ */ new Set();
|
|
@@ -2969,10 +3238,7 @@ var HistoryCleanupManager = class {
|
|
|
2969
3238
|
}
|
|
2970
3239
|
}
|
|
2971
3240
|
for (const metadataId of uniqueIds) {
|
|
2972
|
-
const filter = { metadata_id: metadataId };
|
|
2973
|
-
if (tenantId) {
|
|
2974
|
-
filter.tenant_id = tenantId;
|
|
2975
|
-
}
|
|
3241
|
+
const filter = { metadata_id: metadataId, ...baseWhere };
|
|
2976
3242
|
try {
|
|
2977
3243
|
const historyRecords = await driver.find(historyTableName, {
|
|
2978
3244
|
object: historyTableName,
|
|
@@ -3046,20 +3312,22 @@ var HistoryCleanupManager = class {
|
|
|
3046
3312
|
async getCleanupStats() {
|
|
3047
3313
|
const driver = this.dbLoader.driver;
|
|
3048
3314
|
const historyTableName = this.dbLoader.historyTableName;
|
|
3049
|
-
const
|
|
3315
|
+
const organizationId = this.dbLoader.organizationId;
|
|
3316
|
+
const projectId = this.dbLoader.projectId;
|
|
3050
3317
|
let recordsByAge = 0;
|
|
3051
3318
|
let recordsByCount = 0;
|
|
3052
3319
|
try {
|
|
3320
|
+
const baseWhere = {};
|
|
3321
|
+
if (organizationId) baseWhere.organization_id = organizationId;
|
|
3322
|
+
if (projectId !== void 0) baseWhere.project_id = projectId;
|
|
3053
3323
|
if (this.policy.maxAgeDays) {
|
|
3054
3324
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
3055
3325
|
cutoffDate.setDate(cutoffDate.getDate() - this.policy.maxAgeDays);
|
|
3056
3326
|
const cutoffISO = cutoffDate.toISOString();
|
|
3057
3327
|
const filter = {
|
|
3058
|
-
recorded_at: { $lt: cutoffISO }
|
|
3328
|
+
recorded_at: { $lt: cutoffISO },
|
|
3329
|
+
...baseWhere
|
|
3059
3330
|
};
|
|
3060
|
-
if (tenantId) {
|
|
3061
|
-
filter.tenant_id = tenantId;
|
|
3062
|
-
}
|
|
3063
3331
|
recordsByAge = await driver.count(historyTableName, {
|
|
3064
3332
|
object: historyTableName,
|
|
3065
3333
|
where: filter
|
|
@@ -3068,7 +3336,7 @@ var HistoryCleanupManager = class {
|
|
|
3068
3336
|
if (this.policy.maxVersions) {
|
|
3069
3337
|
const metadataIds = await driver.find(historyTableName, {
|
|
3070
3338
|
object: historyTableName,
|
|
3071
|
-
where:
|
|
3339
|
+
where: baseWhere,
|
|
3072
3340
|
fields: ["metadata_id"]
|
|
3073
3341
|
});
|
|
3074
3342
|
const uniqueIds = /* @__PURE__ */ new Set();
|
|
@@ -3078,10 +3346,7 @@ var HistoryCleanupManager = class {
|
|
|
3078
3346
|
}
|
|
3079
3347
|
}
|
|
3080
3348
|
for (const metadataId of uniqueIds) {
|
|
3081
|
-
const filter = { metadata_id: metadataId };
|
|
3082
|
-
if (tenantId) {
|
|
3083
|
-
filter.tenant_id = tenantId;
|
|
3084
|
-
}
|
|
3349
|
+
const filter = { metadata_id: metadataId, ...baseWhere };
|
|
3085
3350
|
const count = await driver.count(historyTableName, {
|
|
3086
3351
|
object: historyTableName,
|
|
3087
3352
|
where: filter
|
|
@@ -3164,10 +3429,11 @@ export {
|
|
|
3164
3429
|
MemoryLoader,
|
|
3165
3430
|
MetadataManager,
|
|
3166
3431
|
MetadataPlugin,
|
|
3432
|
+
MetadataProjector,
|
|
3167
3433
|
migration_exports as Migration,
|
|
3168
3434
|
RemoteLoader,
|
|
3169
|
-
SysMetadataHistoryObject,
|
|
3170
|
-
SysMetadataObject,
|
|
3435
|
+
SysMetadataHistoryObject2 as SysMetadataHistoryObject,
|
|
3436
|
+
SysMetadataObject2 as SysMetadataObject,
|
|
3171
3437
|
TypeScriptSerializer,
|
|
3172
3438
|
YAMLSerializer,
|
|
3173
3439
|
calculateChecksum,
|