@vsaas/loopback 12.3.0 → 12.4.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.
@@ -311,6 +311,7 @@ var require_loopback = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports
311
311
  */
312
312
  require_lib_builtin_models(loopback);
313
313
  loopback.DataSource = juggler.DataSource;
314
+ loopback.registerNormalizer = juggler.registerNormalizer;
314
315
  function getPackageRoot() {
315
316
  return path.join(__dirname, "..", "..");
316
317
  }
@@ -150,9 +150,11 @@ function installRemoteRateLimiting(app) {
150
150
  });
151
151
  }
152
152
  }
153
- app.on("modelRemoted", function() {
154
- app.models().forEach(installForModel);
155
- });
153
+ function installForSharedClass(sharedClass) {
154
+ installForModel(sharedClass?.ctor);
155
+ }
156
+ app.on("modelRemoted", installForSharedClass);
157
+ app.on("remoteMethodAdded", installForSharedClass);
156
158
  app.installRemoteRateLimits = function() {
157
159
  app.models().forEach(installForModel);
158
160
  return app;
package/dist/package.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
  var require_package = /* @__PURE__ */ require("./_virtual/_rolldown/runtime.cjs").__commonJSMin(((exports, module) => {
3
3
  module.exports = {
4
4
  "name": "@vsaas/loopback",
5
- "version": "12.3.0",
5
+ "version": "12.4.0",
6
6
  "description": "Fork of LoopBack 3 framework for building APIs and microservices in Node.js",
7
7
  "keywords": [
8
8
  "StrongLoop",
@@ -117,7 +117,7 @@ var require_package = /* @__PURE__ */ require("./_virtual/_rolldown/runtime.cjs"
117
117
  "express": "catalog:",
118
118
  "isemail": "^3.2.0",
119
119
  "loopback-filters": "^1.1.1",
120
- "nodemailer": "^9.0.5",
120
+ "nodemailer": "catalog:",
121
121
  "otplib": "catalog:",
122
122
  "serve-favicon": "^2.5.1"
123
123
  },
@@ -1031,6 +1031,132 @@ that the validation entrypoint itself remains callback-oriented. In
1031
1031
  promise-first codebases, persisting with `await` is usually the cleanest way to
1032
1032
  run `validatePromise` rules.
1033
1033
 
1034
+ ### Property normalization
1035
+
1036
+ String properties can declare `normalize`, an ordered list of steps applied
1037
+ before validation and persistence. With `trim`, a `required` property rejects
1038
+ `" "` instead of storing it.
1039
+
1040
+ ```json
1041
+ "properties": {
1042
+ "email": { "type": "string", "required": true, "normalize": ["trim", "lowercase"] },
1043
+ "name": { "type": "string", "normalize": ["trim", "collapseSpaces", "nfc"] },
1044
+ "nickname": { "type": "string", "normalize": ["trim", "emptyToNull"] },
1045
+ "bio": { "type": "string", "normalize": ["stripHtml", "trim"] },
1046
+ "tags": { "type": ["string"], "normalize": ["trim", "lowercase"] }
1047
+ }
1048
+ ```
1049
+
1050
+ Built-in steps:
1051
+
1052
+ | Name | Effect |
1053
+ | ---------------- | --------------------------------------------------------------------- |
1054
+ | `trim` | Remove leading and trailing whitespace |
1055
+ | `lowercase` | Lowercase |
1056
+ | `uppercase` | Uppercase |
1057
+ | `nfc` | Unicode NFC normalization |
1058
+ | `unaccent` | Remove diacritics (`Ñandú` → `Nandu`) |
1059
+ | `collapseSpaces` | Replace whitespace runs, newlines included, with one space |
1060
+ | `emptyToNull` | Store `""` as `null`; place it after `trim` to also catch blanks |
1061
+ | `stripInvisible` | Remove control, zero-width, bidi-override and BOM characters |
1062
+ | `numeric` | Keep digits only |
1063
+ | `alphanumeric` | Keep letters and digits only |
1064
+ | `stripHtml` | Drop tags, comments and `script`/`style` content; keep the plain text |
1065
+
1066
+ `stripHtml` neither decodes nor escapes entities: `Tom & Jerry` stays as is
1067
+ and `a < b` is kept. It is data hygiene, not XSS protection; still escape on
1068
+ output. It keeps no markup, so it is not meant for rich-text fields.
1069
+
1070
+ Register project-specific steps before models are defined, for example at the
1071
+ top of `server/server.js`, and reference them by name. Inline functions also
1072
+ work in programmatic models. A step returning `null` or `undefined` ends the
1073
+ pipeline with that value.
1074
+
1075
+ ```js
1076
+ loopback.registerNormalizer('rut', (value) => value.replace(/[.\s]/g, '').toUpperCase());
1077
+ ```
1078
+
1079
+ When it runs:
1080
+
1081
+ - On every assignment through the model: `create`, `createAll`, `save`,
1082
+ `patchAttributes`/`updateAttributes`, `updateAttribute`, `replaceById`,
1083
+ `replaceOrCreate`, `patchOrCreate`/`upsert`, `upsertWithWhere`,
1084
+ `findOrCreate`, relation and embedded-model writes, and values set by
1085
+ `before save` hooks. `persist` hooks already see normalized data.
1086
+ - On partial updates, only the properties present in the payload are
1087
+ normalized; other stored values are untouched. `updateAll` normalizes its
1088
+ data, and validates the same values it persists.
1089
+ - On `where` filters, values compared for equality (`eq`, `neq`, `inq`, `nin`
1090
+ and plain values) are normalized like stored values, so
1091
+ `{ email: ' Ana@X.com ' }` matches `ana@x.com`. Pattern and range operators
1092
+ (`like`, `ilike`, `regexp`, `gt`, `between`, …) are left as written.
1093
+ - Not on reads: rows written before a property gained `normalize` are returned
1094
+ as stored. They are normalized the next time they are written through
1095
+ `save()`, so migrate existing data when you add a step that changes it,
1096
+ especially `lowercase`, since equality filters will no longer match the old
1097
+ values.
1098
+ - Not inside extended operators (`$set`, `$push`, …) or custom property
1099
+ setters; both receive the value as written.
1100
+
1101
+ Unknown step names, or `normalize` on a non-string property, throw when the
1102
+ model is defined.
1103
+
1104
+ ### Property constraints
1105
+
1106
+ Properties can declare the JSON Schema keywords that the API explorer already
1107
+ publishes, and they are enforced as model validations. They are checked after
1108
+ normalization, so `"normalize": "trim"` with `"maxLength": 128` validates the
1109
+ trimmed value.
1110
+
1111
+ ```json
1112
+ "properties": {
1113
+ "name": { "type": "string", "required": true, "maxLength": 128, "normalize": "trim" },
1114
+ "code": { "type": "string", "pattern": "^[A-Z]{3}-[0-9]{4}$" },
1115
+ "contact": { "type": "string", "format": "email" },
1116
+ "operability": { "type": "number", "min": 0, "max": 100 },
1117
+ "mode": { "type": "string", "enum": ["NONE", "CRON"] },
1118
+ "tags": { "type": ["string"], "maxItems": 20, "maxLength": 32 }
1119
+ }
1120
+ ```
1121
+
1122
+ | Keyword | Applies to | Error code |
1123
+ | -------------------------------- | ----------------------- | ------------------------------------------- |
1124
+ | `minLength`, `maxLength` | strings | `length.min`, `length.max` |
1125
+ | `pattern` | strings | `format` |
1126
+ | `format` | strings | `format.email`, `format.uri`, `format.uuid` |
1127
+ | `minimum`/`min`, `maximum`/`max` | numbers | `range.min`, `range.max` |
1128
+ | `enum` | string, number, boolean | `inclusion` |
1129
+ | `minItems`, `maxItems` | arrays | `items.min`, `items.max` |
1130
+
1131
+ - Lengths count Unicode characters, so an emoji counts as one.
1132
+ - Bounds are inclusive. `min`/`max` are aliases of `minimum`/`maximum`.
1133
+ - On array properties, `minItems`/`maxItems` apply to the array and the other
1134
+ keywords to each item.
1135
+ - `format` validates `email`, `uri` (absolute) and `uuid`. Other formats, such
1136
+ as `date-time` or `int32`, are documentation only.
1137
+ - Constraints only check present values: `null` and `undefined` pass, and
1138
+ `required` decides whether a value must be present. An empty string is a
1139
+ value; use `"normalize": ["trim", "emptyToNull"]` to treat blanks as absent.
1140
+ - A keyword on a property of the wrong type, or an invalid limit, throws when
1141
+ the model is defined.
1142
+
1143
+ Failures are regular validation errors: the write is rejected with
1144
+ `ValidationError` (HTTP 422) and `error.details.codes` lists the codes above
1145
+ per property.
1146
+
1147
+ Constraints run wherever model validation runs: `create`, `save`,
1148
+ `patchAttributes`, `replaceById`, `replaceOrCreate`, `upsertWithWhere`, and
1149
+ `patchOrCreate` with `validateUpsert: true`. Two writes skip full validation by
1150
+ default because their partial data would fail `required`: `updateAll` (unless
1151
+ `validateUpdate: true`) and `patchOrCreate` (unless `validateUpsert: true`).
1152
+ They still check the constraints of the properties they write. Passing
1153
+ `{ validate: false }`, or setting `validateUpdate`/`validateUpsert` or
1154
+ `automaticValidation` to `false`, skips both.
1155
+
1156
+ Constraints declared on existing models start being enforced when you upgrade.
1157
+ Writes of values outside them, including a `patchAttributes` that leaves an
1158
+ invalid stored value untouched, will be rejected, so fix stored data first.
1159
+
1034
1160
  ## Optional packages
1035
1161
 
1036
1162
  ### `@vsaas/loopback-component-storage`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vsaas/loopback",
3
- "version": "12.3.0",
3
+ "version": "12.4.0",
4
4
  "description": "Fork of LoopBack 3 framework for building APIs and microservices in Node.js",
5
5
  "keywords": [
6
6
  "StrongLoop",
@@ -98,15 +98,15 @@
98
98
  "debug": "^2.6.9",
99
99
  "depd": "^1.1.2",
100
100
  "ejs": "5.0.1",
101
- "express": "4.22.2",
101
+ "express": "4.22.3",
102
102
  "isemail": "^3.2.0",
103
103
  "loopback-filters": "^1.1.1",
104
- "nodemailer": "^9.0.5",
104
+ "nodemailer": "^9.1.1",
105
105
  "otplib": "13.4.1",
106
106
  "serve-favicon": "^2.5.1",
107
- "@vsaas/loopback-datasource-juggler": "^13.0.1",
108
- "@vsaas/remoting": "^13.0.1",
109
- "@vsaas/loopback-connector-remote": "^12.0.2"
107
+ "@vsaas/loopback-connector-remote": "^12.0.2",
108
+ "@vsaas/loopback-datasource-juggler": "^13.1.0",
109
+ "@vsaas/remoting": "^13.0.2"
110
110
  },
111
111
  "devDependencies": {
112
112
  "@types/node": "25.5.0",
@@ -124,8 +124,8 @@
124
124
  "tsdown": "0.22.14",
125
125
  "typescript": "6.0.2",
126
126
  "vitest": "4.1.11",
127
- "@vsaas/loopback-boot": "^12.0.2",
128
- "@vsaas/error-handler": "^12.0.1"
127
+ "@vsaas/error-handler": "^12.0.1",
128
+ "@vsaas/loopback-boot": "^12.0.2"
129
129
  },
130
130
  "engines": {
131
131
  "node": ">=24"