@dereekb/dbx-cli 13.11.18 → 13.12.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.
Files changed (156) hide show
  1. package/firebase-api-manifest/main.js +70 -9
  2. package/firebase-api-manifest/package.json +1 -1
  3. package/firestore-indexes/src/firestore-indexes-generate.d.ts +125 -0
  4. package/firestore-indexes/src/firestore-model-identity-resolver.d.ts +93 -0
  5. package/firestore-indexes/src/firestore-query-helpers.d.ts +108 -0
  6. package/firestore-indexes/src/generate-firestore-indexes-cli.d.ts +94 -0
  7. package/firestore-indexes/src/index.d.ts +7 -0
  8. package/firestore-indexes/src/model-firebase-index-analyze.d.ts +68 -0
  9. package/firestore-indexes/src/model-firebase-index-build-manifest.d.ts +123 -0
  10. package/firestore-indexes/src/model-firebase-index-extract.d.ts +246 -0
  11. package/firestore-indexes/src/model-firebase-index-runtime.d.ts +126 -0
  12. package/firestore-indexes/src/model-firebase-index-scan-config-schema.d.ts +58 -0
  13. package/firestore-indexes/src/model-firebase-index-schema.d.ts +366 -0
  14. package/generate-firestore-indexes/main.js +1 -1
  15. package/generate-firestore-indexes/package.json +1 -1
  16. package/generate-mcp-manifest/src/generate-mcp-manifest/main.d.ts +26 -0
  17. package/generate-mcp-manifest/src/generate-mcp-manifest/render.d.ts +38 -0
  18. package/generated/firebase-models.generated.d.ts +3 -0
  19. package/index.cjs.js +45234 -640
  20. package/index.esm.js +44941 -643
  21. package/lint-cache/main.js +19 -19
  22. package/lint-cache/package.json +2 -2
  23. package/manifest-extract/index.cjs.js +169 -4
  24. package/manifest-extract/index.esm.js +169 -4
  25. package/manifest-extract/package.json +1 -1
  26. package/manifest-extract/src/lib/types.d.ts +26 -1
  27. package/package.json +14 -10
  28. package/src/lib/index.d.ts +3 -0
  29. package/src/lib/manifest/types.d.ts +155 -0
  30. package/src/lib/mcp-scan/config/config-schema.d.ts +226 -0
  31. package/src/lib/mcp-scan/config/load-config.d.ts +63 -0
  32. package/src/lib/mcp-scan/index.d.ts +16 -0
  33. package/src/lib/mcp-scan/manifest/actions-loader.d.ts +49 -0
  34. package/src/lib/mcp-scan/manifest/actions-schema.d.ts +328 -0
  35. package/src/lib/mcp-scan/manifest/core-topics.d.ts +38 -0
  36. package/src/lib/mcp-scan/manifest/css-utilities-loader.d.ts +55 -0
  37. package/src/lib/mcp-scan/manifest/css-utilities-schema.d.ts +168 -0
  38. package/src/lib/mcp-scan/manifest/dbx-docs-ui-examples-loader.d.ts +33 -0
  39. package/src/lib/mcp-scan/manifest/dbx-docs-ui-examples-schema.d.ts +133 -0
  40. package/src/lib/mcp-scan/manifest/filters-loader.d.ts +61 -0
  41. package/src/lib/mcp-scan/manifest/filters-schema.d.ts +190 -0
  42. package/src/lib/mcp-scan/manifest/forge-fields-loader.d.ts +53 -0
  43. package/src/lib/mcp-scan/manifest/forge-fields-schema.d.ts +170 -0
  44. package/src/lib/mcp-scan/manifest/index.d.ts +43 -0
  45. package/src/lib/mcp-scan/manifest/load-actions-registry.d.ts +38 -0
  46. package/src/lib/mcp-scan/manifest/load-auth-registry.d.ts +82 -0
  47. package/src/lib/mcp-scan/manifest/load-css-utilities-registry.d.ts +67 -0
  48. package/src/lib/mcp-scan/manifest/load-dbx-docs-ui-examples-registry.d.ts +45 -0
  49. package/src/lib/mcp-scan/manifest/load-filters-registry.d.ts +69 -0
  50. package/src/lib/mcp-scan/manifest/load-forge-fields-registry.d.ts +70 -0
  51. package/src/lib/mcp-scan/manifest/load-model-firebase-index-registry.d.ts +61 -0
  52. package/src/lib/mcp-scan/manifest/load-model-snapshot-fields-registry.d.ts +74 -0
  53. package/src/lib/mcp-scan/manifest/load-pipes-registry.d.ts +69 -0
  54. package/src/lib/mcp-scan/manifest/load-registry.d.ts +76 -0
  55. package/src/lib/mcp-scan/manifest/load-tokens-registry.d.ts +69 -0
  56. package/src/lib/mcp-scan/manifest/load-ui-components-registry.d.ts +70 -0
  57. package/src/lib/mcp-scan/manifest/load-utils-registry.d.ts +73 -0
  58. package/src/lib/mcp-scan/manifest/loader.d.ts +120 -0
  59. package/src/lib/mcp-scan/manifest/manifest-loader-base.d.ts +130 -0
  60. package/src/lib/mcp-scan/manifest/model-firebase-index-loader.d.ts +53 -0
  61. package/src/lib/mcp-scan/manifest/model-snapshot-fields-loader.d.ts +54 -0
  62. package/src/lib/mcp-scan/manifest/model-snapshot-fields-schema.d.ts +127 -0
  63. package/src/lib/mcp-scan/manifest/pipes-loader.d.ts +54 -0
  64. package/src/lib/mcp-scan/manifest/pipes-schema.d.ts +125 -0
  65. package/src/lib/mcp-scan/manifest/semantic-types-schema.d.ts +108 -0
  66. package/src/lib/mcp-scan/manifest/tokens-loader.d.ts +55 -0
  67. package/src/lib/mcp-scan/manifest/tokens-schema.d.ts +116 -0
  68. package/src/lib/mcp-scan/manifest/ui-components-loader.d.ts +54 -0
  69. package/src/lib/mcp-scan/manifest/ui-components-schema.d.ts +149 -0
  70. package/src/lib/mcp-scan/manifest/utils-loader.d.ts +54 -0
  71. package/src/lib/mcp-scan/manifest/utils-schema.d.ts +120 -0
  72. package/src/lib/mcp-scan/registry/actions-runtime.d.ts +173 -0
  73. package/src/lib/mcp-scan/registry/archetypes.d.ts +235 -0
  74. package/src/lib/mcp-scan/registry/auth-builtin.d.ts +59 -0
  75. package/src/lib/mcp-scan/registry/auth-runtime.d.ts +343 -0
  76. package/src/lib/mcp-scan/registry/css-utilities-runtime.d.ts +133 -0
  77. package/src/lib/mcp-scan/registry/dbx-docs-ui-examples-runtime.d.ts +58 -0
  78. package/src/lib/mcp-scan/registry/downstream-models-runtime.d.ts +93 -0
  79. package/src/lib/mcp-scan/registry/filters-runtime.d.ts +128 -0
  80. package/src/lib/mcp-scan/registry/firebase-models.d.ts +387 -0
  81. package/src/lib/mcp-scan/registry/forge-fields.d.ts +101 -0
  82. package/src/lib/mcp-scan/registry/form-fields.d.ts +203 -0
  83. package/src/lib/mcp-scan/registry/index.d.ts +165 -0
  84. package/src/lib/mcp-scan/registry/model-snapshot-fields-runtime.d.ts +138 -0
  85. package/src/lib/mcp-scan/registry/pipes-runtime.d.ts +136 -0
  86. package/src/lib/mcp-scan/registry/reserved-model-folders.d.ts +29 -0
  87. package/src/lib/mcp-scan/registry/semantic-types.d.ts +81 -0
  88. package/src/lib/mcp-scan/registry/tokens-runtime.d.ts +96 -0
  89. package/src/lib/mcp-scan/registry/ui-components-runtime.d.ts +90 -0
  90. package/src/lib/mcp-scan/registry/utils-runtime.d.ts +136 -0
  91. package/src/lib/mcp-scan/scan/_jsdoc-tagged-export/extract-base.d.ts +245 -0
  92. package/src/lib/mcp-scan/scan/actions-build-manifest.d.ts +58 -0
  93. package/src/lib/mcp-scan/scan/actions-cli.d.ts +38 -0
  94. package/src/lib/mcp-scan/scan/actions-extract.d.ts +99 -0
  95. package/src/lib/mcp-scan/scan/actions-scan-config-schema.d.ts +42 -0
  96. package/src/lib/mcp-scan/scan/auth-extract.d.ts +120 -0
  97. package/src/lib/mcp-scan/scan/build-manifest.d.ts +76 -0
  98. package/src/lib/mcp-scan/scan/cli.d.ts +60 -0
  99. package/src/lib/mcp-scan/scan/css-utilities-build-manifest.d.ts +76 -0
  100. package/src/lib/mcp-scan/scan/css-utilities-cli.d.ts +36 -0
  101. package/src/lib/mcp-scan/scan/css-utilities-extract.d.ts +187 -0
  102. package/src/lib/mcp-scan/scan/css-utilities-scan-config-schema.d.ts +57 -0
  103. package/src/lib/mcp-scan/scan/dbx-docs-ui-examples-build-manifest.d.ts +68 -0
  104. package/src/lib/mcp-scan/scan/dbx-docs-ui-examples-cli.d.ts +20 -0
  105. package/src/lib/mcp-scan/scan/dbx-docs-ui-examples-extract.d.ts +160 -0
  106. package/src/lib/mcp-scan/scan/dbx-docs-ui-examples-scan-config-schema.d.ts +56 -0
  107. package/src/lib/mcp-scan/scan/discover-downstream-packages.d.ts +76 -0
  108. package/src/lib/mcp-scan/scan/discover-firebase-packages.d.ts +58 -0
  109. package/src/lib/mcp-scan/scan/extract-models/assemble.d.ts +105 -0
  110. package/src/lib/mcp-scan/scan/extract-models/collect-inherited.d.ts +22 -0
  111. package/src/lib/mcp-scan/scan/extract-models/find-converters.d.ts +19 -0
  112. package/src/lib/mcp-scan/scan/extract-models/find-enums.d.ts +19 -0
  113. package/src/lib/mcp-scan/scan/extract-models/find-identities.d.ts +25 -0
  114. package/src/lib/mcp-scan/scan/extract-models/find-interfaces.d.ts +31 -0
  115. package/src/lib/mcp-scan/scan/extract-models/find-model-groups.d.ts +21 -0
  116. package/src/lib/mcp-scan/scan/extract-models/find-service-factories.d.ts +19 -0
  117. package/src/lib/mcp-scan/scan/extract-models/find-sub-object-consts.d.ts +20 -0
  118. package/src/lib/mcp-scan/scan/extract-models/index.d.ts +74 -0
  119. package/src/lib/mcp-scan/scan/extract-models/infer-collection-kind.d.ts +22 -0
  120. package/src/lib/mcp-scan/scan/extract-models/service-factory-constants.d.ts +6 -0
  121. package/src/lib/mcp-scan/scan/extract-models/types.d.ts +171 -0
  122. package/src/lib/mcp-scan/scan/extract.d.ts +82 -0
  123. package/src/lib/mcp-scan/scan/filters-build-manifest.d.ts +78 -0
  124. package/src/lib/mcp-scan/scan/filters-cli.d.ts +37 -0
  125. package/src/lib/mcp-scan/scan/filters-extract.d.ts +101 -0
  126. package/src/lib/mcp-scan/scan/filters-scan-config-schema.d.ts +56 -0
  127. package/src/lib/mcp-scan/scan/forge-fields-build-manifest.d.ts +78 -0
  128. package/src/lib/mcp-scan/scan/forge-fields-cli.d.ts +37 -0
  129. package/src/lib/mcp-scan/scan/forge-fields-extract.d.ts +165 -0
  130. package/src/lib/mcp-scan/scan/forge-fields-scan-config-schema.d.ts +61 -0
  131. package/src/lib/mcp-scan/scan/index.d.ts +60 -0
  132. package/src/lib/mcp-scan/scan/model-firebase-index-cli.d.ts +22 -0
  133. package/src/lib/mcp-scan/scan/model-firebase-index-dispatcher-credit.d.ts +47 -0
  134. package/src/lib/mcp-scan/scan/model-firebase-index-reference-scan.d.ts +100 -0
  135. package/src/lib/mcp-scan/scan/model-snapshot-fields-build-manifest.d.ts +79 -0
  136. package/src/lib/mcp-scan/scan/model-snapshot-fields-cli.d.ts +37 -0
  137. package/src/lib/mcp-scan/scan/model-snapshot-fields-extract.d.ts +115 -0
  138. package/src/lib/mcp-scan/scan/model-snapshot-fields-scan-config-schema.d.ts +59 -0
  139. package/src/lib/mcp-scan/scan/pipes-build-manifest.d.ts +78 -0
  140. package/src/lib/mcp-scan/scan/pipes-cli.d.ts +37 -0
  141. package/src/lib/mcp-scan/scan/pipes-extract.d.ts +90 -0
  142. package/src/lib/mcp-scan/scan/pipes-scan-config-schema.d.ts +56 -0
  143. package/src/lib/mcp-scan/scan/scan-angular-io.d.ts +89 -0
  144. package/src/lib/mcp-scan/scan/scan-cli-base.d.ts +162 -0
  145. package/src/lib/mcp-scan/scan/scan-config-schema.d.ts +44 -0
  146. package/src/lib/mcp-scan/scan/ui-components-build-manifest.d.ts +78 -0
  147. package/src/lib/mcp-scan/scan/ui-components-cli.d.ts +37 -0
  148. package/src/lib/mcp-scan/scan/ui-components-extract.d.ts +124 -0
  149. package/src/lib/mcp-scan/scan/ui-components-scan-config-schema.d.ts +62 -0
  150. package/src/lib/mcp-scan/scan/utils-build-manifest.d.ts +78 -0
  151. package/src/lib/mcp-scan/scan/utils-cli.d.ts +37 -0
  152. package/src/lib/mcp-scan/scan/utils-extract.d.ts +103 -0
  153. package/src/lib/mcp-scan/scan/utils-scan-config-schema.d.ts +57 -0
  154. package/test/package.json +9 -9
  155. package/index.cjs.default.js +0 -1
  156. package/index.cjs.mjs +0 -2
@@ -161,7 +161,7 @@ import { join as join2 } from "node:path";
161
161
 
162
162
  // packages/dbx-cli/manifest-extract/src/lib/extract-crud.ts
163
163
  import { Node as Node2, Project as Project2 } from "ts-morph";
164
- var SUPPORTED_VERBS = /* @__PURE__ */ new Set(["create", "read", "update", "delete", "query"]);
164
+ var SUPPORTED_VERBS = /* @__PURE__ */ new Set(["create", "read", "update", "delete", "query", "invoke"]);
165
165
  function extractCrudEntries(source) {
166
166
  const project = new Project2({ useInMemoryFileSystem: true, skipAddingFilesFromTsConfig: true });
167
167
  const sourceFile = project.createSourceFile(source.name, source.text, { overwrite: true });
@@ -426,6 +426,9 @@ function readJsDocSummary(node) {
426
426
 
427
427
  // packages/dbx-cli/manifest-extract/src/lib/extract-models.ts
428
428
  import { Node as Node3, Project as Project3 } from "ts-morph";
429
+ var READ_LEVEL_VALUES = /* @__PURE__ */ new Set(["system", "owner", "admin-only", "permissions"]);
430
+ var SERVICE_FACTORY_TAG = "dbxModelServiceFactory";
431
+ var MODEL_TYPE_VALUE_PATTERN = /^[a-z][A-Za-z0-9_$]*$/;
429
432
  var PASSTHROUGH_TYPE_WRAPPERS = /* @__PURE__ */ new Set(["Partial", "Required", "Readonly", "NonNullable", "MaybeMap", "Pick", "Omit"]);
430
433
  var IDENTITY_FN = "firestoreModelIdentity";
431
434
  var CONVERTER_FN_NAMES = ["snapshotConverterFunctions", "firestoreSubObject", "firestoreObjectArray"];
@@ -442,7 +445,8 @@ function extractModelsFromSource(input) {
442
445
  const converters = readConverters(sourceFile);
443
446
  const enums = readEnums(sourceFile);
444
447
  const modelGroups = readModelGroups(sourceFile);
445
- return { identities, interfaces, converters, enums, modelGroups };
448
+ const serviceFactories = readServiceFactories(sourceFile);
449
+ return { identities, interfaces, converters, enums, modelGroups, serviceFactories };
446
450
  }
447
451
  function readIdentities(sourceFile) {
448
452
  const out = [];
@@ -497,6 +501,7 @@ function readInterfaces(sourceFile) {
497
501
  function buildInterface(decl) {
498
502
  const jsDocs = decl.getJsDocs();
499
503
  const hasDbxModelTag = jsDocsHaveTag(jsDocs, "dbxModel");
504
+ const dbxModelRead = readDbxModelReadTag(jsDocs);
500
505
  const extendsNames = decl.getExtends().map(resolveExtendsName);
501
506
  const props = [];
502
507
  for (const prop of decl.getProperties()) {
@@ -519,9 +524,54 @@ function buildInterface(decl) {
519
524
  description: readJsDocDescription(jsDocs),
520
525
  hasDbxModelTag,
521
526
  extendsNames,
522
- props
527
+ props,
528
+ ...dbxModelRead === void 0 ? {} : { dbxModelRead }
523
529
  };
524
530
  }
531
+ function readDbxModelReadTag(jsDocs) {
532
+ let result;
533
+ for (const doc of jsDocs) {
534
+ for (const tag of doc.getTags()) {
535
+ if (tag.getTagName() !== "dbxModelRead") continue;
536
+ if (result !== void 0) continue;
537
+ const raw = tag.getCommentText()?.trim();
538
+ if (raw === void 0 || raw.length === 0) continue;
539
+ const firstToken = raw.split(/\s+/)[0];
540
+ if (READ_LEVEL_VALUES.has(firstToken)) {
541
+ result = firstToken;
542
+ }
543
+ }
544
+ }
545
+ return result;
546
+ }
547
+ function readServiceFactories(sourceFile) {
548
+ const out = [];
549
+ for (const statement of sourceFile.getVariableStatements()) {
550
+ if (!statement.isExported()) continue;
551
+ const modelType = readServiceFactoryModelType(statement.getJsDocs());
552
+ if (modelType === void 0) continue;
553
+ for (const decl of statement.getDeclarations()) {
554
+ out.push({ modelType, exportName: decl.getName() });
555
+ }
556
+ }
557
+ return out;
558
+ }
559
+ function readServiceFactoryModelType(jsDocs) {
560
+ let result;
561
+ for (const doc of jsDocs) {
562
+ for (const tag of doc.getTags()) {
563
+ if (tag.getTagName() !== SERVICE_FACTORY_TAG) continue;
564
+ if (result !== void 0) continue;
565
+ const raw = tag.getCommentText()?.trim();
566
+ if (raw === void 0 || raw.length === 0) continue;
567
+ const firstToken = raw.split(/\s+/)[0];
568
+ if (MODEL_TYPE_VALUE_PATTERN.test(firstToken)) {
569
+ result = firstToken;
570
+ }
571
+ }
572
+ }
573
+ return result;
574
+ }
525
575
  function resolveExtendsName(expr) {
526
576
  const head = expr.getExpression().getText();
527
577
  let result = head;
@@ -850,14 +900,14 @@ function findModelFiles(packageRoot) {
850
900
  const text = readFileSync4(filePath, "utf8");
851
901
  if (!textHasModelMarker(text)) continue;
852
902
  const extraction = extractModelsFromSource({ name: filePath, text });
853
- if (extraction.identities.length === 0 && extraction.modelGroups.length === 0 && extraction.converters.length === 0) continue;
903
+ if (extraction.identities.length === 0 && extraction.modelGroups.length === 0 && extraction.converters.length === 0 && extraction.serviceFactories.length === 0) continue;
854
904
  out.push({ filePath, extraction });
855
905
  }
856
906
  }
857
907
  return out;
858
908
  }
859
909
  function textHasModelMarker(text) {
860
- return text.includes("firestoreModelIdentity(") || text.includes("@dbxModelGroup") || text.includes("snapshotConverterFunctions") || text.includes("firestoreSubObject") || text.includes("firestoreObjectArray");
910
+ return text.includes("firestoreModelIdentity(") || text.includes("@dbxModelGroup") || text.includes("snapshotConverterFunctions") || text.includes("firestoreSubObject") || text.includes("firestoreObjectArray") || text.includes("@dbxModelServiceFactory");
861
911
  }
862
912
  function* walkSourceFiles(dir) {
863
913
  for (const entry of readdirSync2(dir).sort()) {
@@ -901,12 +951,18 @@ function buildGlobalRegistries(extractions) {
901
951
  const converterRegistry = /* @__PURE__ */ new Map();
902
952
  const interfaceRegistry = /* @__PURE__ */ new Map();
903
953
  const groupByModelName = /* @__PURE__ */ new Map();
904
- for (const { extraction } of extractions) {
954
+ const serviceFactoryByModelType = /* @__PURE__ */ new Map();
955
+ for (const { extraction, sourceFile } of extractions) {
905
956
  registerConverters(extraction.converters, converterRegistry);
906
957
  registerInterfaces(extraction.interfaces, interfaceRegistry);
907
958
  registerModelGroups(extraction.modelGroups, groupByModelName);
959
+ for (const factory of extraction.serviceFactories) {
960
+ if (!serviceFactoryByModelType.has(factory.modelType)) {
961
+ serviceFactoryByModelType.set(factory.modelType, { exportName: factory.exportName, sourceFile });
962
+ }
963
+ }
908
964
  }
909
- return { converterRegistry, interfaceRegistry, groupByModelName };
965
+ return { converterRegistry, interfaceRegistry, groupByModelName, serviceFactoryByModelType };
910
966
  }
911
967
  function registerConverters(converters, registry) {
912
968
  for (const converter of converters) {
@@ -957,6 +1013,7 @@ function buildEntryForIdentity(input) {
957
1013
  visitedConverters: /* @__PURE__ */ new Set()
958
1014
  });
959
1015
  const modelGroup = registries.groupByModelName.get(modelName);
1016
+ const serviceFactory = registries.serviceFactoryByModelType.get(identity.modelType);
960
1017
  result = {
961
1018
  modelType: identity.modelType,
962
1019
  modelName,
@@ -967,7 +1024,9 @@ function buildEntryForIdentity(input) {
967
1024
  ...iface.description ? { description: iface.description } : {},
968
1025
  sourcePackage: source.sourcePackage,
969
1026
  sourceFile: source.sourceFile,
970
- fields
1027
+ fields,
1028
+ ...iface.dbxModelRead ? { read: iface.dbxModelRead } : {},
1029
+ ...serviceFactory ? { serviceFactory } : {}
971
1030
  };
972
1031
  }
973
1032
  }
@@ -1290,7 +1349,9 @@ function renderModelEntry(entry, emitConverters) {
1290
1349
  entry.description ? `description: ${JSON.stringify(entry.description)}` : void 0,
1291
1350
  `sourcePackage: ${JSON.stringify(entry.sourcePackage)}`,
1292
1351
  `sourceFile: ${JSON.stringify(entry.sourceFile)}`,
1293
- `fields: ${renderModelFields(entry.fields, emitConverters)}`
1352
+ `fields: ${renderModelFields(entry.fields, emitConverters)}`,
1353
+ entry.read ? `read: ${JSON.stringify(entry.read)}` : void 0,
1354
+ entry.serviceFactory ? `serviceFactory: { exportName: ${JSON.stringify(entry.serviceFactory.exportName)}, sourceFile: ${JSON.stringify(entry.serviceFactory.sourceFile)} }` : void 0
1294
1355
  ];
1295
1356
  return ` { ${fields.filter((v) => Boolean(v)).join(", ")} }`;
1296
1357
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-firebase-api-manifest",
3
- "version": "13.11.18",
3
+ "version": "13.12.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "devDependencies": {
@@ -0,0 +1,125 @@
1
+ /**
2
+ * `firestore.indexes.json` generator.
3
+ *
4
+ * Consumes the {@link ModelFirebaseIndexRegistry} and produces the
5
+ * canonical `firestore.indexes.json` payload (composites + fieldOverrides)
6
+ * Firebase deploys against. Encodes the live-deploy shape observed in
7
+ * HelloSubs:
8
+ *
9
+ * - Every composite ends with a `__name__` tiebreaker whose direction
10
+ * matches the last orderBy in the entry.
11
+ * - Every composite carries `density: "SPARSE_ALL"`.
12
+ * - Every COLLECTION_GROUP single-field variant in a `fieldOverrides`
13
+ * entry is emitted alongside the standard COLLECTION quartet
14
+ * (ASCENDING/DESCENDING/CONTAINS) so the auto-indexes Firebase would
15
+ * have created at COLLECTION scope are explicitly preserved.
16
+ *
17
+ * Preserves user-authored content that the analyzer can't produce:
18
+ *
19
+ * - `fieldOverrides[]` entries whose `(collection, fieldPath)` no
20
+ * analyzed factory touches (TTL flags, hand-trimmed overrides like
21
+ * `sjs.adat`, vector indexes, etc.).
22
+ * - `indexes[]` entries tied to slugs that carry the
23
+ * `@dbxModelFirebaseIndexManual` JSDoc tag — round-tripped untouched.
24
+ *
25
+ * Returns the new JSON + a structured diff (`added`, `removed`,
26
+ * `unchanged`) so the CLI / MCP tool can render a clear CI report.
27
+ */
28
+ import type { ModelFirebaseIndexEntryInfo } from './model-firebase-index-runtime.js';
29
+ /**
30
+ * `firestore.indexes.json` top-level shape.
31
+ */
32
+ export interface FirestoreIndexesJson {
33
+ readonly indexes: readonly FirestoreIndexJsonEntry[];
34
+ readonly fieldOverrides: readonly FirestoreFieldOverrideJsonEntry[];
35
+ }
36
+ /**
37
+ * One `indexes[]` entry. Mirrors the Firestore Admin API schema with the
38
+ * `density` field included (Firebase CLI emits this on every export).
39
+ */
40
+ export interface FirestoreIndexJsonEntry {
41
+ readonly collectionGroup: string;
42
+ readonly queryScope: 'COLLECTION' | 'COLLECTION_GROUP';
43
+ readonly fields: readonly FirestoreIndexJsonField[];
44
+ readonly density?: 'SPARSE_ALL' | 'SPARSE_ANY' | 'DENSE';
45
+ }
46
+ /**
47
+ * One `fields[]` member of an `indexes[]` entry. Includes the `__name__`
48
+ * tiebreaker the generator appends at emission time.
49
+ */
50
+ export interface FirestoreIndexJsonField {
51
+ readonly fieldPath: string;
52
+ readonly order?: 'ASCENDING' | 'DESCENDING';
53
+ readonly arrayConfig?: 'CONTAINS';
54
+ }
55
+ /**
56
+ * One `fieldOverrides[]` entry.
57
+ */
58
+ export interface FirestoreFieldOverrideJsonEntry {
59
+ readonly collectionGroup: string;
60
+ readonly fieldPath: string;
61
+ readonly ttl?: boolean;
62
+ readonly indexes: readonly FirestoreFieldOverrideJsonVariant[];
63
+ }
64
+ /**
65
+ * One variant inside a `fieldOverrides[].indexes` array.
66
+ */
67
+ export interface FirestoreFieldOverrideJsonVariant {
68
+ readonly queryScope: 'COLLECTION' | 'COLLECTION_GROUP';
69
+ readonly order?: 'ASCENDING' | 'DESCENDING';
70
+ readonly arrayConfig?: 'CONTAINS';
71
+ }
72
+ /**
73
+ * Input to {@link generateFirestoreIndexesJson}.
74
+ */
75
+ export interface GenerateFirestoreIndexesJsonInput {
76
+ /**
77
+ * Every entry the registry knows about. Entries with `skip = true` or
78
+ * `manual = true` are filtered out by the generator — manual entries
79
+ * are preserved via {@link existingJson}.
80
+ */
81
+ readonly entries: readonly ModelFirebaseIndexEntryInfo[];
82
+ /**
83
+ * The current on-disk `firestore.indexes.json` if any, used to preserve
84
+ * user-authored content the generator can't reproduce.
85
+ */
86
+ readonly existingJson?: FirestoreIndexesJson;
87
+ }
88
+ /**
89
+ * Diff shape returned alongside the new JSON. Each list carries the
90
+ * canonical-key form of the index (or override) for inclusion in CI
91
+ * reports.
92
+ */
93
+ export interface FirestoreIndexesDiff {
94
+ readonly added: readonly string[];
95
+ readonly removed: readonly string[];
96
+ readonly unchanged: readonly string[];
97
+ readonly fieldOverridesAdded: readonly string[];
98
+ readonly fieldOverridesRemoved: readonly string[];
99
+ readonly fieldOverridesUnchanged: readonly string[];
100
+ }
101
+ /**
102
+ * Output of {@link generateFirestoreIndexesJson}.
103
+ */
104
+ export interface GenerateFirestoreIndexesJsonResult {
105
+ readonly json: FirestoreIndexesJson;
106
+ readonly diff: FirestoreIndexesDiff;
107
+ }
108
+ /**
109
+ * Generates a canonical `firestore.indexes.json` payload from the
110
+ * registry, preserving user-authored content from any prior on-disk
111
+ * version.
112
+ *
113
+ * @param input - The entries and (optionally) the existing JSON to merge against.
114
+ * @returns The new JSON payload plus a diff against the existing version.
115
+ */
116
+ export declare function generateFirestoreIndexesJson(input: GenerateFirestoreIndexesJsonInput): GenerateFirestoreIndexesJsonResult;
117
+ /**
118
+ * Serializes `firestore.indexes.json` with stable key ordering and a
119
+ * trailing newline so `--check` mode can byte-compare against the
120
+ * committed file without false-positive whitespace diffs.
121
+ *
122
+ * @param json - The indexes payload to serialise.
123
+ * @returns The canonical string form.
124
+ */
125
+ export declare function serializeFirestoreIndexesJson(json: FirestoreIndexesJson): string;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Build-time resolver from a TS type name (`JobLocationWeek`) to the
3
+ * model's Firestore short collection name (`'jlw'`) and nested flag.
4
+ *
5
+ * The model-firebase-index extractor needs to map the
6
+ * `@dbxModelFirebaseIndexModel <TypeName>` tag value onto the
7
+ * `collectionPrefix` Firestore uses for the model — that's what becomes
8
+ * the `collectionGroup` field in `firestore.indexes.json`. The resolver
9
+ * builds a per-project map by walking the supplied ts-morph project for
10
+ * `firestoreModelIdentity(...)` calls, then exposes lookup-by-type-name
11
+ * (preferred), lookup-by-modelType (the first-string-arg), and
12
+ * lookup-by-identity-const (e.g. `jobLocationWeekIdentity`).
13
+ *
14
+ * For runtime use (the `dbx_model_firebase_index_*` MCP tools) the
15
+ * upstream `FIREBASE_MODELS` registry + downstream catalog provides the
16
+ * same data — see {@link buildIdentityResolverFromRegistries} below.
17
+ */
18
+ import { type Project } from 'ts-morph';
19
+ /**
20
+ * Resolution result for one model. `collection` is the short collection
21
+ * prefix used by Firestore (and the `collectionGroup` field in
22
+ * `firestore.indexes.json`). `isNested` is `true` when the identity
23
+ * declares a parent — these models default to `COLLECTION_GROUP` scope.
24
+ */
25
+ export interface ResolvedFirestoreModelIdentity {
26
+ readonly modelType: string;
27
+ readonly collection: string;
28
+ readonly isNested: boolean;
29
+ readonly identityConstName: string;
30
+ }
31
+ /**
32
+ * Build-time identity resolver. Methods accept either the TS type name
33
+ * (`'JobLocationWeek'`), the modelType string (`'jobLocationWeek'`), or
34
+ * the identity-const name (`'jobLocationWeekIdentity'`) — extractors
35
+ * commonly have access to whichever surface lines up with how the tagged
36
+ * factory was written.
37
+ */
38
+ export interface FirestoreModelIdentityResolver {
39
+ /**
40
+ * Lookup by the TS type / interface name (e.g. `JobLocationWeek`). The
41
+ * resolver tries the literal name, then the camelCase form, then the
42
+ * derived `<lowerCamelCase>Identity` const name.
43
+ */
44
+ readonly lookupByTypeName: (typeName: string) => ResolvedFirestoreModelIdentity | undefined;
45
+ /**
46
+ * Lookup by the first-arg `modelType` string passed to
47
+ * `firestoreModelIdentity(...)` — exact match.
48
+ */
49
+ readonly lookupByModelType: (modelType: string) => ResolvedFirestoreModelIdentity | undefined;
50
+ /**
51
+ * Lookup by the identity const name (e.g. `jobLocationWeekIdentity`) —
52
+ * exact match.
53
+ */
54
+ readonly lookupByIdentityConst: (identityConstName: string) => ResolvedFirestoreModelIdentity | undefined;
55
+ /**
56
+ * Every resolved record, useful for diagnostics.
57
+ */
58
+ readonly all: () => readonly ResolvedFirestoreModelIdentity[];
59
+ }
60
+ /**
61
+ * Walks the supplied ts-morph project looking for
62
+ * `<name>Identity = firestoreModelIdentity(...)` declarations and assembles
63
+ * a {@link FirestoreModelIdentityResolver}.
64
+ *
65
+ * Identifies three call shapes:
66
+ * 1. `firestoreModelIdentity('modelType', 'prefix')` — root identity.
67
+ * 2. `firestoreModelIdentity(parentIdentity, 'modelType', 'prefix')` — nested identity.
68
+ * 3. `firestoreModelIdentity('modelType')` — root identity with prefix-defaulted-to-modelType (rare; collection equals modelType).
69
+ *
70
+ * Calls that don't match any of these shapes are skipped silently — the
71
+ * extractor only consumes successful matches.
72
+ *
73
+ * @param project - The ts-morph project whose source files to scan.
74
+ * @returns The resolver.
75
+ */
76
+ export declare function buildIdentityResolverFromProject(project: Project): FirestoreModelIdentityResolver;
77
+ /**
78
+ * Builds a resolver from a pre-resolved list of records — used when the
79
+ * MCP tool runtime has access to {@link FIREBASE_MODELS} + the downstream
80
+ * catalog and doesn't need to rescan source files.
81
+ *
82
+ * @param records - The resolved identity records.
83
+ * @returns The resolver.
84
+ */
85
+ export declare function buildIdentityResolverFromRecords(records: readonly ResolvedFirestoreModelIdentity[]): FirestoreModelIdentityResolver;
86
+ /**
87
+ * Converts `JobLocationWeek` → `jobLocationWeek`. Pass-through when the
88
+ * input is already camelCase.
89
+ *
90
+ * @param typeName - The TypeScript type name.
91
+ * @returns The lowerCamelCase identifier.
92
+ */
93
+ export declare function toCamelCase(typeName: string): string;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Constraint-helper expansion registry used by the model-firebase-index
3
+ * extractor.
4
+ *
5
+ * The query factories under scan compose `@dereekb/firebase` helpers
6
+ * (`whereDateIsBeforeWithSort`, `whereDateIsBetween`,
7
+ * `allChildDocumentsUnderRelativePath`, …) that internally fan out to one
8
+ * or more base `where` / `orderBy` constraints. Static AST walking only
9
+ * sees the helper call — to derive the actual Firestore index requirements
10
+ * the extractor needs to know which base constraints the helper produces.
11
+ * This file is the source of truth for that mapping.
12
+ *
13
+ * Each entry declares:
14
+ * - which positional argument carries the field path
15
+ * - which positional argument carries the `OrderByDirection` (if any)
16
+ * - the ordered base constraints the helper emits, with operators or
17
+ * orderBy directions resolved from the call site when possible
18
+ *
19
+ * The extractor reads the call site, plugs the resolved field path and
20
+ * direction into the descriptor, and emits {@link ConstraintSequenceEntry}
21
+ * rows tagged with `fromHelper: <helperName>` so diagnostics remain
22
+ * faithful to the source factory.
23
+ */
24
+ import type { ConstraintSequenceEntry, FirestoreWhereOperator } from './model-firebase-index-schema.js';
25
+ /**
26
+ * Description of one base constraint a helper expands into. `direction`
27
+ * resolves at call-site time from the helper's direction argument when
28
+ * `useCallSiteDirection` is set; otherwise it falls back to the literal
29
+ * `direction` here. `operator` is required when `kind === 'where'`.
30
+ */
31
+ export interface FirestoreQueryHelperExpansionPart {
32
+ readonly kind: 'where' | 'orderBy';
33
+ readonly operator?: FirestoreWhereOperator;
34
+ readonly direction?: 'asc' | 'desc';
35
+ /**
36
+ * When `true` and the helper resolves a direction from its call-site
37
+ * argument, override the literal `direction` above.
38
+ */
39
+ readonly useCallSiteDirection?: boolean;
40
+ }
41
+ /**
42
+ * One entry in the helper expansion registry.
43
+ */
44
+ export interface FirestoreQueryHelperDescriptor {
45
+ /**
46
+ * Helper function name as it appears in source (e.g. `whereDateIsBetween`).
47
+ */
48
+ readonly name: string;
49
+ /**
50
+ * Index of the call argument carrying the field path. The extractor reads
51
+ * the string literal at this position (or the identifier when the call
52
+ * passes a `FieldPath` constant — currently treated as opaque, dropping
53
+ * the entry with a warning).
54
+ */
55
+ readonly fieldArgIndex: number;
56
+ /**
57
+ * Index of the call argument carrying the `OrderByDirection`. Undefined
58
+ * when the helper does not accept one (e.g. `whereDateIsBefore`).
59
+ */
60
+ readonly directionArgIndex?: number;
61
+ /**
62
+ * Default direction when the call-site direction argument is absent.
63
+ */
64
+ readonly defaultDirection?: 'asc' | 'desc';
65
+ /**
66
+ * Ordered list of the base constraints the helper expands into. Field
67
+ * path is the same for every part (helpers always operate on one field).
68
+ */
69
+ readonly parts: readonly FirestoreQueryHelperExpansionPart[];
70
+ }
71
+ /**
72
+ * Built-in helper expansions. Mirrors the upstream
73
+ * `@dereekb/firebase/constraint.template.ts` implementations as of writing.
74
+ * Helpers not listed here are extracted as a single opaque `where` call so
75
+ * the entry still surfaces during validation — the extractor emits a
76
+ * warning so a missing helper can be added here.
77
+ *
78
+ * Order within `parts` is significant: it mirrors the source helper's
79
+ * emitted constraint array order, which the analyzer treats as the user's
80
+ * intended Firestore index field order.
81
+ */
82
+ export declare const FIRESTORE_QUERY_HELPERS: readonly FirestoreQueryHelperDescriptor[];
83
+ /**
84
+ * Lookup helper by name. Returns `undefined` when the helper isn't in the
85
+ * registry — the extractor treats that as an opaque call (no constraints
86
+ * emitted) and surfaces a warning so the registry can be extended.
87
+ *
88
+ * @param name - The helper's source-level identifier.
89
+ * @returns The descriptor or undefined.
90
+ */
91
+ export declare function getFirestoreQueryHelperDescriptor(name: string): FirestoreQueryHelperDescriptor | undefined;
92
+ /**
93
+ * Resolves the constraint entries a helper call produces given the
94
+ * resolved field path and (optional) call-site direction. The extractor
95
+ * supplies the parsed values; this function applies the descriptor's
96
+ * `parts` template.
97
+ *
98
+ * @param input - The resolved descriptor + call-site values.
99
+ * @param input.descriptor - The looked-up helper descriptor.
100
+ * @param input.fieldPath - The resolved field path for the constraints.
101
+ * @param input.direction - Optional call-site direction override for orderBy parts.
102
+ * @returns The ordered constraint entries the helper emits.
103
+ */
104
+ export declare function expandFirestoreQueryHelper(input: {
105
+ readonly descriptor: FirestoreQueryHelperDescriptor;
106
+ readonly fieldPath: string;
107
+ readonly direction?: 'asc' | 'desc';
108
+ }): readonly ConstraintSequenceEntry[];
@@ -0,0 +1,94 @@
1
+ /**
2
+ * `generate-firestore-indexes` subcommand entry point.
3
+ *
4
+ * Walks a downstream `-firebase` component for `@dbxModelFirebaseIndex`-
5
+ * tagged factories (the same pipeline `scan-model-firebase-indexes` uses),
6
+ * runs the analyzer, and emits a canonical `firestore.indexes.json`
7
+ * payload via {@link generateFirestoreIndexesJson}.
8
+ *
9
+ * Two modes:
10
+ *
11
+ * - **write** (default) — write the generated JSON to `--output` (defaults to
12
+ * `firestore.indexes.json` at the workspace root).
13
+ * - **`--check`** — compare the generated JSON to the file on disk and exit
14
+ * 1 on drift. Used in CI to fail PRs that change query factories without
15
+ * regenerating the indexes file.
16
+ *
17
+ * Preserves user-authored content that the analyzer can't reproduce: TTL
18
+ * fieldOverrides, vector indexes, hand-tuned single-field overrides like
19
+ * `sjs.adat`, and any composite tied to a `@dbxModelFirebaseIndexManual`
20
+ * factory (round-tripped verbatim from the existing file).
21
+ */
22
+ /**
23
+ * Function shape used by the CLI to read existing files (the on-disk
24
+ * `firestore.indexes.json`). Defaults to `node:fs/promises.readFile`.
25
+ */
26
+ export type GenerateFirestoreIndexesCliReadFile = (absolutePath: string) => Promise<string>;
27
+ /**
28
+ * Function shape used by the CLI to write the produced file. Defaults to
29
+ * `node:fs/promises.writeFile`.
30
+ */
31
+ export type GenerateFirestoreIndexesCliWriteFile = (absolutePath: string, data: string) => Promise<void>;
32
+ /**
33
+ * Console sink for stdout and stderr lines.
34
+ */
35
+ export type GenerateFirestoreIndexesCliLogger = (message: string) => void;
36
+ /**
37
+ * Result of one CLI invocation.
38
+ */
39
+ export interface RunGenerateFirestoreIndexesCliResult {
40
+ readonly exitCode: number;
41
+ }
42
+ /**
43
+ * Input to {@link runGenerateFirestoreIndexesCli}.
44
+ */
45
+ export interface RunGenerateFirestoreIndexesCliInput {
46
+ /**
47
+ * Subcommand argv slice — everything after `generate-firestore-indexes`.
48
+ */
49
+ readonly argv: readonly string[];
50
+ /**
51
+ * Working directory the CLI resolves relative paths against.
52
+ */
53
+ readonly cwd: string;
54
+ /**
55
+ * Generator string written into the manifest source field (used for
56
+ * diagnostics only — the indexes file does not record this).
57
+ */
58
+ readonly generator: string;
59
+ /**
60
+ * Optional binary name printed in the `--help` usage banner so embedders
61
+ * (e.g. the `dbx-components-mcp generate-firestore-indexes` subcommand)
62
+ * can advertise the right invocation to their users. Defaults to
63
+ * `dbx-cli-generate-firestore-indexes`.
64
+ */
65
+ readonly binName?: string;
66
+ /**
67
+ * Optional now() override. Unused at present; reserved so callers can
68
+ * inject a stable clock when adding wall-time metadata in the future.
69
+ */
70
+ readonly now?: () => Date;
71
+ /**
72
+ * Optional file-reader override (e.g. tests that inject an in-memory FS).
73
+ */
74
+ readonly readFile?: GenerateFirestoreIndexesCliReadFile;
75
+ /**
76
+ * Optional file-writer override (e.g. tests that capture output without
77
+ * touching disk).
78
+ */
79
+ readonly writeFile?: GenerateFirestoreIndexesCliWriteFile;
80
+ /**
81
+ * Optional logger override. Defaults to console.log / console.error.
82
+ */
83
+ readonly stdout?: GenerateFirestoreIndexesCliLogger;
84
+ readonly stderr?: GenerateFirestoreIndexesCliLogger;
85
+ }
86
+ /**
87
+ * Runs one invocation of `generate-firestore-indexes`. Never throws on
88
+ * user errors — every failure path returns a structured exit code so
89
+ * callers can wire this into `process.exit` without try/catch.
90
+ *
91
+ * @param input - Argv plus injectable I/O hooks.
92
+ * @returns The CLI's exit code (0 on success / no drift, 1 on drift / failure, 2 on usage error)
93
+ */
94
+ export declare function runGenerateFirestoreIndexesCli(input: RunGenerateFirestoreIndexesCliInput): Promise<RunGenerateFirestoreIndexesCliResult>;
@@ -0,0 +1,7 @@
1
+ export * from './firestore-indexes-generate';
2
+ export * from './generate-firestore-indexes-cli';
3
+ export * from './model-firebase-index-build-manifest';
4
+ export * from './model-firebase-index-extract';
5
+ export * from './model-firebase-index-runtime';
6
+ export * from './model-firebase-index-scan-config-schema';
7
+ export * from './model-firebase-index-schema';
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Index analyzer.
3
+ *
4
+ * Consumes the {@link ExtractedModelFirebaseIndexEntry}s produced by the
5
+ * extractor and decides, per `(collection, scope, constraintSequence)`,
6
+ * whether the query requires a composite index, a `fieldOverrides[]`
7
+ * variant, or neither (because Firestore's automatic single-field
8
+ * `COLLECTION`-scope index already covers the query). Encodes Firestore's
9
+ * field-order rule for composites:
10
+ *
11
+ * 1. Equality (`==`, `in`) fields first, in source order.
12
+ * 2. A single range/inequality field next (direction taken from any
13
+ * explicit `orderBy` on the same field, else ASCENDING).
14
+ * 3. An optional `array-contains` field.
15
+ * 4. Remaining `orderBy` fields in source order, with their declared
16
+ * direction.
17
+ *
18
+ * The `__name__` tiebreaker is NOT appended here — the generator does that
19
+ * at emission time, picking the direction to match the last orderBy.
20
+ *
21
+ * Returns one `derivedComposites` + one `derivedFieldOverrides` set per
22
+ * entry. The generator merges these across entries when building
23
+ * `firestore.indexes.json` (dedupe, canonical sort, COLLECTION-quartet
24
+ * companion fieldOverrides).
25
+ */
26
+ import { type DerivedComposite, type DerivedFieldOverride } from './model-firebase-index-schema.js';
27
+ import type { ExtractedModelFirebaseIndexEntry } from './model-firebase-index-extract.js';
28
+ /**
29
+ * Per-factory analyzer output. The entry order matches the extractor's
30
+ * input order so warnings + diagnostics can point back to the factory.
31
+ */
32
+ export interface AnalyzedEntry {
33
+ readonly extractedEntry: ExtractedModelFirebaseIndexEntry;
34
+ readonly derivedComposites: readonly DerivedComposite[];
35
+ readonly derivedFieldOverrides: readonly DerivedFieldOverride[];
36
+ readonly warnings: readonly AnalyzerWarning[];
37
+ }
38
+ /**
39
+ * Discriminated union of non-fatal events emitted during analysis.
40
+ */
41
+ export type AnalyzerWarning = {
42
+ readonly kind: 'multiple-range-fields';
43
+ readonly factoryName: string;
44
+ readonly fields: readonly string[];
45
+ } | {
46
+ readonly kind: 'orderby-conflict';
47
+ readonly factoryName: string;
48
+ readonly field: string;
49
+ readonly directions: readonly string[];
50
+ } | {
51
+ readonly kind: 'unsupported-array-contains-any';
52
+ readonly factoryName: string;
53
+ readonly field: string;
54
+ };
55
+ /**
56
+ * Runs the analyzer over every extracted entry.
57
+ *
58
+ * @param entries - The extracted entries to analyze.
59
+ * @returns One analyzed result per input entry.
60
+ */
61
+ export declare function analyzeModelFirebaseIndexEntries(entries: readonly ExtractedModelFirebaseIndexEntry[]): readonly AnalyzedEntry[];
62
+ /**
63
+ * Runs the analyzer over a single extracted entry.
64
+ *
65
+ * @param entry - The extracted entry to analyze.
66
+ * @returns The analyzed result.
67
+ */
68
+ export declare function analyzeEntry(entry: ExtractedModelFirebaseIndexEntry): AnalyzedEntry;