@dbsp/core 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2601,7 +2601,8 @@ function processInclude(include, sourceTable, model, state, opts, intentPath, de
2601
2601
  return;
2602
2602
  }
2603
2603
  const includePath = `${sourceTable}.${relation.name}`;
2604
- if (state.visitedIncludes.has(includePath)) {
2604
+ const isSelfReferentialRelation = relation.source === relation.target;
2605
+ if (!isSelfReferentialRelation && state.visitedIncludes.has(includePath)) {
2605
2606
  state.warnings.push({
2606
2607
  code: "CIRCULAR_INCLUDE",
2607
2608
  message: `Circular include detected: ${includePath}`,
@@ -2609,12 +2610,12 @@ function processInclude(include, sourceTable, model, state, opts, intentPath, de
2609
2610
  });
2610
2611
  return;
2611
2612
  }
2612
- state.visitedIncludes.add(includePath);
2613
+ if (!isSelfReferentialRelation) state.visitedIncludes.add(includePath);
2613
2614
  const relationPath = `${sourceTable}.${relation.name}`;
2614
2615
  const paths = state.relationAccessCounts.get(relationPath) ?? [];
2615
2616
  paths.push(intentPath);
2616
2617
  state.relationAccessCounts.set(relationPath, paths);
2617
- const isRecursiveInclude = (!!include.recursive || !!relation.recursive) && relation.source === relation.target;
2618
+ const isRecursiveInclude = (!!include.recursive || !!relation.recursive) && isSelfReferentialRelation;
2618
2619
  let includeStrategy;
2619
2620
  if (isRecursiveInclude) {
2620
2621
  if (opts.dialectCapabilities?.supportsRecursiveCTE === false) {
@@ -2749,7 +2750,7 @@ function processInclude(include, sourceTable, model, state, opts, intentPath, de
2749
2750
  }
2750
2751
  }
2751
2752
  }
2752
- state.visitedIncludes.delete(includePath);
2753
+ if (!isSelfReferentialRelation) state.visitedIncludes.delete(includePath);
2753
2754
  }
2754
2755
  function disambiguateRelation(relationName, sourceTable, model, state, opts, _intentPath, viaHint) {
2755
2756
  const directRelation = model.getRelation(`${sourceTable}.${relationName}`);
@@ -4105,6 +4106,12 @@ function coalesce(fields, as) {
4105
4106
  };
4106
4107
  }
4107
4108
  function raw(sqlFragment, as) {
4109
+ if (typeof as !== "string" || as.trim().length === 0) {
4110
+ throw new InvalidOperationError(
4111
+ "raw",
4112
+ "raw() requires an alias as second argument, e.g. raw('COUNT(*)', 'count')"
4113
+ );
4114
+ }
4108
4115
  validateIdentifier(as, "column");
4109
4116
  return {
4110
4117
  __expr: true,
@@ -6126,53 +6133,131 @@ function negotiateFeatures(model, capabilities, behavior = "warning", checkers =
6126
6133
  }
6127
6134
 
6128
6135
  // src/dx/nql.ts
6129
- import { compile as nqlCompile } from "@dbsp/nql";
6130
- function toNqlLiteral(value, index) {
6131
- if (value === null) {
6132
- return "null";
6136
+ import {
6137
+ NqlLexer,
6138
+ compile as nqlCompile
6139
+ } from "@dbsp/nql";
6140
+ import { NQL_INTERNAL_COMPILER_OPTIONS } from "@dbsp/types/internal";
6141
+ var NQL_RAW_FRAGMENT = /* @__PURE__ */ Symbol("NqlRawFragment");
6142
+ function nqlRaw(fragment) {
6143
+ if (typeof fragment !== "string") {
6144
+ throw new TypeError("nqlRaw() expects a string fragment");
6145
+ }
6146
+ const raw2 = { fragment };
6147
+ Object.defineProperty(raw2, NQL_RAW_FRAGMENT, {
6148
+ value: true,
6149
+ enumerable: false
6150
+ });
6151
+ return Object.freeze(raw2);
6152
+ }
6153
+ function isNqlRawFragment(value) {
6154
+ if (typeof value !== "object" || value === null) {
6155
+ return false;
6133
6156
  }
6134
- switch (typeof value) {
6135
- case "boolean":
6136
- return value ? "true" : "false";
6137
- case "number": {
6138
- if (!Number.isFinite(value)) {
6139
- throw new Error(
6140
- `nql\`...\`: cannot interpolate non-finite number (${value}) at position ${index}. Only finite numbers are supported. For dynamic NQL structure use the builder API (orm.select()). See issue #134.`
6141
- );
6142
- }
6143
- const s = value < 0 ? `-${Math.abs(value)}` : String(value);
6144
- if (/[eE]/.test(s)) {
6145
- throw new Error(
6146
- `nql\`...\`: number ${value} at position ${index} has no exact NQL numeric literal form (exponential notation). Convert it yourself or use the builder API (orm.select()). See issue #134.`
6147
- );
6148
- }
6149
- return s;
6157
+ if (!Object.hasOwn(value, NQL_RAW_FRAGMENT)) {
6158
+ return false;
6159
+ }
6160
+ return value[NQL_RAW_FRAGMENT] === true;
6161
+ }
6162
+ function isInsideGeneratedRange(start, end, generatedRanges) {
6163
+ return generatedRanges.some(
6164
+ (range) => start >= range.start && end <= range.end
6165
+ );
6166
+ }
6167
+ function findOverlappingGeneratedRange(start, end, generatedRanges) {
6168
+ return generatedRanges.find(
6169
+ (range) => start < range.end && end > range.start
6170
+ );
6171
+ }
6172
+ function findExactGeneratedRange(name, start, end, generatedRanges) {
6173
+ return generatedRanges.find(
6174
+ (range) => range.name === name && range.start === start && range.end === end
6175
+ );
6176
+ }
6177
+ function generatedParamLexError(name) {
6178
+ return `Generated NQL parameter :${name} was not recognized as exactly one NamedParam token after raw-fragment assembly; check adjacent nqlRaw() fragments for quotes, comments, or identifier text that can swallow the placeholder.`;
6179
+ }
6180
+ function findInternalParamSourceError(query, generatedRanges) {
6181
+ const lexResult = NqlLexer.tokenize(query);
6182
+ const generatedTokenCounts = new Map(
6183
+ generatedRanges.map((range) => [range.name, 0])
6184
+ );
6185
+ for (const token of lexResult.tokens) {
6186
+ if (token.tokenType.name !== "NamedParam") {
6187
+ continue;
6150
6188
  }
6151
- case "string": {
6152
- if (value.includes("\n") || value.includes("\r")) {
6153
- throw new Error(
6154
- `nql\`...\`: cannot interpolate a string containing a newline at position ${index}. NQL string literals do not support raw newline characters. For dynamic NQL structure use the builder API (orm.select()). See issue #134.`
6155
- );
6156
- }
6157
- const escaped = value.replaceAll("'", "''");
6158
- return `'${escaped}'`;
6189
+ const name = token.image.slice(1);
6190
+ if (!name.startsWith("__p")) {
6191
+ continue;
6159
6192
  }
6160
- default: {
6161
- const typeName = value === void 0 ? "undefined" : typeof value;
6162
- throw new Error(
6163
- `nql\`...\`: cannot interpolate value of type "${typeName}" at position ${index}. Only string, number, boolean, and null are supported; for dynamic NQL fragments use the builder API (orm.select()). See issue #134.`
6193
+ const start = token.startOffset;
6194
+ const end = (token.endOffset ?? start + token.image.length - 1) + 1;
6195
+ const exactGeneratedRange = findExactGeneratedRange(
6196
+ name,
6197
+ start,
6198
+ end,
6199
+ generatedRanges
6200
+ );
6201
+ if (exactGeneratedRange) {
6202
+ generatedTokenCounts.set(
6203
+ exactGeneratedRange.name,
6204
+ (generatedTokenCounts.get(exactGeneratedRange.name) ?? 0) + 1
6164
6205
  );
6206
+ continue;
6207
+ }
6208
+ const overlappingGeneratedRange = findOverlappingGeneratedRange(
6209
+ start,
6210
+ end,
6211
+ generatedRanges
6212
+ );
6213
+ if (overlappingGeneratedRange) {
6214
+ return generatedParamLexError(overlappingGeneratedRange.name);
6165
6215
  }
6216
+ if (!isInsideGeneratedRange(start, end, generatedRanges)) {
6217
+ return `Reserved NQL parameter namespace "__p" cannot be referenced by user source (${token.image}).`;
6218
+ }
6219
+ }
6220
+ for (const range of generatedRanges) {
6221
+ if (generatedTokenCounts.get(range.name) !== 1) {
6222
+ return generatedParamLexError(range.name);
6223
+ }
6224
+ }
6225
+ return void 0;
6226
+ }
6227
+ function assembleNqlTemplate(strings, values) {
6228
+ const params = /* @__PURE__ */ Object.create(null);
6229
+ const generatedRanges = [];
6230
+ let query = strings[0] ?? "";
6231
+ let boundIndex = 0;
6232
+ for (let i = 0; i < values.length; i++) {
6233
+ const value = values[i];
6234
+ if (isNqlRawFragment(value)) {
6235
+ query += value.fragment;
6236
+ } else {
6237
+ const name = `__p${boundIndex++}`;
6238
+ const placeholder = `:${name}`;
6239
+ const start = query.length;
6240
+ query += placeholder;
6241
+ generatedRanges.push({ name, start, end: start + placeholder.length });
6242
+ params[name] = value;
6243
+ }
6244
+ query += strings[i + 1] ?? "";
6166
6245
  }
6246
+ return {
6247
+ query,
6248
+ params,
6249
+ hasBoundParams: boundIndex > 0,
6250
+ sourceError: findInternalParamSourceError(query, generatedRanges)
6251
+ };
6167
6252
  }
6168
6253
  function createNqlTag(schemaDefinition, model, adapter, schemaName) {
6169
6254
  return function nql(strings, ...values) {
6170
- let query = strings[0] ?? "";
6171
- for (let i = 0; i < values.length; i++) {
6172
- query += toNqlLiteral(values[i], i) + (strings[i + 1] ?? "");
6173
- }
6255
+ const assembled = assembleNqlTemplate(strings, values);
6174
6256
  return new NqlBuilderImpl(
6175
- query,
6257
+ assembled.query,
6258
+ assembled.params,
6259
+ assembled.hasBoundParams,
6260
+ assembled.sourceError,
6176
6261
  schemaDefinition,
6177
6262
  model,
6178
6263
  adapter,
@@ -6183,13 +6268,19 @@ function createNqlTag(schemaDefinition, model, adapter, schemaName) {
6183
6268
  var NqlBuilderImpl = class {
6184
6269
  _intent;
6185
6270
  query;
6271
+ params;
6272
+ hasBoundParams;
6273
+ sourceError;
6186
6274
  schemaDefinition;
6187
6275
  model;
6188
6276
  // biome-ignore lint/correctness/noUnusedPrivateClassMembers: Reserved for future schema-scoping support
6189
6277
  _schemaName;
6190
6278
  adapter;
6191
- constructor(query, schemaDefinition, model, adapter, schemaName) {
6279
+ constructor(query, params, hasBoundParams, sourceError, schemaDefinition, model, adapter, schemaName) {
6192
6280
  this.query = query;
6281
+ this.params = params;
6282
+ this.hasBoundParams = hasBoundParams;
6283
+ this.sourceError = sourceError;
6193
6284
  this.schemaDefinition = schemaDefinition;
6194
6285
  this.model = model;
6195
6286
  this.adapter = adapter;
@@ -6199,7 +6290,14 @@ var NqlBuilderImpl = class {
6199
6290
  if (this._intent) {
6200
6291
  return this._intent;
6201
6292
  }
6202
- const compilerOptions = extractPseudoColumnKeywords(this.model);
6293
+ if (this.sourceError !== void 0) {
6294
+ throw new Error(`NQL compilation failed: ${this.sourceError}`);
6295
+ }
6296
+ const compilerOptions = {
6297
+ ...extractPseudoColumnKeywords(this.model) ?? {},
6298
+ params: this.params,
6299
+ [NQL_INTERNAL_COMPILER_OPTIONS]: { allowInternalParams: true }
6300
+ };
6203
6301
  const result = nqlCompile(
6204
6302
  this.query,
6205
6303
  this.schemaDefinition,
@@ -6208,7 +6306,8 @@ var NqlBuilderImpl = class {
6208
6306
  );
6209
6307
  if (!result.success) {
6210
6308
  const errors = result.errors?.map((e) => e.message).join(", ") ?? "Unknown error";
6211
- throw new Error(`NQL compilation failed: ${errors}`);
6309
+ const rawHint = this.hasBoundParams ? " If an interpolation was intended as NQL structure, wrap a trusted fragment with nqlRaw()." : "";
6310
+ throw new Error(`NQL compilation failed: ${errors}${rawHint}`);
6212
6311
  }
6213
6312
  if (result.ast?.mutation && !result.ast?.query) {
6214
6313
  throw new Error(
@@ -6224,12 +6323,15 @@ var NqlBuilderImpl = class {
6224
6323
  toIntentIR() {
6225
6324
  return this.compile();
6226
6325
  }
6227
- plan() {
6326
+ planInternal() {
6228
6327
  const intent = this.compile();
6229
6328
  return plan(intent, this.model);
6230
6329
  }
6330
+ plan() {
6331
+ return this.planInternal();
6332
+ }
6231
6333
  dump(meta) {
6232
- const planReport = this.plan();
6334
+ const planReport = this.planInternal();
6233
6335
  if (!this.adapter) {
6234
6336
  return {
6235
6337
  plan: planReport,
@@ -6272,7 +6374,7 @@ var NqlBuilderImpl = class {
6272
6374
  "Cannot execute query: no adapter configured. Pass an adapter to createOrm() or use .toIntentIR() / .plan() for debugging."
6273
6375
  );
6274
6376
  }
6275
- const planReport = this.plan();
6377
+ const planReport = this.planInternal();
6276
6378
  const compiled = this.adapter.compile(planReport);
6277
6379
  return this.adapter.execute(compiled);
6278
6380
  }
@@ -6661,8 +6763,152 @@ function hydrateJsonAggIncludes(results, planReport) {
6661
6763
  }
6662
6764
  }
6663
6765
 
6766
+ // src/dx/relation-paths.ts
6767
+ function nonEmptyString(value) {
6768
+ return typeof value === "string" && value.length > 0 ? value : void 0;
6769
+ }
6770
+ function includeNode(value) {
6771
+ return value !== null && typeof value === "object" ? value : void 0;
6772
+ }
6773
+ function parseIntentPathIndexes(intentPath) {
6774
+ const indexes = [];
6775
+ const indexPattern = /include\[(\d+)\]/g;
6776
+ let execResult = indexPattern.exec(intentPath);
6777
+ while (execResult !== null) {
6778
+ const rawIndex = execResult[1];
6779
+ if (rawIndex === void 0) break;
6780
+ indexes.push(parseInt(rawIndex, 10));
6781
+ execResult = indexPattern.exec(intentPath);
6782
+ }
6783
+ return indexes;
6784
+ }
6785
+ function deriveRelationPathFromIntentPath(includes, intentPath, fallbackRelation) {
6786
+ if (includes && intentPath) {
6787
+ let current = includes;
6788
+ const path = [];
6789
+ for (const index of parseIntentPathIndexes(intentPath)) {
6790
+ const item = includeNode(current[index]);
6791
+ if (!item) break;
6792
+ const segment = nonEmptyString(item.via) ?? nonEmptyString(item.relation);
6793
+ if (!segment) break;
6794
+ path.push(segment);
6795
+ current = Array.isArray(item.include) ? item.include : [];
6796
+ }
6797
+ if (path.length > 0) return path.join(".");
6798
+ }
6799
+ return nonEmptyString(fallbackRelation);
6800
+ }
6801
+ function countDistinctRelationPathsByName(usages) {
6802
+ const pathsByRelationName = /* @__PURE__ */ new Map();
6803
+ for (const usage of usages) {
6804
+ const relationName = nonEmptyString(usage.relationName);
6805
+ if (!relationName) continue;
6806
+ const relationPath = nonEmptyString(usage.relationPath) ?? relationName;
6807
+ const paths = pathsByRelationName.get(relationName) ?? /* @__PURE__ */ new Set();
6808
+ paths.add(relationPath);
6809
+ pathsByRelationName.set(relationName, paths);
6810
+ }
6811
+ const counts = /* @__PURE__ */ new Map();
6812
+ for (const [relationName, paths] of pathsByRelationName) {
6813
+ counts.set(relationName, paths.size);
6814
+ }
6815
+ return counts;
6816
+ }
6817
+
6664
6818
  // src/dx/result-hydrator.ts
6665
6819
  var COMPOSITE_KEY_SEP = "\0";
6820
+ function stringProp(source, key) {
6821
+ const value = source?.[key];
6822
+ return typeof value === "string" && value.length > 0 ? value : void 0;
6823
+ }
6824
+ function resolveJoinIncludePath(planReport, decision) {
6825
+ const context = decision.context;
6826
+ const decisionRecord = decision;
6827
+ const contextRecord = context;
6828
+ const explicitRelationPath = stringProp(decisionRecord, "relationPath") ?? stringProp(contextRecord, "relationPath");
6829
+ if (explicitRelationPath) return explicitRelationPath;
6830
+ const fallbackRelation = stringProp(contextRecord, "relation") ?? stringProp(contextRecord, "includeAlias") ?? stringProp(decisionRecord, "relationName");
6831
+ const intentPath = stringProp(contextRecord, "intentPath");
6832
+ return deriveRelationPathFromIntentPath(
6833
+ Array.isArray(planReport.intent?.include) ? planReport.intent.include : void 0,
6834
+ intentPath,
6835
+ fallbackRelation
6836
+ );
6837
+ }
6838
+ function lastRelationSegment(path) {
6839
+ const segments = path.split(".");
6840
+ return segments[segments.length - 1] ?? path;
6841
+ }
6842
+ function resolveJoinRelationName(decision, relationPath) {
6843
+ const context = decision.context;
6844
+ const decisionRecord = decision;
6845
+ return stringProp(context, "relation") ?? stringProp(context, "includeAlias") ?? stringProp(decisionRecord, "relationName") ?? lastRelationSegment(relationPath);
6846
+ }
6847
+ function collectJoinHydrationInfos(planReport) {
6848
+ const candidates = [];
6849
+ for (const decision of planReport.decisions) {
6850
+ if (decision.type !== "include-strategy" || decision.choice !== "join") {
6851
+ continue;
6852
+ }
6853
+ const relationPath = resolveJoinIncludePath(planReport, decision);
6854
+ if (!relationPath) continue;
6855
+ const relationName = resolveJoinRelationName(decision, relationPath);
6856
+ const decisionRecord = decision;
6857
+ const contextRecord = decision.context;
6858
+ const hydrationPrefix = stringProp(decisionRecord, "hydrationPrefix") ?? stringProp(contextRecord, "hydrationPrefix");
6859
+ candidates.push({
6860
+ relationName,
6861
+ relationPath,
6862
+ ...hydrationPrefix && { hydrationPrefix }
6863
+ });
6864
+ }
6865
+ const pathCountsByRelationName = countDistinctRelationPathsByName(candidates);
6866
+ const infosByPath = /* @__PURE__ */ new Map();
6867
+ for (const candidate of candidates) {
6868
+ if (infosByPath.has(candidate.relationPath)) continue;
6869
+ const usesFullPath = (pathCountsByRelationName.get(candidate.relationName) ?? 0) > 1;
6870
+ const keyPrefix = candidate.hydrationPrefix ?? (usesFullPath ? candidate.relationPath : candidate.relationName);
6871
+ if (!keyPrefix) continue;
6872
+ infosByPath.set(candidate.relationPath, {
6873
+ relationPath: candidate.relationPath,
6874
+ segments: candidate.relationPath.split("."),
6875
+ keyPrefix,
6876
+ keyPrefixWithDot: `${keyPrefix}.`
6877
+ });
6878
+ }
6879
+ return [...infosByPath.values()];
6880
+ }
6881
+ function findLongestJoinInfo(key, infos) {
6882
+ let best;
6883
+ for (const info of infos) {
6884
+ if (!key.startsWith(info.keyPrefixWithDot)) continue;
6885
+ if (!best || info.keyPrefixWithDot.length > best.keyPrefixWithDot.length) {
6886
+ best = info;
6887
+ }
6888
+ }
6889
+ return best;
6890
+ }
6891
+ function assignNestedValue(target, path, value) {
6892
+ let cursor = target;
6893
+ for (let i = 0; i < path.length - 1; i++) {
6894
+ const segment = path[i];
6895
+ if (!segment) return;
6896
+ const existing = cursor[segment];
6897
+ if (existing === null) {
6898
+ return;
6899
+ } else if (typeof existing !== "object" || Array.isArray(existing)) {
6900
+ cursor[segment] = {};
6901
+ }
6902
+ cursor = cursor[segment];
6903
+ }
6904
+ const leaf = path[path.length - 1];
6905
+ if (leaf) cursor[leaf] = value;
6906
+ }
6907
+ function assignColumnValue(target, columnPath, value) {
6908
+ const segments = columnPath.split(".").filter(Boolean);
6909
+ if (segments.length === 0) return;
6910
+ assignNestedValue(target, segments, value);
6911
+ }
6666
6912
  var ResultHydrator = class {
6667
6913
  model;
6668
6914
  from;
@@ -6739,51 +6985,42 @@ var ResultHydrator = class {
6739
6985
  * E2E-004: JOIN strategy for to-one relations returns columns like "author.id", "author.name".
6740
6986
  */
6741
6987
  hydrateJoinIncludes(results, planReport) {
6742
- const joinDecisions = planReport.decisions.filter(
6743
- (d) => d.type === "include-strategy" && d.choice === "join"
6988
+ const joinInfos = collectJoinHydrationInfos(planReport);
6989
+ if (joinInfos.length === 0) return;
6990
+ const infosByDepth = [...joinInfos].sort(
6991
+ (a, b) => a.segments.length - b.segments.length
6744
6992
  );
6745
- if (joinDecisions.length === 0) {
6746
- return;
6747
- }
6748
- const joinInfos = joinDecisions.map((d) => d.context?.relation).filter((r) => typeof r === "string").map((relation) => ({ relation, prefix: `${relation}.` }));
6749
- if (joinInfos.length === 0) {
6750
- return;
6751
- }
6752
- const joinInfosWithCache = joinInfos.map((info) => ({
6753
- ...info,
6754
- cachedKeys: null
6755
- }));
6756
- if (results.length > 0) {
6757
- const firstRow = results.find(
6758
- (r) => typeof r === "object" && r !== null
6759
- );
6760
- if (firstRow) {
6761
- const firstRecord = firstRow;
6762
- const allKeys = Object.keys(firstRecord);
6763
- for (const info of joinInfosWithCache) {
6764
- info.cachedKeys = allKeys.filter((k) => k.startsWith(info.prefix));
6765
- }
6766
- }
6767
- }
6768
6993
  for (const row of results) {
6769
6994
  if (typeof row !== "object" || row === null) {
6770
6995
  continue;
6771
6996
  }
6772
6997
  const record2 = row;
6773
- for (const info of joinInfosWithCache) {
6774
- const keys = info.cachedKeys && info.cachedKeys.length > 0 ? info.cachedKeys : Object.keys(record2).filter((k) => k.startsWith(info.prefix));
6775
- if (keys.length === 0) continue;
6776
- const nestedObj = {};
6777
- let allNull = true;
6778
- for (const key of keys) {
6779
- const val = record2[key];
6780
- if (val !== void 0) {
6781
- nestedObj[key.slice(info.prefix.length)] = val;
6782
- delete record2[key];
6783
- if (val !== null) allNull = false;
6784
- }
6998
+ const valuesByPath = /* @__PURE__ */ new Map();
6999
+ for (const key of Object.keys(record2)) {
7000
+ const info = findLongestJoinInfo(key, joinInfos);
7001
+ if (!info) continue;
7002
+ const value = record2[key];
7003
+ let entry = valuesByPath.get(info.relationPath);
7004
+ if (!entry) {
7005
+ entry = { values: {}, allNull: true };
7006
+ valuesByPath.set(info.relationPath, entry);
6785
7007
  }
6786
- record2[info.relation] = allNull ? null : nestedObj;
7008
+ assignColumnValue(
7009
+ entry.values,
7010
+ key.slice(info.keyPrefixWithDot.length),
7011
+ value
7012
+ );
7013
+ if (value !== null && value !== void 0) entry.allNull = false;
7014
+ delete record2[key];
7015
+ }
7016
+ for (const info of infosByDepth) {
7017
+ const entry = valuesByPath.get(info.relationPath);
7018
+ if (!entry) continue;
7019
+ assignNestedValue(
7020
+ record2,
7021
+ info.segments,
7022
+ entry.allNull ? null : entry.values
7023
+ );
6787
7024
  }
6788
7025
  }
6789
7026
  }
@@ -11474,6 +11711,7 @@ export {
11474
11711
  composeBeforeMutationHooks,
11475
11712
  composeBeforeQueryHooks,
11476
11713
  composeOnErrorHooks,
11714
+ countDistinctRelationPathsByName,
11477
11715
  createDialectCapabilities,
11478
11716
  createHookManager,
11479
11717
  createNqlTag,
@@ -11486,6 +11724,7 @@ export {
11486
11724
  defineModel,
11487
11725
  defineSchema,
11488
11726
  denseRank,
11727
+ deriveRelationPathFromIntentPath,
11489
11728
  detectForeignKeys,
11490
11729
  detectManyToMany,
11491
11730
  distinct,
@@ -11588,6 +11827,7 @@ export {
11588
11827
  normalizeSchema,
11589
11828
  not,
11590
11829
  notExists,
11830
+ nqlRaw,
11591
11831
  objectToWhereIntent,
11592
11832
  op,
11593
11833
  or,
@@ -11642,6 +11882,7 @@ export {
11642
11882
  validateAssertionBlocks,
11643
11883
  validateRecursiveInclude,
11644
11884
  validateRecursiveShape,
11885
+ validateTypeName,
11645
11886
  wAvg,
11646
11887
  wCount,
11647
11888
  wMax,