webspresso 0.0.112 → 0.0.113
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/core/client/adapter/datastar.js +587 -26
- package/core/client/api.js +147 -7
- package/core/client/controller.js +134 -11
- package/core/client/devtool.js +59 -33
- package/core/client/forms.js +475 -29
- package/core/client/fragments.js +397 -25
- package/core/client/index.js +13 -1
- package/core/client/server-fragments.js +102 -36
- package/core/client/utils.js +72 -17
- package/core/http/pipeline.js +82 -1
- package/core/openapi/build-from-api-routes.js +5 -5
- package/core/orm/json-fields.js +7 -6
- package/core/orm/schema-helpers.js +18 -15
- package/core/orm/utils/nanoid.js +7 -8
- package/core/services/validator.js +12 -1
- package/core/validation/apply.js +3 -0
- package/core/validation/index.js +25 -0
- package/core/validation/zod-introspect.js +187 -0
- package/index.d.ts +98 -1
- package/package.json +9 -8
- package/plugins/mcp/providers/services.js +2 -2
- package/src/api/define-api.js +2 -0
- package/src/api/validation-i18n.js +6 -2
- package/src/bundle/index.js +67 -3
- package/src/dev/api-typegen.js +79 -22
- package/src/dev/typegen.js +61 -35
- package/src/file-router.js +24 -6
- package/src/routing/mount-discovered-routes.js +24 -6
package/src/dev/api-typegen.js
CHANGED
|
@@ -163,21 +163,39 @@ function extractUrlParamNames(routePath) {
|
|
|
163
163
|
* @param {import('zod').ZodTypeAny | null} zSchema
|
|
164
164
|
* @returns {{ acceptsFormData: boolean, fields: Record<string, { type: string, required: boolean, isFile: boolean, isArray: boolean }> | null, isStrictObject: boolean }}
|
|
165
165
|
*/
|
|
166
|
+
const {
|
|
167
|
+
getZodDef,
|
|
168
|
+
getZodTypeName,
|
|
169
|
+
getZodInner,
|
|
170
|
+
getZodArrayElement,
|
|
171
|
+
getZodObjectShape,
|
|
172
|
+
} = require('../../core/validation/zod-introspect');
|
|
173
|
+
|
|
174
|
+
function unwrapZodObject(zSchema) {
|
|
175
|
+
let current = zSchema;
|
|
176
|
+
while (current && getZodDef(current)) {
|
|
177
|
+
const tName = getZodTypeName(current);
|
|
178
|
+
if (tName === 'ZodEffects' || tName === 'ZodOptional' || tName === 'ZodDefault' || tName === 'ZodPipeline') {
|
|
179
|
+
current = getZodInner(current);
|
|
180
|
+
} else {
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return current;
|
|
185
|
+
}
|
|
186
|
+
|
|
166
187
|
function inspectBodySchemaFields(zSchema) {
|
|
167
|
-
if (!zSchema || !zSchema
|
|
188
|
+
if (!zSchema || !getZodDef(zSchema)) {
|
|
168
189
|
return { acceptsFormData: false, fields: null, isStrictObject: false };
|
|
169
190
|
}
|
|
170
191
|
|
|
171
|
-
|
|
172
|
-
while (current && current._def && (current._def.typeName === 'ZodEffects' || current._def.typeName === 'ZodOptional' || current._def.typeName === 'ZodDefault')) {
|
|
173
|
-
current = current._def.schema || current._def.innerType;
|
|
174
|
-
}
|
|
192
|
+
const current = unwrapZodObject(zSchema);
|
|
175
193
|
|
|
176
|
-
if (!current ||
|
|
194
|
+
if (!current || getZodTypeName(current) !== 'ZodObject') {
|
|
177
195
|
return { acceptsFormData: false, fields: null, isStrictObject: false };
|
|
178
196
|
}
|
|
179
197
|
|
|
180
|
-
const shape =
|
|
198
|
+
const shape = getZodObjectShape(current);
|
|
181
199
|
if (!shape || typeof shape !== 'object') {
|
|
182
200
|
return { acceptsFormData: false, fields: {}, isStrictObject: true };
|
|
183
201
|
}
|
|
@@ -190,18 +208,16 @@ function inspectBodySchemaFields(zSchema) {
|
|
|
190
208
|
let required = true;
|
|
191
209
|
let isArray = false;
|
|
192
210
|
|
|
193
|
-
while (inner && inner
|
|
194
|
-
const tName = inner
|
|
211
|
+
while (inner && getZodDef(inner)) {
|
|
212
|
+
const tName = getZodTypeName(inner);
|
|
195
213
|
if (tName === 'ZodOptional' || tName === 'ZodDefault') {
|
|
196
214
|
required = false;
|
|
197
|
-
inner = inner
|
|
198
|
-
} else if (tName === 'ZodNullable') {
|
|
199
|
-
inner = inner
|
|
200
|
-
} else if (tName === 'ZodEffects') {
|
|
201
|
-
inner = inner._def.schema;
|
|
215
|
+
inner = getZodInner(inner);
|
|
216
|
+
} else if (tName === 'ZodNullable' || tName === 'ZodEffects' || tName === 'ZodPipeline') {
|
|
217
|
+
inner = getZodInner(inner);
|
|
202
218
|
} else if (tName === 'ZodArray') {
|
|
203
219
|
isArray = true;
|
|
204
|
-
inner = inner
|
|
220
|
+
inner = getZodArrayElement(inner);
|
|
205
221
|
} else {
|
|
206
222
|
break;
|
|
207
223
|
}
|
|
@@ -213,7 +229,7 @@ function inspectBodySchemaFields(zSchema) {
|
|
|
213
229
|
);
|
|
214
230
|
if (isFile) acceptsFormData = true;
|
|
215
231
|
|
|
216
|
-
const baseTypeName = inner
|
|
232
|
+
const baseTypeName = getZodTypeName(inner) || 'ZodUnknown';
|
|
217
233
|
let scalarKind = 'string';
|
|
218
234
|
if (isFile) scalarKind = 'file';
|
|
219
235
|
else if (baseTypeName === 'ZodNumber') scalarKind = 'number';
|
|
@@ -271,12 +287,9 @@ function inferHandlerWireResponse(handlerFn, fileSource, schemas, zodToTsTypeFn)
|
|
|
271
287
|
// 2. Build local variable type table from `req.input.body.<prop>` and literal assignments
|
|
272
288
|
const bodyFieldTypes = {};
|
|
273
289
|
if (schemas && schemas.bodySchema) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
if (current && current._def && current._def.typeName === 'ZodObject') {
|
|
279
|
-
const shape = typeof current.shape === 'function' ? current.shape() : current.shape;
|
|
290
|
+
const current = unwrapZodObject(schemas.bodySchema);
|
|
291
|
+
if (current && getZodTypeName(current) === 'ZodObject') {
|
|
292
|
+
const shape = getZodObjectShape(current);
|
|
280
293
|
if (shape) {
|
|
281
294
|
for (const [k, v] of Object.entries(shape)) {
|
|
282
295
|
bodyFieldTypes[k] = zodToTsTypeFn(v, 2, 'wire');
|
|
@@ -1141,6 +1154,50 @@ function checkTemplateFormContracts(rootDir, options = {}) {
|
|
|
1141
1154
|
continue;
|
|
1142
1155
|
}
|
|
1143
1156
|
|
|
1157
|
+
// Validate new client-behavior data-w-* attributes (skipping dynamic {{ ... }} / {% ... %} values)
|
|
1158
|
+
const ignoreAttrRe = /\bdata-w-ignore\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
|
|
1159
|
+
let attrMatch;
|
|
1160
|
+
while ((attrMatch = ignoreAttrRe.exec(content)) !== null) {
|
|
1161
|
+
const val = (attrMatch[1] ?? attrMatch[2] ?? '').trim().toLowerCase();
|
|
1162
|
+
if (!val || val.includes('{{') || val.includes('{%')) continue;
|
|
1163
|
+
if (val !== 'children' && val !== 'self' && val !== 'all' && val !== 'both') {
|
|
1164
|
+
warnings.push({
|
|
1165
|
+
code: 'INVALID_DATA_W_IGNORE',
|
|
1166
|
+
file: relFile,
|
|
1167
|
+
value: val,
|
|
1168
|
+
message: `Template "${relFile}" uses unsupported data-w-ignore="${val}". Expected "children", "self", or "all".`,
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const delayAttrRe = /\bdata-w-loading-delay\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
|
|
1174
|
+
while ((attrMatch = delayAttrRe.exec(content)) !== null) {
|
|
1175
|
+
const val = (attrMatch[1] ?? attrMatch[2] ?? '').trim();
|
|
1176
|
+
if (!val || val.includes('{{') || val.includes('{%')) continue;
|
|
1177
|
+
if (!/^\d+(?:\.\d+)?(?:ms|s)?$/i.test(val)) {
|
|
1178
|
+
warnings.push({
|
|
1179
|
+
code: 'INVALID_LOADING_DELAY',
|
|
1180
|
+
file: relFile,
|
|
1181
|
+
value: val,
|
|
1182
|
+
message: `Template "${relFile}" uses invalid data-w-loading-delay="${val}". Expected a non-negative number or duration (e.g. "200", "200ms", "1s").`,
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
const errorModeRe = /\bdata-w-error-mode\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
|
|
1188
|
+
while ((attrMatch = errorModeRe.exec(content)) !== null) {
|
|
1189
|
+
const val = (attrMatch[1] ?? attrMatch[2] ?? '').trim().toLowerCase();
|
|
1190
|
+
if (!val || val.includes('{{') || val.includes('{%')) continue;
|
|
1191
|
+
if (val !== 'touched' && val !== 'always') {
|
|
1192
|
+
warnings.push({
|
|
1193
|
+
code: 'INVALID_ERROR_MODE',
|
|
1194
|
+
file: relFile,
|
|
1195
|
+
value: val,
|
|
1196
|
+
message: `Template "${relFile}" uses unsupported data-w-error-mode="${val}". Expected "touched" or "always".`,
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1144
1201
|
let formMatch;
|
|
1145
1202
|
while ((formMatch = formRegex.exec(content)) !== null) {
|
|
1146
1203
|
const formAttrs = formMatch[1] || '';
|
package/src/dev/typegen.js
CHANGED
|
@@ -13,6 +13,16 @@ const { hasServiceRegistry, getServiceRegistry } = require('../../core/context')
|
|
|
13
13
|
const { scanRoutes } = require('../../src/discovery/scan-routes');
|
|
14
14
|
const { compileRouteTable } = require('../../src/routing/route-table');
|
|
15
15
|
const { z } = require('../../core/validation');
|
|
16
|
+
const {
|
|
17
|
+
getZodDef,
|
|
18
|
+
getZodTypeName,
|
|
19
|
+
getZodInner,
|
|
20
|
+
getZodArrayElement,
|
|
21
|
+
getZodObjectShape,
|
|
22
|
+
getZodEnumValues,
|
|
23
|
+
getZodLiteralValue,
|
|
24
|
+
isZodCoerced,
|
|
25
|
+
} = require('../../core/validation/zod-introspect');
|
|
16
26
|
const {
|
|
17
27
|
collectApiRouteContracts: collectApiRouteContractsRaw,
|
|
18
28
|
generateApiDeclarationCode,
|
|
@@ -49,7 +59,7 @@ function inferZodTransformOutputType(transformFn, innerSchema, indent, mode) {
|
|
|
49
59
|
return mode === 'wire' ? 'string' : 'Date';
|
|
50
60
|
}
|
|
51
61
|
// Try executing transform on a safe dummy sample if innerSchema is primitive
|
|
52
|
-
const innerType = innerSchema
|
|
62
|
+
const innerType = getZodTypeName(innerSchema);
|
|
53
63
|
try {
|
|
54
64
|
let sample;
|
|
55
65
|
if (innerType === 'ZodString') sample = '1';
|
|
@@ -79,14 +89,14 @@ function inferZodTransformOutputType(transformFn, innerSchema, indent, mode) {
|
|
|
79
89
|
* @returns {boolean}
|
|
80
90
|
*/
|
|
81
91
|
function isZodFieldOptional(zField, mode = 'output') {
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
const t = getZodTypeName(zField);
|
|
93
|
+
if (!t) return false;
|
|
84
94
|
if (t === 'ZodOptional') return true;
|
|
85
95
|
if (t === 'ZodDefault') {
|
|
86
96
|
return mode === 'input';
|
|
87
97
|
}
|
|
88
|
-
if (t === 'ZodEffects' && zField
|
|
89
|
-
return isZodFieldOptional(zField
|
|
98
|
+
if ((t === 'ZodEffects' || t === 'ZodPipeline') && getZodInner(zField)) {
|
|
99
|
+
return isZodFieldOptional(getZodInner(zField), mode);
|
|
90
100
|
}
|
|
91
101
|
return false;
|
|
92
102
|
}
|
|
@@ -95,13 +105,13 @@ function isZodFieldOptional(zField, mode = 'output') {
|
|
|
95
105
|
* Unwraps outer `ZodOptional` / `ZodDefault` for property type emission inside `ZodObject`.
|
|
96
106
|
*/
|
|
97
107
|
function unwrapOptionalOrDefault(zField, mode = 'output') {
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
const t = getZodTypeName(zField);
|
|
109
|
+
if (!t) return zField;
|
|
100
110
|
if (t === 'ZodOptional') {
|
|
101
|
-
return zField
|
|
111
|
+
return getZodInner(zField);
|
|
102
112
|
}
|
|
103
113
|
if (t === 'ZodDefault' && mode === 'input') {
|
|
104
|
-
return zField
|
|
114
|
+
return getZodInner(zField);
|
|
105
115
|
}
|
|
106
116
|
return zField;
|
|
107
117
|
}
|
|
@@ -114,8 +124,9 @@ function unwrapOptionalOrDefault(zField, mode = 'output') {
|
|
|
114
124
|
* @returns {string}
|
|
115
125
|
*/
|
|
116
126
|
function zodToTsType(zType, indent = 0, mode = 'output') {
|
|
117
|
-
|
|
118
|
-
const typeName = zType
|
|
127
|
+
const def = getZodDef(zType);
|
|
128
|
+
const typeName = getZodTypeName(zType);
|
|
129
|
+
if (!def || !typeName) return 'unknown';
|
|
119
130
|
const spaces = ' '.repeat(indent);
|
|
120
131
|
const nextSpaces = ' '.repeat(indent + 1);
|
|
121
132
|
|
|
@@ -128,12 +139,12 @@ function zodToTsType(zType, indent = 0, mode = 'output') {
|
|
|
128
139
|
case 'ZodString':
|
|
129
140
|
return 'string';
|
|
130
141
|
case 'ZodNumber':
|
|
131
|
-
if (mode === 'input' && zType
|
|
142
|
+
if (mode === 'input' && isZodCoerced(zType)) {
|
|
132
143
|
return 'number | string';
|
|
133
144
|
}
|
|
134
145
|
return 'number';
|
|
135
146
|
case 'ZodBoolean':
|
|
136
|
-
if (mode === 'input' && zType
|
|
147
|
+
if (mode === 'input' && isZodCoerced(zType)) {
|
|
137
148
|
return 'boolean | string';
|
|
138
149
|
}
|
|
139
150
|
return 'boolean';
|
|
@@ -155,48 +166,63 @@ function zodToTsType(zType, indent = 0, mode = 'output') {
|
|
|
155
166
|
return 'unknown';
|
|
156
167
|
case 'ZodNever':
|
|
157
168
|
return 'never';
|
|
158
|
-
case 'ZodLiteral':
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
case 'ZodLiteral': {
|
|
170
|
+
const litVal = getZodLiteralValue(zType);
|
|
171
|
+
return typeof litVal === 'string' ? `'${litVal}'` : String(litVal);
|
|
172
|
+
}
|
|
162
173
|
case 'ZodEnum':
|
|
163
|
-
return (zType
|
|
174
|
+
return getZodEnumValues(zType).map((v) => `'${v}'`).join(' | ') || 'string';
|
|
164
175
|
case 'ZodNativeEnum':
|
|
165
176
|
return 'number | string';
|
|
166
177
|
case 'ZodArray':
|
|
167
|
-
return `Array<${zodToTsType(zType
|
|
178
|
+
return `Array<${zodToTsType(getZodArrayElement(zType), indent, mode)}>`;
|
|
168
179
|
case 'ZodOptional':
|
|
169
|
-
return `${zodToTsType(zType
|
|
180
|
+
return `${zodToTsType(getZodInner(zType), indent, mode)} | undefined`;
|
|
170
181
|
case 'ZodNullable':
|
|
171
|
-
return `${zodToTsType(zType
|
|
182
|
+
return `${zodToTsType(getZodInner(zType), indent, mode)} | null`;
|
|
172
183
|
case 'ZodDefault':
|
|
173
|
-
return zodToTsType(zType
|
|
184
|
+
return zodToTsType(getZodInner(zType), indent, mode);
|
|
185
|
+
case 'ZodPipeline': {
|
|
186
|
+
const inDef = getZodDef(def.in);
|
|
187
|
+
const outDef = getZodDef(def.out);
|
|
188
|
+
if (mode === 'input') {
|
|
189
|
+
if (inDef && inDef.type === 'transform' && def.out) {
|
|
190
|
+
return `${zodToTsType(def.out, indent, mode)} | string`;
|
|
191
|
+
}
|
|
192
|
+
return zodToTsType(def.in, indent, mode);
|
|
193
|
+
}
|
|
194
|
+
if (outDef && outDef.type === 'transform' && typeof outDef.transform === 'function') {
|
|
195
|
+
return inferZodTransformOutputType(outDef.transform, def.in, indent, mode);
|
|
196
|
+
}
|
|
197
|
+
return zodToTsType(def.out || def.in, indent, mode);
|
|
198
|
+
}
|
|
174
199
|
case 'ZodEffects': {
|
|
175
200
|
if (mode === 'input') {
|
|
176
|
-
if (
|
|
177
|
-
return `${zodToTsType(
|
|
201
|
+
if (def.effect && def.effect.type === 'preprocess') {
|
|
202
|
+
return `${zodToTsType(def.schema, indent, mode)} | string`;
|
|
178
203
|
}
|
|
179
|
-
return zodToTsType(
|
|
204
|
+
return zodToTsType(def.schema, indent, mode);
|
|
180
205
|
}
|
|
181
|
-
if (
|
|
206
|
+
if (def.effect && def.effect.type === 'transform') {
|
|
182
207
|
return inferZodTransformOutputType(
|
|
183
|
-
|
|
184
|
-
|
|
208
|
+
def.effect.transform,
|
|
209
|
+
def.schema,
|
|
185
210
|
indent,
|
|
186
211
|
mode
|
|
187
212
|
);
|
|
188
213
|
}
|
|
189
|
-
return zodToTsType(
|
|
214
|
+
return zodToTsType(def.schema, indent, mode);
|
|
190
215
|
}
|
|
191
216
|
case 'ZodUnion':
|
|
192
217
|
case 'ZodDiscriminatedUnion':
|
|
193
|
-
return (
|
|
218
|
+
return (def.options || []).map((opt) => zodToTsType(opt, indent, mode)).join(' | ') || 'unknown';
|
|
194
219
|
case 'ZodObject': {
|
|
195
|
-
const shape =
|
|
220
|
+
const shape = getZodObjectShape(zType);
|
|
196
221
|
if (!shape || Object.keys(shape).length === 0) return 'Record<string, unknown>';
|
|
197
222
|
const entries = [];
|
|
198
223
|
for (const [k, v] of Object.entries(shape)) {
|
|
199
|
-
|
|
224
|
+
const vType = getZodTypeName(v);
|
|
225
|
+
if (mode === 'wire' && (vType === 'ZodUndefined' || vType === 'ZodVoid')) {
|
|
200
226
|
continue;
|
|
201
227
|
}
|
|
202
228
|
const isOpt = isZodFieldOptional(v, mode);
|
|
@@ -209,7 +235,7 @@ function zodToTsType(zType, indent = 0, mode = 'output') {
|
|
|
209
235
|
return `{\n${entries.join('\n')}\n${spaces}}`;
|
|
210
236
|
}
|
|
211
237
|
case 'ZodRecord':
|
|
212
|
-
return `Record<string, ${zodToTsType(
|
|
238
|
+
return `Record<string, ${zodToTsType(def.valueType, indent, mode)}>`;
|
|
213
239
|
default:
|
|
214
240
|
return 'unknown';
|
|
215
241
|
}
|
|
@@ -247,8 +273,8 @@ function collectModelMetadata(modelsDir) {
|
|
|
247
273
|
const columns = [];
|
|
248
274
|
if (model.schema && model.schema.shape) {
|
|
249
275
|
for (const [colName, colZod] of Object.entries(model.schema.shape)) {
|
|
250
|
-
const isOpt = colZod
|
|
251
|
-
const innerZod = isOpt ? colZod
|
|
276
|
+
const isOpt = getZodTypeName(colZod) === 'ZodOptional';
|
|
277
|
+
const innerZod = isOpt ? getZodInner(colZod) : colZod;
|
|
252
278
|
columns.push({
|
|
253
279
|
name: colName,
|
|
254
280
|
isOptional: isOpt,
|
package/src/file-router.js
CHANGED
|
@@ -1998,12 +1998,30 @@ function mountPages(app, options) {
|
|
|
1998
1998
|
resolveMiddlewareList: (mwList) => resolveMiddlewares(mwList, middlewares),
|
|
1999
1999
|
});
|
|
2000
2000
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2001
|
+
let fragmentLimiter;
|
|
2002
|
+
try {
|
|
2003
|
+
const expressRateLimit = require('express-rate-limit');
|
|
2004
|
+
const rateLimitFn = expressRateLimit.rateLimit || expressRateLimit;
|
|
2005
|
+
if (typeof rateLimitFn === 'function') {
|
|
2006
|
+
fragmentLimiter = rateLimitFn({
|
|
2007
|
+
windowMs: 60000,
|
|
2008
|
+
limit: 300,
|
|
2009
|
+
standardHeaders: 'draft-7',
|
|
2010
|
+
legacyHeaders: false,
|
|
2011
|
+
skip: () => process.env.NODE_ENV === 'test',
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
} catch (_e) {
|
|
2015
|
+
// Fall back to built-in core/rate-limit when express-rate-limit peer is not installed
|
|
2016
|
+
}
|
|
2017
|
+
if (!fragmentLimiter) {
|
|
2018
|
+
const { rateLimit } = require('../core/rate-limit');
|
|
2019
|
+
fragmentLimiter = rateLimit({
|
|
2020
|
+
window: 60000,
|
|
2021
|
+
limit: 300,
|
|
2022
|
+
skip: () => process.env.NODE_ENV === 'test',
|
|
2023
|
+
});
|
|
2024
|
+
}
|
|
2007
2025
|
|
|
2008
2026
|
if (typeof app.get === 'function') {
|
|
2009
2027
|
app.get('/__webspresso/fragments', fragmentLimiter, fragmentRefreshHandler);
|
|
@@ -120,12 +120,30 @@ function mountDiscoveredRoutes(app, routeTable, context) {
|
|
|
120
120
|
resolveMiddlewareList: (mwList) => resolveMiddlewares(mwList, context.middlewares || {}),
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
let fragmentLimiter;
|
|
124
|
+
try {
|
|
125
|
+
const expressRateLimit = require('express-rate-limit');
|
|
126
|
+
const rateLimitFn = expressRateLimit.rateLimit || expressRateLimit;
|
|
127
|
+
if (typeof rateLimitFn === 'function') {
|
|
128
|
+
fragmentLimiter = rateLimitFn({
|
|
129
|
+
windowMs: 60000,
|
|
130
|
+
limit: 300,
|
|
131
|
+
standardHeaders: 'draft-7',
|
|
132
|
+
legacyHeaders: false,
|
|
133
|
+
skip: () => process.env.NODE_ENV === 'test',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
} catch (_e) {
|
|
137
|
+
// Fall back to built-in core/rate-limit when express-rate-limit peer is not installed
|
|
138
|
+
}
|
|
139
|
+
if (!fragmentLimiter) {
|
|
140
|
+
const { rateLimit } = require('../../core/rate-limit');
|
|
141
|
+
fragmentLimiter = rateLimit({
|
|
142
|
+
window: 60000,
|
|
143
|
+
limit: 300,
|
|
144
|
+
skip: () => process.env.NODE_ENV === 'test',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
129
147
|
|
|
130
148
|
app.get('/__webspresso/fragments', fragmentLimiter, fragmentRefreshHandler);
|
|
131
149
|
app.post('/__webspresso/fragments', fragmentLimiter, fragmentRefreshHandler);
|