@x12i/catalox 1.2.0 → 2.0.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 +424 -406
- package/dist/src/catalox/catalox.d.ts +35 -1
- package/dist/src/catalox/catalox.d.ts.map +1 -1
- package/dist/src/catalox/catalox.js +460 -5
- package/dist/src/catalox/catalox.js.map +1 -1
- package/dist/src/catalox/field-source-resolution.d.ts +16 -0
- package/dist/src/catalox/field-source-resolution.d.ts.map +1 -0
- package/dist/src/catalox/field-source-resolution.js +43 -0
- package/dist/src/catalox/field-source-resolution.js.map +1 -0
- package/dist/src/catalox/reporting/render-inventory-report.d.ts +3 -0
- package/dist/src/catalox/reporting/render-inventory-report.d.ts.map +1 -0
- package/dist/src/catalox/reporting/render-inventory-report.js +78 -0
- package/dist/src/catalox/reporting/render-inventory-report.js.map +1 -0
- package/dist/src/cli/index.d.ts +3 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +267 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/contracts/context.d.ts +7 -1
- package/dist/src/contracts/context.d.ts.map +1 -1
- package/dist/src/contracts/descriptors.d.ts +6 -0
- package/dist/src/contracts/descriptors.d.ts.map +1 -1
- package/dist/src/contracts/diagram-map.d.ts +24 -0
- package/dist/src/contracts/diagram-map.d.ts.map +1 -0
- package/dist/src/contracts/diagram-map.js +2 -0
- package/dist/src/contracts/diagram-map.js.map +1 -0
- package/dist/src/contracts/field-source.d.ts +43 -0
- package/dist/src/contracts/field-source.d.ts.map +1 -0
- package/dist/src/contracts/field-source.js +2 -0
- package/dist/src/contracts/field-source.js.map +1 -0
- package/dist/src/contracts/filters.d.ts +59 -0
- package/dist/src/contracts/filters.d.ts.map +1 -0
- package/dist/src/contracts/filters.js +2 -0
- package/dist/src/contracts/filters.js.map +1 -0
- package/dist/src/contracts/ids.d.ts +1 -0
- package/dist/src/contracts/ids.d.ts.map +1 -1
- package/dist/src/contracts/index.d.ts +11 -1
- package/dist/src/contracts/index.d.ts.map +1 -1
- package/dist/src/contracts/index.js.map +1 -1
- package/dist/src/contracts/inventory-export.d.ts +40 -0
- package/dist/src/contracts/inventory-export.d.ts.map +1 -0
- package/dist/src/contracts/inventory-export.js +2 -0
- package/dist/src/contracts/inventory-export.js.map +1 -0
- package/dist/src/contracts/inventory-report.d.ts +61 -0
- package/dist/src/contracts/inventory-report.d.ts.map +1 -0
- package/dist/src/contracts/inventory-report.js +2 -0
- package/dist/src/contracts/inventory-report.js.map +1 -0
- package/dist/src/contracts/items.d.ts +3 -0
- package/dist/src/contracts/items.d.ts.map +1 -1
- package/dist/src/contracts/markdown-map.d.ts +42 -0
- package/dist/src/contracts/markdown-map.d.ts.map +1 -0
- package/dist/src/contracts/markdown-map.js +2 -0
- package/dist/src/contracts/markdown-map.js.map +1 -0
- package/dist/src/contracts/presentation.d.ts +149 -0
- package/dist/src/contracts/presentation.d.ts.map +1 -0
- package/dist/src/contracts/presentation.js +2 -0
- package/dist/src/contracts/presentation.js.map +1 -0
- package/dist/src/contracts/render-map.d.ts +135 -0
- package/dist/src/contracts/render-map.d.ts.map +1 -0
- package/dist/src/contracts/render-map.js +2 -0
- package/dist/src/contracts/render-map.js.map +1 -0
- package/dist/src/contracts/snapshots.d.ts +12 -0
- package/dist/src/contracts/snapshots.d.ts.map +1 -0
- package/dist/src/contracts/snapshots.js +2 -0
- package/dist/src/contracts/snapshots.js.map +1 -0
- package/dist/src/contracts/stores.d.ts +21 -0
- package/dist/src/contracts/stores.d.ts.map +1 -0
- package/dist/src/contracts/stores.js +2 -0
- package/dist/src/contracts/stores.js.map +1 -0
- package/dist/src/diagrams/render-catalog-diagram.d.ts +3 -0
- package/dist/src/diagrams/render-catalog-diagram.d.ts.map +1 -0
- package/dist/src/diagrams/render-catalog-diagram.js +27 -0
- package/dist/src/diagrams/render-catalog-diagram.js.map +1 -0
- package/dist/src/diagrams/render-item-diagram.d.ts +3 -0
- package/dist/src/diagrams/render-item-diagram.d.ts.map +1 -0
- package/dist/src/diagrams/render-item-diagram.js +26 -0
- package/dist/src/diagrams/render-item-diagram.js.map +1 -0
- package/dist/src/firebase/index.d.ts +1 -0
- package/dist/src/firebase/index.d.ts.map +1 -1
- package/dist/src/firebase/index.js +1 -0
- package/dist/src/firebase/index.js.map +1 -1
- package/dist/src/firebase/snapshot-store.d.ts +7 -0
- package/dist/src/firebase/snapshot-store.d.ts.map +1 -1
- package/dist/src/firebase/snapshot-store.js +30 -0
- package/dist/src/firebase/snapshot-store.js.map +1 -1
- package/dist/src/firebase/store-app-binding-store.d.ts +14 -0
- package/dist/src/firebase/store-app-binding-store.d.ts.map +1 -0
- package/dist/src/firebase/store-app-binding-store.js +36 -0
- package/dist/src/firebase/store-app-binding-store.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/markdown/render-item-markdown.d.ts +5 -0
- package/dist/src/markdown/render-item-markdown.d.ts.map +1 -0
- package/dist/src/markdown/render-item-markdown.js +83 -0
- package/dist/src/markdown/render-item-markdown.js.map +1 -0
- package/dist/src/markdown/render-list-markdown.d.ts +5 -0
- package/dist/src/markdown/render-list-markdown.d.ts.map +1 -0
- package/dist/src/markdown/render-list-markdown.js +90 -0
- package/dist/src/markdown/render-list-markdown.js.map +1 -0
- package/dist/src/validation/index.d.ts +3 -1
- 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 +19 -0
- package/dist/src/validation/ui-spec-schema.d.ts.map +1 -0
- package/dist/src/validation/ui-spec-schema.js +547 -0
- package/dist/src/validation/ui-spec-schema.js.map +1 -0
- package/dist/src/validation/ui-spec-validate.d.ts +19 -0
- package/dist/src/validation/ui-spec-validate.d.ts.map +1 -0
- package/dist/src/validation/ui-spec-validate.js +69 -0
- package/dist/src/validation/ui-spec-validate.js.map +1 -0
- package/dist/test/unit/field-source-resolution.test.d.ts +2 -0
- package/dist/test/unit/field-source-resolution.test.d.ts.map +1 -0
- package/dist/test/unit/field-source-resolution.test.js +45 -0
- package/dist/test/unit/field-source-resolution.test.js.map +1 -0
- package/dist/test/unit/markdown-and-diagrams.test.d.ts +2 -0
- package/dist/test/unit/markdown-and-diagrams.test.d.ts.map +1 -0
- package/dist/test/unit/markdown-and-diagrams.test.js +62 -0
- package/dist/test/unit/markdown-and-diagrams.test.js.map +1 -0
- package/dist/test/unit/ui-spec-validation.test.d.ts +2 -0
- package/dist/test/unit/ui-spec-validation.test.d.ts.map +1 -0
- package/dist/test/unit/ui-spec-validation.test.js +78 -0
- package/dist/test/unit/ui-spec-validation.test.js.map +1 -0
- package/package.json +10 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { AppCatalogBootstrap, AppCatalogEntry, CatalogDescriptor, CatalogItemReference, CatalogItemValidationReport, CatalogListResult, CatalogQueryOptions, CatalogValidationReport, CreateCatalogInput, ItemId, ListAppCatalogsInput, UnifiedCatalogItem, UpdateCatalogInput } from "../contracts/index.js";
|
|
1
|
+
import type { AppCatalogBootstrap, AppCatalogEntry, CatalogDescriptor, CatalogItemReference, CatalogItemValidationReport, CatalogListResult, CatalogQueryOptions, CatalogValidationReport, CreateCatalogInput, ExportInventoryData, ExportInventoryInput, InventoryReportData, InventoryReportInput, ItemId, StoreId, StoreAppBindingRecord, ListAppCatalogsInput, UnifiedCatalogItem, UpdateCatalogInput } from "../contracts/index.js";
|
|
2
2
|
import type { AppId, CatalogId } from "../contracts/ids.js";
|
|
3
3
|
import { ApiCatalogAdapter } from "../adapters/api/api-adapter.js";
|
|
4
4
|
import { MongoCatalogAdapter } from "../adapters/mongo/mongo-adapter.js";
|
|
5
5
|
import { AuthorizationService } from "./authorization.js";
|
|
6
6
|
import type { CataloxContext } from "../contracts/context.js";
|
|
7
|
+
import type { CatalogListRenderMap, CatalogItemRenderMap } from "../contracts/render-map.js";
|
|
7
8
|
import { AppStore } from "../firebase/app-store.js";
|
|
8
9
|
import { AdapterStore } from "../firebase/adapter-store.js";
|
|
9
10
|
import { BindingStore } from "../firebase/binding-store.js";
|
|
@@ -14,10 +15,12 @@ import { MappingStore } from "../firebase/mapping-store.js";
|
|
|
14
15
|
import { NativeItemStore } from "../firebase/native-item-store.js";
|
|
15
16
|
import { ReferenceStore } from "../firebase/reference-store.js";
|
|
16
17
|
import { SnapshotStore } from "../firebase/snapshot-store.js";
|
|
18
|
+
import { StoreAppBindingStore } from "../firebase/store-app-binding-store.js";
|
|
17
19
|
export type CataloxDependencies = {
|
|
18
20
|
apps: AppStore;
|
|
19
21
|
catalogs: CatalogStore;
|
|
20
22
|
bindings: BindingStore;
|
|
23
|
+
storeAppBindings?: StoreAppBindingStore;
|
|
21
24
|
definitions: DefinitionStore;
|
|
22
25
|
mappings: MappingStore;
|
|
23
26
|
descriptors: DescriptorStore;
|
|
@@ -32,7 +35,25 @@ export type CataloxDependencies = {
|
|
|
32
35
|
export declare class Catalox {
|
|
33
36
|
private readonly deps;
|
|
34
37
|
constructor(deps: CataloxDependencies);
|
|
38
|
+
private resolveActorId;
|
|
39
|
+
private stableStringify;
|
|
40
|
+
private fingerprint;
|
|
41
|
+
private resolveEffectiveAppIds;
|
|
35
42
|
private readPath;
|
|
43
|
+
private gatherDescriptorSources;
|
|
44
|
+
private resolveFieldSourceOptions;
|
|
45
|
+
buildCatalogListRenderMap(context: CataloxContext, catalogId: CatalogId, options?: {
|
|
46
|
+
limit?: number;
|
|
47
|
+
resolveSources?: boolean;
|
|
48
|
+
maxSourceOptions?: number;
|
|
49
|
+
displayContext?: "grid" | "list" | "cards";
|
|
50
|
+
}): Promise<CatalogListRenderMap>;
|
|
51
|
+
buildCatalogItemRenderMap(context: CataloxContext, catalogId: CatalogId, itemId: ItemId, options?: {
|
|
52
|
+
includeReferences?: boolean;
|
|
53
|
+
resolveSources?: boolean;
|
|
54
|
+
maxSourceOptions?: number;
|
|
55
|
+
displayContext?: "form" | "card" | "grid-row" | "list-row";
|
|
56
|
+
}): Promise<CatalogItemRenderMap>;
|
|
36
57
|
private deriveIndexed;
|
|
37
58
|
private stripReservedWriteFields;
|
|
38
59
|
private decorateItem;
|
|
@@ -52,6 +73,13 @@ export declare class Catalox {
|
|
|
52
73
|
listCatalogs(_context: CataloxContext, _options?: CatalogQueryOptions): Promise<import("../contracts/catalogs.js").CatalogRecord[]>;
|
|
53
74
|
bindCatalogToApp(_context: CataloxContext, _input: import("../contracts/bindings.js").BindCatalogInput): Promise<any>;
|
|
54
75
|
unbindCatalogFromApp(_context: CataloxContext, _appId: AppId, _catalogId: CatalogId): Promise<void>;
|
|
76
|
+
bindAppToStore(context: CataloxContext, input: {
|
|
77
|
+
storeId: StoreId;
|
|
78
|
+
appId: AppId;
|
|
79
|
+
metadata?: Record<string, unknown>;
|
|
80
|
+
}): Promise<StoreAppBindingRecord>;
|
|
81
|
+
unbindAppFromStore(context: CataloxContext, storeId: StoreId, appId: AppId): Promise<void>;
|
|
82
|
+
listAppsForStore(context: CataloxContext, storeId: StoreId): Promise<StoreAppBindingRecord[]>;
|
|
55
83
|
ensureCatalog(context: CataloxContext, catalog: {
|
|
56
84
|
catalogId: CatalogId;
|
|
57
85
|
name: string;
|
|
@@ -73,6 +101,12 @@ export declare class Catalox {
|
|
|
73
101
|
batchUpsertNativeCatalogItems(context: CataloxContext, catalogId: CatalogId, items: Array<Record<string, unknown>>): Promise<void>;
|
|
74
102
|
importCatalogItemsFromJson<T = unknown>(json: string): T;
|
|
75
103
|
exportCatalogItemsToJson(value: unknown, pretty?: boolean): string;
|
|
104
|
+
exportInventory(context: CataloxContext, input?: ExportInventoryInput): Promise<ExportInventoryData>;
|
|
105
|
+
exportInventoryToJson(context: CataloxContext, input?: ExportInventoryInput, pretty?: boolean): Promise<string>;
|
|
106
|
+
generateInventoryReport(context: CataloxContext, input?: InventoryReportInput): Promise<{
|
|
107
|
+
markdown: string;
|
|
108
|
+
data: InventoryReportData;
|
|
109
|
+
}>;
|
|
76
110
|
syncMappedCatalog(_context: CataloxContext, _catalogId: CatalogId): Promise<{
|
|
77
111
|
syncStatus: "idle";
|
|
78
112
|
lastSyncedAt?: never;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalox.d.ts","sourceRoot":"","sources":["../../../src/catalox/catalox.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,iBAAiB,
|
|
1
|
+
{"version":3,"file":"catalox.d.ts","sourceRoot":"","sources":["../../../src/catalox/catalox.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,9 @@ import { MongoCatalogAdapter } from "../adapters/mongo/mongo-adapter.js";
|
|
|
6
6
|
import { AuthorizationService } from "./authorization.js";
|
|
7
7
|
import { resolveCatalogItemId } from "./identity.js";
|
|
8
8
|
import { parseJson, toJson } from "./json-io.js";
|
|
9
|
-
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
10
|
+
import { renderInventoryReportMarkdown } from "./reporting/render-inventory-report.js";
|
|
11
|
+
import { optionsFromCatalogItems, optionsFromStaticSource, resolveFilterBy } from "./field-source-resolution.js";
|
|
10
12
|
import { AppStore } from "../firebase/app-store.js";
|
|
11
13
|
import { AdapterStore } from "../firebase/adapter-store.js";
|
|
12
14
|
import { BindingStore } from "../firebase/binding-store.js";
|
|
@@ -17,11 +19,47 @@ import { MappingStore } from "../firebase/mapping-store.js";
|
|
|
17
19
|
import { NativeItemStore } from "../firebase/native-item-store.js";
|
|
18
20
|
import { ReferenceStore } from "../firebase/reference-store.js";
|
|
19
21
|
import { SnapshotStore } from "../firebase/snapshot-store.js";
|
|
22
|
+
import { StoreAppBindingStore } from "../firebase/store-app-binding-store.js";
|
|
20
23
|
export class Catalox {
|
|
21
24
|
deps;
|
|
22
25
|
constructor(deps) {
|
|
23
26
|
this.deps = deps;
|
|
24
27
|
}
|
|
28
|
+
resolveActorId(context) {
|
|
29
|
+
return context.userId ?? context.actor?.id;
|
|
30
|
+
}
|
|
31
|
+
stableStringify(value) {
|
|
32
|
+
const seen = new WeakSet();
|
|
33
|
+
const walk = (v) => {
|
|
34
|
+
if (v == null || typeof v !== "object")
|
|
35
|
+
return v;
|
|
36
|
+
if (seen.has(v))
|
|
37
|
+
return "[Circular]";
|
|
38
|
+
seen.add(v);
|
|
39
|
+
if (Array.isArray(v))
|
|
40
|
+
return v.map(walk);
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const k of Object.keys(v).sort())
|
|
43
|
+
out[k] = walk(v[k]);
|
|
44
|
+
return out;
|
|
45
|
+
};
|
|
46
|
+
return JSON.stringify(walk(value));
|
|
47
|
+
}
|
|
48
|
+
fingerprint(value) {
|
|
49
|
+
return createHash("sha256").update(this.stableStringify(value)).digest("hex");
|
|
50
|
+
}
|
|
51
|
+
async resolveEffectiveAppIds(context, input) {
|
|
52
|
+
const storeId = input?.storeId ?? context.storeId;
|
|
53
|
+
if (input?.appIds?.length)
|
|
54
|
+
return { ...(storeId ? { storeId } : {}), appIds: input.appIds };
|
|
55
|
+
if (storeId && this.deps.storeAppBindings) {
|
|
56
|
+
const bindings = await this.deps.storeAppBindings.listAppsByStore(storeId);
|
|
57
|
+
const active = bindings.filter((b) => b.status === "active").map((b) => b.appId);
|
|
58
|
+
if (active.length)
|
|
59
|
+
return { storeId, appIds: active };
|
|
60
|
+
}
|
|
61
|
+
return { ...(storeId ? { storeId } : {}), appIds: [context.appId] };
|
|
62
|
+
}
|
|
25
63
|
readPath(obj, path) {
|
|
26
64
|
if (!path)
|
|
27
65
|
return undefined;
|
|
@@ -34,6 +72,171 @@ export class Catalox {
|
|
|
34
72
|
}
|
|
35
73
|
return cur;
|
|
36
74
|
}
|
|
75
|
+
gatherDescriptorSources(descriptor) {
|
|
76
|
+
const out = {};
|
|
77
|
+
if (!descriptor)
|
|
78
|
+
return out;
|
|
79
|
+
const filterSpec = descriptor.filterSpec;
|
|
80
|
+
for (const f of filterSpec?.filters ?? []) {
|
|
81
|
+
if (f?.fieldPath && f.source)
|
|
82
|
+
out[String(f.fieldPath)] = f.source;
|
|
83
|
+
}
|
|
84
|
+
const presentationFields = descriptor.presentationSpec?.fields;
|
|
85
|
+
for (const fp of presentationFields ?? []) {
|
|
86
|
+
if (fp?.fieldPath && fp.source)
|
|
87
|
+
out[String(fp.fieldPath)] = fp.source;
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
async resolveFieldSourceOptions(params) {
|
|
92
|
+
const maxOptions = params.maxOptions ?? 200;
|
|
93
|
+
const source = params.source;
|
|
94
|
+
if (source.type === "static") {
|
|
95
|
+
return optionsFromStaticSource(source);
|
|
96
|
+
}
|
|
97
|
+
if (source.type === "field-distinct") {
|
|
98
|
+
// Distinct values for this field in the current catalog.
|
|
99
|
+
const list = await this.listCatalogItems(params.context, params.currentCatalogId, { limit: 500 });
|
|
100
|
+
const values = new Map();
|
|
101
|
+
for (const it of list.items) {
|
|
102
|
+
const v = this.readPath(it.data, params.fieldPath);
|
|
103
|
+
if (v == null)
|
|
104
|
+
continue;
|
|
105
|
+
const value = typeof v === "string" || typeof v === "number" ? v : String(v);
|
|
106
|
+
if (!values.has(value))
|
|
107
|
+
values.set(value, { value, label: String(value) });
|
|
108
|
+
}
|
|
109
|
+
return [...values.values()].slice(0, maxOptions);
|
|
110
|
+
}
|
|
111
|
+
if (source.type === "catalog") {
|
|
112
|
+
// Enforce authz: safest default is to require binding (unless god-mode).
|
|
113
|
+
await this.deps.authz.requireBindingAccess(params.context, params.context.appId, source.catalogId, "read");
|
|
114
|
+
const filterEq = resolveFilterBy(source.filterBy, params.currentItemData, (o, p) => this.readPath(o, p));
|
|
115
|
+
const sort = source.sortBy
|
|
116
|
+
? { [source.sortBy.field]: source.sortBy.direction === "asc" ? 1 : -1 }
|
|
117
|
+
: undefined;
|
|
118
|
+
const list = await this.listCatalogItems(params.context, source.catalogId, {
|
|
119
|
+
limit: maxOptions,
|
|
120
|
+
...(Object.keys(filterEq).length ? { filter: filterEq } : {}),
|
|
121
|
+
...(sort ? { sort } : {}),
|
|
122
|
+
});
|
|
123
|
+
return optionsFromCatalogItems({
|
|
124
|
+
items: list.items.map((i) => ({ data: i.data })),
|
|
125
|
+
valueField: source.valueField,
|
|
126
|
+
labelField: source.labelField,
|
|
127
|
+
readPath: (o, p) => this.readPath(o, p),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
async buildCatalogListRenderMap(context, catalogId, options) {
|
|
133
|
+
const descriptor = await this.getCatalogDescriptor(context, catalogId);
|
|
134
|
+
const list = await this.listCatalogItems(context, catalogId, { limit: options?.limit ?? 50 });
|
|
135
|
+
const sources = this.gatherDescriptorSources(descriptor);
|
|
136
|
+
const resolvedSources = {};
|
|
137
|
+
if (options?.resolveSources !== false) {
|
|
138
|
+
for (const [fieldPath, source] of Object.entries(sources)) {
|
|
139
|
+
// In list context we do not have current item data; $. paths in filterBy will be ignored.
|
|
140
|
+
resolvedSources[fieldPath] = await this.resolveFieldSourceOptions({
|
|
141
|
+
context,
|
|
142
|
+
currentCatalogId: catalogId,
|
|
143
|
+
fieldPath,
|
|
144
|
+
source,
|
|
145
|
+
...(options?.maxSourceOptions != null ? { maxOptions: options.maxSourceOptions } : {}),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
catalogId: String(catalogId),
|
|
151
|
+
label: descriptor?.label ?? String(catalogId),
|
|
152
|
+
...(descriptor?.itemLabel != null ? { itemLabel: descriptor.itemLabel } : {}),
|
|
153
|
+
items: list.items.map((i) => ({
|
|
154
|
+
itemId: String(i.itemId),
|
|
155
|
+
data: (i.data ?? {}),
|
|
156
|
+
...(i.title != null ? { title: i.title } : {}),
|
|
157
|
+
...(i.subtitle != null ? { subtitle: i.subtitle } : {}),
|
|
158
|
+
...(i.status != null ? { status: i.status } : {}),
|
|
159
|
+
...(i.metadata != null ? { metadata: i.metadata } : {}),
|
|
160
|
+
})),
|
|
161
|
+
...(list.total != null ? { total: list.total } : {}),
|
|
162
|
+
resolvedSources,
|
|
163
|
+
filters: {
|
|
164
|
+
active: {},
|
|
165
|
+
...(descriptor?.filterSpec ? { spec: descriptor.filterSpec } : {}),
|
|
166
|
+
},
|
|
167
|
+
sort: null,
|
|
168
|
+
pagination: { page: 1, pageSize: options?.limit ?? 50, hasMore: Boolean(list.nextCursor) },
|
|
169
|
+
selection: [],
|
|
170
|
+
...(descriptor?.presentationSpec ? { presentation: descriptor.presentationSpec } : {}),
|
|
171
|
+
capabilities: descriptor?.capabilities ?? {
|
|
172
|
+
canList: true,
|
|
173
|
+
canGet: true,
|
|
174
|
+
canCreate: false,
|
|
175
|
+
canEdit: false,
|
|
176
|
+
canDelete: false,
|
|
177
|
+
},
|
|
178
|
+
context: { mode: "view", displayContext: options?.displayContext ?? "grid" },
|
|
179
|
+
actions: {},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async buildCatalogItemRenderMap(context, catalogId, itemId, options) {
|
|
183
|
+
const descriptor = await this.getCatalogDescriptor(context, catalogId);
|
|
184
|
+
const item = await this.getCatalogItem(context, catalogId, itemId);
|
|
185
|
+
if (!item)
|
|
186
|
+
throw new CatalogNotFoundError({ catalogId, itemId });
|
|
187
|
+
const sources = this.gatherDescriptorSources(descriptor);
|
|
188
|
+
const resolvedSources = {};
|
|
189
|
+
if (options?.resolveSources !== false) {
|
|
190
|
+
for (const [fieldPath, source] of Object.entries(sources)) {
|
|
191
|
+
resolvedSources[fieldPath] = await this.resolveFieldSourceOptions({
|
|
192
|
+
context,
|
|
193
|
+
currentCatalogId: catalogId,
|
|
194
|
+
fieldPath,
|
|
195
|
+
source,
|
|
196
|
+
currentItemData: (item.data ?? {}),
|
|
197
|
+
...(options?.maxSourceOptions != null ? { maxOptions: options.maxSourceOptions } : {}),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const refs = options?.includeReferences
|
|
202
|
+
? await this.getCatalogItemReferences(context, catalogId, itemId)
|
|
203
|
+
: [];
|
|
204
|
+
return {
|
|
205
|
+
catalogId: String(catalogId),
|
|
206
|
+
itemId: String(itemId),
|
|
207
|
+
item: {
|
|
208
|
+
data: (item.data ?? {}),
|
|
209
|
+
...(item.title != null ? { title: item.title } : {}),
|
|
210
|
+
...(item.subtitle != null ? { subtitle: item.subtitle } : {}),
|
|
211
|
+
...(item.status != null ? { status: item.status } : {}),
|
|
212
|
+
...(item.metadata != null ? { metadata: item.metadata } : {}),
|
|
213
|
+
...(item.createdAt != null ? { createdAt: item.createdAt } : {}),
|
|
214
|
+
...(item.updatedAt != null ? { updatedAt: item.updatedAt } : {}),
|
|
215
|
+
},
|
|
216
|
+
resolvedSources,
|
|
217
|
+
...(refs.length
|
|
218
|
+
? {
|
|
219
|
+
references: refs.map((r) => ({
|
|
220
|
+
toCatalogId: String(r.toCatalogId),
|
|
221
|
+
toItemId: String(r.toItemId),
|
|
222
|
+
relationType: String(r.relationType),
|
|
223
|
+
...(r.label != null ? { label: r.label } : {}),
|
|
224
|
+
...(r.metadata != null ? { metadata: r.metadata } : {}),
|
|
225
|
+
})),
|
|
226
|
+
}
|
|
227
|
+
: {}),
|
|
228
|
+
...(descriptor?.presentationSpec ? { presentation: descriptor.presentationSpec } : {}),
|
|
229
|
+
capabilities: descriptor?.capabilities ?? {
|
|
230
|
+
canList: true,
|
|
231
|
+
canGet: true,
|
|
232
|
+
canCreate: false,
|
|
233
|
+
canEdit: false,
|
|
234
|
+
canDelete: false,
|
|
235
|
+
},
|
|
236
|
+
context: { mode: "view", displayContext: options?.displayContext ?? "form" },
|
|
237
|
+
actions: {},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
37
240
|
deriveIndexed(descriptor, data) {
|
|
38
241
|
const out = {};
|
|
39
242
|
for (const f of descriptor.queryableFields ?? []) {
|
|
@@ -381,6 +584,57 @@ export class Catalox {
|
|
|
381
584
|
updatedAt: new Date().toISOString(),
|
|
382
585
|
});
|
|
383
586
|
}
|
|
587
|
+
async bindAppToStore(context, input) {
|
|
588
|
+
if (!this.deps.storeAppBindings)
|
|
589
|
+
throw new Error("storeAppBindings dependency is not configured");
|
|
590
|
+
if (!context.isGodMode && input.appId !== context.appId) {
|
|
591
|
+
throw new CatalogAccessDeniedError({ reason: "not_god_mode" });
|
|
592
|
+
}
|
|
593
|
+
const existing = await this.deps.storeAppBindings.findByStoreApp(input.storeId, input.appId);
|
|
594
|
+
if (existing)
|
|
595
|
+
return existing;
|
|
596
|
+
const now = new Date().toISOString();
|
|
597
|
+
const actorId = this.resolveActorId(context);
|
|
598
|
+
const record = {
|
|
599
|
+
bindingId: `${String(input.storeId)}:${String(input.appId)}`,
|
|
600
|
+
storeId: input.storeId,
|
|
601
|
+
appId: input.appId,
|
|
602
|
+
status: "active",
|
|
603
|
+
...(input.metadata != null ? { metadata: input.metadata } : {}),
|
|
604
|
+
createdAt: now,
|
|
605
|
+
updatedAt: now,
|
|
606
|
+
...(actorId ? { createdBy: actorId, updatedBy: actorId } : {}),
|
|
607
|
+
};
|
|
608
|
+
await this.deps.storeAppBindings.upsert(record);
|
|
609
|
+
return record;
|
|
610
|
+
}
|
|
611
|
+
async unbindAppFromStore(context, storeId, appId) {
|
|
612
|
+
if (!this.deps.storeAppBindings)
|
|
613
|
+
throw new Error("storeAppBindings dependency is not configured");
|
|
614
|
+
if (!context.isGodMode && appId !== context.appId) {
|
|
615
|
+
throw new CatalogAccessDeniedError({ reason: "not_god_mode" });
|
|
616
|
+
}
|
|
617
|
+
const existing = await this.deps.storeAppBindings.findByStoreApp(storeId, appId);
|
|
618
|
+
if (!existing)
|
|
619
|
+
return;
|
|
620
|
+
const actorId = this.resolveActorId(context);
|
|
621
|
+
await this.deps.storeAppBindings.upsert({
|
|
622
|
+
...existing,
|
|
623
|
+
status: "disabled",
|
|
624
|
+
updatedAt: new Date().toISOString(),
|
|
625
|
+
...(actorId ? { updatedBy: actorId } : {}),
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
async listAppsForStore(context, storeId) {
|
|
629
|
+
if (!this.deps.storeAppBindings)
|
|
630
|
+
throw new Error("storeAppBindings dependency is not configured");
|
|
631
|
+
// listing is allowed for any caller; enforce god-mode only if you want to hide cross-app membership.
|
|
632
|
+
const records = await this.deps.storeAppBindings.listAppsByStore(storeId);
|
|
633
|
+
if (context.isGodMode)
|
|
634
|
+
return records;
|
|
635
|
+
// non-god: only reveal memberships that include the caller's own appId
|
|
636
|
+
return records.filter((r) => r.appId === context.appId);
|
|
637
|
+
}
|
|
384
638
|
async ensureCatalog(context, catalog) {
|
|
385
639
|
await this.deps.authz.requireBindingAccess(context, context.appId, catalog.catalogId, "admin");
|
|
386
640
|
const existing = await this.deps.catalogs.get(catalog.catalogId);
|
|
@@ -429,10 +683,13 @@ export class Catalox {
|
|
|
429
683
|
throw new CatalogNotFoundError({ catalogId: _catalogId, itemId: _itemId });
|
|
430
684
|
const updatedAt = new Date().toISOString();
|
|
431
685
|
const merged = { ...(existing.data ?? {}), ..._patch };
|
|
686
|
+
const actorId = this.resolveActorId(_context);
|
|
432
687
|
await this.deps.nativeItems.upsert(_catalogId, {
|
|
433
688
|
...existing,
|
|
434
689
|
data: merged,
|
|
435
690
|
updatedAt,
|
|
691
|
+
...(actorId ? { updatedBy: actorId } : {}),
|
|
692
|
+
version: (existing.version ?? 0) + 1,
|
|
436
693
|
});
|
|
437
694
|
const out = {
|
|
438
695
|
itemId: _itemId,
|
|
@@ -459,14 +716,19 @@ export class Catalox {
|
|
|
459
716
|
const indexed = callerIndexed ?? this.deriveIndexed(descriptor.descriptor, data);
|
|
460
717
|
const itemId = resolveCatalogItemId({ identity: descriptor.descriptor.identity, data });
|
|
461
718
|
const now = new Date().toISOString();
|
|
719
|
+
const existing = await this.deps.nativeItems.get(catalogId, itemId);
|
|
720
|
+
const actorId = this.resolveActorId(context);
|
|
462
721
|
await this.deps.nativeItems.upsert(catalogId, {
|
|
463
722
|
itemId,
|
|
464
723
|
catalogId,
|
|
465
724
|
appScopedOwnerId: context.appId,
|
|
466
725
|
...(indexed != null ? { indexed } : {}),
|
|
467
726
|
data,
|
|
468
|
-
|
|
727
|
+
version: (existing?.version ?? 0) + 1,
|
|
728
|
+
...(existing?.createdAt ? { createdAt: existing.createdAt } : { createdAt: now }),
|
|
469
729
|
updatedAt: now,
|
|
730
|
+
...(existing?.createdBy ? { createdBy: existing.createdBy } : actorId ? { createdBy: actorId } : {}),
|
|
731
|
+
...(actorId ? { updatedBy: actorId } : {}),
|
|
470
732
|
});
|
|
471
733
|
return {
|
|
472
734
|
itemId,
|
|
@@ -475,7 +737,7 @@ export class Catalox {
|
|
|
475
737
|
sourceMode: "native",
|
|
476
738
|
sourceType: "firebase",
|
|
477
739
|
data,
|
|
478
|
-
createdAt: now,
|
|
740
|
+
createdAt: existing?.createdAt ?? now,
|
|
479
741
|
updatedAt: now,
|
|
480
742
|
};
|
|
481
743
|
}
|
|
@@ -485,6 +747,7 @@ export class Catalox {
|
|
|
485
747
|
if (!descriptor)
|
|
486
748
|
throw new CatalogAdapterError({ catalogId, reason: "missing_descriptor" });
|
|
487
749
|
const now = new Date().toISOString();
|
|
750
|
+
const actorId = this.resolveActorId(context);
|
|
488
751
|
const records = items.map((input) => {
|
|
489
752
|
const stripped = this.stripReservedWriteFields(input);
|
|
490
753
|
const indexed = stripped.indexed ?? this.deriveIndexed(descriptor.descriptor, stripped.data);
|
|
@@ -497,6 +760,7 @@ export class Catalox {
|
|
|
497
760
|
data: stripped.data,
|
|
498
761
|
createdAt: now,
|
|
499
762
|
updatedAt: now,
|
|
763
|
+
...(actorId ? { updatedBy: actorId } : {}),
|
|
500
764
|
};
|
|
501
765
|
});
|
|
502
766
|
await this.deps.nativeItems.batchUpsert(catalogId, records);
|
|
@@ -507,6 +771,174 @@ export class Catalox {
|
|
|
507
771
|
exportCatalogItemsToJson(value, pretty = true) {
|
|
508
772
|
return toJson(value, pretty);
|
|
509
773
|
}
|
|
774
|
+
async exportInventory(context, input = {}) {
|
|
775
|
+
const now = new Date().toISOString();
|
|
776
|
+
const resolved = await this.resolveEffectiveAppIds(context, input);
|
|
777
|
+
const out = {
|
|
778
|
+
generatedAt: now,
|
|
779
|
+
input,
|
|
780
|
+
resolved: {
|
|
781
|
+
...(resolved.storeId != null ? { storeId: resolved.storeId } : {}),
|
|
782
|
+
appIds: resolved.appIds,
|
|
783
|
+
...(input.catalogIds?.length ? { catalogIds: input.catalogIds } : {}),
|
|
784
|
+
},
|
|
785
|
+
apps: [],
|
|
786
|
+
};
|
|
787
|
+
for (const appId of resolved.appIds) {
|
|
788
|
+
const appCtx = { ...context, appId };
|
|
789
|
+
const catalogs = await this.listAppCatalogs(appCtx, {
|
|
790
|
+
appId,
|
|
791
|
+
...(input.includeDisabledCatalogs != null ? { includeDisabled: input.includeDisabledCatalogs } : {}),
|
|
792
|
+
...(input.includeHiddenCatalogs != null ? { includeHidden: input.includeHiddenCatalogs } : {}),
|
|
793
|
+
});
|
|
794
|
+
const filteredCatalogs = catalogs.filter((c) => {
|
|
795
|
+
if (input.catalogIds?.length && !input.catalogIds.includes(c.catalogId))
|
|
796
|
+
return false;
|
|
797
|
+
if (input.sourceModes?.length && !input.sourceModes.includes(c.sourceMode))
|
|
798
|
+
return false;
|
|
799
|
+
return true;
|
|
800
|
+
});
|
|
801
|
+
const exportedCatalogs = [];
|
|
802
|
+
for (const c of filteredCatalogs) {
|
|
803
|
+
if (!input.includeItems) {
|
|
804
|
+
exportedCatalogs.push({ catalog: c });
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
const list = await this.listCatalogItems(appCtx, c.catalogId, {
|
|
808
|
+
limit: input.limitPerCatalog ?? 50,
|
|
809
|
+
});
|
|
810
|
+
const items = input.includeItemData === false
|
|
811
|
+
? list.items.map((it) => {
|
|
812
|
+
const { data: _data, ...rest } = it;
|
|
813
|
+
return rest;
|
|
814
|
+
})
|
|
815
|
+
: list.items;
|
|
816
|
+
exportedCatalogs.push({
|
|
817
|
+
catalog: c,
|
|
818
|
+
items,
|
|
819
|
+
...(list.issues ? { issues: list.issues } : {}),
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
out.apps.push({
|
|
823
|
+
appId,
|
|
824
|
+
catalogs: exportedCatalogs,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
return out;
|
|
828
|
+
}
|
|
829
|
+
async exportInventoryToJson(context, input = {}, pretty = true) {
|
|
830
|
+
const data = await this.exportInventory(context, input);
|
|
831
|
+
return toJson(data, pretty);
|
|
832
|
+
}
|
|
833
|
+
async generateInventoryReport(context, input = {}) {
|
|
834
|
+
const now = new Date().toISOString();
|
|
835
|
+
const top = input.top ?? 10;
|
|
836
|
+
const resolved = await this.resolveEffectiveAppIds(context, input);
|
|
837
|
+
const catalogsById = new Map();
|
|
838
|
+
for (const appId of resolved.appIds) {
|
|
839
|
+
const appCtx = { ...context, appId };
|
|
840
|
+
const catalogs = await this.listAppCatalogs(appCtx, {
|
|
841
|
+
appId,
|
|
842
|
+
...(input.includeDisabledCatalogs != null ? { includeDisabled: input.includeDisabledCatalogs } : {}),
|
|
843
|
+
...(input.includeHiddenCatalogs != null ? { includeHidden: input.includeHiddenCatalogs } : {}),
|
|
844
|
+
});
|
|
845
|
+
for (const c of catalogs) {
|
|
846
|
+
if (input.catalogIds?.length && !input.catalogIds.includes(c.catalogId))
|
|
847
|
+
continue;
|
|
848
|
+
catalogsById.set(c.catalogId, c);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
const catalogIds = [...catalogsById.keys()].sort();
|
|
852
|
+
const catalogStats = [];
|
|
853
|
+
for (const catalogId of catalogIds) {
|
|
854
|
+
const c = catalogsById.get(catalogId);
|
|
855
|
+
// pick the first appId that has the binding for fetching examples
|
|
856
|
+
const appId = resolved.appIds[0];
|
|
857
|
+
const appCtx = { ...context, appId };
|
|
858
|
+
const list = await this.listCatalogItems(appCtx, catalogId, { limit: top });
|
|
859
|
+
const examples = list.items.slice(0, top).map((it) => ({
|
|
860
|
+
catalogId,
|
|
861
|
+
itemId: it.itemId,
|
|
862
|
+
...(it.title ? { title: it.title } : {}),
|
|
863
|
+
...(it.subtitle ? { subtitle: it.subtitle } : {}),
|
|
864
|
+
...(it.updatedAt ? { updatedAt: it.updatedAt } : {}),
|
|
865
|
+
}));
|
|
866
|
+
catalogStats.push({
|
|
867
|
+
catalogId,
|
|
868
|
+
label: c.label,
|
|
869
|
+
sourceMode: c.sourceMode,
|
|
870
|
+
status: c.status,
|
|
871
|
+
...(list.issues ? { mappingIssueCount: list.issues.length } : {}),
|
|
872
|
+
topExamples: examples,
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
const data = {
|
|
876
|
+
generatedAt: now,
|
|
877
|
+
input,
|
|
878
|
+
resolved: {
|
|
879
|
+
...(resolved.storeId != null ? { storeId: resolved.storeId } : {}),
|
|
880
|
+
appIds: resolved.appIds,
|
|
881
|
+
...(input.catalogIds?.length ? { catalogIds: input.catalogIds } : {}),
|
|
882
|
+
},
|
|
883
|
+
summary: {
|
|
884
|
+
catalogCount: catalogStats.length,
|
|
885
|
+
nativeCatalogCount: catalogStats.filter((c) => c.sourceMode === "native").length,
|
|
886
|
+
mappedCatalogCount: catalogStats.filter((c) => c.sourceMode === "mapped").length,
|
|
887
|
+
totalExamples: catalogStats.reduce((n, c) => n + (c.topExamples?.length ?? 0), 0),
|
|
888
|
+
},
|
|
889
|
+
catalogs: catalogStats,
|
|
890
|
+
};
|
|
891
|
+
if (input.includeDiff) {
|
|
892
|
+
const diffCatalogs = [];
|
|
893
|
+
for (const catalogId of catalogIds) {
|
|
894
|
+
const runs = await this.deps.snapshots.listRuns(catalogId, 2);
|
|
895
|
+
const latest = runs[0];
|
|
896
|
+
const prev = runs[1];
|
|
897
|
+
if (!latest || !prev) {
|
|
898
|
+
diffCatalogs.push({ catalogId, added: 0, removed: 0, changed: 0 });
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
const [newItems, oldItems] = await Promise.all([
|
|
902
|
+
this.deps.snapshots.listRunItems(catalogId, latest.runId),
|
|
903
|
+
this.deps.snapshots.listRunItems(catalogId, prev.runId),
|
|
904
|
+
]);
|
|
905
|
+
const oldById = new Map(oldItems.map((r) => [r.itemId, r.sourceFingerprint ?? this.fingerprint(r.data)]));
|
|
906
|
+
const newById = new Map(newItems.map((r) => [r.itemId, r.sourceFingerprint ?? this.fingerprint(r.data)]));
|
|
907
|
+
const added = [];
|
|
908
|
+
const removed = [];
|
|
909
|
+
const changed = [];
|
|
910
|
+
for (const id of newById.keys())
|
|
911
|
+
if (!oldById.has(id))
|
|
912
|
+
added.push(id);
|
|
913
|
+
for (const id of oldById.keys())
|
|
914
|
+
if (!newById.has(id))
|
|
915
|
+
removed.push(id);
|
|
916
|
+
for (const [id, fp] of newById) {
|
|
917
|
+
const oldFp = oldById.get(id);
|
|
918
|
+
if (oldFp && oldFp !== fp)
|
|
919
|
+
changed.push(id);
|
|
920
|
+
}
|
|
921
|
+
const ex = (ids) => ids.slice(0, top).map((itemId) => ({ catalogId, itemId }));
|
|
922
|
+
diffCatalogs.push({
|
|
923
|
+
catalogId,
|
|
924
|
+
added: added.length,
|
|
925
|
+
removed: removed.length,
|
|
926
|
+
changed: changed.length,
|
|
927
|
+
examples: {
|
|
928
|
+
...(added.length ? { added: ex(added) } : {}),
|
|
929
|
+
...(removed.length ? { removed: ex(removed) } : {}),
|
|
930
|
+
...(changed.length ? { changed: ex(changed) } : {}),
|
|
931
|
+
},
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
data.diff = {
|
|
935
|
+
baseline: { kind: "previous_snapshot_run" },
|
|
936
|
+
catalogs: diffCatalogs,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
const markdown = renderInventoryReportMarkdown(data);
|
|
940
|
+
return { markdown, data };
|
|
941
|
+
}
|
|
510
942
|
async syncMappedCatalog(_context, _catalogId) {
|
|
511
943
|
const catalog = await this.deps.catalogs.get(_catalogId);
|
|
512
944
|
if (!catalog)
|
|
@@ -518,15 +950,38 @@ export class Catalox {
|
|
|
518
950
|
if (mode !== "snapshot")
|
|
519
951
|
return { syncStatus: "idle" };
|
|
520
952
|
const now = new Date().toISOString();
|
|
953
|
+
const runId = randomUUID();
|
|
954
|
+
const actorId = this.resolveActorId(_context);
|
|
955
|
+
await this.deps.snapshots.upsertRun(_catalogId, {
|
|
956
|
+
runId,
|
|
957
|
+
catalogId: _catalogId,
|
|
958
|
+
startedAt: now,
|
|
959
|
+
status: "running",
|
|
960
|
+
...(actorId ? { triggeredBy: actorId } : {}),
|
|
961
|
+
});
|
|
521
962
|
const list = await this.listCatalogItems(_context, _catalogId, { limit: 500 });
|
|
522
963
|
for (const item of list.items) {
|
|
523
|
-
|
|
964
|
+
const record = {
|
|
524
965
|
itemId: item.itemId,
|
|
525
966
|
catalogId: _catalogId,
|
|
967
|
+
sourceFingerprint: this.fingerprint(item.data),
|
|
968
|
+
...(item.updatedAt ? { sourceUpdatedAt: item.updatedAt } : {}),
|
|
526
969
|
data: item.data,
|
|
527
970
|
sync: { lastSyncedAt: now, syncStatus: "success" },
|
|
528
|
-
|
|
971
|
+
...(actorId ? { metadata: { syncedBy: actorId } } : {}),
|
|
972
|
+
};
|
|
973
|
+
await this.deps.snapshots.upsert(_catalogId, record);
|
|
974
|
+
await this.deps.snapshots.upsertRunItem(_catalogId, runId, record);
|
|
529
975
|
}
|
|
976
|
+
await this.deps.snapshots.upsertRun(_catalogId, {
|
|
977
|
+
runId,
|
|
978
|
+
catalogId: _catalogId,
|
|
979
|
+
startedAt: now,
|
|
980
|
+
finishedAt: new Date().toISOString(),
|
|
981
|
+
status: "success",
|
|
982
|
+
itemCount: list.items.length,
|
|
983
|
+
...(actorId ? { triggeredBy: actorId } : {}),
|
|
984
|
+
});
|
|
530
985
|
await this.deps.definitions.upsert({
|
|
531
986
|
...def,
|
|
532
987
|
sync: {
|