@x12i/catalox 1.1.1 → 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 -222
- package/dist/src/catalox/catalox.d.ts +38 -1
- package/dist/src/catalox/catalox.d.ts.map +1 -1
- package/dist/src/catalox/catalox.js +499 -13
- 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
package/README.md
CHANGED
|
@@ -1,222 +1,424 @@
|
|
|
1
|
-
# `@x12i/catalox`
|
|
2
|
-
|
|
3
|
-
Catalox is a **data-tier** package for managing **app-scoped catalogs** in Firebase **Firestore**, including:
|
|
4
|
-
|
|
5
|
-
- **Catalog discovery** for an `appId` (what catalogs are available + access)
|
|
6
|
-
- **First-class catalog descriptors** (capabilities, query metadata, identity metadata, field metadata)
|
|
7
|
-
- **Native catalogs** (items stored in Firestore)
|
|
8
|
-
- **Mapped catalogs** (items normalized from MongoDB or APIs)
|
|
9
|
-
- **References + validation contracts** (standardized cross-catalog shapes)
|
|
10
|
-
- **Seed/import/export + batch upsert** workflows for native catalogs
|
|
11
|
-
|
|
12
|
-
Catalox **does not** own UI, workflow orchestration, remote execution, artifact blobs, or secret storage.
|
|
13
|
-
|
|
14
|
-
## Install
|
|
15
|
-
|
|
16
|
-
This repo is currently set up as a workspace package.
|
|
17
|
-
|
|
18
|
-
- **Node**: `>=20`
|
|
19
|
-
- **TypeScript**: builds to `dist/`
|
|
20
|
-
|
|
21
|
-
`@x12i/helpers` is a **required dependency** (installed as `@x12i/helpers@^1.2.0`).
|
|
22
|
-
|
|
23
|
-
## Configuration (real connections)
|
|
24
|
-
|
|
25
|
-
Catalox is a library: you provide initialized clients + runtime env.
|
|
26
|
-
|
|
27
|
-
### Firestore (Firebase Admin SDK)
|
|
28
|
-
|
|
29
|
-
This implementation of Catalox expects a **Firebase Admin SDK** Firestore instance (`firebase-admin`).
|
|
30
|
-
|
|
31
|
-
Important clarification:
|
|
32
|
-
|
|
33
|
-
- **“Admin” here means privileged access to your Firebase/GCP project’s Firestore**, using a service account.
|
|
34
|
-
- It is **not** “admin of the host machine / server OS”.
|
|
35
|
-
- Admin SDK access typically **bypasses Firestore Security Rules** (rules are for client SDKs).
|
|
36
|
-
|
|
37
|
-
Scoping:
|
|
38
|
-
|
|
39
|
-
- You can scope the service account to **Firestore-related IAM roles** and to a specific **project**.
|
|
40
|
-
- You generally cannot scope an Admin SDK credential to only certain collections/documents via IAM the way client rules work.
|
|
41
|
-
|
|
42
|
-
To connect to a real Firebase project, initialize `firebase-admin` using `GOOGLE_APPLICATION_CREDENTIALS` (service account JSON) or your preferred Admin initialization strategy.
|
|
43
|
-
|
|
44
|
-
Minimal example:
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
import { initializeApp, applicationDefault } from "firebase-admin/app";
|
|
48
|
-
import { getFirestore } from "firebase-admin/firestore";
|
|
49
|
-
|
|
50
|
-
initializeApp({ credential: applicationDefault() });
|
|
51
|
-
const firestore = getFirestore();
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Mongo (mapped catalogs)
|
|
55
|
-
|
|
56
|
-
For Mongo-mapped catalogs, provide:
|
|
57
|
-
|
|
58
|
-
- `MONGO_URI` (or whatever you store in adapter config as `mongoUriEnvVar`)
|
|
59
|
-
|
|
60
|
-
Example `.env` (do not commit secrets):
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
MONGO_URI=mongodb://127.0.0.1:27017
|
|
64
|
-
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
|
|
65
|
-
FIRESTORE_LIVE_TESTS=1
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Firestore data model (logical collections)
|
|
69
|
-
|
|
70
|
-
Metadata:
|
|
71
|
-
|
|
72
|
-
- `apps/{appId}`
|
|
73
|
-
- `catalogs/{catalogId}`
|
|
74
|
-
- `catalogBindings/{bindingId}` (many-to-many app↔catalog)
|
|
75
|
-
- `catalogDefinitions/{catalogId}` (native vs mapped specifics)
|
|
76
|
-
- `catalogAdapters/{adapterId}` (mongo/api adapter definitions)
|
|
77
|
-
- `catalogMappings/{mappingId}` (field mapping specs)
|
|
78
|
-
- `catalogDescriptors/{catalogId}` (**descriptor metadata** for generic consumption)
|
|
79
|
-
- `catalogReferences/{referenceId}` (standardized reference records)
|
|
80
|
-
|
|
81
|
-
Data:
|
|
82
|
-
|
|
83
|
-
- `catalogData/{catalogId}/items/{itemId}` (native items)
|
|
84
|
-
- `catalogSnapshots/{catalogId}/items/{itemId}` (mapped snapshot mode)
|
|
85
|
-
|
|
86
|
-
## Core usage (generic from `appId`)
|
|
87
|
-
|
|
88
|
-
### Create stores and `Catalox`
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
import { FirestoreStore } from "@x12i/catalox";
|
|
92
|
-
import { AppStore, CatalogStore, BindingStore, DefinitionStore, MappingStore, DescriptorStore, ReferenceStore, NativeItemStore, SnapshotStore, AdapterStore } from "@x12i/catalox";
|
|
93
|
-
import { AuthorizationService, Catalox } from "@x12i/catalox";
|
|
94
|
-
|
|
95
|
-
// firebase-admin initialization is up to the consumer
|
|
96
|
-
import { getFirestore } from "firebase-admin/firestore";
|
|
97
|
-
|
|
98
|
-
const firestore = getFirestore();
|
|
99
|
-
const store = new FirestoreStore({ firestore });
|
|
100
|
-
|
|
101
|
-
const deps = {
|
|
102
|
-
apps: new AppStore(store),
|
|
103
|
-
catalogs: new CatalogStore(store),
|
|
104
|
-
bindings: new BindingStore(store),
|
|
105
|
-
definitions: new DefinitionStore(store),
|
|
106
|
-
mappings: new MappingStore(store),
|
|
107
|
-
descriptors: new DescriptorStore(store),
|
|
108
|
-
references: new ReferenceStore(store),
|
|
109
|
-
nativeItems: new NativeItemStore(store),
|
|
110
|
-
snapshots: new SnapshotStore(store),
|
|
111
|
-
adapters: new AdapterStore(store),
|
|
112
|
-
authz: new AuthorizationService(new BindingStore(store)),
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const catalox = new Catalox(deps);
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
1
|
+
# `@x12i/catalox`
|
|
2
|
+
|
|
3
|
+
Catalox is a **data-tier** package for managing **app-scoped catalogs** in Firebase **Firestore**, including:
|
|
4
|
+
|
|
5
|
+
- **Catalog discovery** for an `appId` (what catalogs are available + access)
|
|
6
|
+
- **First-class catalog descriptors** (capabilities, query metadata, identity metadata, field metadata)
|
|
7
|
+
- **Native catalogs** (items stored in Firestore)
|
|
8
|
+
- **Mapped catalogs** (items normalized from MongoDB or APIs)
|
|
9
|
+
- **References + validation contracts** (standardized cross-catalog shapes)
|
|
10
|
+
- **Seed/import/export + batch upsert** workflows for native catalogs
|
|
11
|
+
|
|
12
|
+
Catalox **does not** own UI, workflow orchestration, remote execution, artifact blobs, or secret storage.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
This repo is currently set up as a workspace package.
|
|
17
|
+
|
|
18
|
+
- **Node**: `>=20`
|
|
19
|
+
- **TypeScript**: builds to `dist/`
|
|
20
|
+
|
|
21
|
+
`@x12i/helpers` is a **required dependency** (installed as `@x12i/helpers@^1.2.0`).
|
|
22
|
+
|
|
23
|
+
## Configuration (real connections)
|
|
24
|
+
|
|
25
|
+
Catalox is a library: you provide initialized clients + runtime env.
|
|
26
|
+
|
|
27
|
+
### Firestore (Firebase Admin SDK)
|
|
28
|
+
|
|
29
|
+
This implementation of Catalox expects a **Firebase Admin SDK** Firestore instance (`firebase-admin`).
|
|
30
|
+
|
|
31
|
+
Important clarification:
|
|
32
|
+
|
|
33
|
+
- **“Admin” here means privileged access to your Firebase/GCP project’s Firestore**, using a service account.
|
|
34
|
+
- It is **not** “admin of the host machine / server OS”.
|
|
35
|
+
- Admin SDK access typically **bypasses Firestore Security Rules** (rules are for client SDKs).
|
|
36
|
+
|
|
37
|
+
Scoping:
|
|
38
|
+
|
|
39
|
+
- You can scope the service account to **Firestore-related IAM roles** and to a specific **project**.
|
|
40
|
+
- You generally cannot scope an Admin SDK credential to only certain collections/documents via IAM the way client rules work.
|
|
41
|
+
|
|
42
|
+
To connect to a real Firebase project, initialize `firebase-admin` using `GOOGLE_APPLICATION_CREDENTIALS` (service account JSON) or your preferred Admin initialization strategy.
|
|
43
|
+
|
|
44
|
+
Minimal example:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { initializeApp, applicationDefault } from "firebase-admin/app";
|
|
48
|
+
import { getFirestore } from "firebase-admin/firestore";
|
|
49
|
+
|
|
50
|
+
initializeApp({ credential: applicationDefault() });
|
|
51
|
+
const firestore = getFirestore();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Mongo (mapped catalogs)
|
|
55
|
+
|
|
56
|
+
For Mongo-mapped catalogs, provide:
|
|
57
|
+
|
|
58
|
+
- `MONGO_URI` (or whatever you store in adapter config as `mongoUriEnvVar`)
|
|
59
|
+
|
|
60
|
+
Example `.env` (do not commit secrets):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
MONGO_URI=mongodb://127.0.0.1:27017
|
|
64
|
+
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
|
|
65
|
+
FIRESTORE_LIVE_TESTS=1
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Firestore data model (logical collections)
|
|
69
|
+
|
|
70
|
+
Metadata:
|
|
71
|
+
|
|
72
|
+
- `apps/{appId}`
|
|
73
|
+
- `catalogs/{catalogId}`
|
|
74
|
+
- `catalogBindings/{bindingId}` (many-to-many app↔catalog)
|
|
75
|
+
- `catalogDefinitions/{catalogId}` (native vs mapped specifics)
|
|
76
|
+
- `catalogAdapters/{adapterId}` (mongo/api adapter definitions)
|
|
77
|
+
- `catalogMappings/{mappingId}` (field mapping specs)
|
|
78
|
+
- `catalogDescriptors/{catalogId}` (**descriptor metadata** for generic consumption)
|
|
79
|
+
- `catalogReferences/{referenceId}` (standardized reference records)
|
|
80
|
+
|
|
81
|
+
Data:
|
|
82
|
+
|
|
83
|
+
- `catalogData/{catalogId}/items/{itemId}` (native items)
|
|
84
|
+
- `catalogSnapshots/{catalogId}/items/{itemId}` (mapped snapshot mode)
|
|
85
|
+
|
|
86
|
+
## Core usage (generic from `appId`)
|
|
87
|
+
|
|
88
|
+
### Create stores and `Catalox`
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { FirestoreStore } from "@x12i/catalox";
|
|
92
|
+
import { AppStore, CatalogStore, BindingStore, DefinitionStore, MappingStore, DescriptorStore, ReferenceStore, NativeItemStore, SnapshotStore, AdapterStore } from "@x12i/catalox";
|
|
93
|
+
import { AuthorizationService, Catalox } from "@x12i/catalox";
|
|
94
|
+
|
|
95
|
+
// firebase-admin initialization is up to the consumer
|
|
96
|
+
import { getFirestore } from "firebase-admin/firestore";
|
|
97
|
+
|
|
98
|
+
const firestore = getFirestore();
|
|
99
|
+
const store = new FirestoreStore({ firestore });
|
|
100
|
+
|
|
101
|
+
const deps = {
|
|
102
|
+
apps: new AppStore(store),
|
|
103
|
+
catalogs: new CatalogStore(store),
|
|
104
|
+
bindings: new BindingStore(store),
|
|
105
|
+
definitions: new DefinitionStore(store),
|
|
106
|
+
mappings: new MappingStore(store),
|
|
107
|
+
descriptors: new DescriptorStore(store),
|
|
108
|
+
references: new ReferenceStore(store),
|
|
109
|
+
nativeItems: new NativeItemStore(store),
|
|
110
|
+
snapshots: new SnapshotStore(store),
|
|
111
|
+
adapters: new AdapterStore(store),
|
|
112
|
+
authz: new AuthorizationService(new BindingStore(store)),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const catalox = new Catalox(deps);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Descriptor contract (planning-critical)
|
|
119
|
+
|
|
120
|
+
Catalox is designed so upstream packages can be “generic” (no hardcoded catalog registrations). The stable contract is the persisted **descriptor**:
|
|
121
|
+
|
|
122
|
+
- Stored at `catalogDescriptors/{catalogId}`
|
|
123
|
+
- Retrieved via `getCatalogDescriptor(...)` or `getAppCatalogBootstrap(...)`
|
|
124
|
+
- Used for identity, query, and client rendering metadata
|
|
125
|
+
|
|
126
|
+
Below is the **actual current descriptor shape** (TypeScript), with the most planning-relevant subtypes.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
export type CatalogCapabilitiesDescriptor = {
|
|
130
|
+
canList: boolean;
|
|
131
|
+
canGet: boolean;
|
|
132
|
+
canCreate: boolean;
|
|
133
|
+
canEdit: boolean;
|
|
134
|
+
canDelete: boolean;
|
|
135
|
+
canImport?: boolean;
|
|
136
|
+
canExport?: boolean;
|
|
137
|
+
canSync?: boolean;
|
|
138
|
+
canValidate?: boolean;
|
|
139
|
+
canViewReferences?: boolean;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export type CatalogFieldDescriptor = {
|
|
143
|
+
key: string;
|
|
144
|
+
label: string;
|
|
145
|
+
type:
|
|
146
|
+
| "string"
|
|
147
|
+
| "number"
|
|
148
|
+
| "boolean"
|
|
149
|
+
| "date"
|
|
150
|
+
| "datetime"
|
|
151
|
+
| "enum"
|
|
152
|
+
| "object"
|
|
153
|
+
| "array"
|
|
154
|
+
| "reference";
|
|
155
|
+
// Optional path into the stored item payload. If omitted, `key` is assumed.
|
|
156
|
+
path?: string;
|
|
157
|
+
|
|
158
|
+
// Query + indexing metadata.
|
|
159
|
+
filterable?: boolean;
|
|
160
|
+
sortable?: boolean;
|
|
161
|
+
indexed?: boolean;
|
|
162
|
+
multiValue?: boolean;
|
|
163
|
+
|
|
164
|
+
// Presentation + contract metadata.
|
|
165
|
+
listVisible?: boolean;
|
|
166
|
+
detailVisible?: boolean;
|
|
167
|
+
required?: boolean;
|
|
168
|
+
enumValues?: Array<string | number>;
|
|
169
|
+
reference?: { targetCatalogId?: string; targetField?: string };
|
|
170
|
+
metadata?: Record<string, unknown>;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export type CatalogIdentityDescriptor = {
|
|
174
|
+
itemIdStrategy: "natural" | "composite" | "generated";
|
|
175
|
+
itemIdField?: string;
|
|
176
|
+
compositeFields?: string[];
|
|
177
|
+
|
|
178
|
+
// Optional display decoration fields.
|
|
179
|
+
titleField?: string;
|
|
180
|
+
subtitleField?: string;
|
|
181
|
+
statusField?: string;
|
|
182
|
+
updatedAtField?: string;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export type CatalogDescriptor = {
|
|
186
|
+
catalogId: string;
|
|
187
|
+
label: string;
|
|
188
|
+
description?: string;
|
|
189
|
+
itemLabel?: string;
|
|
190
|
+
sourceMode: "native" | "mapped";
|
|
191
|
+
mappedSourceType?: "mongo" | "api";
|
|
192
|
+
status: "active" | "disabled" | "draft";
|
|
193
|
+
visibility?: "visible" | "hidden";
|
|
194
|
+
defaultSort?: { field: string; direction: "asc" | "desc" };
|
|
195
|
+
defaultFilters?: Record<string, unknown>;
|
|
196
|
+
capabilities: CatalogCapabilitiesDescriptor;
|
|
197
|
+
queryableFields: CatalogFieldDescriptor[];
|
|
198
|
+
queryCapabilities?: Record<string, unknown>;
|
|
199
|
+
filterSpec?: Record<string, unknown>;
|
|
200
|
+
presentationSpec?: Record<string, unknown>;
|
|
201
|
+
customRenderer?: Record<string, unknown>;
|
|
202
|
+
identity: CatalogIdentityDescriptor;
|
|
203
|
+
metadata?: Record<string, unknown>;
|
|
204
|
+
};
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### UI metadata & custom renderers (optional)
|
|
208
|
+
|
|
209
|
+
Catalog descriptors can include optional UI metadata for generic presentation layers:
|
|
210
|
+
|
|
211
|
+
- `filterSpec`: declarative filter configuration (built on `FieldSource`)
|
|
212
|
+
- `presentationSpec`: declarative layout + view/edit semantics (grid/list/cards/form)
|
|
213
|
+
- `customRenderer`: escape hatch that assigns a host-resolved JSX component (by registry key) and passes a stable render-map contract
|
|
214
|
+
|
|
215
|
+
Custom renderer contracts:
|
|
216
|
+
|
|
217
|
+
- [`docs/catalog-list-render-map.md`](docs/catalog-list-render-map.md)
|
|
218
|
+
- [`docs/catalog-item-render-map.md`](docs/catalog-item-render-map.md)
|
|
219
|
+
|
|
220
|
+
Important: host presentation layers are responsible for resolving `FieldSource` lookups (cross-catalog enums/refs) and populating `resolvedSources` for renderers.
|
|
221
|
+
|
|
222
|
+
### What you need persisted for “generic consumption”
|
|
223
|
+
|
|
224
|
+
To let consumers operate *purely* from `appId` with no hardcoded catalog registrations, Catalox relies on these being present in Firestore:
|
|
225
|
+
|
|
226
|
+
- **Bindings**: `catalogBindings` determine which catalogs an app can see/use.
|
|
227
|
+
- **Descriptors**: `catalogDescriptors/{catalogId}` provide capabilities, query metadata, identity metadata, and field metadata.
|
|
228
|
+
|
|
229
|
+
Minimum viable setup for generic consumption is **catalog + descriptor + binding**.
|
|
230
|
+
|
|
231
|
+
For **mapped** catalogs, the minimum viable setup is also **definition + mapping + adapter** (created automatically if you use `createCatalog` with `sourceMode: "mapped"`).
|
|
232
|
+
|
|
233
|
+
### Discover catalogs for an app
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const context = { appId: "myAppId" };
|
|
237
|
+
const catalogs = await catalox.listAppCatalogs(context, { appId: "myAppId" });
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Get a catalog descriptor
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
const descriptor = await catalox.getCatalogDescriptor(context, "signals");
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Bootstrap (all descriptors accessible to an app)
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
const bootstrap = await catalox.getAppCatalogBootstrap(context, "myAppId");
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Common “generic client” flow (no hardcoded catalog logic)
|
|
253
|
+
|
|
254
|
+
1) `listAppCatalogs(appId)` → get catalog list + access
|
|
255
|
+
2) `getAppCatalogBootstrap(appId)` → get descriptors (capabilities/query/identity/fields)
|
|
256
|
+
3) `listCatalogItems(catalogId, filter/sort)` → fetch normalized items
|
|
257
|
+
4) `getCatalogItem(catalogId, itemId)` → fetch one item
|
|
258
|
+
5) Optional: `getCatalogItemReferences(...)`, `validateCatalogItem(...)`
|
|
259
|
+
|
|
260
|
+
## Provisioning (create catalog + bind + descriptor)
|
|
261
|
+
|
|
262
|
+
Catalox includes provisioning helpers to reduce “manual Firestore wiring”.
|
|
263
|
+
|
|
264
|
+
### Create a native catalog (also seeds a minimal descriptor)
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
const ctx = { appId: "myAppId", isGodMode: true }; // or bind admin access appropriately
|
|
268
|
+
|
|
269
|
+
await catalox.createCatalog(ctx, {
|
|
270
|
+
catalogId: "signals",
|
|
271
|
+
name: "Signals",
|
|
272
|
+
sourceMode: "native",
|
|
273
|
+
native: { type: "native", firestoreCollectionPath: "catalogData/signals/items" },
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Bind a catalog to an app (enables discovery + access)
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
await catalox.bindCatalogToApp(ctx, {
|
|
281
|
+
appId: "myAppId",
|
|
282
|
+
catalogId: "signals",
|
|
283
|
+
access: { canRead: true, canWrite: true, canAdmin: true },
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Patch/upsert a descriptor (recommended for identity + query metadata)
|
|
288
|
+
|
|
289
|
+
There is currently no `Catalox.setCatalogDescriptor(...)` method; consumers can upsert the persisted record via `DescriptorStore`.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
await descriptors.upsert({
|
|
293
|
+
catalogId: "signals",
|
|
294
|
+
descriptorVersion: "2",
|
|
295
|
+
descriptor: {
|
|
296
|
+
catalogId: "signals",
|
|
297
|
+
label: "Signals",
|
|
298
|
+
sourceMode: "native",
|
|
299
|
+
status: "active",
|
|
300
|
+
capabilities: { canList: true, canGet: true, canCreate: true, canEdit: true, canDelete: true },
|
|
301
|
+
queryableFields: [
|
|
302
|
+
{ key: "categoryId", label: "Category", type: "string", indexed: true, filterable: true },
|
|
303
|
+
{ key: "code", label: "Code", type: "string", indexed: true, filterable: true },
|
|
304
|
+
{ key: "title", label: "Title", type: "string" },
|
|
305
|
+
],
|
|
306
|
+
identity: {
|
|
307
|
+
itemIdStrategy: "composite",
|
|
308
|
+
compositeFields: ["categoryId", "code"],
|
|
309
|
+
titleField: "title",
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
createdAt: new Date().toISOString(),
|
|
313
|
+
updatedAt: new Date().toISOString(),
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Ensure helpers (idempotent)
|
|
318
|
+
|
|
319
|
+
- `ensureCatalog(...)`: creates a minimal catalog record if missing (requires admin binding access).
|
|
320
|
+
- `ensureBinding(...)`: creates a binding if missing (enforces god-mode for cross-app provisioning).
|
|
321
|
+
|
|
322
|
+
## Native catalogs (seed/import/export + batch upsert)
|
|
323
|
+
|
|
324
|
+
### Import/Export JSON (helpers)
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
const items = catalox.importCatalogItemsFromJson<Array<Record<string, unknown>>>(jsonString);
|
|
328
|
+
const jsonOut = catalox.exportCatalogItemsToJson(items);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Upsert one item (descriptor-driven identity)
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
await catalox.upsertNativeCatalogItem({ appId: "myAppId" }, "signals", {
|
|
335
|
+
categoryId: "core",
|
|
336
|
+
code: "S1",
|
|
337
|
+
title: "Example",
|
|
338
|
+
indexed: { categoryId: "core", code: "S1" }
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Native write contract (exact)
|
|
343
|
+
|
|
344
|
+
- **Input shape**: the write API accepts a plain object payload. `indexed` is a reserved top-level field used only for query performance and is **not** part of the domain payload.
|
|
345
|
+
- **Stored shape** (native): items are stored as `{ itemId, catalogId, indexed, data }`.
|
|
346
|
+
- **Identity**: `itemId` is resolved from `descriptor.identity`.
|
|
347
|
+
- `natural`: uses `identity.itemIdField` from the input payload.
|
|
348
|
+
- `composite`: joins `identity.compositeFields` with `:`.
|
|
349
|
+
- `generated`: **currently requires caller-supplied id** (Catalox will throw if descriptor uses `generated` and no id is provided).
|
|
350
|
+
|
|
351
|
+
### Batch upsert
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
await catalox.batchUpsertNativeCatalogItems({ appId: "myAppId" }, "signals", items);
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### List native items with equality filtering
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
const res = await catalox.listCatalogItems({ appId: "myAppId" }, "signals", {
|
|
361
|
+
filter: { categoryId: "core" }
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Filtering is performed on `indexed.<field>` in stored native records (payload remains in `data`).
|
|
366
|
+
|
|
367
|
+
### Canonical `indexed` rule (native catalogs)
|
|
368
|
+
|
|
369
|
+
- **Caller may provide `indexed`**: `indexed: { ... }` is accepted on writes.
|
|
370
|
+
- **Catalox may derive `indexed`**: if omitted, Catalox derives `indexed` from the descriptor’s `queryableFields` (fields marked `indexed: true`, using `field.path ?? field.key`).
|
|
371
|
+
- **Data cleanliness**: `indexed` is treated as reserved metadata and is **not duplicated inside `data`**.
|
|
372
|
+
- **If `indexed` is missing/wrong**: equality filtering and indexed sorting may return incomplete results or fail due to missing Firestore indexes; the canonical fix is to correct the catalog descriptor and/or the write input and re-upsert.
|
|
373
|
+
|
|
374
|
+
## References
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
const refs = await catalox.getCatalogItemReferences({ appId: "myAppId" }, "signals", "core:S1");
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Reference write/provision model (current)
|
|
381
|
+
|
|
382
|
+
- References are persisted in `catalogReferences/{referenceId}`.
|
|
383
|
+
- Catalox currently **reads** references via `getCatalogItemReferences(...)`; reference creation/maintenance is **consumer-owned** for now.
|
|
384
|
+
- `referenceId` is the Firestore document id; consumers should adopt a deterministic scheme, e.g.:
|
|
385
|
+
- `${fromCatalogId}:${fromItemId}:${relationType}:${toCatalogId}:${toItemId}`
|
|
386
|
+
|
|
387
|
+
## Validation
|
|
388
|
+
|
|
389
|
+
Validation APIs exist with standardized contracts, but **current behavior is minimal**:
|
|
390
|
+
|
|
391
|
+
- `validateCatalog(...)` and `validateCatalogItem(...)` currently return `{ isValid: true, issues: [] }`.
|
|
392
|
+
- Upstream domain validation is expected to be layered on in later work while keeping the response contract stable.
|
|
393
|
+
|
|
394
|
+
## Tests
|
|
395
|
+
|
|
396
|
+
Unit tests (default):
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
npm test
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Integration tests (live Firestore, no mocks/emulators):
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
npm run test:integration
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Integration tests are **live-only** (no mocks, no emulators). They require:
|
|
409
|
+
|
|
410
|
+
- `FIRESTORE_LIVE_TESTS=1`
|
|
411
|
+
- `FIREBASE_SERVICE_ACCOUNT_PATH=...`
|
|
412
|
+
- `FIREBASE_PROJECT_ID=...`
|
|
413
|
+
|
|
414
|
+
### Live test safety (read before running)
|
|
415
|
+
|
|
416
|
+
- **Never run against production credentials/projects.** Use a dedicated Firebase project for tests.
|
|
417
|
+
- **Touched collections**: `apps`, `catalogs`, `catalogBindings`, `catalogDefinitions`, `catalogDescriptors`, and `catalogData/{catalogId}/items/...`.
|
|
418
|
+
- **Cleanup**: tests do best-effort deletes of the docs they created, but do not guarantee full teardown.
|
|
419
|
+
|
|
420
|
+
## Boundaries (important)
|
|
421
|
+
|
|
422
|
+
- **Secrets**: do not store secret material (API keys, cloud creds) in Catalox. Store only non-secret refs like `credentialsRef`.
|
|
423
|
+
- **Artifacts**: store descriptor metadata and remote keys; artifact blobs live in external object storage.
|
|
424
|
+
|