@dereekb/dbx-cli 13.41.0 → 13.42.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 +156 -18
- package/firebase-api-manifest/package.json +3 -3
- package/firestore-indexes/src/generate-firestore-indexes-cli.d.ts +12 -4
- package/firestore-indexes/src/model-firebase-index-analyze.d.ts +12 -2
- package/firestore-query-manifest/main.js +21 -11
- package/firestore-query-manifest/package.json +3 -3
- package/generate-firestore-indexes/main.js +118 -20
- package/generate-firestore-indexes/package.json +2 -2
- package/generate-mcp-manifest/main.js +5 -2
- package/generate-mcp-manifest/package.json +3 -3
- package/generate-route-manifest/main.js +1 -1
- package/generate-route-manifest/package.json +2 -2
- package/index.esm.js +889 -809
- package/lint-cache/package.json +2 -2
- package/manifest-extract/index.esm.js +148 -2
- package/manifest-extract/package.json +2 -2
- package/manifest-extract/src/lib/extract-models.d.ts +4 -2
- package/manifest-extract/src/lib/types.d.ts +65 -0
- package/model-test/index.esm.js +4 -54
- package/model-test/package.json +2 -2
- package/package.json +6 -6
- package/route/package.json +7 -7
- package/src/lib/manifest/types.d.ts +52 -2
- package/src/lib/mcp-scan/manifest/css-utilities-schema.d.ts +2 -2
- package/src/lib/mcp-scan/manifest/tokens-schema.d.ts +2 -2
- package/test/package.json +9 -9
- package/validate/index.js +12 -7
- package/validate/package.json +3 -3
package/eslint/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli/eslint",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.42.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"peerDependencies": {
|
|
6
|
-
"@dereekb/dbx-cli": "13.
|
|
7
|
-
"@dereekb/util": "13.
|
|
6
|
+
"@dereekb/dbx-cli": "13.42.0",
|
|
7
|
+
"@dereekb/util": "13.42.0",
|
|
8
8
|
"@typescript-eslint/utils": "8.59.3",
|
|
9
9
|
"typescript": "5.9.3"
|
|
10
10
|
},
|
|
@@ -22,8 +22,8 @@ function renderGroupedImportLines(imports) {
|
|
|
22
22
|
set.add(identifier);
|
|
23
23
|
importsByPackage.set(packageName, set);
|
|
24
24
|
}
|
|
25
|
-
return
|
|
26
|
-
const sortedNames =
|
|
25
|
+
return Array.from(importsByPackage.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([pkg, names]) => {
|
|
26
|
+
const sortedNames = Array.from(names).sort(compareStrings).join(", ");
|
|
27
27
|
return `import { ${sortedNames} } from '${pkg}';`;
|
|
28
28
|
});
|
|
29
29
|
}
|
|
@@ -695,13 +695,18 @@ function escapeRegExp(value) {
|
|
|
695
695
|
}
|
|
696
696
|
|
|
697
697
|
// packages/dbx-cli/manifest-extract/src/lib/extract-models.ts
|
|
698
|
-
import { Node as Node4, Project as Project3 } from "ts-morph";
|
|
698
|
+
import { Node as Node4, SyntaxKind, Project as Project3 } from "ts-morph";
|
|
699
699
|
var READ_LEVEL_VALUES = /* @__PURE__ */ new Set(["system", "owner", "admin-only", "permissions"]);
|
|
700
700
|
var SERVICE_FACTORY_TAG = "dbxModelServiceFactory";
|
|
701
701
|
var MCP_TOOL_NAME_SEGMENT_TAG = "dbxModelMcpToolNameSegment";
|
|
702
702
|
var MODEL_TYPE_VALUE_PATTERN = /^[a-z][A-Za-z0-9_$]*$/;
|
|
703
703
|
var TOOL_NAME_SEGMENT_PATTERN = /^[A-Za-z][A-Za-z0-9_$]*$/;
|
|
704
704
|
var IDENTITY_FN = "firestoreModelIdentity";
|
|
705
|
+
var SINGLE_ITEM_COLLECTION_FN = "singleItemFirestoreCollection";
|
|
706
|
+
var ROOT_SINGLE_ITEM_COLLECTION_FN = "rootSingleItemFirestoreCollection";
|
|
707
|
+
var SINGLE_ITEM_COLLECTION_FN_NAMES = [SINGLE_ITEM_COLLECTION_FN, ROOT_SINGLE_ITEM_COLLECTION_FN];
|
|
708
|
+
var MODEL_IDENTITY_KEY = "modelIdentity";
|
|
709
|
+
var SINGLE_ITEM_IDENTIFIER_KEY = "singleItemIdentifier";
|
|
705
710
|
var CONVERTER_FN_NAMES = ["snapshotConverterFunctions", "firestoreSubObject", "firestoreObjectArray"];
|
|
706
711
|
var SUB_OBJECT_FN = "firestoreSubObject";
|
|
707
712
|
var OBJECT_ARRAY_FN = "firestoreObjectArray";
|
|
@@ -718,7 +723,9 @@ function extractModelsFromSource(input) {
|
|
|
718
723
|
const enums = readEnums(sourceFile);
|
|
719
724
|
const modelGroups = readModelGroups(sourceFile);
|
|
720
725
|
const serviceFactories = readServiceFactories(sourceFile);
|
|
721
|
-
|
|
726
|
+
const singleItemCollections = readSingleItemCollections(sourceFile);
|
|
727
|
+
const stringConstants = readStringConstants(sourceFile);
|
|
728
|
+
return { identities, interfaces, converters, enums, modelGroups, serviceFactories, singleItemCollections, stringConstants };
|
|
722
729
|
}
|
|
723
730
|
function readIdentities(sourceFile) {
|
|
724
731
|
const out = [];
|
|
@@ -838,6 +845,65 @@ function readServiceFactoryModelType(jsDocs) {
|
|
|
838
845
|
}
|
|
839
846
|
return result;
|
|
840
847
|
}
|
|
848
|
+
function readSingleItemCollections(sourceFile) {
|
|
849
|
+
const out = [];
|
|
850
|
+
const text = sourceFile.getFullText();
|
|
851
|
+
if (SINGLE_ITEM_COLLECTION_FN_NAMES.some((name) => text.includes(name))) {
|
|
852
|
+
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
853
|
+
const fnName = readCalleeName(call);
|
|
854
|
+
if (fnName !== SINGLE_ITEM_COLLECTION_FN && fnName !== ROOT_SINGLE_ITEM_COLLECTION_FN) continue;
|
|
855
|
+
const entry = buildSingleItemCollection(call, fnName === ROOT_SINGLE_ITEM_COLLECTION_FN);
|
|
856
|
+
if (entry) out.push(entry);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return out;
|
|
860
|
+
}
|
|
861
|
+
function readCalleeName(call) {
|
|
862
|
+
const expression = call.getExpression();
|
|
863
|
+
return Node4.isPropertyAccessExpression(expression) ? expression.getName() : expression.getText();
|
|
864
|
+
}
|
|
865
|
+
function buildSingleItemCollection(call, root) {
|
|
866
|
+
let result;
|
|
867
|
+
const args = call.getArguments();
|
|
868
|
+
if (args.length > 0) {
|
|
869
|
+
const config = args[0];
|
|
870
|
+
if (Node4.isObjectLiteralExpression(config)) {
|
|
871
|
+
const identityNode = readPropertyValue(config, MODEL_IDENTITY_KEY);
|
|
872
|
+
if (identityNode && Node4.isIdentifier(identityNode)) {
|
|
873
|
+
const identifierNode = readPropertyValue(config, SINGLE_ITEM_IDENTIFIER_KEY);
|
|
874
|
+
result = {
|
|
875
|
+
identityConst: identityNode.getText(),
|
|
876
|
+
root,
|
|
877
|
+
documentId: readStringLiteralText(identifierNode),
|
|
878
|
+
documentIdConstRef: identifierNode && Node4.isIdentifier(identifierNode) ? identifierNode.getText() : void 0,
|
|
879
|
+
explicitIdentifier: config.getProperty(SINGLE_ITEM_IDENTIFIER_KEY) !== void 0,
|
|
880
|
+
line: call.getStartLineNumber()
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return result;
|
|
886
|
+
}
|
|
887
|
+
function readStringConstants(sourceFile) {
|
|
888
|
+
const out = [];
|
|
889
|
+
for (const statement of sourceFile.getVariableStatements()) {
|
|
890
|
+
if (!statement.isExported()) continue;
|
|
891
|
+
for (const decl of statement.getDeclarations()) {
|
|
892
|
+
const value = readStringLiteralText(decl.getInitializer());
|
|
893
|
+
if (value !== void 0) {
|
|
894
|
+
out.push({ name: decl.getName(), value });
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return out;
|
|
899
|
+
}
|
|
900
|
+
function readStringLiteralText(node) {
|
|
901
|
+
let current = node;
|
|
902
|
+
while (current !== void 0 && Node4.isAsExpression(current)) {
|
|
903
|
+
current = current.getExpression();
|
|
904
|
+
}
|
|
905
|
+
return current !== void 0 && (Node4.isStringLiteral(current) || Node4.isNoSubstitutionTemplateLiteral(current)) ? current.getLiteralText() : void 0;
|
|
906
|
+
}
|
|
841
907
|
function readConverters(sourceFile) {
|
|
842
908
|
const out = [];
|
|
843
909
|
for (const statement of sourceFile.getVariableStatements()) {
|
|
@@ -1134,14 +1200,17 @@ function findModelFiles(packageRoot) {
|
|
|
1134
1200
|
const text = readFileSync6(filePath, "utf8");
|
|
1135
1201
|
if (!textHasModelMarker(text)) continue;
|
|
1136
1202
|
const extraction = extractModelsFromSource({ name: filePath, text });
|
|
1137
|
-
if (extraction
|
|
1203
|
+
if (!hasExtractedArtifact(extraction)) continue;
|
|
1138
1204
|
out.push({ filePath, extraction });
|
|
1139
1205
|
}
|
|
1140
1206
|
}
|
|
1141
1207
|
return out;
|
|
1142
1208
|
}
|
|
1143
1209
|
function textHasModelMarker(text) {
|
|
1144
|
-
return text.includes("firestoreModelIdentity(") || text.includes("@dbxModelGroup") || text.includes("snapshotConverterFunctions") || text.includes("firestoreSubObject") || text.includes("firestoreObjectArray") || text.includes("@dbxModelServiceFactory");
|
|
1210
|
+
return text.includes("firestoreModelIdentity(") || text.includes("@dbxModelGroup") || text.includes("snapshotConverterFunctions") || text.includes("firestoreSubObject") || text.includes("firestoreObjectArray") || text.includes("@dbxModelServiceFactory") || text.includes("singleItemFirestoreCollection") || text.includes("rootSingleItemFirestoreCollection") || text.includes("export enum ");
|
|
1211
|
+
}
|
|
1212
|
+
function hasExtractedArtifact(extraction) {
|
|
1213
|
+
return extraction.identities.length > 0 || extraction.modelGroups.length > 0 || extraction.converters.length > 0 || extraction.serviceFactories.length > 0 || extraction.singleItemCollections.length > 0 || extraction.enums.length > 0;
|
|
1145
1214
|
}
|
|
1146
1215
|
function* walkSourceFiles(dir) {
|
|
1147
1216
|
for (const entry of readdirSync2(dir).sort()) {
|
|
@@ -1156,7 +1225,7 @@ function* walkSourceFiles(dir) {
|
|
|
1156
1225
|
}
|
|
1157
1226
|
}
|
|
1158
1227
|
function isCandidateSourceFile(name) {
|
|
1159
|
-
return name.endsWith(".ts") && !name.endsWith(".api.ts") && !name.endsWith(".spec.ts") && !name.endsWith(".test.ts") && !name.endsWith(".
|
|
1228
|
+
return name.endsWith(".ts") && !name.endsWith(".api.ts") && !name.endsWith(".spec.ts") && !name.endsWith(".test.ts") && !name.endsWith(".d.ts");
|
|
1160
1229
|
}
|
|
1161
1230
|
function safeIsDirectory2(p) {
|
|
1162
1231
|
let result;
|
|
@@ -1172,6 +1241,8 @@ function safeIsDirectory2(p) {
|
|
|
1172
1241
|
var MAX_NESTED_DEPTH = 8;
|
|
1173
1242
|
var LONG_NAME_RE = /^[a-z][a-zA-Z0-9]*$/;
|
|
1174
1243
|
var ENUM_GENERIC_RE = /firestoreEnum<(\w+)>|optionalFirestoreEnum<(\w+)>/;
|
|
1244
|
+
var TS_TYPE_TOKEN_RE = /[A-Za-z_$][\w$]*/g;
|
|
1245
|
+
var DEFAULT_SINGLE_ITEM_DOCUMENT_ID = "0";
|
|
1175
1246
|
function assembleModels(input) {
|
|
1176
1247
|
const registries = buildGlobalRegistries(input.extractions);
|
|
1177
1248
|
const accumulator = { seen: /* @__PURE__ */ new Set(), entries: [] };
|
|
@@ -1185,7 +1256,7 @@ function collectModelEnums(input) {
|
|
|
1185
1256
|
const registry = buildEnumRegistry(input.extractions);
|
|
1186
1257
|
const referenced = collectReferencedEnumNames(input.models);
|
|
1187
1258
|
const out = {};
|
|
1188
|
-
for (const name of
|
|
1259
|
+
for (const name of Array.from(referenced).sort((a, b) => a.localeCompare(b))) {
|
|
1189
1260
|
const extracted = registry.get(name);
|
|
1190
1261
|
if (extracted) {
|
|
1191
1262
|
out[name] = buildModelEnumEntry(extracted);
|
|
@@ -1227,17 +1298,31 @@ function buildGlobalRegistries(extractions) {
|
|
|
1227
1298
|
const interfaceRegistry = /* @__PURE__ */ new Map();
|
|
1228
1299
|
const groupByModelName = /* @__PURE__ */ new Map();
|
|
1229
1300
|
const serviceFactoryByModelType = /* @__PURE__ */ new Map();
|
|
1301
|
+
const enumNames = /* @__PURE__ */ new Set();
|
|
1302
|
+
const singleItemByIdentityConst = /* @__PURE__ */ new Map();
|
|
1303
|
+
const identityByConst = /* @__PURE__ */ new Map();
|
|
1304
|
+
const stringConstants = /* @__PURE__ */ new Map();
|
|
1230
1305
|
for (const { extraction, sourceFile } of extractions) {
|
|
1231
1306
|
registerConverters(extraction.converters, converterRegistry);
|
|
1232
1307
|
registerInterfaces(extraction.interfaces, interfaceRegistry);
|
|
1233
1308
|
registerModelGroups(extraction.modelGroups, groupByModelName);
|
|
1309
|
+
for (const e of extraction.enums) enumNames.add(e.name);
|
|
1310
|
+
for (const identity of extraction.identities) {
|
|
1311
|
+
if (!identityByConst.has(identity.identityConst)) identityByConst.set(identity.identityConst, identity);
|
|
1312
|
+
}
|
|
1313
|
+
for (const single of extraction.singleItemCollections) {
|
|
1314
|
+
if (!singleItemByIdentityConst.has(single.identityConst)) singleItemByIdentityConst.set(single.identityConst, single);
|
|
1315
|
+
}
|
|
1316
|
+
for (const constant of extraction.stringConstants) {
|
|
1317
|
+
if (!stringConstants.has(constant.name)) stringConstants.set(constant.name, constant.value);
|
|
1318
|
+
}
|
|
1234
1319
|
for (const factory of extraction.serviceFactories) {
|
|
1235
1320
|
if (!serviceFactoryByModelType.has(factory.modelType)) {
|
|
1236
1321
|
serviceFactoryByModelType.set(factory.modelType, { exportName: factory.exportName, sourceFile });
|
|
1237
1322
|
}
|
|
1238
1323
|
}
|
|
1239
1324
|
}
|
|
1240
|
-
return { converterRegistry, interfaceRegistry, groupByModelName, serviceFactoryByModelType };
|
|
1325
|
+
return { converterRegistry, interfaceRegistry, groupByModelName, serviceFactoryByModelType, enumNames, singleItemByIdentityConst, identityByConst, stringConstants };
|
|
1241
1326
|
}
|
|
1242
1327
|
function registerConverters(converters, registry) {
|
|
1243
1328
|
for (const converter of converters) {
|
|
@@ -1259,10 +1344,9 @@ function registerModelGroups(modelGroups, registry) {
|
|
|
1259
1344
|
}
|
|
1260
1345
|
}
|
|
1261
1346
|
function appendEntriesFromSource(source, registries, accumulator) {
|
|
1262
|
-
const enumNames = new Set(source.extraction.enums.map((e) => e.name));
|
|
1263
1347
|
for (const identity of source.extraction.identities) {
|
|
1264
1348
|
if (accumulator.seen.has(identity.identityConst)) continue;
|
|
1265
|
-
const entry = buildEntryForIdentity({ identity, source, registries
|
|
1349
|
+
const entry = buildEntryForIdentity({ identity, source, registries });
|
|
1266
1350
|
if (entry) {
|
|
1267
1351
|
accumulator.seen.add(identity.identityConst);
|
|
1268
1352
|
accumulator.entries.push(entry);
|
|
@@ -1270,7 +1354,7 @@ function appendEntriesFromSource(source, registries, accumulator) {
|
|
|
1270
1354
|
}
|
|
1271
1355
|
}
|
|
1272
1356
|
function buildEntryForIdentity(input) {
|
|
1273
|
-
const { identity, source, registries
|
|
1357
|
+
const { identity, source, registries } = input;
|
|
1274
1358
|
let result;
|
|
1275
1359
|
if (identity.collectionPrefix !== void 0) {
|
|
1276
1360
|
const modelName = capitalize(identity.modelType);
|
|
@@ -1283,7 +1367,7 @@ function buildEntryForIdentity(input) {
|
|
|
1283
1367
|
iface,
|
|
1284
1368
|
interfaceRegistry: registries.interfaceRegistry,
|
|
1285
1369
|
converterRegistry: registries.converterRegistry,
|
|
1286
|
-
enumNames,
|
|
1370
|
+
enumNames: registries.enumNames,
|
|
1287
1371
|
depth: 0,
|
|
1288
1372
|
visitedConverters: /* @__PURE__ */ new Set()
|
|
1289
1373
|
});
|
|
@@ -1297,6 +1381,12 @@ function buildManifestEntry(input) {
|
|
|
1297
1381
|
const { identity, modelName, collectionPrefix, iface, fields, source, registries } = input;
|
|
1298
1382
|
const modelGroup = registries.groupByModelName.get(modelName);
|
|
1299
1383
|
const serviceFactory = registries.serviceFactoryByModelType.get(identity.modelType);
|
|
1384
|
+
const single = registries.singleItemByIdentityConst.get(identity.identityConst);
|
|
1385
|
+
const singleItemIdentifier = single ? resolveSingleItemDocumentId(single, registries) : void 0;
|
|
1386
|
+
if (single && singleItemIdentifier === void 0) {
|
|
1387
|
+
console.warn(`[single-item-id-unresolved] ${source.sourceFile}:${single.line} \xB7 ${identity.modelType} \u2192 singleItemIdentifier ${single.documentIdConstRef ?? "(expression)"} could not be resolved to a string; the model's fixed document id is omitted from the manifest`);
|
|
1388
|
+
}
|
|
1389
|
+
const exampleKey = buildExampleKey({ identity, collectionPrefix, singleItemIdentifier, registries });
|
|
1300
1390
|
return {
|
|
1301
1391
|
modelType: identity.modelType,
|
|
1302
1392
|
modelName,
|
|
@@ -1304,6 +1394,9 @@ function buildManifestEntry(input) {
|
|
|
1304
1394
|
identityConst: identity.identityConst,
|
|
1305
1395
|
collectionPrefix,
|
|
1306
1396
|
...identity.parentIdentityConst ? { parentIdentityConst: identity.parentIdentityConst } : {},
|
|
1397
|
+
...single ? { singleton: true } : {},
|
|
1398
|
+
...singleItemIdentifier === void 0 ? {} : { singleItemIdentifier },
|
|
1399
|
+
...exampleKey === void 0 ? {} : { exampleKey },
|
|
1307
1400
|
...iface.description ? { description: iface.description } : {},
|
|
1308
1401
|
sourcePackage: source.sourcePackage,
|
|
1309
1402
|
sourceFile: source.sourceFile,
|
|
@@ -1314,6 +1407,46 @@ function buildManifestEntry(input) {
|
|
|
1314
1407
|
...serviceFactory ? { serviceFactory } : {}
|
|
1315
1408
|
};
|
|
1316
1409
|
}
|
|
1410
|
+
function resolveSingleItemDocumentId(single, registries) {
|
|
1411
|
+
let result;
|
|
1412
|
+
if (!single.explicitIdentifier) {
|
|
1413
|
+
result = DEFAULT_SINGLE_ITEM_DOCUMENT_ID;
|
|
1414
|
+
} else if (single.documentId !== void 0) {
|
|
1415
|
+
result = single.documentId;
|
|
1416
|
+
} else if (single.documentIdConstRef !== void 0) {
|
|
1417
|
+
result = registries.stringConstants.get(single.documentIdConstRef);
|
|
1418
|
+
}
|
|
1419
|
+
return result;
|
|
1420
|
+
}
|
|
1421
|
+
function buildExampleKey(input) {
|
|
1422
|
+
const { identity, collectionPrefix, singleItemIdentifier, registries } = input;
|
|
1423
|
+
const segments = [`${collectionPrefix}/${singleItemIdentifier ?? modelIdPlaceholder(identity.modelType)}`];
|
|
1424
|
+
const visited = /* @__PURE__ */ new Set([identity.identityConst]);
|
|
1425
|
+
let parentConst = identity.parentIdentityConst;
|
|
1426
|
+
let resolved = true;
|
|
1427
|
+
while (resolved && parentConst !== void 0) {
|
|
1428
|
+
const parent = visited.has(parentConst) ? void 0 : registries.identityByConst.get(parentConst);
|
|
1429
|
+
if (parent === void 0) {
|
|
1430
|
+
resolved = false;
|
|
1431
|
+
} else {
|
|
1432
|
+
visited.add(parent.identityConst);
|
|
1433
|
+
segments.unshift(`${collectionNameFor(parent)}/${ancestorDocumentIdFor(parent, registries)}`);
|
|
1434
|
+
parentConst = parent.parentIdentityConst;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return resolved ? segments.join("/") : void 0;
|
|
1438
|
+
}
|
|
1439
|
+
function collectionNameFor(identity) {
|
|
1440
|
+
return identity.collectionPrefix ?? identity.modelType.toLowerCase();
|
|
1441
|
+
}
|
|
1442
|
+
function ancestorDocumentIdFor(identity, registries) {
|
|
1443
|
+
const single = registries.singleItemByIdentityConst.get(identity.identityConst);
|
|
1444
|
+
const documentId = single ? resolveSingleItemDocumentId(single, registries) : void 0;
|
|
1445
|
+
return documentId ?? modelIdPlaceholder(identity.modelType);
|
|
1446
|
+
}
|
|
1447
|
+
function modelIdPlaceholder(modelType) {
|
|
1448
|
+
return `<${modelType}Id>`;
|
|
1449
|
+
}
|
|
1317
1450
|
function findConverterForInterface(extraction, interfaceName) {
|
|
1318
1451
|
return extraction.converters.find((c) => c.interfaceName === interfaceName);
|
|
1319
1452
|
}
|
|
@@ -1425,11 +1558,13 @@ function resolveLongName(fieldName, propLongName) {
|
|
|
1425
1558
|
function resolveEnumRef(converter, tsType, enumNames) {
|
|
1426
1559
|
let result;
|
|
1427
1560
|
if (tsType) {
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1561
|
+
TS_TYPE_TOKEN_RE.lastIndex = 0;
|
|
1562
|
+
let match = TS_TYPE_TOKEN_RE.exec(tsType);
|
|
1563
|
+
while (result === void 0 && match !== null) {
|
|
1564
|
+
if (enumNames.has(match[0])) {
|
|
1565
|
+
result = match[0];
|
|
1566
|
+
} else {
|
|
1567
|
+
match = TS_TYPE_TOKEN_RE.exec(tsType);
|
|
1433
1568
|
}
|
|
1434
1569
|
}
|
|
1435
1570
|
}
|
|
@@ -1525,6 +1660,9 @@ function renderModelEntry(entry, emitConverters) {
|
|
|
1525
1660
|
`identityConst: ${JSON.stringify(entry.identityConst)}`,
|
|
1526
1661
|
`collectionPrefix: ${JSON.stringify(entry.collectionPrefix)}`,
|
|
1527
1662
|
entry.parentIdentityConst ? `parentIdentityConst: ${JSON.stringify(entry.parentIdentityConst)}` : void 0,
|
|
1663
|
+
entry.singleton ? "singleton: true" : void 0,
|
|
1664
|
+
entry.singleItemIdentifier === void 0 ? void 0 : `singleItemIdentifier: ${JSON.stringify(entry.singleItemIdentifier)}`,
|
|
1665
|
+
entry.exampleKey === void 0 ? void 0 : `exampleKey: ${JSON.stringify(entry.exampleKey)}`,
|
|
1528
1666
|
entry.description ? `description: ${JSON.stringify(entry.description)}` : void 0,
|
|
1529
1667
|
`sourcePackage: ${JSON.stringify(entry.sourcePackage)}`,
|
|
1530
1668
|
`sourceFile: ${JSON.stringify(entry.sourceFile)}`,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-firebase-api-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.42.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"devDependencies": {
|
|
7
7
|
"ts-morph": "^21.0.0"
|
|
8
8
|
},
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"@dereekb/dbx-cli": "13.
|
|
11
|
-
"@dereekb/util": "13.
|
|
10
|
+
"@dereekb/dbx-cli": "13.42.0",
|
|
11
|
+
"@dereekb/util": "13.42.0",
|
|
12
12
|
"prettier": "3.8.3"
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `generate-firestore-indexes` subcommand entry point.
|
|
3
3
|
*
|
|
4
|
-
* Walks
|
|
5
|
-
* tagged factories (the same pipeline
|
|
6
|
-
* runs the analyzer, and emits a
|
|
7
|
-
* payload via
|
|
4
|
+
* Walks one or more `-firebase` components/packages for
|
|
5
|
+
* `@dbxModelFirebaseIndex`-tagged factories (the same pipeline
|
|
6
|
+
* `scan-model-firebase-indexes` uses), runs the analyzer, and emits a
|
|
7
|
+
* canonical `firestore.indexes.json` payload via
|
|
8
|
+
* {@link generateFirestoreIndexesJson}.
|
|
9
|
+
*
|
|
10
|
+
* `--component` is repeatable, and that is the supported way to derive a
|
|
11
|
+
* single deployed indexes file from several packages. Firebase deploys one
|
|
12
|
+
* indexes file per database, and nothing merges per-package files — a
|
|
13
|
+
* package's composites otherwise survive in a downstream file only by the
|
|
14
|
+
* "collections no factory touches" preservation rule, which silently drops
|
|
15
|
+
* them the moment a local factory touches the same collection.
|
|
8
16
|
*
|
|
9
17
|
* Two modes:
|
|
10
18
|
*
|
|
@@ -5,8 +5,14 @@
|
|
|
5
5
|
* extractor and decides, per `(collection, scope, constraintSequence)`,
|
|
6
6
|
* whether the query requires a composite index, a `fieldOverrides[]`
|
|
7
7
|
* variant, or neither (because Firestore's automatic single-field
|
|
8
|
-
* `COLLECTION`-scope index already covers the query).
|
|
9
|
-
*
|
|
8
|
+
* `COLLECTION`-scope index already covers the query).
|
|
9
|
+
*
|
|
10
|
+
* "Neither" also covers a multi-field `COLLECTION`-scope sequence made up
|
|
11
|
+
* purely of `==` filters: Firestore merges the automatic single-field
|
|
12
|
+
* indexes to serve those, so emitting a composite would only add write
|
|
13
|
+
* latency and storage. See {@link isEqualityOnlySequence}.
|
|
14
|
+
*
|
|
15
|
+
* Encodes Firestore's field-order rule for composites:
|
|
10
16
|
*
|
|
11
17
|
* 1. Equality (`==`, `in`) fields first, in source order.
|
|
12
18
|
* 2. A single range/inequality field next (direction taken from any
|
|
@@ -51,6 +57,10 @@ export type AnalyzerWarning = {
|
|
|
51
57
|
readonly kind: 'unsupported-array-contains-any';
|
|
52
58
|
readonly factoryName: string;
|
|
53
59
|
readonly field: string;
|
|
60
|
+
} | {
|
|
61
|
+
readonly kind: 'equality-only-composite-skipped';
|
|
62
|
+
readonly factoryName: string;
|
|
63
|
+
readonly fields: readonly string[];
|
|
54
64
|
};
|
|
55
65
|
/**
|
|
56
66
|
* Runs the analyzer over every extracted entry.
|
|
@@ -9,15 +9,15 @@ 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: "13.
|
|
12
|
+
version: "13.42.0",
|
|
13
13
|
private: true,
|
|
14
14
|
type: "module",
|
|
15
15
|
devDependencies: {
|
|
16
16
|
eslint: "10.4.0"
|
|
17
17
|
},
|
|
18
18
|
peerDependencies: {
|
|
19
|
-
"@dereekb/dbx-cli": "13.
|
|
20
|
-
"@dereekb/util": "13.
|
|
19
|
+
"@dereekb/dbx-cli": "13.42.0",
|
|
20
|
+
"@dereekb/util": "13.42.0"
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
|
|
@@ -37,8 +37,8 @@ function renderGroupedImportLines(imports) {
|
|
|
37
37
|
set.add(identifier);
|
|
38
38
|
importsByPackage.set(packageName, set);
|
|
39
39
|
}
|
|
40
|
-
return
|
|
41
|
-
const sortedNames =
|
|
40
|
+
return Array.from(importsByPackage.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([pkg, names]) => {
|
|
41
|
+
const sortedNames = Array.from(names).sort(compareStrings).join(", ");
|
|
42
42
|
return `import { ${sortedNames} } from '${pkg}';`;
|
|
43
43
|
});
|
|
44
44
|
}
|
|
@@ -82,9 +82,9 @@ function scanFirestoreRules(source) {
|
|
|
82
82
|
if (allow.ops.has("list") || allow.ops.has("read")) current.list = mergeAccess(current.list, allow.access);
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
|
-
const collections =
|
|
85
|
+
const collections = Array.from(accumulated.entries()).map(([collection, value]) => ({
|
|
86
86
|
collection,
|
|
87
|
-
paths:
|
|
87
|
+
paths: Array.from(value.paths).sort(),
|
|
88
88
|
get: value.get,
|
|
89
89
|
list: value.list,
|
|
90
90
|
collectionGroup: value.collectionGroup,
|
|
@@ -294,7 +294,7 @@ function parentPathsForCollection(scan, collection) {
|
|
|
294
294
|
parents.add(parentSegments.join("/"));
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
|
-
return
|
|
297
|
+
return Array.from(parents);
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
// packages/dbx-cli/src/lib/scan-helpers/exported-from-package.ts
|
|
@@ -670,11 +670,18 @@ function analyzeSequence(input) {
|
|
|
670
670
|
if (isSingleFieldQuery(buckets)) {
|
|
671
671
|
return analyzeSingleField({ buckets, collection, scope, warnings });
|
|
672
672
|
}
|
|
673
|
+
if (scope === "COLLECTION" && isEqualityOnlySequence(buckets)) {
|
|
674
|
+
warnings.push({ kind: "equality-only-composite-skipped", factoryName, fields: buckets.equalities.map((e) => e.fieldPath) });
|
|
675
|
+
return { kind: "auto", warnings };
|
|
676
|
+
}
|
|
673
677
|
return analyzeMultiField({ buckets, sequenceEntries: sequence.entries, collection, scope, factoryName, warnings });
|
|
674
678
|
}
|
|
675
679
|
function isSingleFieldQuery(buckets) {
|
|
676
680
|
return buckets.distinctFieldCount === 1;
|
|
677
681
|
}
|
|
682
|
+
function isEqualityOnlySequence(buckets) {
|
|
683
|
+
return buckets.ranges.length === 0 && buckets.arrays.length === 0 && buckets.orderBys.length === 0 && buckets.equalities.length > 0 && buckets.equalities.every((e) => (e.operator ?? "==") === "==");
|
|
684
|
+
}
|
|
678
685
|
function analyzeSingleField(input) {
|
|
679
686
|
const { buckets, collection, scope, warnings } = input;
|
|
680
687
|
if (scope === "COLLECTION") {
|
|
@@ -769,7 +776,7 @@ function collectOrderByDirections(input) {
|
|
|
769
776
|
}
|
|
770
777
|
}
|
|
771
778
|
for (const [field, dirs] of conflicts) {
|
|
772
|
-
warnings.push({ kind: "orderby-conflict", factoryName, field, directions:
|
|
779
|
+
warnings.push({ kind: "orderby-conflict", factoryName, field, directions: Array.from(dirs) });
|
|
773
780
|
}
|
|
774
781
|
return out;
|
|
775
782
|
}
|
|
@@ -1250,7 +1257,7 @@ function composeEntry(input) {
|
|
|
1250
1257
|
}
|
|
1251
1258
|
function resolveConstraintSequences(input) {
|
|
1252
1259
|
const { candidate, tags, name, line, filePath, warnings } = input;
|
|
1253
|
-
const bodyResult = extractConstraintsFromBody({ decl: candidate.decl, factoryName: name, filePath, dispatcher: tags.dispatcher });
|
|
1260
|
+
const bodyResult = extractConstraintsFromBody({ decl: candidate.decl, factoryName: name, filePath, dispatcher: tags.dispatcher, suppressed: tags.skip || tags.specOnly });
|
|
1254
1261
|
for (const warning of bodyResult.warnings) {
|
|
1255
1262
|
warnings.push(warning);
|
|
1256
1263
|
}
|
|
@@ -1331,11 +1338,14 @@ function buildConstraintSequences(input) {
|
|
|
1331
1338
|
return sequences;
|
|
1332
1339
|
}
|
|
1333
1340
|
function extractConstraintsFromBody(input) {
|
|
1334
|
-
const { decl, factoryName, filePath, dispatcher } = input;
|
|
1341
|
+
const { decl, factoryName, filePath, dispatcher, suppressed } = input;
|
|
1335
1342
|
const entries = [];
|
|
1336
1343
|
const conditionalFieldSet = /* @__PURE__ */ new Set();
|
|
1337
1344
|
const warnings = [];
|
|
1338
1345
|
const body = decl.getBody();
|
|
1346
|
+
if (suppressed) {
|
|
1347
|
+
return { entries, conditionalFields: [], warnings, skipped: true, dispatcherDelegates: [] };
|
|
1348
|
+
}
|
|
1339
1349
|
if (dispatcher) {
|
|
1340
1350
|
const violation = body === void 0 ? void 0 : findFirstConstraintCall(body);
|
|
1341
1351
|
if (violation !== void 0) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/dbx-cli-firestore-query-manifest",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.42.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"devDependencies": {
|
|
7
7
|
"eslint": "10.4.0"
|
|
8
8
|
},
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"@dereekb/dbx-cli": "13.
|
|
11
|
-
"@dereekb/util": "13.
|
|
10
|
+
"@dereekb/dbx-cli": "13.42.0",
|
|
11
|
+
"@dereekb/util": "13.42.0"
|
|
12
12
|
}
|
|
13
13
|
}
|