@dereekb/dbx-components-mcp 13.11.6 → 13.11.7

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.
@@ -5,7 +5,7 @@ const require = __createRequire(import.meta.url);
5
5
  // packages/dbx-components-mcp/package.json
6
6
  var package_default = {
7
7
  name: "@dereekb/dbx-components-mcp",
8
- version: "13.11.6",
8
+ version: "13.11.7",
9
9
  type: "module",
10
10
  bin: {
11
11
  "dbx-components-mcp": "dbx-components-mcp.js"
@@ -20,20 +20,20 @@ var package_default = {
20
20
  "@angular/core": "21.2.11",
21
21
  "@angular/forms": "21.2.11",
22
22
  "@angular/material": "^21.2.9",
23
- "@dereekb/dbx-cli": "13.11.6",
24
- "@dereekb/dbx-core": "13.11.6",
25
- "@dereekb/dbx-firebase": "13.11.6",
26
- "@dereekb/dbx-form": "13.11.6",
27
- "@dereekb/dbx-web": "13.11.6",
28
- "@dereekb/firebase": "13.11.6",
29
- "@dereekb/firebase-server": "13.11.6",
30
- "@dereekb/rxjs": "13.11.6",
31
- "@dereekb/util": "13.11.6",
23
+ "@dereekb/dbx-cli": "13.11.7",
24
+ "@dereekb/dbx-core": "13.11.7",
25
+ "@dereekb/dbx-firebase": "13.11.7",
26
+ "@dereekb/dbx-form": "13.11.7",
27
+ "@dereekb/dbx-web": "13.11.7",
28
+ "@dereekb/firebase": "13.11.7",
29
+ "@dereekb/firebase-server": "13.11.7",
30
+ "@dereekb/rxjs": "13.11.7",
31
+ "@dereekb/util": "13.11.7",
32
32
  "@uirouter/angular": "21.0.0",
33
33
  rxjs: "^7.8.2"
34
34
  },
35
35
  devDependencies: {
36
- "@dereekb/model": "13.11.6"
36
+ "@dereekb/model": "13.11.7"
37
37
  }
38
38
  };
39
39
 
@@ -14280,6 +14280,14 @@ function createListAppTool(config) {
14280
14280
 
14281
14281
  // packages/dbx-components-mcp/src/tools/model-validate/extract.ts
14282
14282
  import { Node as Node11, Project as Project4, SyntaxKind as SyntaxKind4 } from "ts-morph";
14283
+
14284
+ // packages/dbx-components-mcp/src/tools/model-validate/types.ts
14285
+ var MAX_FIELD_NAME_LENGTH = 5;
14286
+ var ROOT_MODEL_ORDER = ["identity", "interface", "rolesType", "documentClass", "converter", "referenceFn", "collectionType", "collectionFn"];
14287
+ var SUBCOLLECTION_MODEL_ORDER = ["identity", "interface", "rolesType", "documentClass", "converter", "referenceFn", "collectionType", "collectionFactoryType", "collectionFn", "referenceGroupFn", "collectionGroupType", "collectionGroupFn"];
14288
+ var SUB_OBJECT_FACTORY_NAMES = ["firestoreSubObject", "firestoreObjectArray", "firestoreMap"];
14289
+
14290
+ // packages/dbx-components-mcp/src/tools/model-validate/extract.ts
14283
14291
  function extractFile(source) {
14284
14292
  const project = new Project4({ useInMemoryFileSystem: true, skipAddingFilesFromTsConfig: true });
14285
14293
  const sourceFile = project.createSourceFile(source.name, source.text, { overwrite: true });
@@ -14289,16 +14297,56 @@ function extractFile(source) {
14289
14297
  const firstModelLine = identities.length > 0 ? Math.min(...identities.map((i) => i.line)) : void 0;
14290
14298
  const models = identities.map((identity) => extractModel(sourceFile, identity));
14291
14299
  const dataInterfaces = findDataInterfaces(sourceFile, groupInterface);
14300
+ const subObjectCalls = findSubObjectFactoryCalls(sourceFile);
14292
14301
  const result = {
14293
14302
  name: source.name,
14294
14303
  groupInterface,
14295
14304
  groupTypes,
14296
14305
  firstModelLine,
14297
14306
  models,
14298
- dataInterfaces
14307
+ dataInterfaces,
14308
+ subObjectCalls
14299
14309
  };
14300
14310
  return result;
14301
14311
  }
14312
+ function findSubObjectFactoryCalls(sourceFile) {
14313
+ const out = [];
14314
+ const factorySet = new Set(SUB_OBJECT_FACTORY_NAMES);
14315
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind4.CallExpression)) {
14316
+ const expr = call.getExpression();
14317
+ if (!Node11.isIdentifier(expr)) {
14318
+ continue;
14319
+ }
14320
+ const factoryName = expr.getText();
14321
+ if (!factorySet.has(factoryName)) {
14322
+ continue;
14323
+ }
14324
+ const typeArgs = call.getTypeArguments();
14325
+ if (typeArgs.length === 0) {
14326
+ continue;
14327
+ }
14328
+ const typeArgName = readTypeReferenceIdentifier(typeArgs[0]);
14329
+ if (!typeArgName) {
14330
+ continue;
14331
+ }
14332
+ out.push({
14333
+ factoryName,
14334
+ typeArgName,
14335
+ line: call.getStartLineNumber()
14336
+ });
14337
+ }
14338
+ return out;
14339
+ }
14340
+ function readTypeReferenceIdentifier(typeNode) {
14341
+ if (!Node11.isTypeReference(typeNode)) {
14342
+ return void 0;
14343
+ }
14344
+ const nameNode = typeNode.getTypeName();
14345
+ if (!Node11.isIdentifier(nameNode)) {
14346
+ return void 0;
14347
+ }
14348
+ return nameNode.getText();
14349
+ }
14302
14350
  function findDataInterfaces(sourceFile, groupInterface) {
14303
14351
  const out = [];
14304
14352
  const groupName = groupInterface?.name;
@@ -14309,7 +14357,8 @@ function findDataInterfaces(sourceFile, groupInterface) {
14309
14357
  }
14310
14358
  const fields = extractInterfaceFields(iface);
14311
14359
  const dbxModelTag = readDbxModelTag(iface.getJsDocs());
14312
- out.push({ name, line: iface.getStartLineNumber(), dbxModelTag, fields });
14360
+ const dbxModelSubObjectTag = readDbxModelSubObjectTag(iface.getJsDocs());
14361
+ out.push({ name, line: iface.getStartLineNumber(), dbxModelTag, dbxModelSubObjectTag, fields });
14313
14362
  }
14314
14363
  return out;
14315
14364
  }
@@ -14379,6 +14428,17 @@ function readDbxModelTag(jsDocs) {
14379
14428
  }
14380
14429
  return result;
14381
14430
  }
14431
+ function readDbxModelSubObjectTag(jsDocs) {
14432
+ let result = false;
14433
+ for (const jsDoc of jsDocs) {
14434
+ for (const tag of jsDoc.getTags()) {
14435
+ if (tag.getTagName() === "dbxModelSubObject") {
14436
+ result = true;
14437
+ }
14438
+ }
14439
+ }
14440
+ return result;
14441
+ }
14382
14442
  function readDbxModelVariableTag(jsDocs) {
14383
14443
  let result;
14384
14444
  for (const jsDoc of jsDocs) {
@@ -15141,7 +15201,7 @@ var RULE_CATALOG = [
15141
15201
  severity: "warning",
15142
15202
  title: "A persisted field's `@dbxModelVariable <longName>` tag value matches the field's short name verbatim, defeating the tag's purpose (the long name should be the field's unabbreviated camelCase variable name)",
15143
15203
  whatItFlags: "A persisted field's `@dbxModelVariable <longName>` tag value matches the field's short name verbatim, defeating the tag's purpose (the long name should be the field's unabbreviated camelCase variable name).",
15144
- whenItApplies: "Every field on a `@dbxModel` interface where `@dbxModelVariable` is present and its value equals the field's name (e.g. field `h` tagged `@dbxModelVariable h`, field `ub` tagged `@dbxModelVariable ub`). The tag must always carry the unabbreviated form so the catalog and decoder surface a meaningful long name.",
15204
+ whenItApplies: "Every field on a `@dbxModel` interface or on a `@dbxModelSubObject`-tagged embedded sub-object interface where `@dbxModelVariable` is present and its value equals the field's name (e.g. field `h` tagged `@dbxModelVariable h`, field `ub` tagged `@dbxModelVariable ub`). The tag must always carry the unabbreviated form so the catalog and decoder surface a meaningful long name.",
15145
15205
  whenItDoesNotApply: "Fields whose name is already the unabbreviated camelCase form (e.g. `name`, `email`, `id`, `at`) \u2014 add those to `dbx-mcp.config.json` `modelValidate.ignoredFieldNames` to silence the warning workspace-wide.",
15146
15206
  canonicalFix: "Replace the tag value with the field's unabbreviated camelCase variable name (e.g. `@dbxModelVariable h` \u2192 `@dbxModelVariable hours`, `@dbxModelVariable ub` \u2192 `@dbxModelVariable usedBudget`). To exempt fields whose long name legitimately equals the short name, add them to `modelValidate.ignoredFieldNames`."
15147
15207
  },
@@ -15161,8 +15221,8 @@ var RULE_CATALOG = [
15161
15221
  severity: "warning",
15162
15222
  title: "A persisted field is missing its `@dbxModelVariable <name>` JSDoc tag",
15163
15223
  whatItFlags: "A persisted field is missing its `@dbxModelVariable <name>` JSDoc tag.",
15164
- whenItApplies: "Every field on a `@dbxModel` interface \u2014 the tag carries the human-readable long name the catalog and decoder use when surfacing the field to operators. The long name is the variable name the field would have unabbreviated, written in camelCase (e.g. `uid` \u2192 `userUid`, `n` \u2192 `name`, `crAt` \u2192 `createdAt`).",
15165
- whenItDoesNotApply: "Fields on embedded sub-object interfaces (no `@dbxModel` parent) and fields the project deliberately leaves untagged (rare).",
15224
+ whenItApplies: "Every field on a `@dbxModel` interface or on a `@dbxModelSubObject`-tagged embedded sub-object interface \u2014 the tag carries the human-readable long name the catalog and decoder use when surfacing the field to operators. The long name is the variable name the field would have unabbreviated, written in camelCase (e.g. `uid` \u2192 `userUid`, `n` \u2192 `name`, `crAt` \u2192 `createdAt`).",
15225
+ whenItDoesNotApply: "Fields on embedded sub-object interfaces that are not tagged with `@dbxModelSubObject` (the tag is opt-in) and fields the project deliberately leaves untagged (rare).",
15166
15226
  canonicalFix: "Append `@dbxModelVariable <longName>` to the field's JSDoc block, where `<longName>` is the field's unabbreviated camelCase variable name."
15167
15227
  },
15168
15228
  {
@@ -15349,6 +15409,32 @@ var RULE_CATALOG = [
15349
15409
  whenItDoesNotApply: "When intentionally module-private.",
15350
15410
  canonicalFix: "Add `export` to the alias."
15351
15411
  },
15412
+ {
15413
+ code: "MODEL_SUBOBJECT_NOT_TAGGED",
15414
+ source: "dbx_model_validate",
15415
+ severity: "warning",
15416
+ title: "An interface is referenced as the first generic type-argument of a sub-object factory call (`firestoreSubObject<T>(...)`, `firestoreObjectArray<T>(...)`, or `firestoreMap<T>(...)`) but its declaration carries neither `@dbxModel` nor `@dbxModelSubObject`. The interface therefore opts out of the workspace's `@dbxModelVariable` long-name conventions even though its fields are persisted to Firestore",
15417
+ whatItFlags: "An interface is referenced as the first generic type-argument of a sub-object factory call (`firestoreSubObject<T>(...)`, `firestoreObjectArray<T>(...)`, or `firestoreMap<T>(...)`) but its declaration carries neither `@dbxModel` nor `@dbxModelSubObject`. The interface therefore opts out of the workspace's `@dbxModelVariable` long-name conventions even though its fields are persisted to Firestore.",
15418
+ whenItApplies: "Any interface declared in the validated source set that is the type-arg of at least one sub-object factory call and lacks both `@dbxModel` and `@dbxModelSubObject`. The warning is emitted once per interface (de-duplicated across all call sites) and anchored at the interface declaration line. Unresolved type-args \u2014 interfaces declared outside the validated source set, generic parameters, inline types \u2014 are silently skipped to avoid false positives.",
15419
+ whenItDoesNotApply: "Interfaces already tagged with `@dbxModel` (top-level models) or `@dbxModelSubObject` (embedded sub-objects), and interfaces referenced only from outside the supplied source set (different package, etc.).",
15420
+ canonicalFix: "Add `@dbxModelSubObject` to the interface's JSDoc block so its fields are subject to `@dbxModelVariable` long-name checks. If the interface is actually a top-level Firestore model, declare a matching `firestoreModelIdentity` and tag it `@dbxModel` instead.",
15421
+ seeAlso: [
15422
+ {
15423
+ kind: "artifact",
15424
+ target: "firestore-model"
15425
+ }
15426
+ ]
15427
+ },
15428
+ {
15429
+ code: "MODEL_SUBOBJECT_TAG_CONFLICT",
15430
+ source: "dbx_model_validate",
15431
+ severity: "error",
15432
+ title: "An interface declaration carries both `@dbxModel` and `@dbxModelSubObject` JSDoc tags. The two tags model mutually exclusive concepts \u2014 `@dbxModel` marks a top-level Firestore model registered in the catalog via `firestoreModelIdentity`, while `@dbxModelSubObject` marks an embedded sub-object interface persisted only as part of a parent model's converter \u2014 so an interface must carry at most one",
15433
+ whatItFlags: "An interface declaration carries both `@dbxModel` and `@dbxModelSubObject` JSDoc tags. The two tags model mutually exclusive concepts \u2014 `@dbxModel` marks a top-level Firestore model registered in the catalog via `firestoreModelIdentity`, while `@dbxModelSubObject` marks an embedded sub-object interface persisted only as part of a parent model's converter \u2014 so an interface must carry at most one.",
15434
+ whenItApplies: "Any interface JSDoc block where both `@dbxModel` and `@dbxModelSubObject` are present.",
15435
+ whenItDoesNotApply: "Interfaces carrying only one of the two tags (or neither).",
15436
+ canonicalFix: "Remove whichever tag does not describe the interface. If the interface has a matching `firestoreModelIdentity` call, keep `@dbxModel`; if it is only embedded inside another model via `firestoreSubObject<T>()`, keep `@dbxModelSubObject`."
15437
+ },
15352
15438
  {
15353
15439
  code: "SUB_MISSING_COLLECTION_FACTORY_FN",
15354
15440
  source: "dbx_model_validate",
@@ -16975,15 +17061,11 @@ function formatSeeAlso(ref) {
16975
17061
  return `Doc: ${ref.target}${tail}`;
16976
17062
  }
16977
17063
 
16978
- // packages/dbx-components-mcp/src/tools/model-validate/types.ts
16979
- var MAX_FIELD_NAME_LENGTH = 5;
16980
- var ROOT_MODEL_ORDER = ["identity", "interface", "rolesType", "documentClass", "converter", "referenceFn", "collectionType", "collectionFn"];
16981
- var SUBCOLLECTION_MODEL_ORDER = ["identity", "interface", "rolesType", "documentClass", "converter", "referenceFn", "collectionType", "collectionFactoryType", "collectionFn", "referenceGroupFn", "collectionGroupType", "collectionGroupFn"];
16982
-
16983
17064
  // packages/dbx-components-mcp/src/tools/model-validate/rules.ts
16984
- function runRules(file, options) {
17065
+ function runRules(file, options, context) {
16985
17066
  const violations = [];
16986
17067
  if (file.models.length === 0) {
17068
+ checkSubObjectFactoryCallSites(file, violations, context);
16987
17069
  return violations;
16988
17070
  }
16989
17071
  checkFileLevel(file, violations);
@@ -16996,6 +17078,8 @@ function runRules(file, options) {
16996
17078
  checkDeclarationOrder(file, model, violations);
16997
17079
  }
16998
17080
  checkFieldNameLengths(file, violations, options);
17081
+ checkSubObjectInterfaceTags(file, violations);
17082
+ checkSubObjectFactoryCallSites(file, violations, context);
16999
17083
  return violations;
17000
17084
  }
17001
17085
  function checkFieldNameLengths(file, violations, options) {
@@ -17035,7 +17119,8 @@ function checkFieldJsDocs(file, violations, options) {
17035
17119
  model: iface.name
17036
17120
  });
17037
17121
  }
17038
- if (iface.dbxModelTag && field.dbxModelVariableTag === void 0) {
17122
+ const fieldRulesApply = iface.dbxModelTag || iface.dbxModelSubObjectTag;
17123
+ if (fieldRulesApply && field.dbxModelVariableTag === void 0) {
17039
17124
  pushViolation(violations, {
17040
17125
  code: "MODEL_FIELD_MISSING_VARIABLE_TAG",
17041
17126
  severity: "warning",
@@ -17045,7 +17130,7 @@ function checkFieldJsDocs(file, violations, options) {
17045
17130
  model: iface.name
17046
17131
  });
17047
17132
  }
17048
- if (iface.dbxModelTag && field.dbxModelVariableTag !== void 0 && field.dbxModelVariableTag === field.name && !ignored?.has(field.name)) {
17133
+ if (fieldRulesApply && field.dbxModelVariableTag !== void 0 && field.dbxModelVariableTag === field.name && !ignored?.has(field.name)) {
17049
17134
  pushViolation(violations, {
17050
17135
  code: "MODEL_FIELD_LONG_NAME_EQUALS_NAME",
17051
17136
  severity: "warning",
@@ -17058,6 +17143,49 @@ function checkFieldJsDocs(file, violations, options) {
17058
17143
  }
17059
17144
  }
17060
17145
  }
17146
+ function checkSubObjectInterfaceTags(file, violations) {
17147
+ for (const iface of file.dataInterfaces) {
17148
+ if (iface.dbxModelTag && iface.dbxModelSubObjectTag) {
17149
+ pushViolation(violations, {
17150
+ code: "MODEL_SUBOBJECT_TAG_CONFLICT",
17151
+ message: `Interface \`${iface.name}\` carries both \`@dbxModel\` and \`@dbxModelSubObject\`. Use only \`@dbxModel\` for top-level Firestore models; use \`@dbxModelSubObject\` only for embedded sub-objects without a \`firestoreModelIdentity\`.`,
17152
+ file: file.name,
17153
+ line: iface.line,
17154
+ model: iface.name
17155
+ });
17156
+ }
17157
+ }
17158
+ }
17159
+ function checkSubObjectFactoryCallSites(file, violations, context) {
17160
+ if (!context) {
17161
+ return;
17162
+ }
17163
+ if (file.subObjectCalls.length === 0) {
17164
+ return;
17165
+ }
17166
+ for (const call of file.subObjectCalls) {
17167
+ const entry = context.interfacesByName.get(call.typeArgName);
17168
+ if (!entry) {
17169
+ continue;
17170
+ }
17171
+ const { iface, file: declFile } = entry;
17172
+ if (iface.dbxModelTag || iface.dbxModelSubObjectTag) {
17173
+ continue;
17174
+ }
17175
+ if (context.emittedSubObjectInterfaces.has(call.typeArgName)) {
17176
+ continue;
17177
+ }
17178
+ context.emittedSubObjectInterfaces.add(call.typeArgName);
17179
+ pushViolation(violations, {
17180
+ code: "MODEL_SUBOBJECT_NOT_TAGGED",
17181
+ severity: "warning",
17182
+ message: `Interface \`${iface.name}\` is referenced by \`${call.factoryName}<${iface.name}>(...)\` at ${file.name}:${call.line} but carries neither \`@dbxModel\` nor \`@dbxModelSubObject\`. Tag the interface with \`@dbxModelSubObject\` so its persisted fields are subject to the same \`@dbxModelVariable\` long-name checks as a top-level model.`,
17183
+ file: declFile,
17184
+ line: iface.line,
17185
+ model: iface.name
17186
+ });
17187
+ }
17188
+ }
17061
17189
  function checkFileLevel(file, violations) {
17062
17190
  const { groupInterface, groupTypes, firstModelLine, models, name } = file;
17063
17191
  if (groupInterface) {
@@ -17759,10 +17887,15 @@ function validateFirebaseModelSources(sources, options) {
17759
17887
  let modelsChecked = 0;
17760
17888
  let errorCount = 0;
17761
17889
  let warningCount = 0;
17890
+ const extracteds = [];
17762
17891
  for (const source of sources) {
17763
17892
  const extracted = extractFile(source);
17893
+ extracteds.push(extracted);
17764
17894
  modelsChecked += extracted.models.length;
17765
- const fileViolations = runRules(extracted, options);
17895
+ }
17896
+ const context = buildCrossFileRuleContext(extracteds);
17897
+ for (const extracted of extracteds) {
17898
+ const fileViolations = runRules(extracted, options, context);
17766
17899
  for (const v of fileViolations) {
17767
17900
  violations.push(v);
17768
17901
  if (v.severity === "error") {
@@ -17781,6 +17914,22 @@ function validateFirebaseModelSources(sources, options) {
17781
17914
  };
17782
17915
  return result;
17783
17916
  }
17917
+ function buildCrossFileRuleContext(extracteds) {
17918
+ const interfacesByName = /* @__PURE__ */ new Map();
17919
+ for (const file of extracteds) {
17920
+ for (const iface of file.dataInterfaces) {
17921
+ if (interfacesByName.has(iface.name)) {
17922
+ continue;
17923
+ }
17924
+ interfacesByName.set(iface.name, { file: file.name, iface });
17925
+ }
17926
+ }
17927
+ const context = {
17928
+ interfacesByName,
17929
+ emittedSubObjectInterfaces: /* @__PURE__ */ new Set()
17930
+ };
17931
+ return context;
17932
+ }
17784
17933
 
17785
17934
  // packages/dbx-components-mcp/src/tools/model-validate.tool.ts
17786
17935
  var DBX_MODEL_VALIDATE_TOOL = {
@@ -26250,10 +26399,26 @@ function collectHandlerBindingsFromValue(options) {
26250
26399
  if (Node19.isIdentifier(callee)) {
26251
26400
  return collectHandlerBindingsFromIdentifier({ name: callee.getText(), sf, index, trustedExternal, resolved, unresolved, visited });
26252
26401
  }
26402
+ return false;
26253
26403
  }
26254
26404
  if (Node19.isIdentifier(inner)) {
26255
26405
  return collectHandlerBindingsFromIdentifier({ name: inner.getText(), sf, index, trustedExternal, resolved, unresolved, visited });
26256
26406
  }
26407
+ if (Node19.isArrayLiteralExpression(inner)) {
26408
+ let any = false;
26409
+ for (const el of inner.getElements()) {
26410
+ if (Node19.isSpreadElement(el)) {
26411
+ if (collectHandlerBindingsFromValue({ node: el.getExpression(), sf, index, trustedExternal, resolved, unresolved, visited })) {
26412
+ any = true;
26413
+ }
26414
+ continue;
26415
+ }
26416
+ if (collectHandlerBindingsFromValue({ node: el, sf, index, trustedExternal, resolved, unresolved, visited })) {
26417
+ any = true;
26418
+ }
26419
+ }
26420
+ return any;
26421
+ }
26257
26422
  return false;
26258
26423
  }
26259
26424
  function collectHandlerBindingsFromIdentifier(options) {
@@ -479,7 +479,7 @@
479
479
  "severity": "warning",
480
480
  "title": "A persisted field's `@dbxModelVariable <longName>` tag value matches the field's short name verbatim, defeating the tag's purpose (the long name should be the field's unabbreviated camelCase variable name)",
481
481
  "whatItFlags": "A persisted field's `@dbxModelVariable <longName>` tag value matches the field's short name verbatim, defeating the tag's purpose (the long name should be the field's unabbreviated camelCase variable name).",
482
- "whenItApplies": "Every field on a `@dbxModel` interface where `@dbxModelVariable` is present and its value equals the field's name (e.g. field `h` tagged `@dbxModelVariable h`, field `ub` tagged `@dbxModelVariable ub`). The tag must always carry the unabbreviated form so the catalog and decoder surface a meaningful long name.",
482
+ "whenItApplies": "Every field on a `@dbxModel` interface or on a `@dbxModelSubObject`-tagged embedded sub-object interface where `@dbxModelVariable` is present and its value equals the field's name (e.g. field `h` tagged `@dbxModelVariable h`, field `ub` tagged `@dbxModelVariable ub`). The tag must always carry the unabbreviated form so the catalog and decoder surface a meaningful long name.",
483
483
  "whenItDoesNotApply": "Fields whose name is already the unabbreviated camelCase form (e.g. `name`, `email`, `id`, `at`) — add those to `dbx-mcp.config.json` `modelValidate.ignoredFieldNames` to silence the warning workspace-wide.",
484
484
  "canonicalFix": "Replace the tag value with the field's unabbreviated camelCase variable name (e.g. `@dbxModelVariable h` → `@dbxModelVariable hours`, `@dbxModelVariable ub` → `@dbxModelVariable usedBudget`). To exempt fields whose long name legitimately equals the short name, add them to `modelValidate.ignoredFieldNames`."
485
485
  },
@@ -499,8 +499,8 @@
499
499
  "severity": "warning",
500
500
  "title": "A persisted field is missing its `@dbxModelVariable <name>` JSDoc tag",
501
501
  "whatItFlags": "A persisted field is missing its `@dbxModelVariable <name>` JSDoc tag.",
502
- "whenItApplies": "Every field on a `@dbxModel` interface — the tag carries the human-readable long name the catalog and decoder use when surfacing the field to operators. The long name is the variable name the field would have unabbreviated, written in camelCase (e.g. `uid` → `userUid`, `n` → `name`, `crAt` → `createdAt`).",
503
- "whenItDoesNotApply": "Fields on embedded sub-object interfaces (no `@dbxModel` parent) and fields the project deliberately leaves untagged (rare).",
502
+ "whenItApplies": "Every field on a `@dbxModel` interface or on a `@dbxModelSubObject`-tagged embedded sub-object interface — the tag carries the human-readable long name the catalog and decoder use when surfacing the field to operators. The long name is the variable name the field would have unabbreviated, written in camelCase (e.g. `uid` → `userUid`, `n` → `name`, `crAt` → `createdAt`).",
503
+ "whenItDoesNotApply": "Fields on embedded sub-object interfaces that are not tagged with `@dbxModelSubObject` (the tag is opt-in) and fields the project deliberately leaves untagged (rare).",
504
504
  "canonicalFix": "Append `@dbxModelVariable <longName>` to the field's JSDoc block, where `<longName>` is the field's unabbreviated camelCase variable name."
505
505
  },
506
506
  {
@@ -687,6 +687,32 @@
687
687
  "whenItDoesNotApply": "When intentionally module-private.",
688
688
  "canonicalFix": "Add `export` to the alias."
689
689
  },
690
+ {
691
+ "code": "MODEL_SUBOBJECT_NOT_TAGGED",
692
+ "source": "dbx_model_validate",
693
+ "severity": "warning",
694
+ "title": "An interface is referenced as the first generic type-argument of a sub-object factory call (`firestoreSubObject<T>(...)`, `firestoreObjectArray<T>(...)`, or `firestoreMap<T>(...)`) but its declaration carries neither `@dbxModel` nor `@dbxModelSubObject`. The interface therefore opts out of the workspace's `@dbxModelVariable` long-name conventions even though its fields are persisted to Firestore",
695
+ "whatItFlags": "An interface is referenced as the first generic type-argument of a sub-object factory call (`firestoreSubObject<T>(...)`, `firestoreObjectArray<T>(...)`, or `firestoreMap<T>(...)`) but its declaration carries neither `@dbxModel` nor `@dbxModelSubObject`. The interface therefore opts out of the workspace's `@dbxModelVariable` long-name conventions even though its fields are persisted to Firestore.",
696
+ "whenItApplies": "Any interface declared in the validated source set that is the type-arg of at least one sub-object factory call and lacks both `@dbxModel` and `@dbxModelSubObject`. The warning is emitted once per interface (de-duplicated across all call sites) and anchored at the interface declaration line. Unresolved type-args — interfaces declared outside the validated source set, generic parameters, inline types — are silently skipped to avoid false positives.",
697
+ "whenItDoesNotApply": "Interfaces already tagged with `@dbxModel` (top-level models) or `@dbxModelSubObject` (embedded sub-objects), and interfaces referenced only from outside the supplied source set (different package, etc.).",
698
+ "canonicalFix": "Add `@dbxModelSubObject` to the interface's JSDoc block so its fields are subject to `@dbxModelVariable` long-name checks. If the interface is actually a top-level Firestore model, declare a matching `firestoreModelIdentity` and tag it `@dbxModel` instead.",
699
+ "seeAlso": [
700
+ {
701
+ "kind": "artifact",
702
+ "target": "firestore-model"
703
+ }
704
+ ]
705
+ },
706
+ {
707
+ "code": "MODEL_SUBOBJECT_TAG_CONFLICT",
708
+ "source": "dbx_model_validate",
709
+ "severity": "error",
710
+ "title": "An interface declaration carries both `@dbxModel` and `@dbxModelSubObject` JSDoc tags. The two tags model mutually exclusive concepts — `@dbxModel` marks a top-level Firestore model registered in the catalog via `firestoreModelIdentity`, while `@dbxModelSubObject` marks an embedded sub-object interface persisted only as part of a parent model's converter — so an interface must carry at most one",
711
+ "whatItFlags": "An interface declaration carries both `@dbxModel` and `@dbxModelSubObject` JSDoc tags. The two tags model mutually exclusive concepts — `@dbxModel` marks a top-level Firestore model registered in the catalog via `firestoreModelIdentity`, while `@dbxModelSubObject` marks an embedded sub-object interface persisted only as part of a parent model's converter — so an interface must carry at most one.",
712
+ "whenItApplies": "Any interface JSDoc block where both `@dbxModel` and `@dbxModelSubObject` are present.",
713
+ "whenItDoesNotApply": "Interfaces carrying only one of the two tags (or neither).",
714
+ "canonicalFix": "Remove whichever tag does not describe the interface. If the interface has a matching `firestoreModelIdentity` call, keep `@dbxModel`; if it is only embedded inside another model via `firestoreSubObject<T>()`, keep `@dbxModelSubObject`."
715
+ },
690
716
  {
691
717
  "code": "SUB_MISSING_COLLECTION_FACTORY_FN",
692
718
  "source": "dbx_model_validate",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-components-mcp",
3
- "version": "13.11.6",
3
+ "version": "13.11.7",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "dbx-components-mcp": "dbx-components-mcp.js"
@@ -15,19 +15,19 @@
15
15
  "@angular/core": "21.2.11",
16
16
  "@angular/forms": "21.2.11",
17
17
  "@angular/material": "^21.2.9",
18
- "@dereekb/dbx-cli": "13.11.6",
19
- "@dereekb/dbx-core": "13.11.6",
20
- "@dereekb/dbx-firebase": "13.11.6",
21
- "@dereekb/dbx-form": "13.11.6",
22
- "@dereekb/dbx-web": "13.11.6",
23
- "@dereekb/firebase": "13.11.6",
24
- "@dereekb/firebase-server": "13.11.6",
25
- "@dereekb/rxjs": "13.11.6",
26
- "@dereekb/util": "13.11.6",
18
+ "@dereekb/dbx-cli": "13.11.7",
19
+ "@dereekb/dbx-core": "13.11.7",
20
+ "@dereekb/dbx-firebase": "13.11.7",
21
+ "@dereekb/dbx-form": "13.11.7",
22
+ "@dereekb/dbx-web": "13.11.7",
23
+ "@dereekb/firebase": "13.11.7",
24
+ "@dereekb/firebase-server": "13.11.7",
25
+ "@dereekb/rxjs": "13.11.7",
26
+ "@dereekb/util": "13.11.7",
27
27
  "@uirouter/angular": "21.0.0",
28
28
  "rxjs": "^7.8.2"
29
29
  },
30
30
  "devDependencies": {
31
- "@dereekb/model": "13.11.6"
31
+ "@dereekb/model": "13.11.7"
32
32
  }
33
33
  }