@dereekb/dbx-cli 13.41.0 → 13.43.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 (38) hide show
  1. package/eslint/package.json +3 -3
  2. package/firebase-api-manifest/main.js +177 -21
  3. package/firebase-api-manifest/package.json +3 -3
  4. package/firestore-indexes/src/generate-firestore-indexes-cli.d.ts +12 -4
  5. package/firestore-indexes/src/model-firebase-index-analyze.d.ts +12 -2
  6. package/firestore-query-manifest/main.js +26 -14
  7. package/firestore-query-manifest/package.json +3 -3
  8. package/generate-firestore-indexes/main.js +118 -20
  9. package/generate-firestore-indexes/package.json +2 -2
  10. package/generate-mcp-manifest/main.js +5 -2
  11. package/generate-mcp-manifest/package.json +3 -3
  12. package/generate-route-manifest/main.js +1 -1
  13. package/generate-route-manifest/package.json +2 -2
  14. package/index.esm.js +2651 -1387
  15. package/lint-cache/package.json +2 -2
  16. package/manifest-extract/index.esm.js +148 -2
  17. package/manifest-extract/package.json +2 -2
  18. package/manifest-extract/src/lib/extract-models.d.ts +4 -2
  19. package/manifest-extract/src/lib/types.d.ts +65 -0
  20. package/model-test/index.esm.js +4 -54
  21. package/model-test/package.json +2 -2
  22. package/package.json +6 -6
  23. package/route/package.json +7 -7
  24. package/src/lib/doctor/build-drift.check.d.ts +21 -0
  25. package/src/lib/doctor/doctor.command.factory.d.ts +21 -2
  26. package/src/lib/doctor/firestore-session.check.d.ts +7 -1
  27. package/src/lib/doctor/index.d.ts +1 -0
  28. package/src/lib/firestore/firestore.sdk-identity.d.ts +193 -0
  29. package/src/lib/firestore/index.d.ts +1 -0
  30. package/src/lib/manifest/types.d.ts +73 -2
  31. package/src/lib/mcp-scan/manifest/css-utilities-schema.d.ts +2 -2
  32. package/src/lib/mcp-scan/manifest/tokens-schema.d.ts +2 -2
  33. package/src/lib/runner/build-stamp.d.ts +138 -0
  34. package/src/lib/runner/index.d.ts +1 -0
  35. package/src/lib/runner/run.d.ts +13 -0
  36. package/test/package.json +9 -9
  37. package/validate/index.js +17 -7
  38. package/validate/package.json +3 -3
@@ -5,14 +5,14 @@ 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: "13.41.0",
8
+ version: "13.43.0",
9
9
  private: true,
10
10
  type: "module",
11
11
  devDependencies: {
12
12
  eslint: "10.4.0"
13
13
  },
14
14
  peerDependencies: {
15
- "@dereekb/dbx-cli": "13.41.0"
15
+ "@dereekb/dbx-cli": "13.43.0"
16
16
  }
17
17
  };
18
18
 
@@ -438,11 +438,18 @@ function analyzeSequence(input) {
438
438
  if (isSingleFieldQuery(buckets)) {
439
439
  return analyzeSingleField({ buckets, collection, scope, warnings });
440
440
  }
441
+ if (scope === "COLLECTION" && isEqualityOnlySequence(buckets)) {
442
+ warnings.push({ kind: "equality-only-composite-skipped", factoryName, fields: buckets.equalities.map((e) => e.fieldPath) });
443
+ return { kind: "auto", warnings };
444
+ }
441
445
  return analyzeMultiField({ buckets, sequenceEntries: sequence.entries, collection, scope, factoryName, warnings });
442
446
  }
443
447
  function isSingleFieldQuery(buckets) {
444
448
  return buckets.distinctFieldCount === 1;
445
449
  }
450
+ function isEqualityOnlySequence(buckets) {
451
+ return buckets.ranges.length === 0 && buckets.arrays.length === 0 && buckets.orderBys.length === 0 && buckets.equalities.length > 0 && buckets.equalities.every((e) => (e.operator ?? "==") === "==");
452
+ }
446
453
  function analyzeSingleField(input) {
447
454
  const { buckets, collection, scope, warnings } = input;
448
455
  if (scope === "COLLECTION") {
@@ -537,7 +544,7 @@ function collectOrderByDirections(input) {
537
544
  }
538
545
  }
539
546
  for (const [field, dirs] of conflicts) {
540
- warnings.push({ kind: "orderby-conflict", factoryName, field, directions: [...dirs] });
547
+ warnings.push({ kind: "orderby-conflict", factoryName, field, directions: Array.from(dirs) });
541
548
  }
542
549
  return out;
543
550
  }
@@ -1018,7 +1025,7 @@ function composeEntry(input) {
1018
1025
  }
1019
1026
  function resolveConstraintSequences(input) {
1020
1027
  const { candidate, tags, name, line, filePath, warnings } = input;
1021
- const bodyResult = extractConstraintsFromBody({ decl: candidate.decl, factoryName: name, filePath, dispatcher: tags.dispatcher });
1028
+ const bodyResult = extractConstraintsFromBody({ decl: candidate.decl, factoryName: name, filePath, dispatcher: tags.dispatcher, suppressed: tags.skip || tags.specOnly });
1022
1029
  for (const warning of bodyResult.warnings) {
1023
1030
  warnings.push(warning);
1024
1031
  }
@@ -1099,11 +1106,14 @@ function buildConstraintSequences(input) {
1099
1106
  return sequences;
1100
1107
  }
1101
1108
  function extractConstraintsFromBody(input) {
1102
- const { decl, factoryName, filePath, dispatcher } = input;
1109
+ const { decl, factoryName, filePath, dispatcher, suppressed } = input;
1103
1110
  const entries = [];
1104
1111
  const conditionalFieldSet = /* @__PURE__ */ new Set();
1105
1112
  const warnings = [];
1106
1113
  const body = decl.getBody();
1114
+ if (suppressed) {
1115
+ return { entries, conditionalFields: [], warnings, skipped: true, dispatcherDelegates: [] };
1116
+ }
1107
1117
  if (dispatcher) {
1108
1118
  const violation = body === void 0 ? void 0 : findFirstConstraintCall(body);
1109
1119
  if (violation !== void 0) {
@@ -1901,6 +1911,76 @@ function assembleEntry(input) {
1901
1911
  };
1902
1912
  return out;
1903
1913
  }
1914
+ function formatModelFirebaseIndexBuildWarning(warning) {
1915
+ let result2;
1916
+ if (warning.stage === "extract") {
1917
+ const w = warning.warning;
1918
+ switch (w.kind) {
1919
+ case "missing-name":
1920
+ result2 = `(anonymous) (${w.filePath}:${w.line}) tagged export has no resolvable name`;
1921
+ break;
1922
+ case "missing-model-tag":
1923
+ result2 = `${w.name} (${w.filePath}:${w.line}) missing required @dbxModelFirebaseIndexModel tag`;
1924
+ break;
1925
+ case "unresolved-model":
1926
+ result2 = `${w.name} (${w.filePath}:${w.line}) could not resolve model "${w.model}" to a Firestore identity`;
1927
+ break;
1928
+ case "unsupported-scope":
1929
+ result2 = `${w.name} (${w.filePath}:${w.line}) unsupported @dbxModelFirebaseIndexScope value "${w.scope}"`;
1930
+ break;
1931
+ case "duplicate-slug":
1932
+ result2 = `${w.name} (${w.filePath}:${w.line}) duplicate slug "${w.slug}" \u2014 already used by ${w.previousName}`;
1933
+ break;
1934
+ case "unknown-helper":
1935
+ result2 = `${w.name} (${w.filePath}:${w.line}) unknown constraint helper "${w.helper}"`;
1936
+ break;
1937
+ case "unresolved-field":
1938
+ result2 = `${w.name} (${w.filePath}:${w.line}) could not resolve field-path argument to "${w.callee}"`;
1939
+ break;
1940
+ case "missing-paths":
1941
+ result2 = `${w.name} (${w.filePath}:${w.line}) missing path coverage for conditional fields [${w.conditionalFields.join(", ")}]`;
1942
+ break;
1943
+ case "unknown-path-field":
1944
+ result2 = `${w.name} (${w.filePath}:${w.line}) @dbxModelFirebaseIndexPath references unknown field "${w.field}"`;
1945
+ break;
1946
+ case "unannotated-query-helper":
1947
+ result2 = `${w.name} (${w.filePath}:${w.line}) calls query helper "${w.callee}" (${w.calleeFilePath}:${w.calleeLine}) that is not tagged with @dbxModelFirebaseIndexHelper`;
1948
+ break;
1949
+ case "transitive-cycle":
1950
+ result2 = `${w.name} (${w.filePath}:${w.line}) transitive constraint resolution hit a cycle through "${w.callee}"`;
1951
+ break;
1952
+ case "unresolvable-transitive-callee":
1953
+ result2 = `${w.name} (${w.filePath}:${w.line}) could not resolve transitive callee "${w.callee}"`;
1954
+ break;
1955
+ case "complex-query-body":
1956
+ result2 = `${w.name} (${w.filePath}:${w.line}) tagged query body contains a "${w.branchKind}" construct \u2014 split into one factory per target index or mark as @dbxModelFirebaseIndexDispatcher`;
1957
+ break;
1958
+ case "non-delegating-dispatcher":
1959
+ result2 = `${w.name} (${w.filePath}:${w.line}) @dbxModelFirebaseIndexDispatcher calls "${w.callee}" directly \u2014 dispatchers must only delegate to other tagged query functions`;
1960
+ break;
1961
+ case "excluded-factory":
1962
+ result2 = `${w.name} (${w.filePath}:${w.line}) tagged @dbxModelFirebaseIndexExclude \u2014 analyzer is suppressing composites + fieldOverrides for this factory`;
1963
+ break;
1964
+ }
1965
+ } else {
1966
+ const w = warning.warning;
1967
+ switch (w.kind) {
1968
+ case "multiple-range-fields":
1969
+ result2 = `${w.factoryName} multiple range-field constraints on [${w.fields.join(", ")}] \u2014 Firestore allows only one range field per query`;
1970
+ break;
1971
+ case "orderby-conflict":
1972
+ result2 = `${w.factoryName} field "${w.field}" has conflicting orderBy directions [${w.directions.join(", ")}]`;
1973
+ break;
1974
+ case "unsupported-array-contains-any":
1975
+ result2 = `${w.factoryName} field "${w.field}" uses array-contains-any \u2014 index support is partial`;
1976
+ break;
1977
+ case "equality-only-composite-skipped":
1978
+ result2 = `${w.factoryName} is equality-only on [${w.fields.join(", ")}] \u2014 no composite emitted, Firestore serves this by merging the automatic single-field indexes`;
1979
+ break;
1980
+ }
1981
+ }
1982
+ return result2;
1983
+ }
1904
1984
 
1905
1985
  // packages/dbx-cli/firestore-indexes/src/model-firebase-index-runtime.ts
1906
1986
  // @__NO_SIDE_EFFECTS__
@@ -2000,12 +2080,13 @@ function pushInto(map, key, entry) {
2000
2080
  var DEFAULT_BIN_NAME = "dbx-cli-generate-firestore-indexes";
2001
2081
  function buildUsage(binName) {
2002
2082
  return [
2003
- `Usage: ${binName} --component <dir> [--output <path>] [--check] [--json] [--help]`,
2083
+ `Usage: ${binName} --component <dir> [--component <dir> ...] [--output <path>] [--check] [--json] [--help]`,
2004
2084
  "",
2005
2085
  "Generates `firestore.indexes.json` from `@dbxModelFirebaseIndex`-tagged factories.",
2006
2086
  "",
2007
2087
  "Options:",
2008
- " --component <dir> Required. Relative path to the `-firebase` component package.",
2088
+ " --component <dir> Required, repeatable. Relative path to a `-firebase` component or package.",
2089
+ " Repeat to derive one indexes file from several packages at once.",
2009
2090
  " --output <path> Output path. Defaults to `firestore.indexes.json` at the cwd.",
2010
2091
  " --check Compare against the on-disk file; exit 1 on drift, do not write.",
2011
2092
  " --json Print the diff summary as JSON instead of human-readable text.",
@@ -2025,23 +2106,40 @@ async function runGenerateFirestoreIndexesCli(input) {
2025
2106
  stderr(usage);
2026
2107
  return { exitCode: 2 };
2027
2108
  }
2028
- if (parsed.component === void 0) {
2109
+ if (parsed.components.length === 0) {
2029
2110
  stderr("generate-firestore-indexes: --component is required");
2030
2111
  stderr(usage);
2031
2112
  return { exitCode: 2 };
2032
2113
  }
2033
- const componentAbs = resolve2(cwd, parsed.component);
2034
2114
  const outputAbs = resolve2(cwd, parsed.output);
2035
- const buildOutcome = await buildModelFirebaseIndexManifest({
2036
- projectRoot: componentAbs,
2037
- generator
2038
- });
2039
- if (buildOutcome.kind !== "success") {
2040
- stderr(formatBuildFailure(buildOutcome));
2115
+ const entries = [];
2116
+ const loadedSources = [];
2117
+ let sawUnresolvedModel = false;
2118
+ for (const component of parsed.components) {
2119
+ const buildOutcome = await buildModelFirebaseIndexManifest({
2120
+ projectRoot: resolve2(cwd, component),
2121
+ generator
2122
+ });
2123
+ if (buildOutcome.kind !== "success") {
2124
+ stderr(formatBuildFailure(buildOutcome));
2125
+ return { exitCode: 1 };
2126
+ }
2127
+ for (const warning of buildOutcome.extractWarnings) {
2128
+ stderr(`${warning.stage}-warning: ${formatModelFirebaseIndexBuildWarning(warning)}`);
2129
+ if (warning.stage === "extract" && warning.warning.kind === "unresolved-model") {
2130
+ sawUnresolvedModel = true;
2131
+ }
2132
+ }
2133
+ for (const entry of buildOutcome.manifest.entries) {
2134
+ entries.push(toModelFirebaseIndexEntryInfo(entry));
2135
+ }
2136
+ loadedSources.push(buildOutcome.manifest.source);
2137
+ }
2138
+ if (sawUnresolvedModel) {
2139
+ 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.");
2041
2140
  return { exitCode: 1 };
2042
2141
  }
2043
- const entries = buildOutcome.manifest.entries.map(toModelFirebaseIndexEntryInfo);
2044
- const registry = createModelFirebaseIndexRegistryFromEntries({ entries, loadedSources: [buildOutcome.manifest.source] });
2142
+ const registry = createModelFirebaseIndexRegistryFromEntries({ entries, loadedSources });
2045
2143
  const existingJson = await readExistingIndexes({ outputAbs, readFile, stderr });
2046
2144
  const { json, diff } = generateFirestoreIndexesJson({ entries: registry.all, existingJson });
2047
2145
  const serialized = serializeFirestoreIndexesJson(json);
@@ -2064,7 +2162,7 @@ async function runGenerateFirestoreIndexesCli(input) {
2064
2162
  return { exitCode: 0 };
2065
2163
  }
2066
2164
  function parseArgv(argv) {
2067
- let component;
2165
+ const components = [];
2068
2166
  let output = "firestore.indexes.json";
2069
2167
  let check = false;
2070
2168
  let json = false;
@@ -2079,7 +2177,7 @@ function parseArgv(argv) {
2079
2177
  if (i >= argv.length) {
2080
2178
  error = "generate-firestore-indexes: --component requires a value";
2081
2179
  } else {
2082
- component = argv[i];
2180
+ components.push(argv[i]);
2083
2181
  }
2084
2182
  break;
2085
2183
  case "--output":
@@ -2106,7 +2204,7 @@ function parseArgv(argv) {
2106
2204
  }
2107
2205
  i += 1;
2108
2206
  }
2109
- return { component, output, check, json, help, error };
2207
+ return { components, output, check, json, help, error };
2110
2208
  }
2111
2209
  async function readExistingIndexes(input) {
2112
2210
  const text = await readExistingIndexesText(input);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-generate-firestore-indexes",
3
- "version": "13.41.0",
3
+ "version": "13.43.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.41.0"
10
+ "@dereekb/dbx-cli": "13.43.0"
11
11
  }
12
12
  }
@@ -909,13 +909,13 @@ async function discoverClaimsFiles(cwd) {
909
909
  } catch {
910
910
  }
911
911
  }
912
- return [...found].sort((a, b) => a.localeCompare(b));
912
+ return Array.from(found).sort((a, b) => a.localeCompare(b));
913
913
  }
914
914
  function mergeAndNormalisePaths(discovered, extra) {
915
915
  const out = /* @__PURE__ */ new Set();
916
916
  for (const p of discovered) out.add(p);
917
917
  for (const p of extra) out.add(toPosix(p));
918
- return [...out].sort((a, b) => a.localeCompare(b));
918
+ return Array.from(out).sort((a, b) => a.localeCompare(b));
919
919
  }
920
920
  function toPosix(value) {
921
921
  return value.split(sep).join("/");
@@ -1131,6 +1131,9 @@ function projectModelEntry(entry) {
1131
1131
  fields: entry.fields.map(projectModelField),
1132
1132
  ...entry.modelGroup == null ? {} : { modelGroup: entry.modelGroup },
1133
1133
  ...entry.parentIdentityConst == null ? {} : { parentIdentityConst: entry.parentIdentityConst },
1134
+ ...entry.singleton == null ? {} : { singleton: entry.singleton },
1135
+ ...entry.singleItemIdentifier == null ? {} : { singleItemIdentifier: entry.singleItemIdentifier },
1136
+ ...entry.exampleKey == null ? {} : { exampleKey: entry.exampleKey },
1134
1137
  ...entry.description == null ? {} : { description: entry.description },
1135
1138
  ...entry.mcpToolNameSegment == null ? {} : { mcpToolNameSegment: entry.mcpToolNameSegment },
1136
1139
  ...entry.read == null ? {} : { read: entry.read },
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-generate-mcp-manifest",
3
- "version": "13.41.0",
3
+ "version": "13.43.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "peerDependencies": {
7
- "@dereekb/dbx-cli": "13.41.0",
8
- "@dereekb/model": "13.41.0",
7
+ "@dereekb/dbx-cli": "13.43.0",
8
+ "@dereekb/model": "13.43.0",
9
9
  "arktype": "^2.2.0",
10
10
  "jiti": "2.6.1"
11
11
  }
@@ -845,7 +845,7 @@ function warnDroppedFutureStates(tree, realNames, warnings) {
845
845
  continue;
846
846
  }
847
847
  const prefix = node.data.name.slice(0, -3);
848
- const hasRealSubtree = realNames.has(prefix) || [...realNames].some((name) => name.startsWith(`${prefix}.`));
848
+ const hasRealSubtree = realNames.has(prefix) || Array.from(realNames).some((name) => name.startsWith(`${prefix}.`));
849
849
  if (!hasRealSubtree) {
850
850
  warnings.push({ kind: "dropped-future-state", severity: "warning", message: `Future state \`${node.data.name}\` was dropped and has no real subtree under \`${prefix}\`; its page will not match any URL.`, stateName: node.data.name });
851
851
  }
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-generate-route-manifest",
3
- "version": "13.41.0",
3
+ "version": "13.43.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "peerDependencies": {
7
- "@dereekb/dbx-cli": "13.41.0",
7
+ "@dereekb/dbx-cli": "13.43.0",
8
8
  "ts-morph": "^21.0.0"
9
9
  }
10
10
  }