@dereekb/dbx-cli 14.5.0 → 14.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/eslint/package.json +3 -3
- package/firebase-api-manifest/main.js +48 -6
- package/firebase-api-manifest/package.json +3 -3
- package/firestore-indexes/src/index.d.ts +1 -0
- package/firestore-indexes/src/model-firebase-index-package-manifests.d.ts +135 -0
- package/firestore-query-manifest/main.js +3 -3
- package/firestore-query-manifest/package.json +3 -3
- package/generate-firestore-indexes/main.js +165 -13
- package/generate-firestore-indexes/package.json +2 -2
- package/generate-mcp-manifest/main.js +2 -1
- package/generate-mcp-manifest/package.json +3 -3
- package/generate-route-manifest/package.json +2 -2
- package/index.esm.js +210 -68
- package/lint-cache/package.json +2 -2
- package/manifest-extract/index.esm.js +113 -0
- package/manifest-extract/package.json +10 -10
- package/manifest-extract/src/lib/types.d.ts +21 -0
- package/model-test/package.json +2 -2
- package/package.json +7 -7
- package/route/package.json +8 -8
- package/src/lib/manifest/build-model-decode-command.d.ts +68 -5
- package/src/lib/manifest/types.d.ts +36 -0
- package/test/package.json +10 -10
- package/validate/package.json +3 -3
package/eslint/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli/eslint",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.6.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"@dereekb/dbx-cli": "14.
|
|
8
|
-
"@dereekb/util": "14.
|
|
7
|
+
"@dereekb/dbx-cli": "14.6.0",
|
|
8
|
+
"@dereekb/util": "14.6.0",
|
|
9
9
|
"@typescript-eslint/utils": "8.69.0",
|
|
10
10
|
"typescript": "6.0.3"
|
|
11
11
|
},
|
|
@@ -728,6 +728,7 @@ var SERVICE_FACTORY_TAG = "dbxModelServiceFactory";
|
|
|
728
728
|
var MCP_TOOL_NAME_SEGMENT_TAG = "dbxModelMcpToolNameSegment";
|
|
729
729
|
var MODEL_TYPE_VALUE_PATTERN = /^[a-z][A-Za-z0-9_$]*$/;
|
|
730
730
|
var TOOL_NAME_SEGMENT_PATTERN = /^[A-Za-z][A-Za-z0-9_$]*$/;
|
|
731
|
+
var COMPOSITE_KEY_MODEL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_$]*$/;
|
|
731
732
|
var IDENTITY_FN = "firestoreModelIdentity";
|
|
732
733
|
var IDENTITY_TYPE = "FirestoreModelIdentity";
|
|
733
734
|
var ROOT_IDENTITY_TYPE = "RootFirestoreModelIdentity";
|
|
@@ -844,6 +845,7 @@ function buildInterface(decl) {
|
|
|
844
845
|
const dbxModelRead = readDbxModelReadTag(jsDocs);
|
|
845
846
|
const dbxModelServerOnly = jsDocsHaveTag(jsDocs, "dbxModelServerOnly");
|
|
846
847
|
const mcpToolNameSegment = readMcpToolNameSegmentTag(jsDocs);
|
|
848
|
+
const compositeKey = readDbxModelCompositeKeyTag(jsDocs);
|
|
847
849
|
const extendsNames = decl.getExtends().map(resolveExtendsName);
|
|
848
850
|
const props = [];
|
|
849
851
|
for (const prop of decl.getProperties()) {
|
|
@@ -869,7 +871,8 @@ function buildInterface(decl) {
|
|
|
869
871
|
props,
|
|
870
872
|
...dbxModelRead === void 0 ? {} : { dbxModelRead },
|
|
871
873
|
...dbxModelServerOnly ? { dbxModelServerOnly: true } : {},
|
|
872
|
-
...mcpToolNameSegment === void 0 ? {} : { mcpToolNameSegment }
|
|
874
|
+
...mcpToolNameSegment === void 0 ? {} : { mcpToolNameSegment },
|
|
875
|
+
...compositeKey === void 0 ? {} : { compositeKey }
|
|
873
876
|
};
|
|
874
877
|
}
|
|
875
878
|
function readMcpToolNameSegmentTag(jsDocs) {
|
|
@@ -904,6 +907,43 @@ function readDbxModelReadTag(jsDocs) {
|
|
|
904
907
|
}
|
|
905
908
|
return result;
|
|
906
909
|
}
|
|
910
|
+
function readDbxModelCompositeKeyTag(jsDocs) {
|
|
911
|
+
let result;
|
|
912
|
+
for (const doc of jsDocs) {
|
|
913
|
+
for (const tag of doc.getTags()) {
|
|
914
|
+
if (tag.getTagName() !== "dbxModelCompositeKey") continue;
|
|
915
|
+
if (result !== void 0) continue;
|
|
916
|
+
result = parseCompositeKeyTagValue(tag.getCommentText() ?? "");
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return result;
|
|
920
|
+
}
|
|
921
|
+
function parseCompositeKeyTagValue(raw) {
|
|
922
|
+
let from;
|
|
923
|
+
let encoding;
|
|
924
|
+
for (const token of raw.trim().split(/\s+/)) {
|
|
925
|
+
const eq = token.indexOf("=");
|
|
926
|
+
if (eq <= 0) continue;
|
|
927
|
+
const key = token.slice(0, eq);
|
|
928
|
+
const value = token.slice(eq + 1);
|
|
929
|
+
if (key === "from") {
|
|
930
|
+
from = parseCompositeKeyFromValue(value);
|
|
931
|
+
} else if (key === "encoding" && (value === "one-way" || value === "two-way")) {
|
|
932
|
+
encoding = value;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return from !== void 0 && encoding !== void 0 ? { from, encoding } : void 0;
|
|
936
|
+
}
|
|
937
|
+
function parseCompositeKeyFromValue(value) {
|
|
938
|
+
let result;
|
|
939
|
+
if (value === "*") {
|
|
940
|
+
result = "*";
|
|
941
|
+
} else {
|
|
942
|
+
const names = value.split(",").map((x) => x.trim()).filter((x) => COMPOSITE_KEY_MODEL_NAME_PATTERN.test(x));
|
|
943
|
+
result = names.length > 0 ? names : void 0;
|
|
944
|
+
}
|
|
945
|
+
return result;
|
|
946
|
+
}
|
|
907
947
|
function readServiceFactories(sourceFile) {
|
|
908
948
|
const out = [];
|
|
909
949
|
for (const statement of sourceFile.getVariableStatements()) {
|
|
@@ -1541,7 +1581,8 @@ function buildManifestEntry(input) {
|
|
|
1541
1581
|
...iface.mcpToolNameSegment ? { mcpToolNameSegment: iface.mcpToolNameSegment } : {},
|
|
1542
1582
|
...iface.dbxModelRead ? { read: iface.dbxModelRead } : {},
|
|
1543
1583
|
...iface.dbxModelServerOnly ? { serverOnly: true } : {},
|
|
1544
|
-
...serviceFactory ? { serviceFactory } : {}
|
|
1584
|
+
...serviceFactory ? { serviceFactory } : {},
|
|
1585
|
+
...iface.compositeKey ? { compositeKey: iface.compositeKey } : {}
|
|
1545
1586
|
};
|
|
1546
1587
|
}
|
|
1547
1588
|
function resolveSingleItemDocumentId(single, registries) {
|
|
@@ -1809,7 +1850,8 @@ function renderModelEntry(entry, emitConverters) {
|
|
|
1809
1850
|
entry.mcpToolNameSegment ? `mcpToolNameSegment: ${JSON.stringify(entry.mcpToolNameSegment)}` : void 0,
|
|
1810
1851
|
entry.read ? `read: ${JSON.stringify(entry.read)}` : void 0,
|
|
1811
1852
|
entry.serverOnly ? "serverOnly: true" : void 0,
|
|
1812
|
-
entry.serviceFactory ? `serviceFactory: { exportName: ${JSON.stringify(entry.serviceFactory.exportName)}, sourceFile: ${JSON.stringify(entry.serviceFactory.sourceFile)} }` : void 0
|
|
1853
|
+
entry.serviceFactory ? `serviceFactory: { exportName: ${JSON.stringify(entry.serviceFactory.exportName)}, sourceFile: ${JSON.stringify(entry.serviceFactory.sourceFile)} }` : void 0,
|
|
1854
|
+
entry.compositeKey ? `compositeKey: { from: ${JSON.stringify(entry.compositeKey.from)}, encoding: ${JSON.stringify(entry.compositeKey.encoding)} }` : void 0
|
|
1813
1855
|
];
|
|
1814
1856
|
return ` { ${fields.filter(Boolean).join(", ")} }`;
|
|
1815
1857
|
}
|
|
@@ -1860,13 +1902,13 @@ function renderModelField(field, emitConverters) {
|
|
|
1860
1902
|
// packages/dbx-cli/firebase-api-manifest/package.json
|
|
1861
1903
|
var package_default = {
|
|
1862
1904
|
name: "@dereekb/dbx-cli-firebase-api-manifest",
|
|
1863
|
-
version: "14.
|
|
1905
|
+
version: "14.6.0",
|
|
1864
1906
|
sideEffects: false,
|
|
1865
1907
|
private: true,
|
|
1866
1908
|
type: "module",
|
|
1867
1909
|
peerDependencies: {
|
|
1868
|
-
"@dereekb/dbx-cli": "14.
|
|
1869
|
-
"@dereekb/util": "14.
|
|
1910
|
+
"@dereekb/dbx-cli": "14.6.0",
|
|
1911
|
+
"@dereekb/util": "14.6.0",
|
|
1870
1912
|
oxfmt: "^0.66.0",
|
|
1871
1913
|
"ts-morph": "^28.0.0"
|
|
1872
1914
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-firebase-api-manifest",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.6.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"private": true,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"@dereekb/dbx-cli": "14.
|
|
9
|
-
"@dereekb/util": "14.
|
|
8
|
+
"@dereekb/dbx-cli": "14.6.0",
|
|
9
|
+
"@dereekb/util": "14.6.0",
|
|
10
10
|
"oxfmt": "^0.66.0",
|
|
11
11
|
"ts-morph": "^28.0.0"
|
|
12
12
|
}
|
|
@@ -2,6 +2,7 @@ export * from './firestore-indexes-generate';
|
|
|
2
2
|
export * from './generate-firestore-indexes-cli';
|
|
3
3
|
export * from './model-firebase-index-build-manifest';
|
|
4
4
|
export * from './model-firebase-index-extract';
|
|
5
|
+
export * from './model-firebase-index-package-manifests';
|
|
5
6
|
export * from './model-firebase-index-runtime';
|
|
6
7
|
export * from './model-firebase-index-scan-config-schema';
|
|
7
8
|
export * from './model-firebase-index-schema';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader for PRE-BUILT model-firebase-index manifests.
|
|
3
|
+
*
|
|
4
|
+
* `buildModelFirebaseIndexManifest` derives a manifest by parsing query-factory
|
|
5
|
+
* bodies with ts-morph, which only works against a scannable source tree. A
|
|
6
|
+
* published `@dereekb/*` tarball ships `.d.ts` only, so a downstream app cannot
|
|
7
|
+
* derive the framework models' indexes that way and is forced to hand-carry them
|
|
8
|
+
* in its own `firestore.indexes.json` — the gap that nearly dropped the `nbn`
|
|
9
|
+
* notification send-sweep composite in a production workspace.
|
|
10
|
+
*
|
|
11
|
+
* This module closes that gap from the other side: it reads the manifests the
|
|
12
|
+
* framework already generates at release time (`generate-manifests.mjs` writes
|
|
13
|
+
* them into `@dereekb/dbx-components-mcp`'s `generated/` directory) so
|
|
14
|
+
* `generate-firestore-indexes` can merge framework entries without any source to
|
|
15
|
+
* scan.
|
|
16
|
+
*
|
|
17
|
+
* Merge policy is intentionally ADDITIVE and unfiltered: every entry a package
|
|
18
|
+
* declares is emitted, whether or not the consuming app wires that collection.
|
|
19
|
+
* The two failure directions are not symmetric — an index for a collection the
|
|
20
|
+
* app never writes to holds no index entries, so it costs nothing, while a
|
|
21
|
+
* MISSING index breaks the query outright. Filtering to "collections this app
|
|
22
|
+
* wires" is also not the cheap text match it looks like: collection ids are
|
|
23
|
+
* framework constants that never appear literally in consumer source, and the
|
|
24
|
+
* wiring is split across the client model service and the separate server-only
|
|
25
|
+
* `firebaseModelServiceFactory` path. Any escape hatch added later should be an
|
|
26
|
+
* opt-OUT list, which fails safe, rather than an opt-in one, which does not.
|
|
27
|
+
*
|
|
28
|
+
* I/O is fully injectable so tests drive every branch without touching disk.
|
|
29
|
+
*/
|
|
30
|
+
import type { Maybe } from '@dereekb/util';
|
|
31
|
+
import { type ScanReadFile } from '../../src/lib/scan-helpers/scan-io.js';
|
|
32
|
+
import { ModelFirebaseIndexManifest } from './model-firebase-index-schema.js';
|
|
33
|
+
/**
|
|
34
|
+
* Filename suffix every generated model-firebase-index manifest carries. The
|
|
35
|
+
* leading segment is the source package's slug (`dereekb-firebase`), so
|
|
36
|
+
* discovery matches on the suffix alone and picks up any package that starts
|
|
37
|
+
* publishing one without a code change here.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MODEL_FIREBASE_INDEX_MANIFEST_FILENAME_SUFFIX = ".model-firebase-index.mcp.generated.json";
|
|
40
|
+
/**
|
|
41
|
+
* Package that bundles the generated `@dereekb/*` manifests. The manifests do
|
|
42
|
+
* not ship inside each model-owning package (`@dereekb/firebase` and friends
|
|
43
|
+
* publish no JSON assets); `generate-manifests.mjs` collects them all into this
|
|
44
|
+
* one package's `generated/` directory, which is what actually gets published.
|
|
45
|
+
*/
|
|
46
|
+
export declare const BUNDLED_MODEL_FIREBASE_INDEX_MANIFEST_PACKAGE = "@dereekb/dbx-components-mcp";
|
|
47
|
+
/**
|
|
48
|
+
* Function shape used to list a directory's entry names. Defaults to
|
|
49
|
+
* `node:fs/promises.readdir`.
|
|
50
|
+
*/
|
|
51
|
+
export type ModelFirebaseIndexManifestReadDir = (absolutePath: string) => Promise<readonly string[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Function shape used to locate the bundled manifest directory from the
|
|
54
|
+
* consuming workspace's cwd. Returns `null` when the bundling package is not
|
|
55
|
+
* installed.
|
|
56
|
+
*/
|
|
57
|
+
export type ModelFirebaseIndexManifestDirectoryResolver = (cwd: string) => Maybe<string>;
|
|
58
|
+
/**
|
|
59
|
+
* One successfully loaded manifest plus the absolute path it came from. The
|
|
60
|
+
* path is retained so the CLI can name the offending file in diagnostics.
|
|
61
|
+
*/
|
|
62
|
+
export interface LoadedModelFirebaseIndexManifest {
|
|
63
|
+
readonly path: string;
|
|
64
|
+
readonly manifest: ModelFirebaseIndexManifest;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Outcome of one {@link loadModelFirebaseIndexPackageManifests} call.
|
|
68
|
+
*/
|
|
69
|
+
export type LoadModelFirebaseIndexPackageManifestsOutcome = {
|
|
70
|
+
readonly kind: 'success';
|
|
71
|
+
readonly loaded: readonly LoadedModelFirebaseIndexManifest[];
|
|
72
|
+
} | {
|
|
73
|
+
readonly kind: 'unreadable-manifest';
|
|
74
|
+
readonly path: string;
|
|
75
|
+
readonly error: string;
|
|
76
|
+
} | {
|
|
77
|
+
readonly kind: 'invalid-manifest';
|
|
78
|
+
readonly path: string;
|
|
79
|
+
readonly error: string;
|
|
80
|
+
} | {
|
|
81
|
+
readonly kind: 'no-bundled-package';
|
|
82
|
+
readonly packageName: string;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Input to {@link loadModelFirebaseIndexPackageManifests}.
|
|
86
|
+
*/
|
|
87
|
+
export interface LoadModelFirebaseIndexPackageManifestsInput {
|
|
88
|
+
/**
|
|
89
|
+
* Working directory explicit `manifestPaths` resolve against, and the origin
|
|
90
|
+
* bundled-package resolution starts from.
|
|
91
|
+
*/
|
|
92
|
+
readonly cwd: string;
|
|
93
|
+
/**
|
|
94
|
+
* Explicit manifest paths (cwd-relative or absolute). Loaded in the order
|
|
95
|
+
* given, ahead of any discovered bundled manifests.
|
|
96
|
+
*/
|
|
97
|
+
readonly manifestPaths?: readonly string[];
|
|
98
|
+
/**
|
|
99
|
+
* When true, also discover every manifest bundled in the installed
|
|
100
|
+
* {@link BUNDLED_MODEL_FIREBASE_INDEX_MANIFEST_PACKAGE}.
|
|
101
|
+
*/
|
|
102
|
+
readonly discoverBundled?: boolean;
|
|
103
|
+
readonly readFile?: ScanReadFile;
|
|
104
|
+
readonly readDir?: ModelFirebaseIndexManifestReadDir;
|
|
105
|
+
readonly resolveBundledDirectory?: ModelFirebaseIndexManifestDirectoryResolver;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolves the bundled manifest directory by asking Node to resolve the
|
|
109
|
+
* bundling package's `package.json` from the consuming workspace. Going through
|
|
110
|
+
* module resolution (rather than joining `node_modules` by hand) keeps this
|
|
111
|
+
* correct under hoisting and workspace layouts.
|
|
112
|
+
*
|
|
113
|
+
* @param cwd - Working directory the resolution originates from.
|
|
114
|
+
* @returns The absolute `generated/` directory, or null when the package is not installed.
|
|
115
|
+
*/
|
|
116
|
+
export declare const defaultBundledManifestDirectoryResolver: ModelFirebaseIndexManifestDirectoryResolver;
|
|
117
|
+
/**
|
|
118
|
+
* Loads and validates pre-built model-firebase-index manifests from explicit
|
|
119
|
+
* paths and/or the installed bundling package. Never throws — every failure
|
|
120
|
+
* path returns a discriminated outcome the CLI renders as a diagnostic.
|
|
121
|
+
*
|
|
122
|
+
* @param input - Cwd, the manifest sources to load, and injectable I/O hooks.
|
|
123
|
+
* @returns The loaded manifests, or the first failure encountered.
|
|
124
|
+
*/
|
|
125
|
+
export declare function loadModelFirebaseIndexPackageManifests(input: LoadModelFirebaseIndexPackageManifestsInput): Promise<LoadModelFirebaseIndexPackageManifestsOutcome>;
|
|
126
|
+
/**
|
|
127
|
+
* Renders a non-success outcome as a single actionable line, naming both the
|
|
128
|
+
* cause and the fix.
|
|
129
|
+
*
|
|
130
|
+
* @param outcome - The non-success load outcome.
|
|
131
|
+
* @returns A one-line description suitable for stderr.
|
|
132
|
+
*/
|
|
133
|
+
export declare function formatModelFirebaseIndexPackageManifestFailure(outcome: Exclude<LoadModelFirebaseIndexPackageManifestsOutcome, {
|
|
134
|
+
kind: 'success';
|
|
135
|
+
}>): string;
|
|
@@ -9,7 +9,7 @@ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 }
|
|
|
9
9
|
// packages/dbx-cli/firestore-query-manifest/package.json
|
|
10
10
|
var package_default = {
|
|
11
11
|
name: "@dereekb/dbx-cli-firestore-query-manifest",
|
|
12
|
-
version: "14.
|
|
12
|
+
version: "14.6.0",
|
|
13
13
|
sideEffects: false,
|
|
14
14
|
private: true,
|
|
15
15
|
type: "module",
|
|
@@ -17,8 +17,8 @@ var package_default = {
|
|
|
17
17
|
eslint: "10.9.1"
|
|
18
18
|
},
|
|
19
19
|
peerDependencies: {
|
|
20
|
-
"@dereekb/dbx-cli": "14.
|
|
21
|
-
"@dereekb/util": "14.
|
|
20
|
+
"@dereekb/dbx-cli": "14.6.0",
|
|
21
|
+
"@dereekb/util": "14.6.0"
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-firestore-query-manifest",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.6.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"private": true,
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"eslint": "10.9.1"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
|
-
"@dereekb/dbx-cli": "14.
|
|
12
|
-
"@dereekb/util": "14.
|
|
11
|
+
"@dereekb/dbx-cli": "14.6.0",
|
|
12
|
+
"@dereekb/util": "14.6.0"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -5,7 +5,7 @@ const require = __createRequire(import.meta.url);
|
|
|
5
5
|
// packages/dbx-cli/generate-firestore-indexes/package.json
|
|
6
6
|
var package_default = {
|
|
7
7
|
name: "@dereekb/dbx-cli-generate-firestore-indexes",
|
|
8
|
-
version: "14.
|
|
8
|
+
version: "14.6.0",
|
|
9
9
|
sideEffects: false,
|
|
10
10
|
private: true,
|
|
11
11
|
type: "module",
|
|
@@ -13,7 +13,7 @@ var package_default = {
|
|
|
13
13
|
eslint: "10.9.1"
|
|
14
14
|
},
|
|
15
15
|
peerDependencies: {
|
|
16
|
-
"@dereekb/dbx-cli": "14.
|
|
16
|
+
"@dereekb/dbx-cli": "14.6.0"
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -324,7 +324,7 @@ function computeDiff(input) {
|
|
|
324
324
|
|
|
325
325
|
// packages/dbx-cli/firestore-indexes/src/generate-firestore-indexes-cli.ts
|
|
326
326
|
import { readFile as nodeReadFile2, writeFile as nodeWriteFile } from "node:fs/promises";
|
|
327
|
-
import { resolve as
|
|
327
|
+
import { resolve as resolve3 } from "node:path";
|
|
328
328
|
|
|
329
329
|
// packages/dbx-cli/firestore-indexes/src/model-firebase-index-build-manifest.ts
|
|
330
330
|
import { relative, resolve } from "node:path";
|
|
@@ -1063,10 +1063,10 @@ function collectParams(decl, descriptions) {
|
|
|
1063
1063
|
for (const param of params) {
|
|
1064
1064
|
const name = param.getName();
|
|
1065
1065
|
const typeNode = param.getTypeNode()?.getText();
|
|
1066
|
-
const
|
|
1066
|
+
const type5 = typeNode ?? param.getType().getText() ?? "unknown";
|
|
1067
1067
|
const description = descriptions.get(name) ?? "";
|
|
1068
1068
|
const optional = param.isOptional() || param.hasInitializer();
|
|
1069
|
-
out.push({ name, type:
|
|
1069
|
+
out.push({ name, type: type5, description, optional });
|
|
1070
1070
|
}
|
|
1071
1071
|
return out;
|
|
1072
1072
|
}
|
|
@@ -2077,17 +2077,128 @@ function pushInto(map, key, entry) {
|
|
|
2077
2077
|
}
|
|
2078
2078
|
}
|
|
2079
2079
|
|
|
2080
|
+
// packages/dbx-cli/firestore-indexes/src/model-firebase-index-package-manifests.ts
|
|
2081
|
+
import { type as type4 } from "arktype";
|
|
2082
|
+
import { readdir as nodeReadDir } from "node:fs/promises";
|
|
2083
|
+
import { createRequire } from "node:module";
|
|
2084
|
+
import { dirname, resolve as resolve2 } from "node:path";
|
|
2085
|
+
var MODEL_FIREBASE_INDEX_MANIFEST_FILENAME_SUFFIX = ".model-firebase-index.mcp.generated.json";
|
|
2086
|
+
var BUNDLED_MODEL_FIREBASE_INDEX_MANIFEST_PACKAGE = "@dereekb/dbx-components-mcp";
|
|
2087
|
+
var BUNDLED_MANIFEST_DIRECTORY_NAME = "generated";
|
|
2088
|
+
var defaultBundledManifestDirectoryResolver = (cwd) => {
|
|
2089
|
+
let result2;
|
|
2090
|
+
try {
|
|
2091
|
+
const requireFromCwd = createRequire(resolve2(cwd, "noop.js"));
|
|
2092
|
+
const packageJsonPath = requireFromCwd.resolve(`${BUNDLED_MODEL_FIREBASE_INDEX_MANIFEST_PACKAGE}/package.json`);
|
|
2093
|
+
result2 = resolve2(dirname(packageJsonPath), BUNDLED_MANIFEST_DIRECTORY_NAME);
|
|
2094
|
+
} catch {
|
|
2095
|
+
result2 = null;
|
|
2096
|
+
}
|
|
2097
|
+
return result2;
|
|
2098
|
+
};
|
|
2099
|
+
var defaultReadDir = (path) => nodeReadDir(path);
|
|
2100
|
+
async function loadModelFirebaseIndexPackageManifests(input) {
|
|
2101
|
+
const { cwd, manifestPaths = [], discoverBundled = false, readFile = defaultReadFile, readDir = defaultReadDir, resolveBundledDirectory = defaultBundledManifestDirectoryResolver } = input;
|
|
2102
|
+
const paths = manifestPaths.map((path) => resolve2(cwd, path));
|
|
2103
|
+
let failure = null;
|
|
2104
|
+
if (discoverBundled) {
|
|
2105
|
+
const bundledDirectory = resolveBundledDirectory(cwd);
|
|
2106
|
+
if (bundledDirectory == null) {
|
|
2107
|
+
failure = { kind: "no-bundled-package", packageName: BUNDLED_MODEL_FIREBASE_INDEX_MANIFEST_PACKAGE };
|
|
2108
|
+
} else {
|
|
2109
|
+
paths.push(...await readBundledManifestPaths(bundledDirectory, readDir));
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
const loaded = [];
|
|
2113
|
+
for (const path of paths) {
|
|
2114
|
+
if (failure != null) {
|
|
2115
|
+
break;
|
|
2116
|
+
}
|
|
2117
|
+
const readResult = await readManifestFile(path, readFile);
|
|
2118
|
+
if (readResult.kind === "ok") {
|
|
2119
|
+
loaded.push({ path, manifest: readResult.manifest });
|
|
2120
|
+
} else {
|
|
2121
|
+
failure = readResult.outcome;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
return failure ?? { kind: "success", loaded };
|
|
2125
|
+
}
|
|
2126
|
+
function formatModelFirebaseIndexPackageManifestFailure(outcome) {
|
|
2127
|
+
let result2;
|
|
2128
|
+
switch (outcome.kind) {
|
|
2129
|
+
case "unreadable-manifest":
|
|
2130
|
+
result2 = `could not read manifest at ${outcome.path} \u2014 ${outcome.error}`;
|
|
2131
|
+
break;
|
|
2132
|
+
case "invalid-manifest":
|
|
2133
|
+
result2 = `manifest at ${outcome.path} failed validation \u2014 ${outcome.error}`;
|
|
2134
|
+
break;
|
|
2135
|
+
default:
|
|
2136
|
+
result2 = `${outcome.packageName} is not installed, so no bundled index manifests could be discovered. Install it, or pass --manifest <path> explicitly.`;
|
|
2137
|
+
break;
|
|
2138
|
+
}
|
|
2139
|
+
return result2;
|
|
2140
|
+
}
|
|
2141
|
+
async function readBundledManifestPaths(directory, readDir) {
|
|
2142
|
+
let result2;
|
|
2143
|
+
try {
|
|
2144
|
+
const names = await readDir(directory);
|
|
2145
|
+
result2 = names.filter((name) => name.endsWith(MODEL_FIREBASE_INDEX_MANIFEST_FILENAME_SUFFIX)).toSorted().map((name) => resolve2(directory, name));
|
|
2146
|
+
} catch {
|
|
2147
|
+
result2 = [];
|
|
2148
|
+
}
|
|
2149
|
+
return result2;
|
|
2150
|
+
}
|
|
2151
|
+
async function readManifestFile(path, readFile) {
|
|
2152
|
+
let raw = null;
|
|
2153
|
+
let readError = null;
|
|
2154
|
+
try {
|
|
2155
|
+
raw = await readFile(path);
|
|
2156
|
+
} catch (err) {
|
|
2157
|
+
readError = err instanceof Error ? err.message : String(err);
|
|
2158
|
+
}
|
|
2159
|
+
let result2;
|
|
2160
|
+
if (raw == null) {
|
|
2161
|
+
result2 = { kind: "fail", outcome: { kind: "unreadable-manifest", path, error: readError ?? "unknown error" } };
|
|
2162
|
+
} else {
|
|
2163
|
+
let parsed;
|
|
2164
|
+
let parseError = null;
|
|
2165
|
+
try {
|
|
2166
|
+
parsed = JSON.parse(raw);
|
|
2167
|
+
} catch (err) {
|
|
2168
|
+
parseError = err instanceof Error ? err.message : String(err);
|
|
2169
|
+
}
|
|
2170
|
+
if (parseError == null) {
|
|
2171
|
+
const validated = ModelFirebaseIndexManifest(parsed);
|
|
2172
|
+
if (validated instanceof type4.errors) {
|
|
2173
|
+
result2 = { kind: "fail", outcome: { kind: "invalid-manifest", path, error: validated.summary } };
|
|
2174
|
+
} else {
|
|
2175
|
+
result2 = { kind: "ok", manifest: validated };
|
|
2176
|
+
}
|
|
2177
|
+
} else {
|
|
2178
|
+
result2 = { kind: "fail", outcome: { kind: "invalid-manifest", path, error: parseError } };
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
return result2;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2080
2184
|
// packages/dbx-cli/firestore-indexes/src/generate-firestore-indexes-cli.ts
|
|
2081
2185
|
var DEFAULT_BIN_NAME = "dbx-cli-generate-firestore-indexes";
|
|
2082
2186
|
function buildUsage(binName) {
|
|
2083
2187
|
return [
|
|
2084
|
-
`Usage: ${binName} --component <dir> [--
|
|
2188
|
+
`Usage: ${binName} [--component <dir> ...] [--packages] [--manifest <path> ...] [--output <path>] [--check] [--json] [--help]`,
|
|
2085
2189
|
"",
|
|
2086
2190
|
"Generates `firestore.indexes.json` from `@dbxModelFirebaseIndex`-tagged factories.",
|
|
2087
2191
|
"",
|
|
2088
2192
|
"Options:",
|
|
2089
|
-
" --component <dir>
|
|
2090
|
-
"
|
|
2193
|
+
" --component <dir> Repeatable. Relative path to a `-firebase` component or package whose",
|
|
2194
|
+
" source is scanned for tagged factories. Repeat to derive one indexes",
|
|
2195
|
+
" file from several packages at once.",
|
|
2196
|
+
" --packages Also merge the pre-built index manifests bundled in the installed",
|
|
2197
|
+
" @dereekb/dbx-components-mcp. Lets an app derive the framework models\u2019",
|
|
2198
|
+
" indexes without a scannable copy of their source.",
|
|
2199
|
+
" --manifest <path> Repeatable. Merge a specific pre-built manifest JSON file.",
|
|
2200
|
+
"",
|
|
2201
|
+
" At least one of --component / --packages / --manifest is required.",
|
|
2091
2202
|
" --output <path> Output path. Defaults to `firestore.indexes.json` at the cwd.",
|
|
2092
2203
|
" --check Compare against the on-disk file; exit 1 on drift, do not write.",
|
|
2093
2204
|
" --json Print the diff summary as JSON instead of human-readable text.",
|
|
@@ -2107,18 +2218,18 @@ async function runGenerateFirestoreIndexesCli(input) {
|
|
|
2107
2218
|
stderr(usage);
|
|
2108
2219
|
return { exitCode: 2 };
|
|
2109
2220
|
}
|
|
2110
|
-
if (parsed.components.length === 0) {
|
|
2111
|
-
stderr("generate-firestore-indexes: --component is required");
|
|
2221
|
+
if (parsed.components.length === 0 && parsed.manifests.length === 0 && !parsed.packages) {
|
|
2222
|
+
stderr("generate-firestore-indexes: at least one of --component / --packages / --manifest is required");
|
|
2112
2223
|
stderr(usage);
|
|
2113
2224
|
return { exitCode: 2 };
|
|
2114
2225
|
}
|
|
2115
|
-
const outputAbs =
|
|
2226
|
+
const outputAbs = resolve3(cwd, parsed.output);
|
|
2116
2227
|
const entries = [];
|
|
2117
2228
|
const loadedSources = [];
|
|
2118
2229
|
let sawUnresolvedModel = false;
|
|
2119
2230
|
for (const component of parsed.components) {
|
|
2120
2231
|
const buildOutcome = await buildModelFirebaseIndexManifest({
|
|
2121
|
-
projectRoot:
|
|
2232
|
+
projectRoot: resolve3(cwd, component),
|
|
2122
2233
|
generator
|
|
2123
2234
|
});
|
|
2124
2235
|
if (buildOutcome.kind !== "success") {
|
|
@@ -2136,6 +2247,15 @@ async function runGenerateFirestoreIndexesCli(input) {
|
|
|
2136
2247
|
}
|
|
2137
2248
|
loadedSources.push(buildOutcome.manifest.source);
|
|
2138
2249
|
}
|
|
2250
|
+
if (parsed.packages || parsed.manifests.length > 0) {
|
|
2251
|
+
const merged = await collectPackageManifestEntries({ cwd, manifestPaths: parsed.manifests, discoverBundled: parsed.packages, readFile });
|
|
2252
|
+
if (merged.kind === "fail") {
|
|
2253
|
+
stderr(merged.message);
|
|
2254
|
+
return { exitCode: 1 };
|
|
2255
|
+
}
|
|
2256
|
+
entries.push(...merged.entries);
|
|
2257
|
+
loadedSources.push(...merged.sources);
|
|
2258
|
+
}
|
|
2139
2259
|
if (sawUnresolvedModel) {
|
|
2140
2260
|
stderr("generate-firestore-indexes: one or more @dbxModelFirebaseIndexModel tags could not be resolved, so their indexes were dropped. Add the missing identity file to the component\u2019s `dbx-mcp.scan.json` include globs.");
|
|
2141
2261
|
return { exitCode: 1 };
|
|
@@ -2162,8 +2282,29 @@ async function runGenerateFirestoreIndexesCli(input) {
|
|
|
2162
2282
|
}
|
|
2163
2283
|
return { exitCode: 0 };
|
|
2164
2284
|
}
|
|
2285
|
+
async function collectPackageManifestEntries(input) {
|
|
2286
|
+
const { cwd, manifestPaths, discoverBundled, readFile } = input;
|
|
2287
|
+
const outcome = await loadModelFirebaseIndexPackageManifests({ cwd, manifestPaths, discoverBundled, readFile });
|
|
2288
|
+
let result2;
|
|
2289
|
+
if (outcome.kind === "success") {
|
|
2290
|
+
const entries = [];
|
|
2291
|
+
const sources = [];
|
|
2292
|
+
for (const { manifest } of outcome.loaded) {
|
|
2293
|
+
for (const entry of manifest.entries) {
|
|
2294
|
+
entries.push(toModelFirebaseIndexEntryInfo(entry));
|
|
2295
|
+
}
|
|
2296
|
+
sources.push(manifest.source);
|
|
2297
|
+
}
|
|
2298
|
+
result2 = { kind: "ok", entries, sources };
|
|
2299
|
+
} else {
|
|
2300
|
+
result2 = { kind: "fail", message: `generate-firestore-indexes: ${formatModelFirebaseIndexPackageManifestFailure(outcome)}` };
|
|
2301
|
+
}
|
|
2302
|
+
return result2;
|
|
2303
|
+
}
|
|
2165
2304
|
function parseArgv(argv) {
|
|
2166
2305
|
const components = [];
|
|
2306
|
+
const manifests = [];
|
|
2307
|
+
let packages = false;
|
|
2167
2308
|
let output = "firestore.indexes.json";
|
|
2168
2309
|
let check = false;
|
|
2169
2310
|
let json = false;
|
|
@@ -2181,6 +2322,17 @@ function parseArgv(argv) {
|
|
|
2181
2322
|
components.push(argv[i]);
|
|
2182
2323
|
}
|
|
2183
2324
|
break;
|
|
2325
|
+
case "--manifest":
|
|
2326
|
+
i += 1;
|
|
2327
|
+
if (i >= argv.length) {
|
|
2328
|
+
error = "generate-firestore-indexes: --manifest requires a value";
|
|
2329
|
+
} else {
|
|
2330
|
+
manifests.push(argv[i]);
|
|
2331
|
+
}
|
|
2332
|
+
break;
|
|
2333
|
+
case "--packages":
|
|
2334
|
+
packages = true;
|
|
2335
|
+
break;
|
|
2184
2336
|
case "--output":
|
|
2185
2337
|
i += 1;
|
|
2186
2338
|
if (i >= argv.length) {
|
|
@@ -2205,7 +2357,7 @@ function parseArgv(argv) {
|
|
|
2205
2357
|
}
|
|
2206
2358
|
i += 1;
|
|
2207
2359
|
}
|
|
2208
|
-
return { components, output, check, json, help, error };
|
|
2360
|
+
return { components, manifests, packages, output, check, json, help, error };
|
|
2209
2361
|
}
|
|
2210
2362
|
async function readExistingIndexes(input) {
|
|
2211
2363
|
const text = await readExistingIndexesText(input);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-generate-firestore-indexes",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.6.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"private": true,
|
|
6
6
|
"type": "module",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"eslint": "10.9.1"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
|
-
"@dereekb/dbx-cli": "14.
|
|
11
|
+
"@dereekb/dbx-cli": "14.6.0"
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -1138,7 +1138,8 @@ function projectModelEntry(entry) {
|
|
|
1138
1138
|
...entry.mcpToolNameSegment == null ? {} : { mcpToolNameSegment: entry.mcpToolNameSegment },
|
|
1139
1139
|
...entry.read == null ? {} : { read: entry.read },
|
|
1140
1140
|
...entry.serverOnly == null ? {} : { serverOnly: entry.serverOnly },
|
|
1141
|
-
...entry.serviceFactory == null ? {} : { serviceFactory: entry.serviceFactory }
|
|
1141
|
+
...entry.serviceFactory == null ? {} : { serviceFactory: entry.serviceFactory },
|
|
1142
|
+
...entry.compositeKey == null ? {} : { compositeKey: entry.compositeKey }
|
|
1142
1143
|
};
|
|
1143
1144
|
return projected;
|
|
1144
1145
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-generate-mcp-manifest",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.6.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"private": true,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"@dereekb/dbx-cli": "14.
|
|
9
|
-
"@dereekb/model": "14.
|
|
8
|
+
"@dereekb/dbx-cli": "14.6.0",
|
|
9
|
+
"@dereekb/model": "14.6.0",
|
|
10
10
|
"arktype": "^2.2.0",
|
|
11
11
|
"jiti": "2.7.0"
|
|
12
12
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-generate-route-manifest",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.6.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"private": true,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"@dereekb/dbx-cli": "14.
|
|
8
|
+
"@dereekb/dbx-cli": "14.6.0",
|
|
9
9
|
"ts-morph": "^28.0.0"
|
|
10
10
|
}
|
|
11
11
|
}
|