@atscript/moost-db 0.1.63 → 0.1.64

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.cjs CHANGED
@@ -991,14 +991,27 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
991
991
  _gates;
992
992
  _preferredIdSet;
993
993
  _overlayIsNoOp;
994
+ /** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
995
+ _quantityRefByPath;
994
996
  constructor(readable, app) {
995
997
  super(readable.type, readable.tableName, app, readable.isView ? "view" : "table");
996
998
  this.readable = readable;
997
999
  this._gates = this._buildGates();
998
1000
  this._preferredIdSet = new Set(readable.preferredId ?? []);
1001
+ this._quantityRefByPath = this._collectQuantityRefs();
999
1002
  const defaultOverlay = AsReadableController.prototype.applyMetaOverlay;
1000
1003
  this._overlayIsNoOp = this.applyMetaOverlay === defaultOverlay;
1001
1004
  }
1005
+ _collectQuantityRefs() {
1006
+ const out = /* @__PURE__ */ new Map();
1007
+ if (!this.readable.flatMap) return out;
1008
+ for (const [path, entry] of this.readable.flatMap) {
1009
+ const meta = entry?.metadata;
1010
+ const ref = meta?.get("db.amount.currency.ref") ?? meta?.get("db.unit.ref");
1011
+ if (ref) out.set(path, ref);
1012
+ }
1013
+ return out;
1014
+ }
1002
1015
  _buildGates() {
1003
1016
  const meta = this.readable.type.metadata;
1004
1017
  const gates = {};
@@ -1066,9 +1079,11 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
1066
1079
  return projection;
1067
1080
  }
1068
1081
  widenPreferredIdProjection(projection) {
1069
- if (this._preferredIdSet.size === 0 || projection === void 0) return projection;
1070
- if (Array.isArray(projection)) return this._widenArrayProjection(projection);
1071
- return this._widenMapProjection(projection);
1082
+ const widened = this.widenQuantityRefProjection(projection);
1083
+ if (widened instanceof _moostjs_event_http.HttpError) return widened;
1084
+ if (this._preferredIdSet.size === 0 || widened === void 0) return widened;
1085
+ if (Array.isArray(widened)) return this._widenArrayProjection(widened);
1086
+ return this._widenMapProjection(widened);
1072
1087
  }
1073
1088
  _widenArrayProjection(projection) {
1074
1089
  const stringItems = /* @__PURE__ */ new Set();
@@ -1108,6 +1123,46 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
1108
1123
  for (const field of this._preferredIdSet) widened[field] = 1;
1109
1124
  return widened;
1110
1125
  }
1126
+ /**
1127
+ * Auto-includes the sibling-ref field whenever its `@db.amount.currency.ref`
1128
+ * / `@db.unit.ref` quantity is selected — UI must never get a value without
1129
+ * its dimension. No-op when `$select` is undefined (full row covers it).
1130
+ */
1131
+ widenQuantityRefProjection(projection) {
1132
+ if (this._quantityRefByPath.size === 0 || projection === void 0) return projection;
1133
+ if (Array.isArray(projection)) return this._widenQuantityArrayProjection(projection);
1134
+ return this._widenQuantityMapProjection(projection);
1135
+ }
1136
+ _widenQuantityArrayProjection(projection) {
1137
+ const stringItems = /* @__PURE__ */ new Set();
1138
+ for (const item of projection) if (typeof item === "string") stringItems.add(item);
1139
+ const toAdd = [];
1140
+ for (const [valuePath, refPath] of this._quantityRefByPath) if (stringItems.has(valuePath) && !stringItems.has(refPath)) {
1141
+ toAdd.push(refPath);
1142
+ stringItems.add(refPath);
1143
+ }
1144
+ if (toAdd.length === 0) return projection;
1145
+ return [...projection, ...toAdd];
1146
+ }
1147
+ _widenQuantityMapProjection(projection) {
1148
+ const entries = Object.entries(projection);
1149
+ if (entries.length === 0) return projection;
1150
+ const included = /* @__PURE__ */ new Set();
1151
+ const excluded = /* @__PURE__ */ new Set();
1152
+ for (const [k, v] of entries) if (v === 1 || v === true) included.add(k);
1153
+ else if (v === 0 || v === false) excluded.add(k);
1154
+ if (included.size > 0 && excluded.size > 0) return new _moostjs_event_http.HttpError(400, "Mixed inclusion/exclusion $select maps are not supported");
1155
+ if (excluded.size === 0) {
1156
+ const toAdd = [];
1157
+ for (const [valuePath, refPath] of this._quantityRefByPath) if (included.has(valuePath) && !included.has(refPath)) toAdd.push(refPath);
1158
+ if (toAdd.length === 0) return projection;
1159
+ const widened = {};
1160
+ for (const k of included) widened[k] = 1;
1161
+ for (const k of toAdd) widened[k] = 1;
1162
+ return widened;
1163
+ }
1164
+ return projection;
1165
+ }
1111
1166
  /** WHY: the URL parser only auto-coerces `$count`; every other boolean control reaches us as `"true"`/`"1"` and would fail DTO validation. */
1112
1167
  _coerceActionsControl(controls) {
1113
1168
  const v = controls.$actions;
package/dist/index.d.cts CHANGED
@@ -180,7 +180,10 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
180
180
  private readonly _gates;
181
181
  private readonly _preferredIdSet;
182
182
  private readonly _overlayIsNoOp;
183
+ /** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
184
+ private readonly _quantityRefByPath;
183
185
  constructor(readable: AtscriptDbReadable<T>, app: Moost);
186
+ private _collectQuantityRefs;
184
187
  private _buildGates;
185
188
  private _collectAnnotated;
186
189
  protected hasField(path: string): boolean;
@@ -205,6 +208,14 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
205
208
  private widenPreferredIdProjection;
206
209
  private _widenArrayProjection;
207
210
  private _widenMapProjection;
211
+ /**
212
+ * Auto-includes the sibling-ref field whenever its `@db.amount.currency.ref`
213
+ * / `@db.unit.ref` quantity is selected — UI must never get a value without
214
+ * its dimension. No-op when `$select` is undefined (full row covers it).
215
+ */
216
+ private widenQuantityRefProjection;
217
+ private _widenQuantityArrayProjection;
218
+ private _widenQuantityMapProjection;
208
219
  /** WHY: the URL parser only auto-coerces `$count`; every other boolean control reaches us as `"true"`/`"1"` and would fail DTO validation. */
209
220
  private _coerceActionsControl;
210
221
  /** Normalize a post-`widenPreferredIdProjection` $select into `string[] | null` (`null` = all fields). */
package/dist/index.d.mts CHANGED
@@ -180,7 +180,10 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
180
180
  private readonly _gates;
181
181
  private readonly _preferredIdSet;
182
182
  private readonly _overlayIsNoOp;
183
+ /** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
184
+ private readonly _quantityRefByPath;
183
185
  constructor(readable: AtscriptDbReadable<T>, app: Moost);
186
+ private _collectQuantityRefs;
184
187
  private _buildGates;
185
188
  private _collectAnnotated;
186
189
  protected hasField(path: string): boolean;
@@ -205,6 +208,14 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
205
208
  private widenPreferredIdProjection;
206
209
  private _widenArrayProjection;
207
210
  private _widenMapProjection;
211
+ /**
212
+ * Auto-includes the sibling-ref field whenever its `@db.amount.currency.ref`
213
+ * / `@db.unit.ref` quantity is selected — UI must never get a value without
214
+ * its dimension. No-op when `$select` is undefined (full row covers it).
215
+ */
216
+ private widenQuantityRefProjection;
217
+ private _widenQuantityArrayProjection;
218
+ private _widenQuantityMapProjection;
208
219
  /** WHY: the URL parser only auto-coerces `$count`; every other boolean control reaches us as `"true"`/`"1"` and would fail DTO validation. */
209
220
  private _coerceActionsControl;
210
221
  /** Normalize a post-`widenPreferredIdProjection` $select into `string[] | null` (`null` = all fields). */
package/dist/index.mjs CHANGED
@@ -990,14 +990,27 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
990
990
  _gates;
991
991
  _preferredIdSet;
992
992
  _overlayIsNoOp;
993
+ /** path → sibling-ref path for `@db.amount.currency.ref` / `@db.unit.ref`. */
994
+ _quantityRefByPath;
993
995
  constructor(readable, app) {
994
996
  super(readable.type, readable.tableName, app, readable.isView ? "view" : "table");
995
997
  this.readable = readable;
996
998
  this._gates = this._buildGates();
997
999
  this._preferredIdSet = new Set(readable.preferredId ?? []);
1000
+ this._quantityRefByPath = this._collectQuantityRefs();
998
1001
  const defaultOverlay = AsReadableController.prototype.applyMetaOverlay;
999
1002
  this._overlayIsNoOp = this.applyMetaOverlay === defaultOverlay;
1000
1003
  }
1004
+ _collectQuantityRefs() {
1005
+ const out = /* @__PURE__ */ new Map();
1006
+ if (!this.readable.flatMap) return out;
1007
+ for (const [path, entry] of this.readable.flatMap) {
1008
+ const meta = entry?.metadata;
1009
+ const ref = meta?.get("db.amount.currency.ref") ?? meta?.get("db.unit.ref");
1010
+ if (ref) out.set(path, ref);
1011
+ }
1012
+ return out;
1013
+ }
1001
1014
  _buildGates() {
1002
1015
  const meta = this.readable.type.metadata;
1003
1016
  const gates = {};
@@ -1065,9 +1078,11 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
1065
1078
  return projection;
1066
1079
  }
1067
1080
  widenPreferredIdProjection(projection) {
1068
- if (this._preferredIdSet.size === 0 || projection === void 0) return projection;
1069
- if (Array.isArray(projection)) return this._widenArrayProjection(projection);
1070
- return this._widenMapProjection(projection);
1081
+ const widened = this.widenQuantityRefProjection(projection);
1082
+ if (widened instanceof HttpError) return widened;
1083
+ if (this._preferredIdSet.size === 0 || widened === void 0) return widened;
1084
+ if (Array.isArray(widened)) return this._widenArrayProjection(widened);
1085
+ return this._widenMapProjection(widened);
1071
1086
  }
1072
1087
  _widenArrayProjection(projection) {
1073
1088
  const stringItems = /* @__PURE__ */ new Set();
@@ -1107,6 +1122,46 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
1107
1122
  for (const field of this._preferredIdSet) widened[field] = 1;
1108
1123
  return widened;
1109
1124
  }
1125
+ /**
1126
+ * Auto-includes the sibling-ref field whenever its `@db.amount.currency.ref`
1127
+ * / `@db.unit.ref` quantity is selected — UI must never get a value without
1128
+ * its dimension. No-op when `$select` is undefined (full row covers it).
1129
+ */
1130
+ widenQuantityRefProjection(projection) {
1131
+ if (this._quantityRefByPath.size === 0 || projection === void 0) return projection;
1132
+ if (Array.isArray(projection)) return this._widenQuantityArrayProjection(projection);
1133
+ return this._widenQuantityMapProjection(projection);
1134
+ }
1135
+ _widenQuantityArrayProjection(projection) {
1136
+ const stringItems = /* @__PURE__ */ new Set();
1137
+ for (const item of projection) if (typeof item === "string") stringItems.add(item);
1138
+ const toAdd = [];
1139
+ for (const [valuePath, refPath] of this._quantityRefByPath) if (stringItems.has(valuePath) && !stringItems.has(refPath)) {
1140
+ toAdd.push(refPath);
1141
+ stringItems.add(refPath);
1142
+ }
1143
+ if (toAdd.length === 0) return projection;
1144
+ return [...projection, ...toAdd];
1145
+ }
1146
+ _widenQuantityMapProjection(projection) {
1147
+ const entries = Object.entries(projection);
1148
+ if (entries.length === 0) return projection;
1149
+ const included = /* @__PURE__ */ new Set();
1150
+ const excluded = /* @__PURE__ */ new Set();
1151
+ for (const [k, v] of entries) if (v === 1 || v === true) included.add(k);
1152
+ else if (v === 0 || v === false) excluded.add(k);
1153
+ if (included.size > 0 && excluded.size > 0) return new HttpError(400, "Mixed inclusion/exclusion $select maps are not supported");
1154
+ if (excluded.size === 0) {
1155
+ const toAdd = [];
1156
+ for (const [valuePath, refPath] of this._quantityRefByPath) if (included.has(valuePath) && !included.has(refPath)) toAdd.push(refPath);
1157
+ if (toAdd.length === 0) return projection;
1158
+ const widened = {};
1159
+ for (const k of included) widened[k] = 1;
1160
+ for (const k of toAdd) widened[k] = 1;
1161
+ return widened;
1162
+ }
1163
+ return projection;
1164
+ }
1110
1165
  /** WHY: the URL parser only auto-coerces `$count`; every other boolean control reaches us as `"true"`/`"1"` and would fail DTO validation. */
1111
1166
  _coerceActionsControl(controls) {
1112
1167
  const v = controls.$actions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/moost-db",
3
- "version": "0.1.63",
3
+ "version": "0.1.64",
4
4
  "description": "Generic database controller for Moost with Atscript.",
5
5
  "keywords": [
6
6
  "annotations",
@@ -58,7 +58,7 @@
58
58
  "@wooksjs/event-core": "^0.7.10",
59
59
  "@wooksjs/http-body": "^0.7.10",
60
60
  "moost": "^0.6.8",
61
- "@atscript/db": "^0.1.63"
61
+ "@atscript/db": "^0.1.64"
62
62
  },
63
63
  "scripts": {
64
64
  "postinstall": "asc -f dts",