@dereekb/dbx-cli 13.18.0 → 13.20.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.
@@ -669,6 +669,13 @@ var LINE_PREFIX_REGEX = /^(\s*\*?\s?)(.*)$/;
669
669
  * promoted to `<collectionName>/<id>` at runtime via the model identity).
670
670
  * - `gb/:id/gbe/{authUid}` — alternating literal / placeholder segments (even
671
671
  * count) → `kind: 'key'` (a full FirestoreModelKey for a subcollection model).
672
+ * An odd (id) segment may also be a `{const:<id>}` token for a fixed/singleton
673
+ * id (e.g. `wk/:uid/wkn/{const:0}`); it is normalized to the bare literal in
674
+ * the parsed `keyTemplate` so the runtime emits it verbatim, while a forgotten
675
+ * `:` (a bare `note`) still fails as malformed.
676
+ * - `{flatKey:<param>}` — single token → `kind: 'flatKey'`: the `<param>` URL
677
+ * value IS a whole two-way-flat FirestoreModelKey (`r_<id>_cs_<id>_d_<id>`),
678
+ * un-flattened at runtime. For pages that pack a full key into one URL segment.
672
679
  * - (absent, list tag) → `kind: 'list'`.
673
680
  *
674
681
  * This module is deliberately runtime-dependency-free (no ts-morph): the same
@@ -677,8 +684,8 @@ var LINE_PREFIX_REGEX = /^(\s*\*?\s?)(.*)$/;
677
684
  * valid tag is. The ts-morph consumer (`extractComponentRouteModelTags`) lives in
678
685
  * `./route-models-extract.ts` and re-exports these symbols for existing importers.
679
686
  */ /**
680
- * Whether a route-model entry resolves to a promoted id, a full key, or a
681
- * keyless list.
687
+ * Whether a route-model entry resolves to a promoted id, a full key, a
688
+ * single-param flattened key, or a keyless list.
682
689
  */ function _array_like_to_array(arr, len) {
683
690
  if (len == null || len > arr.length) len = arr.length;
684
691
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -728,6 +735,14 @@ function _unsupported_iterable_to_array(o, minLen) {
728
735
  var MODEL_TYPE_RE = RegExp("^[a-zA-Z][a-zA-Z0-9]*$", "u");
729
736
  var LITERAL_SEGMENT_RE = RegExp("^[a-zA-Z0-9][a-zA-Z0-9_-]*$", "u");
730
737
  var AUTH_UID_PLACEHOLDER = '{authUid}';
738
+ /**
739
+ * Matches a `{const:<id>}` fixed-id token (e.g. `{const:0}`), capturing the
740
+ * literal id. The id obeys the same shape as a literal collection segment.
741
+ */ var CONST_TOKEN_RE = RegExp("^\\{const:([a-zA-Z0-9][a-zA-Z0-9_-]*)\\}$", "u");
742
+ /**
743
+ * Matches a `{flatKey:<param>}` token (e.g. `{flatKey:region}`), capturing the
744
+ * route param name whose URL value holds a whole two-way-flat FirestoreModelKey.
745
+ */ var FLAT_KEY_TOKEN_RE = RegExp("^\\{flatKey:([a-zA-Z_][a-zA-Z0-9_]*)\\}$", "u");
731
746
  /**
732
747
  * The bare `@dbxRouteModel` tag name (without the leading `@`).
733
748
  */ var ROUTE_MODEL_TAG = 'dbxRouteModel';
@@ -807,7 +822,7 @@ function parseModelTag(tokens, description) {
807
822
  model: {
808
823
  modelType: tokens[0],
809
824
  kind: parsedKey.kind,
810
- keyTemplate: tokens[1],
825
+ keyTemplate: parsedKey.keyTemplate,
811
826
  description: description,
812
827
  routeParams: parsedKey.routeParams
813
828
  }
@@ -842,17 +857,29 @@ function parseKeyTemplate(keyTemplate) {
842
857
  return result;
843
858
  }
844
859
  function parseSingleSegmentKey(segment, keyTemplate) {
860
+ var flatKeyParam = flatKeyTokenParam(segment);
845
861
  var placeholder = placeholderParam(segment);
846
862
  var result;
847
- if (placeholder === undefined) {
863
+ if (flatKeyParam !== undefined) {
864
+ // The whole key lives in one URL param; the runtime un-flattens it.
865
+ result = {
866
+ ok: true,
867
+ kind: 'flatKey',
868
+ keyTemplate: keyTemplate,
869
+ routeParams: [
870
+ flatKeyParam
871
+ ]
872
+ };
873
+ } else if (placeholder === undefined) {
848
874
  result = {
849
875
  ok: false,
850
- message: "Single-segment key template `".concat(keyTemplate, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`).")
876
+ message: "Single-segment key template `".concat(keyTemplate, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`) or a flattened-key token (`{flatKey:<param>}`).")
851
877
  };
852
878
  } else {
853
879
  result = {
854
880
  ok: true,
855
881
  kind: 'id',
882
+ keyTemplate: keyTemplate,
856
883
  routeParams: placeholder.routeParam === undefined ? [] : [
857
884
  placeholder.routeParam
858
885
  ]
@@ -862,6 +889,10 @@ function parseSingleSegmentKey(segment, keyTemplate) {
862
889
  }
863
890
  function parseAlternatingKey(segments, keyTemplate) {
864
891
  var routeParams = [];
892
+ // The normalized template substitutes any `{const:<id>}` token back to its
893
+ // bare literal so the runtime `resolveFullKey` (which emits non-placeholder
894
+ // segments verbatim) round-trips without needing to understand the token.
895
+ var normalizedSegments = [];
865
896
  var message;
866
897
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
867
898
  try {
@@ -872,14 +903,20 @@ function parseAlternatingKey(segments, keyTemplate) {
872
903
  message = "Key template `".concat(keyTemplate, "` segment `").concat(segment, "` must be a literal collection name.");
873
904
  break;
874
905
  }
906
+ normalizedSegments.push(segment);
875
907
  } else {
876
908
  var placeholder = placeholderParam(segment);
877
- if (placeholder === undefined) {
878
- message = "Key template `".concat(keyTemplate, "` segment `").concat(segment, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`).");
909
+ var constId = constTokenId(segment);
910
+ if (placeholder !== undefined) {
911
+ if (placeholder.routeParam !== undefined) {
912
+ routeParams.push(placeholder.routeParam);
913
+ }
914
+ normalizedSegments.push(segment);
915
+ } else if (constId === undefined) {
916
+ message = "Key template `".concat(keyTemplate, "` segment `").concat(segment, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`) or a fixed id (`{const:<id>}`).");
879
917
  break;
880
- }
881
- if (placeholder.routeParam !== undefined) {
882
- routeParams.push(placeholder.routeParam);
918
+ } else {
919
+ normalizedSegments.push(constId);
883
920
  }
884
921
  }
885
922
  }
@@ -900,6 +937,7 @@ function parseAlternatingKey(segments, keyTemplate) {
900
937
  return message === undefined ? {
901
938
  ok: true,
902
939
  kind: 'key',
940
+ keyTemplate: normalizedSegments.join('/'),
903
941
  routeParams: routeParams
904
942
  } : {
905
943
  ok: false,
@@ -929,6 +967,28 @@ function parseAlternatingKey(segments, keyTemplate) {
929
967
  }
930
968
  return result;
931
969
  }
970
+ /**
971
+ * Extracts the literal id from a `{const:<id>}` fixed-id token, used for a
972
+ * fixed/singleton subcollection id at an odd key-template position. Returns
973
+ * `undefined` for any non-`{const:…}` segment.
974
+ *
975
+ * @param segment - The single key-template segment to classify.
976
+ * @returns The captured literal id, or `undefined` when not a const token.
977
+ */ function constTokenId(segment) {
978
+ var match = CONST_TOKEN_RE.exec(segment);
979
+ return match === null ? undefined : match[1];
980
+ }
981
+ /**
982
+ * Extracts the route param name from a `{flatKey:<param>}` token, whose URL
983
+ * value is a whole two-way-flat FirestoreModelKey un-flattened at runtime.
984
+ * Returns `undefined` for any non-`{flatKey:…}` segment.
985
+ *
986
+ * @param segment - The single key-template segment to classify.
987
+ * @returns The captured route param name, or `undefined` when not a flatKey token.
988
+ */ function flatKeyTokenParam(segment) {
989
+ var match = FLAT_KEY_TOKEN_RE.exec(segment);
990
+ return match === null ? undefined : match[1];
991
+ }
932
992
 
933
993
  /**
934
994
  * ESLint rule enforcing that `@dbxRouteModel` / `@dbxRouteModelList` tags parse
@@ -667,6 +667,13 @@ var LINE_PREFIX_REGEX = /^(\s*\*?\s?)(.*)$/;
667
667
  * promoted to `<collectionName>/<id>` at runtime via the model identity).
668
668
  * - `gb/:id/gbe/{authUid}` — alternating literal / placeholder segments (even
669
669
  * count) → `kind: 'key'` (a full FirestoreModelKey for a subcollection model).
670
+ * An odd (id) segment may also be a `{const:<id>}` token for a fixed/singleton
671
+ * id (e.g. `wk/:uid/wkn/{const:0}`); it is normalized to the bare literal in
672
+ * the parsed `keyTemplate` so the runtime emits it verbatim, while a forgotten
673
+ * `:` (a bare `note`) still fails as malformed.
674
+ * - `{flatKey:<param>}` — single token → `kind: 'flatKey'`: the `<param>` URL
675
+ * value IS a whole two-way-flat FirestoreModelKey (`r_<id>_cs_<id>_d_<id>`),
676
+ * un-flattened at runtime. For pages that pack a full key into one URL segment.
670
677
  * - (absent, list tag) → `kind: 'list'`.
671
678
  *
672
679
  * This module is deliberately runtime-dependency-free (no ts-morph): the same
@@ -675,8 +682,8 @@ var LINE_PREFIX_REGEX = /^(\s*\*?\s?)(.*)$/;
675
682
  * valid tag is. The ts-morph consumer (`extractComponentRouteModelTags`) lives in
676
683
  * `./route-models-extract.ts` and re-exports these symbols for existing importers.
677
684
  */ /**
678
- * Whether a route-model entry resolves to a promoted id, a full key, or a
679
- * keyless list.
685
+ * Whether a route-model entry resolves to a promoted id, a full key, a
686
+ * single-param flattened key, or a keyless list.
680
687
  */ function _array_like_to_array(arr, len) {
681
688
  if (len == null || len > arr.length) len = arr.length;
682
689
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -726,6 +733,14 @@ function _unsupported_iterable_to_array(o, minLen) {
726
733
  var MODEL_TYPE_RE = RegExp("^[a-zA-Z][a-zA-Z0-9]*$", "u");
727
734
  var LITERAL_SEGMENT_RE = RegExp("^[a-zA-Z0-9][a-zA-Z0-9_-]*$", "u");
728
735
  var AUTH_UID_PLACEHOLDER = '{authUid}';
736
+ /**
737
+ * Matches a `{const:<id>}` fixed-id token (e.g. `{const:0}`), capturing the
738
+ * literal id. The id obeys the same shape as a literal collection segment.
739
+ */ var CONST_TOKEN_RE = RegExp("^\\{const:([a-zA-Z0-9][a-zA-Z0-9_-]*)\\}$", "u");
740
+ /**
741
+ * Matches a `{flatKey:<param>}` token (e.g. `{flatKey:region}`), capturing the
742
+ * route param name whose URL value holds a whole two-way-flat FirestoreModelKey.
743
+ */ var FLAT_KEY_TOKEN_RE = RegExp("^\\{flatKey:([a-zA-Z_][a-zA-Z0-9_]*)\\}$", "u");
729
744
  /**
730
745
  * The bare `@dbxRouteModel` tag name (without the leading `@`).
731
746
  */ var ROUTE_MODEL_TAG = 'dbxRouteModel';
@@ -805,7 +820,7 @@ function parseModelTag(tokens, description) {
805
820
  model: {
806
821
  modelType: tokens[0],
807
822
  kind: parsedKey.kind,
808
- keyTemplate: tokens[1],
823
+ keyTemplate: parsedKey.keyTemplate,
809
824
  description: description,
810
825
  routeParams: parsedKey.routeParams
811
826
  }
@@ -840,17 +855,29 @@ function parseKeyTemplate(keyTemplate) {
840
855
  return result;
841
856
  }
842
857
  function parseSingleSegmentKey(segment, keyTemplate) {
858
+ var flatKeyParam = flatKeyTokenParam(segment);
843
859
  var placeholder = placeholderParam(segment);
844
860
  var result;
845
- if (placeholder === undefined) {
861
+ if (flatKeyParam !== undefined) {
862
+ // The whole key lives in one URL param; the runtime un-flattens it.
863
+ result = {
864
+ ok: true,
865
+ kind: 'flatKey',
866
+ keyTemplate: keyTemplate,
867
+ routeParams: [
868
+ flatKeyParam
869
+ ]
870
+ };
871
+ } else if (placeholder === undefined) {
846
872
  result = {
847
873
  ok: false,
848
- message: "Single-segment key template `".concat(keyTemplate, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`).")
874
+ message: "Single-segment key template `".concat(keyTemplate, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`) or a flattened-key token (`{flatKey:<param>}`).")
849
875
  };
850
876
  } else {
851
877
  result = {
852
878
  ok: true,
853
879
  kind: 'id',
880
+ keyTemplate: keyTemplate,
854
881
  routeParams: placeholder.routeParam === undefined ? [] : [
855
882
  placeholder.routeParam
856
883
  ]
@@ -860,6 +887,10 @@ function parseSingleSegmentKey(segment, keyTemplate) {
860
887
  }
861
888
  function parseAlternatingKey(segments, keyTemplate) {
862
889
  var routeParams = [];
890
+ // The normalized template substitutes any `{const:<id>}` token back to its
891
+ // bare literal so the runtime `resolveFullKey` (which emits non-placeholder
892
+ // segments verbatim) round-trips without needing to understand the token.
893
+ var normalizedSegments = [];
863
894
  var message;
864
895
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
865
896
  try {
@@ -870,14 +901,20 @@ function parseAlternatingKey(segments, keyTemplate) {
870
901
  message = "Key template `".concat(keyTemplate, "` segment `").concat(segment, "` must be a literal collection name.");
871
902
  break;
872
903
  }
904
+ normalizedSegments.push(segment);
873
905
  } else {
874
906
  var placeholder = placeholderParam(segment);
875
- if (placeholder === undefined) {
876
- message = "Key template `".concat(keyTemplate, "` segment `").concat(segment, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`).");
907
+ var constId = constTokenId(segment);
908
+ if (placeholder !== undefined) {
909
+ if (placeholder.routeParam !== undefined) {
910
+ routeParams.push(placeholder.routeParam);
911
+ }
912
+ normalizedSegments.push(segment);
913
+ } else if (constId === undefined) {
914
+ message = "Key template `".concat(keyTemplate, "` segment `").concat(segment, "` must be a placeholder (`:param` or `").concat(AUTH_UID_PLACEHOLDER, "`) or a fixed id (`{const:<id>}`).");
877
915
  break;
878
- }
879
- if (placeholder.routeParam !== undefined) {
880
- routeParams.push(placeholder.routeParam);
916
+ } else {
917
+ normalizedSegments.push(constId);
881
918
  }
882
919
  }
883
920
  }
@@ -898,6 +935,7 @@ function parseAlternatingKey(segments, keyTemplate) {
898
935
  return message === undefined ? {
899
936
  ok: true,
900
937
  kind: 'key',
938
+ keyTemplate: normalizedSegments.join('/'),
901
939
  routeParams: routeParams
902
940
  } : {
903
941
  ok: false,
@@ -927,6 +965,28 @@ function parseAlternatingKey(segments, keyTemplate) {
927
965
  }
928
966
  return result;
929
967
  }
968
+ /**
969
+ * Extracts the literal id from a `{const:<id>}` fixed-id token, used for a
970
+ * fixed/singleton subcollection id at an odd key-template position. Returns
971
+ * `undefined` for any non-`{const:…}` segment.
972
+ *
973
+ * @param segment - The single key-template segment to classify.
974
+ * @returns The captured literal id, or `undefined` when not a const token.
975
+ */ function constTokenId(segment) {
976
+ var match = CONST_TOKEN_RE.exec(segment);
977
+ return match === null ? undefined : match[1];
978
+ }
979
+ /**
980
+ * Extracts the route param name from a `{flatKey:<param>}` token, whose URL
981
+ * value is a whole two-way-flat FirestoreModelKey un-flattened at runtime.
982
+ * Returns `undefined` for any non-`{flatKey:…}` segment.
983
+ *
984
+ * @param segment - The single key-template segment to classify.
985
+ * @returns The captured route param name, or `undefined` when not a flatKey token.
986
+ */ function flatKeyTokenParam(segment) {
987
+ var match = FLAT_KEY_TOKEN_RE.exec(segment);
988
+ return match === null ? undefined : match[1];
989
+ }
930
990
 
931
991
  /**
932
992
  * ESLint rule enforcing that `@dbxRouteModel` / `@dbxRouteModelList` tags parse
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli/eslint",
3
- "version": "13.18.0",
3
+ "version": "13.20.0",
4
4
  "peerDependencies": {
5
- "@dereekb/dbx-cli": "13.18.0",
6
- "@dereekb/util": "13.18.0",
5
+ "@dereekb/dbx-cli": "13.20.0",
6
+ "@dereekb/util": "13.20.0",
7
7
  "@typescript-eslint/utils": "8.59.3"
8
8
  },
9
9
  "devDependencies": {
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-firebase-api-manifest",
3
- "version": "13.18.0",
3
+ "version": "13.20.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.18.0",
11
- "@dereekb/util": "13.18.0",
10
+ "@dereekb/dbx-cli": "13.20.0",
11
+ "@dereekb/util": "13.20.0",
12
12
  "prettier": "3.8.3"
13
13
  }
14
14
  }
@@ -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.18.0",
8
+ version: "13.20.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.18.0"
15
+ "@dereekb/dbx-cli": "13.20.0"
16
16
  }
17
17
  };
18
18
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-generate-firestore-indexes",
3
- "version": "13.18.0",
3
+ "version": "13.20.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.18.0"
10
+ "@dereekb/dbx-cli": "13.20.0"
11
11
  }
12
12
  }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-generate-mcp-manifest",
3
- "version": "13.18.0",
3
+ "version": "13.20.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "peerDependencies": {
7
- "@dereekb/dbx-cli": "13.18.0",
8
- "@dereekb/model": "13.18.0",
7
+ "@dereekb/dbx-cli": "13.20.0",
8
+ "@dereekb/model": "13.20.0",
9
9
  "arktype": "^2.2.0",
10
10
  "jiti": "2.6.1"
11
11
  }
@@ -334,6 +334,42 @@ function collectRelativeImports(sourceFile, fileName) {
334
334
  return out;
335
335
  }
336
336
 
337
+ // packages/dbx-cli/src/lib/route/url-match.ts
338
+ function stripUrlQueryAndHash(url) {
339
+ const hashStripped = url.split("#", 1)[0];
340
+ return hashStripped.split("?", 1)[0];
341
+ }
342
+ function extractUrlParamKeys(fullUrl) {
343
+ if (fullUrl === void 0 || fullUrl.length === 0) {
344
+ return [];
345
+ }
346
+ const seen = /* @__PURE__ */ new Set();
347
+ const keys = [];
348
+ for (const segment of fullUrl.split("/")) {
349
+ const key = extractParamKeyFromSegment(segment);
350
+ if (key !== void 0 && !seen.has(key)) {
351
+ seen.add(key);
352
+ keys.push(key);
353
+ }
354
+ }
355
+ return keys;
356
+ }
357
+ function extractParamKeyFromSegment(rawSegment) {
358
+ const segment = stripUrlQueryAndHash(rawSegment);
359
+ if (segment.startsWith(":")) {
360
+ const key = segment.slice(1);
361
+ return key.length > 0 ? key : void 0;
362
+ }
363
+ if (segment.startsWith("{") && segment.endsWith("}")) {
364
+ const inner = segment.slice(1, -1);
365
+ const colonIdx = inner.indexOf(":");
366
+ const rawKey = colonIdx >= 0 ? inner.slice(0, colonIdx) : inner;
367
+ const key = rawKey.trim();
368
+ return key.length > 0 ? key : void 0;
369
+ }
370
+ return void 0;
371
+ }
372
+
337
373
  // packages/dbx-cli/src/lib/route/route-build-tree.ts
338
374
  // @__NO_SIDE_EFFECTS__
339
375
  function buildRouteTree(nodes, extractIssues) {
@@ -493,7 +529,7 @@ function composeFullUrl(node) {
493
529
  return void 0;
494
530
  }
495
531
  const joined = segments.join("");
496
- const collapsed = joined.replaceAll(/\/{2,}/g, "/");
532
+ const collapsed = stripUrlQueryAndHash(joined).replaceAll(/\/{2,}/g, "/");
497
533
  return collapsed.length === 0 ? "/" : collapsed;
498
534
  }
499
535
  function compareByName(a, b) {
@@ -541,37 +577,6 @@ function loadRouteTree(args) {
541
577
  return result;
542
578
  }
543
579
 
544
- // packages/dbx-cli/src/lib/route/url-match.ts
545
- function extractUrlParamKeys(fullUrl) {
546
- if (fullUrl === void 0 || fullUrl.length === 0) {
547
- return [];
548
- }
549
- const seen = /* @__PURE__ */ new Set();
550
- const keys = [];
551
- for (const segment of fullUrl.split("/")) {
552
- const key = extractParamKeyFromSegment(segment);
553
- if (key !== void 0 && !seen.has(key)) {
554
- seen.add(key);
555
- keys.push(key);
556
- }
557
- }
558
- return keys;
559
- }
560
- function extractParamKeyFromSegment(segment) {
561
- if (segment.startsWith(":")) {
562
- const key = segment.slice(1);
563
- return key.length > 0 ? key : void 0;
564
- }
565
- if (segment.startsWith("{") && segment.endsWith("}")) {
566
- const inner = segment.slice(1, -1);
567
- const colonIdx = inner.indexOf(":");
568
- const rawKey = colonIdx >= 0 ? inner.slice(0, colonIdx) : inner;
569
- const key = rawKey.trim();
570
- return key.length > 0 ? key : void 0;
571
- }
572
- return void 0;
573
- }
574
-
575
580
  // packages/dbx-cli/src/lib/route/component-resolve.ts
576
581
  import { dirname, join, normalize } from "node:path/posix";
577
582
  var TRY_EXTENSIONS = [".ts", ".tsx", "/index.ts", "/index.tsx"];
@@ -646,6 +651,8 @@ function containsImportedSymbol(importStatement, symbol) {
646
651
  var MODEL_TYPE_RE = /^[a-zA-Z][a-zA-Z0-9]*$/u;
647
652
  var LITERAL_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/u;
648
653
  var AUTH_UID_PLACEHOLDER = "{authUid}";
654
+ var CONST_TOKEN_RE = /^\{const:([a-zA-Z0-9][a-zA-Z0-9_-]*)\}$/u;
655
+ var FLAT_KEY_TOKEN_RE = /^\{flatKey:([a-zA-Z_][a-zA-Z0-9_]*)\}$/u;
649
656
  var ROUTE_MODEL_TAG = "dbxRouteModel";
650
657
  var ROUTE_MODEL_LIST_TAG = "dbxRouteModelList";
651
658
  function parseRouteModelTag(tag) {
@@ -681,7 +688,7 @@ function parseModelTag(tokens, description) {
681
688
  } else if (MODEL_TYPE_RE.test(tokens[0])) {
682
689
  const parsedKey = parseKeyTemplate(tokens[1]);
683
690
  if (parsedKey.ok) {
684
- result = { ok: true, model: { modelType: tokens[0], kind: parsedKey.kind, keyTemplate: tokens[1], description, routeParams: parsedKey.routeParams } };
691
+ result = { ok: true, model: { modelType: tokens[0], kind: parsedKey.kind, keyTemplate: parsedKey.keyTemplate, description, routeParams: parsedKey.routeParams } };
685
692
  } else {
686
693
  result = { ok: false, message: parsedKey.message };
687
694
  }
@@ -703,17 +710,21 @@ function parseKeyTemplate(keyTemplate) {
703
710
  return result;
704
711
  }
705
712
  function parseSingleSegmentKey(segment, keyTemplate) {
713
+ const flatKeyParam = flatKeyTokenParam(segment);
706
714
  const placeholder = placeholderParam(segment);
707
715
  let result;
708
- if (placeholder === void 0) {
709
- result = { ok: false, message: `Single-segment key template \`${keyTemplate}\` must be a placeholder (\`:param\` or \`${AUTH_UID_PLACEHOLDER}\`).` };
716
+ if (flatKeyParam !== void 0) {
717
+ result = { ok: true, kind: "flatKey", keyTemplate, routeParams: [flatKeyParam] };
718
+ } else if (placeholder === void 0) {
719
+ result = { ok: false, message: `Single-segment key template \`${keyTemplate}\` must be a placeholder (\`:param\` or \`${AUTH_UID_PLACEHOLDER}\`) or a flattened-key token (\`{flatKey:<param>}\`).` };
710
720
  } else {
711
- result = { ok: true, kind: "id", routeParams: placeholder.routeParam === void 0 ? [] : [placeholder.routeParam] };
721
+ result = { ok: true, kind: "id", keyTemplate, routeParams: placeholder.routeParam === void 0 ? [] : [placeholder.routeParam] };
712
722
  }
713
723
  return result;
714
724
  }
715
725
  function parseAlternatingKey(segments, keyTemplate) {
716
726
  const routeParams = [];
727
+ const normalizedSegments = [];
717
728
  let message;
718
729
  for (const [i, segment] of segments.entries()) {
719
730
  if (i % 2 === 0) {
@@ -721,18 +732,24 @@ function parseAlternatingKey(segments, keyTemplate) {
721
732
  message = `Key template \`${keyTemplate}\` segment \`${segment}\` must be a literal collection name.`;
722
733
  break;
723
734
  }
735
+ normalizedSegments.push(segment);
724
736
  } else {
725
737
  const placeholder = placeholderParam(segment);
726
- if (placeholder === void 0) {
727
- message = `Key template \`${keyTemplate}\` segment \`${segment}\` must be a placeholder (\`:param\` or \`${AUTH_UID_PLACEHOLDER}\`).`;
738
+ const constId = constTokenId(segment);
739
+ if (placeholder !== void 0) {
740
+ if (placeholder.routeParam !== void 0) {
741
+ routeParams.push(placeholder.routeParam);
742
+ }
743
+ normalizedSegments.push(segment);
744
+ } else if (constId === void 0) {
745
+ message = `Key template \`${keyTemplate}\` segment \`${segment}\` must be a placeholder (\`:param\` or \`${AUTH_UID_PLACEHOLDER}\`) or a fixed id (\`{const:<id>}\`).`;
728
746
  break;
729
- }
730
- if (placeholder.routeParam !== void 0) {
731
- routeParams.push(placeholder.routeParam);
747
+ } else {
748
+ normalizedSegments.push(constId);
732
749
  }
733
750
  }
734
751
  }
735
- return message === void 0 ? { ok: true, kind: "key", routeParams } : { ok: false, message };
752
+ return message === void 0 ? { ok: true, kind: "key", keyTemplate: normalizedSegments.join("/"), routeParams } : { ok: false, message };
736
753
  }
737
754
  function placeholderParam(segment) {
738
755
  let result;
@@ -745,6 +762,14 @@ function placeholderParam(segment) {
745
762
  }
746
763
  return result;
747
764
  }
765
+ function constTokenId(segment) {
766
+ const match = CONST_TOKEN_RE.exec(segment);
767
+ return match === null ? void 0 : match[1];
768
+ }
769
+ function flatKeyTokenParam(segment) {
770
+ const match = FLAT_KEY_TOKEN_RE.exec(segment);
771
+ return match === null ? void 0 : match[1];
772
+ }
748
773
 
749
774
  // packages/dbx-cli/src/lib/route/route-models-extract.ts
750
775
  function extractComponentRouteModelTags(sourceFile, component) {
@@ -766,7 +791,7 @@ function collectRouteModelTags2(jsDocs) {
766
791
 
767
792
  // packages/dbx-cli/src/lib/route/route-manifest.ts
768
793
  import { Project as Project2 } from "ts-morph";
769
- var ROUTE_MANIFEST_VERSION = 1;
794
+ var ROUTE_MANIFEST_VERSION = 2;
770
795
  function buildRouteManifest(input, now = /* @__PURE__ */ new Date()) {
771
796
  const warnings = [];
772
797
  const tree = loadRouteTree({ sources: input.sources });
@@ -990,7 +1015,12 @@ function formatRouteManifestWarning(warning) {
990
1015
  return `[generate-route-manifest] ${warning.severity}: ${warning.kind}: ${warning.message}`;
991
1016
  }
992
1017
  function countRouteManifestGenerationErrors(input) {
993
- return input.warnings.filter((warning) => warning.severity === "error" || input.strict && warning.severity === "warning").length;
1018
+ const allow = new Set(input.allowWarning ?? []);
1019
+ const errorCount = input.warnings.filter((warning) => warning.severity === "error").length;
1020
+ const blockableWarnings = input.warnings.filter((warning) => warning.severity === "warning" && !allow.has(warning.kind));
1021
+ const exceedsMax = input.maxWarnings !== void 0 && blockableWarnings.length > input.maxWarnings;
1022
+ const blockingWarningCount = input.strict || exceedsMax ? blockableWarnings.length : 0;
1023
+ return errorCount + blockingWarningCount;
994
1024
  }
995
1025
  function extractModelTypesFromModelsInput(parsed) {
996
1026
  const models = parsed?.models;
@@ -1030,9 +1060,10 @@ async function main() {
1030
1060
  console.error(`generate-route-manifest: 0 states extracted from ${describePatterns(flags.src)}; not writing ${relative(WORKSPACE_ROOT, outputPath)}.`);
1031
1061
  process.exit(1);
1032
1062
  }
1033
- const errorCount = countRouteManifestGenerationErrors({ warnings, strict: flags.strict });
1063
+ const errorCount = countRouteManifestGenerationErrors({ warnings, strict: flags.strict, allowWarning: flags.allowWarning, ...flags.maxWarnings == null ? {} : { maxWarnings: flags.maxWarnings } });
1034
1064
  if (errorCount > 0) {
1035
- console.error(`generate-route-manifest: ${errorCount} error-severity issue(s)${flags.strict ? " (--strict)" : ""}; not writing ${relative(WORKSPACE_ROOT, outputPath)}.`);
1065
+ const gate = flags.strict ? " (--strict)" : flags.maxWarnings == null ? "" : ` (--max-warnings=${flags.maxWarnings})`;
1066
+ console.error(`generate-route-manifest: ${errorCount} blocking issue(s)${gate}; not writing ${relative(WORKSPACE_ROOT, outputPath)}.`);
1036
1067
  process.exit(1);
1037
1068
  }
1038
1069
  const serialized = `${JSON.stringify(manifest, null, 2)}
@@ -1085,11 +1116,13 @@ function resolveWorkspacePath(value) {
1085
1116
  }
1086
1117
  function parseFlags(argv) {
1087
1118
  const src = [];
1119
+ const allowWarning = [];
1088
1120
  let app;
1089
1121
  let baseUrl;
1090
1122
  let output;
1091
1123
  let modelsInput;
1092
1124
  let strict = false;
1125
+ let maxWarnings;
1093
1126
  for (const arg of argv) {
1094
1127
  if (arg.startsWith("--src=")) {
1095
1128
  src.push(arg.slice("--src=".length));
@@ -1101,11 +1134,19 @@ function parseFlags(argv) {
1101
1134
  output = arg.slice("--output=".length);
1102
1135
  } else if (arg.startsWith("--models-input=")) {
1103
1136
  modelsInput = arg.slice("--models-input=".length);
1137
+ } else if (arg.startsWith("--allow-warning=")) {
1138
+ const kind = arg.slice("--allow-warning=".length).trim();
1139
+ if (kind.length > 0) {
1140
+ allowWarning.push(kind);
1141
+ }
1142
+ } else if (arg.startsWith("--max-warnings=")) {
1143
+ const parsed = Number.parseInt(arg.slice("--max-warnings=".length), 10);
1144
+ maxWarnings = Number.isNaN(parsed) ? void 0 : Math.max(0, parsed);
1104
1145
  } else if (arg === "--strict") {
1105
1146
  strict = true;
1106
1147
  }
1107
1148
  }
1108
- return { src, app, baseUrl, output, modelsInput, strict };
1149
+ return { src, app, baseUrl, output, modelsInput, strict, allowWarning, maxWarnings };
1109
1150
  }
1110
1151
  function printUsageAndExit() {
1111
1152
  console.error(String.raw`generate-route-manifest
@@ -1126,7 +1167,12 @@ Optional:
1126
1167
  --models-input=<path> MCP manifest JSON whose models[].modelType seed the
1127
1168
  unknown-model-type validation.
1128
1169
  --strict Promote all warnings to errors for the exit decision
1129
- (any finding then fails generation).`);
1170
+ (any finding then fails generation).
1171
+ --allow-warning=<kind> Tolerate a warning kind (repeatable); it never fails generation,
1172
+ even under --strict / --max-warnings. Kinds: unknown-route-param,
1173
+ unknown-model-type, duplicate-route-model, dropped-future-state,
1174
+ missing-route-model. (malformed-tag is an error and cannot be allowed.)
1175
+ --max-warnings=<N> Fail when non-allowlisted warnings exceed N (0 = fail on any new warning).`);
1130
1176
  process.exit(1);
1131
1177
  }
1132
1178
  try {
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-generate-route-manifest",
3
- "version": "13.18.0",
3
+ "version": "13.20.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "peerDependencies": {
7
- "@dereekb/dbx-cli": "13.18.0",
7
+ "@dereekb/dbx-cli": "13.20.0",
8
8
  "ts-morph": "^21.0.0"
9
9
  }
10
10
  }