@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.
- package/dist/lib/loopback.cjs +1 -0
- package/dist/lib/rate-limit/remote.cjs +5 -3
- package/dist/package.cjs +2 -2
- package/docs/application-guide.md +126 -0
- package/package.json +8 -8
package/dist/lib/loopback.cjs
CHANGED
|
@@ -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
|
-
|
|
154
|
-
|
|
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.
|
|
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": "
|
|
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
|
+
"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.
|
|
101
|
+
"express": "4.22.3",
|
|
102
102
|
"isemail": "^3.2.0",
|
|
103
103
|
"loopback-filters": "^1.1.1",
|
|
104
|
-
"nodemailer": "^9.
|
|
104
|
+
"nodemailer": "^9.1.1",
|
|
105
105
|
"otplib": "13.4.1",
|
|
106
106
|
"serve-favicon": "^2.5.1",
|
|
107
|
-
"@vsaas/loopback-
|
|
108
|
-
"@vsaas/
|
|
109
|
-
"@vsaas/
|
|
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/
|
|
128
|
-
"@vsaas/
|
|
127
|
+
"@vsaas/error-handler": "^12.0.1",
|
|
128
|
+
"@vsaas/loopback-boot": "^12.0.2"
|
|
129
129
|
},
|
|
130
130
|
"engines": {
|
|
131
131
|
"node": ">=24"
|