@x12i/catalox 3.5.1 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -1
- package/dist/src/adapters/api/api-adapter.d.ts.map +1 -1
- package/dist/src/adapters/api/api-adapter.js +1 -0
- package/dist/src/adapters/api/api-adapter.js.map +1 -1
- package/dist/src/adapters/mongo/mongo-adapter.d.ts.map +1 -1
- package/dist/src/adapters/mongo/mongo-adapter.js +1 -0
- package/dist/src/adapters/mongo/mongo-adapter.js.map +1 -1
- package/dist/src/catalox/catalog-discovery.d.ts.map +1 -1
- package/dist/src/catalox/catalog-discovery.js +8 -6
- package/dist/src/catalox/catalog-discovery.js.map +1 -1
- package/dist/src/catalox/catalog-lifecycle.d.ts.map +1 -1
- package/dist/src/catalox/catalog-lifecycle.js +26 -19
- package/dist/src/catalox/catalog-lifecycle.js.map +1 -1
- package/dist/src/catalox/catalox-bound.d.ts +42 -0
- package/dist/src/catalox/catalox-bound.d.ts.map +1 -1
- package/dist/src/catalox/catalox-bound.js +15 -0
- package/dist/src/catalox/catalox-bound.js.map +1 -1
- package/dist/src/catalox/catalox.d.ts +84 -1
- package/dist/src/catalox/catalox.d.ts.map +1 -1
- package/dist/src/catalox/catalox.js +318 -58
- package/dist/src/catalox/catalox.js.map +1 -1
- package/dist/src/catalox/create-catalox.d.ts +4 -0
- package/dist/src/catalox/create-catalox.d.ts.map +1 -1
- package/dist/src/catalox/create-catalox.js +15 -0
- package/dist/src/catalox/create-catalox.js.map +1 -1
- package/dist/src/catalox/native-catalog-merge.d.ts.map +1 -1
- package/dist/src/catalox/native-catalog-merge.js +12 -11
- package/dist/src/catalox/native-catalog-merge.js.map +1 -1
- package/dist/src/contracts/agents.d.ts +19 -0
- package/dist/src/contracts/agents.d.ts.map +1 -0
- package/dist/src/contracts/agents.js +2 -0
- package/dist/src/contracts/agents.js.map +1 -0
- package/dist/src/contracts/catalog-types.d.ts +19 -0
- package/dist/src/contracts/catalog-types.d.ts.map +1 -0
- package/dist/src/contracts/catalog-types.js +2 -0
- package/dist/src/contracts/catalog-types.js.map +1 -0
- package/dist/src/contracts/catalogs.d.ts +26 -6
- package/dist/src/contracts/catalogs.d.ts.map +1 -1
- package/dist/src/contracts/catalogs.js.map +1 -1
- package/dist/src/contracts/descriptors.d.ts +5 -0
- package/dist/src/contracts/descriptors.d.ts.map +1 -1
- package/dist/src/contracts/design-objects.d.ts +38 -0
- package/dist/src/contracts/design-objects.d.ts.map +1 -0
- package/dist/src/contracts/design-objects.js +2 -0
- package/dist/src/contracts/design-objects.js.map +1 -0
- package/dist/src/contracts/discovery.d.ts +7 -8
- package/dist/src/contracts/discovery.d.ts.map +1 -1
- package/dist/src/contracts/domains.d.ts +19 -0
- package/dist/src/contracts/domains.d.ts.map +1 -0
- package/dist/src/contracts/domains.js +2 -0
- package/dist/src/contracts/domains.js.map +1 -0
- package/dist/src/contracts/ids.d.ts +2 -0
- package/dist/src/contracts/ids.d.ts.map +1 -1
- package/dist/src/contracts/index.d.ts +8 -2
- package/dist/src/contracts/index.d.ts.map +1 -1
- package/dist/src/contracts/index.js +1 -0
- package/dist/src/contracts/index.js.map +1 -1
- package/dist/src/contracts/inputs.d.ts +1 -0
- package/dist/src/contracts/inputs.d.ts.map +1 -1
- package/dist/src/contracts/items.d.ts +9 -7
- package/dist/src/contracts/items.d.ts.map +1 -1
- package/dist/src/contracts/presentation-binding.d.ts +116 -0
- package/dist/src/contracts/presentation-binding.d.ts.map +1 -0
- package/dist/src/contracts/presentation-binding.js +215 -0
- package/dist/src/contracts/presentation-binding.js.map +1 -0
- package/dist/src/contracts/presentation.d.ts +41 -0
- package/dist/src/contracts/presentation.d.ts.map +1 -1
- package/dist/src/firebase/agent-store.d.ts +18 -0
- package/dist/src/firebase/agent-store.d.ts.map +1 -0
- package/dist/src/firebase/agent-store.js +47 -0
- package/dist/src/firebase/agent-store.js.map +1 -0
- package/dist/src/firebase/catalog-type-store.d.ts +22 -0
- package/dist/src/firebase/catalog-type-store.d.ts.map +1 -0
- package/dist/src/firebase/catalog-type-store.js +51 -0
- package/dist/src/firebase/catalog-type-store.js.map +1 -0
- package/dist/src/firebase/definition-store.d.ts +4 -18
- package/dist/src/firebase/definition-store.d.ts.map +1 -1
- package/dist/src/firebase/definition-store.js.map +1 -1
- package/dist/src/firebase/design-object-store.d.ts +15 -0
- package/dist/src/firebase/design-object-store.d.ts.map +1 -0
- package/dist/src/firebase/design-object-store.js +51 -0
- package/dist/src/firebase/design-object-store.js.map +1 -0
- package/dist/src/firebase/domain-store.d.ts +18 -0
- package/dist/src/firebase/domain-store.d.ts.map +1 -0
- package/dist/src/firebase/domain-store.js +47 -0
- package/dist/src/firebase/domain-store.js.map +1 -0
- package/dist/src/firebase/index.d.ts +2 -0
- package/dist/src/firebase/index.d.ts.map +1 -1
- package/dist/src/firebase/index.js +2 -0
- package/dist/src/firebase/index.js.map +1 -1
- package/dist/src/firebase/presentation-profile-store.d.ts +18 -0
- package/dist/src/firebase/presentation-profile-store.d.ts.map +1 -0
- package/dist/src/firebase/presentation-profile-store.js +44 -0
- package/dist/src/firebase/presentation-profile-store.js.map +1 -0
- package/dist/src/migrations/backfill-catalog-model.d.ts +29 -0
- package/dist/src/migrations/backfill-catalog-model.d.ts.map +1 -0
- package/dist/src/migrations/backfill-catalog-model.js +124 -0
- package/dist/src/migrations/backfill-catalog-model.js.map +1 -0
- package/dist/src/migrations/migrate-native-catalog-layout.js +4 -4
- package/dist/src/migrations/migrate-native-catalog-layout.js.map +1 -1
- package/dist/src/validation/index.d.ts +2 -2
- package/dist/src/validation/index.d.ts.map +1 -1
- package/dist/src/validation/index.js +2 -2
- package/dist/src/validation/index.js.map +1 -1
- package/dist/src/validation/ui-spec-schema.d.ts +5 -0
- package/dist/src/validation/ui-spec-schema.d.ts.map +1 -1
- package/dist/src/validation/ui-spec-schema.js +114 -0
- package/dist/src/validation/ui-spec-schema.js.map +1 -1
- package/dist/src/validation/ui-spec-validate.d.ts +3 -0
- package/dist/src/validation/ui-spec-validate.d.ts.map +1 -1
- package/dist/src/validation/ui-spec-validate.js +13 -1
- package/dist/src/validation/ui-spec-validate.js.map +1 -1
- package/package.json +2 -2
|
@@ -12,11 +12,17 @@ import { parseJson, toJson } from "./json-io.js";
|
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
13
|
import { renderInventoryReportMarkdown } from "./reporting/render-inventory-report.js";
|
|
14
14
|
import { optionsFromCatalogItems, optionsFromStaticSource, resolveFilterBy } from "./field-source-resolution.js";
|
|
15
|
+
import { resolveCatalogPresentationBinding } from "../contracts/presentation-binding.js";
|
|
16
|
+
import { buildRendererSnippetDocId } from "../migrations/renderer-metadata.js";
|
|
17
|
+
import { backfillCatalogModel } from "../migrations/backfill-catalog-model.js";
|
|
15
18
|
import { AppStore } from "../firebase/app-store.js";
|
|
16
19
|
import { AdapterStore } from "../firebase/adapter-store.js";
|
|
17
20
|
import { BindingStore } from "../firebase/binding-store.js";
|
|
18
21
|
import { CatalogStore } from "../firebase/catalog-store.js";
|
|
19
22
|
import { CatalogDataIndexStore } from "../firebase/catalog-data-index-store.js";
|
|
23
|
+
import { CatalogTypeStore } from "../firebase/catalog-type-store.js";
|
|
24
|
+
import { DomainStore } from "../firebase/domain-store.js";
|
|
25
|
+
import { AgentStore } from "../firebase/agent-store.js";
|
|
20
26
|
import { DefinitionStore } from "../firebase/definition-store.js";
|
|
21
27
|
import { DescriptorStore } from "../firebase/descriptor-store.js";
|
|
22
28
|
import { FirestoreStore } from "../firebase/firestore-store.js";
|
|
@@ -46,6 +52,23 @@ export class Catalox {
|
|
|
46
52
|
constructor(deps) {
|
|
47
53
|
this.deps = deps;
|
|
48
54
|
}
|
|
55
|
+
async validateCatalogTypeForContext(context, catalogType) {
|
|
56
|
+
const registry = this.deps.catalogTypes
|
|
57
|
+
? await this.deps.catalogTypes.resolveForContext({
|
|
58
|
+
...(context.storeId ? { storeId: context.storeId } : {}),
|
|
59
|
+
...(context.appId ? { appId: context.appId } : {}),
|
|
60
|
+
})
|
|
61
|
+
: null;
|
|
62
|
+
if (!registry)
|
|
63
|
+
return; // registry not configured or no entry exists yet
|
|
64
|
+
const allowed = new Set((registry.types ?? []).map((t) => String(t)));
|
|
65
|
+
if (!allowed.has(String(catalogType))) {
|
|
66
|
+
throw new CatalogAdapterError({
|
|
67
|
+
reason: "catalog_type_not_allowed",
|
|
68
|
+
message: `catalogType "${String(catalogType)}" is not allowed for scope ${JSON.stringify(registry.scope)}.`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
49
72
|
catalogLifecycleDeps() {
|
|
50
73
|
return {
|
|
51
74
|
firestoreStore: this.deps.firestoreStore,
|
|
@@ -245,7 +268,7 @@ export class Catalox {
|
|
|
245
268
|
data: (i.data ?? {}),
|
|
246
269
|
...(i.title != null ? { title: i.title } : {}),
|
|
247
270
|
...(i.subtitle != null ? { subtitle: i.subtitle } : {}),
|
|
248
|
-
...(i.status != null ? { status: i.status } : {}),
|
|
271
|
+
...(i.metadata?.status != null ? { status: String(i.metadata.status) } : {}),
|
|
249
272
|
...(i.metadata != null ? { metadata: i.metadata } : {}),
|
|
250
273
|
})),
|
|
251
274
|
...(list.total != null ? { total: list.total } : {}),
|
|
@@ -269,6 +292,119 @@ export class Catalox {
|
|
|
269
292
|
actions: {},
|
|
270
293
|
};
|
|
271
294
|
}
|
|
295
|
+
async getDesignObject(context, designId) {
|
|
296
|
+
void context;
|
|
297
|
+
if (!this.deps.designObjects)
|
|
298
|
+
throw new Error("designObjects dependency is not configured");
|
|
299
|
+
return this.deps.designObjects.get(String(designId));
|
|
300
|
+
}
|
|
301
|
+
async listDesignObjects(context, scope) {
|
|
302
|
+
void context;
|
|
303
|
+
if (!this.deps.designObjects)
|
|
304
|
+
throw new Error("designObjects dependency is not configured");
|
|
305
|
+
return this.deps.designObjects.listByScope(scope);
|
|
306
|
+
}
|
|
307
|
+
async getCatalogPresentationBinding(context, catalogId, role, options) {
|
|
308
|
+
const descriptor = await this.getCatalogDescriptor(context, catalogId);
|
|
309
|
+
const profiles = options?.profiles
|
|
310
|
+
? options.profiles
|
|
311
|
+
: this.deps.presentationProfiles
|
|
312
|
+
? await this.deps.presentationProfiles.listForCatalog?.({
|
|
313
|
+
catalogId: String(catalogId),
|
|
314
|
+
...(descriptor?.catalogType ? { catalogType: String(descriptor.catalogType) } : {}),
|
|
315
|
+
})
|
|
316
|
+
: [];
|
|
317
|
+
const bindingBase = resolveCatalogPresentationBinding({
|
|
318
|
+
descriptor,
|
|
319
|
+
profiles,
|
|
320
|
+
role: role,
|
|
321
|
+
...(options?.mode ? { mode: options.mode } : {}),
|
|
322
|
+
...(options?.displayContext ? { displayContext: options.displayContext } : {}),
|
|
323
|
+
...(options?.designScope ? { designScope: options.designScope } : {}),
|
|
324
|
+
...(options?.designObjects ? { designObjects: options.designObjects } : {}),
|
|
325
|
+
});
|
|
326
|
+
// Optionally fetch snippet metadata when a snippetRef is involved.
|
|
327
|
+
if (options?.includeSnippet && bindingBase.customRenderer?.entry?.snippetRef) {
|
|
328
|
+
const entry = bindingBase.customRenderer.entry;
|
|
329
|
+
const snippetId = entry.snippetRef?.snippetId;
|
|
330
|
+
const catalogScoped = entry.snippetRef?.catalogScoped !== false;
|
|
331
|
+
if (!this.deps.rendererSnippets)
|
|
332
|
+
throw new Error("rendererSnippets dependency is not configured");
|
|
333
|
+
// Support canonical catalog-scoped ids; for anything else, require host-side snippet resolution.
|
|
334
|
+
if (!snippetId) {
|
|
335
|
+
const rec = await this.getCatalogRendererSnippet(context, catalogId, role, options?.mode);
|
|
336
|
+
return { ...bindingBase, customRenderer: { ...bindingBase.customRenderer, snippet: rec } };
|
|
337
|
+
}
|
|
338
|
+
if (catalogScoped) {
|
|
339
|
+
const expected1 = buildRendererSnippetDocId({ catalogId: String(catalogId), role: role, ...(options?.mode ? { mode: options.mode } : {}) });
|
|
340
|
+
if (String(snippetId) !== expected1) {
|
|
341
|
+
throw new Error(`snippetRef.snippetId is not a canonical catalog-scoped id: ${String(snippetId)}`);
|
|
342
|
+
}
|
|
343
|
+
const rec = await this.getCatalogRendererSnippet(context, catalogId, role, options?.mode);
|
|
344
|
+
return { ...bindingBase, customRenderer: { ...bindingBase.customRenderer, snippet: rec } };
|
|
345
|
+
}
|
|
346
|
+
throw new Error("Global (non-catalog-scoped) snippetRef resolution is host-defined.");
|
|
347
|
+
}
|
|
348
|
+
// Optionally include design merge when requested and available.
|
|
349
|
+
if (options?.includeDesignObjects && !options?.designObjects) {
|
|
350
|
+
if (!this.deps.designObjects)
|
|
351
|
+
throw new Error("designObjects dependency is not configured");
|
|
352
|
+
const designObjects = await this.deps.designObjects.listByScope?.({
|
|
353
|
+
...(options?.designScope?.storeId ? { storeId: options.designScope.storeId } : {}),
|
|
354
|
+
...(options?.designScope?.appId ? { appId: options.designScope.appId } : {}),
|
|
355
|
+
...(options?.designScope?.accountId ? { accountId: options.designScope.accountId } : {}),
|
|
356
|
+
...(options?.designScope?.agentId ? { agentId: options.designScope.agentId } : {}),
|
|
357
|
+
});
|
|
358
|
+
return resolveCatalogPresentationBinding({
|
|
359
|
+
descriptor,
|
|
360
|
+
profiles,
|
|
361
|
+
role: role,
|
|
362
|
+
...(options?.mode ? { mode: options.mode } : {}),
|
|
363
|
+
...(options?.displayContext ? { displayContext: options.displayContext } : {}),
|
|
364
|
+
...(options?.designScope ? { designScope: options.designScope } : {}),
|
|
365
|
+
designObjects,
|
|
366
|
+
// Preserve snippet result if caller already asked for it separately above.
|
|
367
|
+
...(bindingBase.customRenderer?.snippet !== undefined ? { snippet: bindingBase.customRenderer.snippet } : {}),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return bindingBase;
|
|
371
|
+
}
|
|
372
|
+
async buildCatalogListPresentationSurface(context, catalogId, options) {
|
|
373
|
+
const map = await this.buildCatalogListRenderMap(context, catalogId, {
|
|
374
|
+
...(options?.limit != null ? { limit: options.limit } : {}),
|
|
375
|
+
...(options?.resolveSources != null ? { resolveSources: options.resolveSources } : {}),
|
|
376
|
+
...(options?.maxSourceOptions != null ? { maxSourceOptions: options.maxSourceOptions } : {}),
|
|
377
|
+
...(options?.displayContext != null ? { displayContext: options.displayContext } : {}),
|
|
378
|
+
});
|
|
379
|
+
const binding = await this.getCatalogPresentationBinding(context, catalogId, (options?.displayContext ?? "grid") === "grid" ? "grid" : "list", {
|
|
380
|
+
...(options?.mode ? { mode: options.mode } : {}),
|
|
381
|
+
displayContext: options?.displayContext ?? "grid",
|
|
382
|
+
...(options?.includeSnippet ? { includeSnippet: true } : {}),
|
|
383
|
+
...(options?.includeDesignObjects ? { includeDesignObjects: true } : {}),
|
|
384
|
+
...(options?.designScope ? { designScope: options.designScope } : {}),
|
|
385
|
+
...(options?.profiles ? { profiles: options.profiles } : {}),
|
|
386
|
+
...(options?.designObjects ? { designObjects: options.designObjects } : {}),
|
|
387
|
+
});
|
|
388
|
+
return { map, binding };
|
|
389
|
+
}
|
|
390
|
+
async buildCatalogItemPresentationSurface(context, catalogId, itemId, options) {
|
|
391
|
+
const map = await this.buildCatalogItemRenderMap(context, catalogId, itemId, {
|
|
392
|
+
...(options?.includeReferences != null ? { includeReferences: options.includeReferences } : {}),
|
|
393
|
+
...(options?.resolveSources != null ? { resolveSources: options.resolveSources } : {}),
|
|
394
|
+
...(options?.maxSourceOptions != null ? { maxSourceOptions: options.maxSourceOptions } : {}),
|
|
395
|
+
...(options?.displayContext != null ? { displayContext: options.displayContext } : {}),
|
|
396
|
+
});
|
|
397
|
+
const binding = await this.getCatalogPresentationBinding(context, catalogId, "item", {
|
|
398
|
+
...(options?.mode ? { mode: options.mode } : {}),
|
|
399
|
+
displayContext: "form",
|
|
400
|
+
...(options?.includeSnippet ? { includeSnippet: true } : {}),
|
|
401
|
+
...(options?.includeDesignObjects ? { includeDesignObjects: true } : {}),
|
|
402
|
+
...(options?.designScope ? { designScope: options.designScope } : {}),
|
|
403
|
+
...(options?.profiles ? { profiles: options.profiles } : {}),
|
|
404
|
+
...(options?.designObjects ? { designObjects: options.designObjects } : {}),
|
|
405
|
+
});
|
|
406
|
+
return { map, binding };
|
|
407
|
+
}
|
|
272
408
|
async buildCatalogItemRenderMap(context, catalogId, itemId, options) {
|
|
273
409
|
const descriptor = await this.getCatalogDescriptor(context, catalogId);
|
|
274
410
|
const got = await this.getCatalogItem(context, catalogId, itemId);
|
|
@@ -302,10 +438,10 @@ export class Catalox {
|
|
|
302
438
|
data: (item.data ?? {}),
|
|
303
439
|
...(item.title != null ? { title: item.title } : {}),
|
|
304
440
|
...(item.subtitle != null ? { subtitle: item.subtitle } : {}),
|
|
305
|
-
...(item.status != null ? { status: item.status } : {}),
|
|
441
|
+
...(item.metadata?.status != null ? { status: String(item.metadata.status) } : {}),
|
|
306
442
|
...(item.metadata != null ? { metadata: item.metadata } : {}),
|
|
307
|
-
...(item.createdAt != null ? { createdAt: item.createdAt } : {}),
|
|
308
|
-
...(item.
|
|
443
|
+
...(item.metadata?.createdAt != null ? { createdAt: String(item.metadata.createdAt) } : {}),
|
|
444
|
+
...(item.metadata?.lastUpdate != null ? { updatedAt: String(item.metadata.lastUpdate) } : {}),
|
|
309
445
|
},
|
|
310
446
|
resolvedSources,
|
|
311
447
|
...(refs.length
|
|
@@ -453,8 +589,13 @@ export class Catalox {
|
|
|
453
589
|
...item,
|
|
454
590
|
...(title != null ? { title: String(title) } : {}),
|
|
455
591
|
...(subtitle != null ? { subtitle: String(subtitle) } : {}),
|
|
456
|
-
|
|
457
|
-
|
|
592
|
+
metadata: {
|
|
593
|
+
...(item.metadata ?? {}),
|
|
594
|
+
...(item.metadata?.domainIds == null ? { domainIds: [] } : {}),
|
|
595
|
+
...(item.metadata?.agentIds == null ? { agentIds: [] } : {}),
|
|
596
|
+
...(status != null ? { status: String(status) } : {}),
|
|
597
|
+
...(updatedAt != null && item.metadata?.lastUpdate == null ? { lastUpdate: String(updatedAt) } : {}),
|
|
598
|
+
},
|
|
458
599
|
};
|
|
459
600
|
}
|
|
460
601
|
async listAppCatalogs(context, input) {
|
|
@@ -470,26 +611,26 @@ export class Catalox {
|
|
|
470
611
|
const cat = byId.get(b.catalogId);
|
|
471
612
|
if (!cat)
|
|
472
613
|
continue;
|
|
473
|
-
if (!input?.includeDisabled && cat.status !== "active")
|
|
614
|
+
if (!input?.includeDisabled && cat.metadata.status !== "active")
|
|
474
615
|
continue;
|
|
475
616
|
const descriptor = await this.deps.descriptors.get(cat.catalogId);
|
|
476
617
|
const description = descriptor?.descriptor.description ?? cat.description;
|
|
477
618
|
const itemLabel = descriptor?.descriptor.itemLabel ?? cat.itemLabel;
|
|
478
619
|
const visibility = descriptor?.descriptor.visibility;
|
|
479
620
|
const descriptorVersion = descriptor?.descriptorVersion;
|
|
480
|
-
const metadata = descriptor?.descriptor.metadata ??
|
|
621
|
+
const metadata = { ...cat.metadata, ...(descriptor?.descriptor.metadata ?? {}) };
|
|
481
622
|
out.push({
|
|
482
623
|
catalogId: cat.catalogId,
|
|
483
624
|
label: descriptor?.descriptor.label ?? cat.name,
|
|
484
625
|
...(description != null ? { description } : {}),
|
|
485
626
|
...(itemLabel != null ? { itemLabel } : {}),
|
|
627
|
+
catalogType: cat.catalogType,
|
|
486
628
|
sourceMode: cat.sourceMode,
|
|
487
629
|
...(cat.mappedSourceType != null ? { mappedSourceType: cat.mappedSourceType } : {}),
|
|
488
|
-
status: cat.status,
|
|
489
630
|
...(visibility != null ? { visibility } : {}),
|
|
490
631
|
access: b.access,
|
|
491
632
|
...(descriptorVersion != null ? { descriptorVersion } : {}),
|
|
492
|
-
|
|
633
|
+
metadata,
|
|
493
634
|
});
|
|
494
635
|
}
|
|
495
636
|
return out.filter((e) => (input?.includeHidden ? true : e.visibility !== "hidden"));
|
|
@@ -536,26 +677,26 @@ export class Catalox {
|
|
|
536
677
|
const cat = byId.get(b.catalogId);
|
|
537
678
|
if (!cat)
|
|
538
679
|
continue;
|
|
539
|
-
if (!input.includeDisabled && cat.status !== "active")
|
|
680
|
+
if (!input.includeDisabled && cat.metadata.status !== "active")
|
|
540
681
|
continue;
|
|
541
682
|
const descriptor = await this.deps.descriptors.get(cat.catalogId);
|
|
542
683
|
const description = descriptor?.descriptor.description ?? cat.description;
|
|
543
684
|
const itemLabel = descriptor?.descriptor.itemLabel ?? cat.itemLabel;
|
|
544
685
|
const visibility = descriptor?.descriptor.visibility;
|
|
545
686
|
const descriptorVersion = descriptor?.descriptorVersion;
|
|
546
|
-
const metadata = descriptor?.descriptor.metadata ??
|
|
687
|
+
const metadata = { ...cat.metadata, ...(descriptor?.descriptor.metadata ?? {}) };
|
|
547
688
|
out.push({
|
|
548
689
|
catalogId: cat.catalogId,
|
|
549
690
|
label: descriptor?.descriptor.label ?? cat.name,
|
|
550
691
|
...(description != null ? { description } : {}),
|
|
551
692
|
...(itemLabel != null ? { itemLabel } : {}),
|
|
693
|
+
catalogType: cat.catalogType,
|
|
552
694
|
sourceMode: cat.sourceMode,
|
|
553
695
|
...(cat.mappedSourceType != null ? { mappedSourceType: cat.mappedSourceType } : {}),
|
|
554
|
-
status: cat.status,
|
|
555
696
|
...(visibility != null ? { visibility } : {}),
|
|
556
697
|
access: b.access,
|
|
557
698
|
...(descriptorVersion != null ? { descriptorVersion } : {}),
|
|
558
|
-
|
|
699
|
+
metadata,
|
|
559
700
|
});
|
|
560
701
|
}
|
|
561
702
|
return out.filter((e) => (input.includeHidden ? true : e.visibility !== "hidden"));
|
|
@@ -679,9 +820,19 @@ export class Catalox {
|
|
|
679
820
|
};
|
|
680
821
|
}
|
|
681
822
|
const def = await this.deps.definitions.get(catalogId);
|
|
682
|
-
if (!def
|
|
823
|
+
if (!def)
|
|
824
|
+
throw new CatalogAdapterError({ catalogId, reason: "missing_definition" });
|
|
825
|
+
if (def.catalogItems.providerType !== "external") {
|
|
683
826
|
throw new CatalogAdapterError({ catalogId, reason: "missing_definition" });
|
|
684
|
-
|
|
827
|
+
}
|
|
828
|
+
const map = def.catalogItems.map;
|
|
829
|
+
const mappingId = map?.mappingId;
|
|
830
|
+
const adapterId = map?.adapterId;
|
|
831
|
+
if (!mappingId)
|
|
832
|
+
throw new CatalogAdapterError({ catalogId, reason: "missing_mapping" });
|
|
833
|
+
if (!adapterId)
|
|
834
|
+
throw new CatalogAdapterError({ catalogId, reason: "missing_adapter" });
|
|
835
|
+
const mapping = await this.deps.mappings.get(mappingId);
|
|
685
836
|
if (!mapping)
|
|
686
837
|
throw new CatalogAdapterError({ catalogId, reason: "missing_mapping" });
|
|
687
838
|
const mappingIssues = validateMappingSpec(mapping.mapping);
|
|
@@ -694,10 +845,10 @@ export class Catalox {
|
|
|
694
845
|
return rest;
|
|
695
846
|
})()
|
|
696
847
|
: undefined;
|
|
697
|
-
if (def.
|
|
848
|
+
if (def.catalogItems.provider === "mongo") {
|
|
698
849
|
if (!this.deps.mongoAdapter)
|
|
699
850
|
throw new CatalogAdapterError({ catalogId, reason: "mongo_adapter_unconfigured" });
|
|
700
|
-
const adapterConfig = await this.deps.adapters.get(
|
|
851
|
+
const adapterConfig = await this.deps.adapters.get(adapterId);
|
|
701
852
|
if (!adapterConfig)
|
|
702
853
|
throw new CatalogAdapterError({ catalogId, reason: "missing_adapter" });
|
|
703
854
|
const result = await this.deps.mongoAdapter.listItems(appCtx, catalogId, adapterConfig, { mapping: mapping.mapping, ...(mapping.options ? { options: mapping.options } : {}) }, mappedListQueryOptions);
|
|
@@ -705,10 +856,10 @@ export class Catalox {
|
|
|
705
856
|
? { listOutcome: "ok", items: result.items, issues: result.issues }
|
|
706
857
|
: { listOutcome: "ok", items: result.items };
|
|
707
858
|
}
|
|
708
|
-
if (def.
|
|
859
|
+
if (def.catalogItems.provider === "api") {
|
|
709
860
|
if (!this.deps.apiAdapter)
|
|
710
861
|
throw new CatalogAdapterError({ catalogId, reason: "api_adapter_unconfigured" });
|
|
711
|
-
const adapterConfig = await this.deps.adapters.get(
|
|
862
|
+
const adapterConfig = await this.deps.adapters.get(adapterId);
|
|
712
863
|
if (!adapterConfig)
|
|
713
864
|
throw new CatalogAdapterError({ catalogId, reason: "missing_adapter" });
|
|
714
865
|
const result = await this.deps.apiAdapter.listItems(appCtx, catalogId, adapterConfig, { responseMapping: mapping.mapping, ...(mapping.options ? { options: mapping.options } : {}) }, mappedListQueryOptions);
|
|
@@ -767,8 +918,6 @@ export class Catalox {
|
|
|
767
918
|
sourceType: "firebase",
|
|
768
919
|
data: rec.data,
|
|
769
920
|
metadata: meta,
|
|
770
|
-
createdAt: rec.createdAt,
|
|
771
|
-
updatedAt: rec.updatedAt,
|
|
772
921
|
};
|
|
773
922
|
return { outcome: "found", item: await this.decorateItem(catalogId, base) };
|
|
774
923
|
}
|
|
@@ -854,18 +1003,35 @@ export class Catalox {
|
|
|
854
1003
|
async createCatalog(_context, _input) {
|
|
855
1004
|
const now = new Date().toISOString();
|
|
856
1005
|
const catalogId = (_input.catalogId ?? randomUUID());
|
|
1006
|
+
await this.validateCatalogTypeForContext(_context, _input.catalogType);
|
|
857
1007
|
await this.deps.catalogs.upsert({
|
|
858
1008
|
catalogId,
|
|
859
1009
|
name: _input.name,
|
|
860
1010
|
...(_input.description != null ? { description: _input.description } : {}),
|
|
1011
|
+
catalogType: _input.catalogType,
|
|
861
1012
|
sourceMode: _input.sourceMode,
|
|
862
1013
|
...(_input.sourceMode === "mapped" && _input.mapped?.sourceType
|
|
863
1014
|
? { mappedSourceType: _input.mapped.sourceType }
|
|
864
1015
|
: {}),
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1016
|
+
catalogItems: _input.sourceMode === "native"
|
|
1017
|
+
? {
|
|
1018
|
+
providerType: "internal",
|
|
1019
|
+
...(_input.native?.itemSchema != null ? { itemSchema: _input.native.itemSchema } : {}),
|
|
1020
|
+
metadata: {},
|
|
1021
|
+
}
|
|
1022
|
+
: {
|
|
1023
|
+
providerType: "external",
|
|
1024
|
+
provider: _input.mapped?.sourceType ?? "api",
|
|
1025
|
+
// map references existing adapter + mapping documents (see createCatalog mapped branch below)
|
|
1026
|
+
map: {},
|
|
1027
|
+
metadata: {},
|
|
1028
|
+
},
|
|
1029
|
+
metadata: {
|
|
1030
|
+
status: "active",
|
|
1031
|
+
createdAt: now,
|
|
1032
|
+
lastUpdate: now,
|
|
1033
|
+
...(_input.metadata ?? {}),
|
|
1034
|
+
},
|
|
869
1035
|
});
|
|
870
1036
|
if (_input.sourceMode === "native") {
|
|
871
1037
|
if (!_input.native)
|
|
@@ -873,12 +1039,12 @@ export class Catalox {
|
|
|
873
1039
|
const collectionPath = _input.native.firestoreCollectionPath ?? nativeItemsCollectionId(catalogId);
|
|
874
1040
|
await this.deps.definitions.upsert({
|
|
875
1041
|
catalogId,
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1042
|
+
sourceMode: "native",
|
|
1043
|
+
catalogItems: {
|
|
1044
|
+
providerType: "internal",
|
|
1045
|
+
...(_input.native.itemSchema != null ? { itemSchema: _input.native.itemSchema } : {}),
|
|
1046
|
+
metadata: { firestoreCollectionPath: collectionPath },
|
|
880
1047
|
},
|
|
881
|
-
...(_input.native.itemSchema != null ? { itemSchema: _input.native.itemSchema } : {}),
|
|
882
1048
|
createdAt: now,
|
|
883
1049
|
updatedAt: now,
|
|
884
1050
|
});
|
|
@@ -902,16 +1068,31 @@ export class Catalox {
|
|
|
902
1068
|
});
|
|
903
1069
|
await this.deps.definitions.upsert({
|
|
904
1070
|
catalogId,
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1071
|
+
sourceMode: "mapped",
|
|
1072
|
+
catalogItems: {
|
|
1073
|
+
providerType: "external",
|
|
1074
|
+
provider: _input.mapped.sourceType,
|
|
1075
|
+
map: { adapterId, mappingId },
|
|
1076
|
+
metadata: {},
|
|
1077
|
+
},
|
|
1078
|
+
...(_input.mapped.syncMode ? { sync: { mode: _input.mapped.syncMode, syncStatus: "idle" } } : {}),
|
|
912
1079
|
createdAt: now,
|
|
913
1080
|
updatedAt: now,
|
|
914
1081
|
});
|
|
1082
|
+
// Update catalog record to point at the external map references.
|
|
1083
|
+
await this.deps.catalogs.upsert({
|
|
1084
|
+
...(await this.deps.catalogs.get(catalogId)),
|
|
1085
|
+
catalogItems: {
|
|
1086
|
+
providerType: "external",
|
|
1087
|
+
provider: _input.mapped.sourceType,
|
|
1088
|
+
map: { adapterId, mappingId },
|
|
1089
|
+
metadata: {},
|
|
1090
|
+
},
|
|
1091
|
+
metadata: {
|
|
1092
|
+
...(await this.deps.catalogs.get(catalogId)).metadata,
|
|
1093
|
+
lastUpdate: now,
|
|
1094
|
+
},
|
|
1095
|
+
});
|
|
915
1096
|
}
|
|
916
1097
|
// Seed a minimal descriptor if none exists.
|
|
917
1098
|
const existingDescriptor = await this.deps.descriptors.get(catalogId);
|
|
@@ -959,12 +1140,19 @@ export class Catalox {
|
|
|
959
1140
|
if (!existing)
|
|
960
1141
|
throw new CatalogNotFoundError({ catalogId: _catalogId });
|
|
961
1142
|
const now = new Date().toISOString();
|
|
1143
|
+
if (_patch.catalogType != null) {
|
|
1144
|
+
await this.validateCatalogTypeForContext(_context, String(_patch.catalogType));
|
|
1145
|
+
}
|
|
962
1146
|
await this.deps.catalogs.upsert({
|
|
963
1147
|
...existing,
|
|
964
1148
|
...(_patch.name != null ? { name: _patch.name } : {}),
|
|
965
1149
|
...(_patch.description != null ? { description: _patch.description } : {}),
|
|
966
|
-
...(_patch.
|
|
967
|
-
|
|
1150
|
+
...(_patch.catalogType != null ? { catalogType: String(_patch.catalogType) } : {}),
|
|
1151
|
+
metadata: {
|
|
1152
|
+
...existing.metadata,
|
|
1153
|
+
...(_patch.metadata ?? {}),
|
|
1154
|
+
lastUpdate: now,
|
|
1155
|
+
},
|
|
968
1156
|
});
|
|
969
1157
|
return (await this.deps.catalogs.get(_catalogId));
|
|
970
1158
|
}
|
|
@@ -1264,12 +1452,67 @@ export class Catalox {
|
|
|
1264
1452
|
await this.deps.catalogs.upsert({
|
|
1265
1453
|
catalogId: catalog.catalogId,
|
|
1266
1454
|
name: catalog.name,
|
|
1455
|
+
catalogType: "generic",
|
|
1267
1456
|
sourceMode: "native",
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1457
|
+
catalogItems: { providerType: "internal", metadata: {} },
|
|
1458
|
+
metadata: {
|
|
1459
|
+
status: catalog.status ?? "active",
|
|
1460
|
+
createdAt: now,
|
|
1461
|
+
lastUpdate: now,
|
|
1462
|
+
},
|
|
1271
1463
|
});
|
|
1272
1464
|
}
|
|
1465
|
+
async resolveAllowedCatalogTypes(context) {
|
|
1466
|
+
if (!this.deps.catalogTypes)
|
|
1467
|
+
return null;
|
|
1468
|
+
return this.deps.catalogTypes.resolveForContext({
|
|
1469
|
+
...(context.storeId ? { storeId: context.storeId } : {}),
|
|
1470
|
+
...(context.appId ? { appId: context.appId } : {}),
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
async upsertCatalogTypeRegistry(context, scope, input) {
|
|
1474
|
+
if (!context.superAdmin)
|
|
1475
|
+
throw new CatalogAccessDeniedError({ reason: "super_admin_required" });
|
|
1476
|
+
if (!this.deps.catalogTypes)
|
|
1477
|
+
throw new Error("catalogTypes dependency is not configured");
|
|
1478
|
+
await this.deps.catalogTypes.upsert(scope, input);
|
|
1479
|
+
}
|
|
1480
|
+
async resolveAllowedDomains(context) {
|
|
1481
|
+
if (!this.deps.domains)
|
|
1482
|
+
return null;
|
|
1483
|
+
return this.deps.domains.resolveForContext({
|
|
1484
|
+
...(context.storeId ? { storeId: context.storeId } : {}),
|
|
1485
|
+
...(context.appId ? { appId: context.appId } : {}),
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
async upsertDomainRegistry(context, scope, input) {
|
|
1489
|
+
if (!context.superAdmin)
|
|
1490
|
+
throw new CatalogAccessDeniedError({ reason: "super_admin_required" });
|
|
1491
|
+
if (!this.deps.domains)
|
|
1492
|
+
throw new Error("domains dependency is not configured");
|
|
1493
|
+
await this.deps.domains.upsert(scope, input);
|
|
1494
|
+
}
|
|
1495
|
+
async resolveAllowedAgents(context) {
|
|
1496
|
+
if (!this.deps.agents)
|
|
1497
|
+
return null;
|
|
1498
|
+
return this.deps.agents.resolveForContext({
|
|
1499
|
+
...(context.storeId ? { storeId: context.storeId } : {}),
|
|
1500
|
+
...(context.appId ? { appId: context.appId } : {}),
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
async upsertAgentRegistry(context, scope, input) {
|
|
1504
|
+
if (!context.superAdmin)
|
|
1505
|
+
throw new CatalogAccessDeniedError({ reason: "super_admin_required" });
|
|
1506
|
+
if (!this.deps.agents)
|
|
1507
|
+
throw new Error("agents dependency is not configured");
|
|
1508
|
+
await this.deps.agents.upsert(scope, input);
|
|
1509
|
+
}
|
|
1510
|
+
async backfillCatalogModel(context, input = {}) {
|
|
1511
|
+
if (!context.superAdmin)
|
|
1512
|
+
throw new CatalogAccessDeniedError({ reason: "super_admin_required" });
|
|
1513
|
+
const fs = this.deps.firestoreStore.firestore;
|
|
1514
|
+
return backfillCatalogModel(fs, input);
|
|
1515
|
+
}
|
|
1273
1516
|
async ensureBinding(context, input) {
|
|
1274
1517
|
// only super-admin apps can provision cross-app bindings.
|
|
1275
1518
|
if (!context.superAdmin && (!context.appId || input.appId !== context.appId)) {
|
|
@@ -1334,7 +1577,13 @@ export class Catalox {
|
|
|
1334
1577
|
data: mergedData,
|
|
1335
1578
|
indexed: idx,
|
|
1336
1579
|
...(nextScope.kind !== "global" ? { scope: nextScope } : {}),
|
|
1337
|
-
|
|
1580
|
+
metadata: {
|
|
1581
|
+
...(existing.metadata ?? {}),
|
|
1582
|
+
createdAt: String(existing.metadata?.createdAt ?? updatedAt),
|
|
1583
|
+
lastUpdate: updatedAt,
|
|
1584
|
+
domainIds: Array.isArray(existing.metadata?.domainIds) ? existing.metadata.domainIds : [],
|
|
1585
|
+
agentIds: Array.isArray(existing.metadata?.agentIds) ? existing.metadata.agentIds : [],
|
|
1586
|
+
},
|
|
1338
1587
|
version: (existing.version ?? 0) + 1,
|
|
1339
1588
|
...(this.resolveActorId(_context) ? { updatedBy: this.resolveActorId(_context) } : {}),
|
|
1340
1589
|
};
|
|
@@ -1358,8 +1607,12 @@ export class Catalox {
|
|
|
1358
1607
|
sourceMode: "native",
|
|
1359
1608
|
sourceType: "firebase",
|
|
1360
1609
|
data: mergedData,
|
|
1361
|
-
|
|
1362
|
-
|
|
1610
|
+
metadata: {
|
|
1611
|
+
createdAt: String(existing.metadata?.createdAt ?? updatedAt),
|
|
1612
|
+
lastUpdate: updatedAt,
|
|
1613
|
+
domainIds: Array.isArray(existing.metadata?.domainIds) ? existing.metadata.domainIds : [],
|
|
1614
|
+
agentIds: Array.isArray(existing.metadata?.agentIds) ? existing.metadata.agentIds : [],
|
|
1615
|
+
},
|
|
1363
1616
|
};
|
|
1364
1617
|
return this.decorateItem(_catalogId, out);
|
|
1365
1618
|
}
|
|
@@ -1449,9 +1702,13 @@ export class Catalox {
|
|
|
1449
1702
|
appScopedOwnerId: appId,
|
|
1450
1703
|
...(indexed != null ? { indexed } : {}),
|
|
1451
1704
|
data,
|
|
1705
|
+
metadata: {
|
|
1706
|
+
createdAt: String(existing?.metadata?.createdAt ?? now),
|
|
1707
|
+
lastUpdate: now,
|
|
1708
|
+
domainIds: Array.isArray(existing?.metadata?.domainIds) ? (existing?.metadata).domainIds : [],
|
|
1709
|
+
agentIds: Array.isArray(existing?.metadata?.agentIds) ? (existing?.metadata).agentIds : [],
|
|
1710
|
+
},
|
|
1452
1711
|
version: (existing?.version ?? 0) + 1,
|
|
1453
|
-
...(existing?.createdAt ? { createdAt: existing.createdAt } : { createdAt: now }),
|
|
1454
|
-
updatedAt: now,
|
|
1455
1712
|
...(existing?.createdBy ? { createdBy: existing.createdBy } : actorId ? { createdBy: actorId } : {}),
|
|
1456
1713
|
...(actorId ? { updatedBy: actorId } : {}),
|
|
1457
1714
|
});
|
|
@@ -1471,8 +1728,12 @@ export class Catalox {
|
|
|
1471
1728
|
sourceMode: "native",
|
|
1472
1729
|
sourceType: "firebase",
|
|
1473
1730
|
data,
|
|
1474
|
-
|
|
1475
|
-
|
|
1731
|
+
metadata: {
|
|
1732
|
+
createdAt: String(existing?.metadata?.createdAt ?? now),
|
|
1733
|
+
lastUpdate: now,
|
|
1734
|
+
domainIds: Array.isArray(existing?.metadata?.domainIds) ? (existing?.metadata).domainIds : [],
|
|
1735
|
+
agentIds: Array.isArray(existing?.metadata?.agentIds) ? (existing?.metadata).agentIds : [],
|
|
1736
|
+
},
|
|
1476
1737
|
};
|
|
1477
1738
|
}
|
|
1478
1739
|
async batchUpsertNativeCatalogItems(context, catalogId, items) {
|
|
@@ -1501,8 +1762,7 @@ export class Catalox {
|
|
|
1501
1762
|
appScopedOwnerId: appId,
|
|
1502
1763
|
...(indexed != null ? { indexed } : {}),
|
|
1503
1764
|
data: stripped.data,
|
|
1504
|
-
createdAt: now,
|
|
1505
|
-
updatedAt: now,
|
|
1765
|
+
metadata: { createdAt: now, lastUpdate: now, domainIds: [], agentIds: [] },
|
|
1506
1766
|
...(actorId ? { updatedBy: actorId } : {}),
|
|
1507
1767
|
};
|
|
1508
1768
|
});
|
|
@@ -1622,13 +1882,13 @@ export class Catalox {
|
|
|
1622
1882
|
itemId: it.itemId,
|
|
1623
1883
|
...(it.title ? { title: it.title } : {}),
|
|
1624
1884
|
...(it.subtitle ? { subtitle: it.subtitle } : {}),
|
|
1625
|
-
...(it.
|
|
1885
|
+
...(it.metadata?.lastUpdate ? { updatedAt: String(it.metadata.lastUpdate) } : {}),
|
|
1626
1886
|
}));
|
|
1627
1887
|
catalogStats.push({
|
|
1628
1888
|
catalogId,
|
|
1629
1889
|
label: c.label,
|
|
1630
1890
|
sourceMode: c.sourceMode,
|
|
1631
|
-
status: c.status,
|
|
1891
|
+
status: c.metadata.status,
|
|
1632
1892
|
...(list.issues ? { mappingIssueCount: list.issues.length } : {}),
|
|
1633
1893
|
topExamples: examples,
|
|
1634
1894
|
});
|
|
@@ -1705,8 +1965,9 @@ export class Catalox {
|
|
|
1705
1965
|
if (!catalog)
|
|
1706
1966
|
throw new CatalogNotFoundError({ catalogId: _catalogId });
|
|
1707
1967
|
const def = await this.deps.definitions.get(_catalogId);
|
|
1708
|
-
if (!def || def.
|
|
1968
|
+
if (!def || def.catalogItems.providerType !== "external") {
|
|
1709
1969
|
throw new CatalogAdapterError({ catalogId: _catalogId, reason: "not_mapped" });
|
|
1970
|
+
}
|
|
1710
1971
|
const mode = def.sync?.mode;
|
|
1711
1972
|
if (mode !== "snapshot")
|
|
1712
1973
|
return { syncStatus: "idle" };
|
|
@@ -1726,7 +1987,7 @@ export class Catalox {
|
|
|
1726
1987
|
itemId: item.itemId,
|
|
1727
1988
|
catalogId: _catalogId,
|
|
1728
1989
|
sourceFingerprint: this.fingerprint(item.data),
|
|
1729
|
-
...(item.
|
|
1990
|
+
...(item.metadata?.lastUpdate ? { sourceUpdatedAt: String(item.metadata.lastUpdate) } : {}),
|
|
1730
1991
|
data: item.data,
|
|
1731
1992
|
sync: { lastSyncedAt: now, syncStatus: "success" },
|
|
1732
1993
|
...(actorId ? { metadata: { syncedBy: actorId } } : {}),
|
|
@@ -1920,8 +2181,7 @@ export class Catalox {
|
|
|
1920
2181
|
sourceMode: "native",
|
|
1921
2182
|
sourceType: "firebase",
|
|
1922
2183
|
data: liveAfter.data,
|
|
1923
|
-
|
|
1924
|
-
updatedAt: liveAfter.updatedAt,
|
|
2184
|
+
metadata: liveAfter.metadata ?? {},
|
|
1925
2185
|
});
|
|
1926
2186
|
}
|
|
1927
2187
|
async replayCatalogToPointInTime(context, catalogId, input) {
|