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/core/validation/index.js
CHANGED
|
@@ -28,6 +28,31 @@ if (z.ZodType && z.ZodType.prototype && typeof z.ZodType.prototype._refinement =
|
|
|
28
28
|
};
|
|
29
29
|
wrappedRefinement.__webspresso_wrapped = true;
|
|
30
30
|
z.ZodType.prototype._refinement = wrappedRefinement;
|
|
31
|
+
} else if (z.ZodType && z.ZodType.prototype && typeof z.ZodType.prototype.refine === 'function' && !z.ZodType.prototype.refine.__webspresso_wrapped) {
|
|
32
|
+
const origRefine = z.ZodType.prototype.refine;
|
|
33
|
+
const wrappedRefine = function (check, params) {
|
|
34
|
+
const wrappedCheck = typeof check === 'function'
|
|
35
|
+
? (val) => {
|
|
36
|
+
const res = check(val);
|
|
37
|
+
if (res && typeof res.catch === 'function') {
|
|
38
|
+
res.catch(() => {});
|
|
39
|
+
}
|
|
40
|
+
return res;
|
|
41
|
+
}
|
|
42
|
+
: check;
|
|
43
|
+
return origRefine.call(this, wrappedCheck, params);
|
|
44
|
+
};
|
|
45
|
+
wrappedRefine.__webspresso_wrapped = true;
|
|
46
|
+
z.ZodType.prototype.refine = wrappedRefine;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (z.ZodError && z.ZodError.prototype && !Object.getOwnPropertyDescriptor(z.ZodError.prototype, 'errors')) {
|
|
50
|
+
Object.defineProperty(z.ZodError.prototype, 'errors', {
|
|
51
|
+
get() {
|
|
52
|
+
return this.issues;
|
|
53
|
+
},
|
|
54
|
+
configurable: true,
|
|
55
|
+
});
|
|
31
56
|
}
|
|
32
57
|
|
|
33
58
|
/**
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod v3 & v4 Introspection Helpers
|
|
3
|
+
* Provides a unified abstraction over Zod v3 (`_def.typeName`) and Zod v4 (`_zod.def.type`)
|
|
4
|
+
* for ORM column inference, TypeGen, API TypeGen, and OpenAPI/MCP JSON Schema generation.
|
|
5
|
+
* @module core/validation/zod-introspect
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const z = require('zod');
|
|
9
|
+
|
|
10
|
+
const V4_TYPE_MAP = Object.freeze({
|
|
11
|
+
string: 'ZodString',
|
|
12
|
+
number: 'ZodNumber',
|
|
13
|
+
boolean: 'ZodBoolean',
|
|
14
|
+
date: 'ZodDate',
|
|
15
|
+
bigint: 'ZodBigInt',
|
|
16
|
+
any: 'ZodAny',
|
|
17
|
+
unknown: 'ZodUnknown',
|
|
18
|
+
never: 'ZodNever',
|
|
19
|
+
undefined: 'ZodUndefined',
|
|
20
|
+
void: 'ZodVoid',
|
|
21
|
+
null: 'ZodNull',
|
|
22
|
+
literal: 'ZodLiteral',
|
|
23
|
+
enum: 'ZodEnum',
|
|
24
|
+
array: 'ZodArray',
|
|
25
|
+
optional: 'ZodOptional',
|
|
26
|
+
nullable: 'ZodNullable',
|
|
27
|
+
default: 'ZodDefault',
|
|
28
|
+
prefault: 'ZodDefault',
|
|
29
|
+
transform: 'ZodEffects',
|
|
30
|
+
pipe: 'ZodPipeline',
|
|
31
|
+
union: 'ZodUnion',
|
|
32
|
+
object: 'ZodObject',
|
|
33
|
+
record: 'ZodRecord',
|
|
34
|
+
tuple: 'ZodTuple',
|
|
35
|
+
set: 'ZodSet',
|
|
36
|
+
map: 'ZodMap',
|
|
37
|
+
nonoptional: 'ZodNonOptional',
|
|
38
|
+
readonly: 'ZodReadonly',
|
|
39
|
+
catch: 'ZodCatch',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function getZodDef(schema) {
|
|
43
|
+
if (!schema || typeof schema !== 'object') return null;
|
|
44
|
+
return (schema._zod && schema._zod.def) || schema._def || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getZodTypeName(schema) {
|
|
48
|
+
const def = getZodDef(schema);
|
|
49
|
+
if (!def) return null;
|
|
50
|
+
if (def.typeName) return def.typeName;
|
|
51
|
+
if (typeof def.type === 'string') {
|
|
52
|
+
return V4_TYPE_MAP[def.type] || null;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isZodSchema(val) {
|
|
58
|
+
return Boolean(val && typeof val === 'object' && (val._zod || val._def) && typeof val.safeParse === 'function');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getZodInner(schema) {
|
|
62
|
+
const def = getZodDef(schema);
|
|
63
|
+
if (!def) return null;
|
|
64
|
+
return def.innerType || def.schema || def.in || null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getZodPipeOut(schema) {
|
|
68
|
+
const def = getZodDef(schema);
|
|
69
|
+
if (!def) return null;
|
|
70
|
+
return def.out || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getZodArrayElement(schema) {
|
|
74
|
+
const def = getZodDef(schema);
|
|
75
|
+
if (!def) return null;
|
|
76
|
+
if (def.element) return def.element;
|
|
77
|
+
if (def.type && typeof def.type === 'object') return def.type;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getZodObjectShape(schema) {
|
|
82
|
+
if (!schema || typeof schema !== 'object') return {};
|
|
83
|
+
if (typeof schema.shape === 'object' && schema.shape !== null) return schema.shape;
|
|
84
|
+
const def = getZodDef(schema);
|
|
85
|
+
if (!def) return {};
|
|
86
|
+
if (typeof def.shape === 'function') return def.shape() || {};
|
|
87
|
+
if (typeof def.shape === 'object' && def.shape !== null) return def.shape;
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getZodEnumValues(schema) {
|
|
92
|
+
const def = getZodDef(schema);
|
|
93
|
+
if (!def) return [];
|
|
94
|
+
if (Array.isArray(def.values)) return def.values;
|
|
95
|
+
if (def.entries && typeof def.entries === 'object') return Object.values(def.entries);
|
|
96
|
+
if (def.values && typeof def.values === 'object') return Object.values(def.values);
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getZodLiteralValue(schema) {
|
|
101
|
+
const def = getZodDef(schema);
|
|
102
|
+
if (!def) return undefined;
|
|
103
|
+
if (def.value !== undefined) return def.value;
|
|
104
|
+
if (Array.isArray(def.values)) return def.values[0];
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isZodInt(schema) {
|
|
109
|
+
const def = getZodDef(schema);
|
|
110
|
+
if (!def) return false;
|
|
111
|
+
if (def.format === 'safeint' || def.format === 'int32' || def.format === 'uint32') return true;
|
|
112
|
+
if (!Array.isArray(def.checks)) return false;
|
|
113
|
+
return def.checks.some((c) => {
|
|
114
|
+
if (!c) return false;
|
|
115
|
+
if (c.kind === 'int') return true;
|
|
116
|
+
const cDef = c._zod && c._zod.def;
|
|
117
|
+
return Boolean(cDef && cDef.check === 'number_format' && (cDef.format === 'safeint' || cDef.format === 'int32'));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isZodCoerced(schema) {
|
|
122
|
+
const def = getZodDef(schema);
|
|
123
|
+
return Boolean(def && def.coerce);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function zodToJsonSchemaCompat(schema, options = {}) {
|
|
127
|
+
if (typeof z.toJSONSchema === 'function') {
|
|
128
|
+
try {
|
|
129
|
+
const target = options.target === 'openApi3' ? 'openapi-3.0' : (options.target || 'draft-7');
|
|
130
|
+
const jsonSchema = z.toJSONSchema(schema, {
|
|
131
|
+
target,
|
|
132
|
+
unrepresentable: 'any',
|
|
133
|
+
});
|
|
134
|
+
if (jsonSchema && typeof jsonSchema === 'object') {
|
|
135
|
+
delete jsonSchema.$schema;
|
|
136
|
+
}
|
|
137
|
+
return jsonSchema;
|
|
138
|
+
} catch (_) {
|
|
139
|
+
// Fall through to legacy helper if needed
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const { zodToJsonSchema } = require('zod-to-json-schema');
|
|
143
|
+
return zodToJsonSchema(schema, options);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Apply Zod v3 compatibility shims onto Zod v4 prototypes (idempotent).
|
|
148
|
+
* - Exposes `ZodError.prototype.errors` -> `this.issues`
|
|
149
|
+
*/
|
|
150
|
+
function ensureZodCompat(zodModule = z) {
|
|
151
|
+
if (!zodModule) return;
|
|
152
|
+
|
|
153
|
+
const sampleError = typeof zodModule.string === 'function'
|
|
154
|
+
? zodModule.string().safeParse(123)?.error
|
|
155
|
+
: null;
|
|
156
|
+
const errProtos = [
|
|
157
|
+
sampleError ? Object.getPrototypeOf(sampleError) : null,
|
|
158
|
+
zodModule.ZodError?.prototype,
|
|
159
|
+
];
|
|
160
|
+
for (const errProto of errProtos) {
|
|
161
|
+
if (errProto && !Object.getOwnPropertyDescriptor(errProto, 'errors')) {
|
|
162
|
+
Object.defineProperty(errProto, 'errors', {
|
|
163
|
+
get() {
|
|
164
|
+
return this.issues;
|
|
165
|
+
},
|
|
166
|
+
configurable: true,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
getZodDef,
|
|
174
|
+
getZodTypeName,
|
|
175
|
+
isZodSchema,
|
|
176
|
+
getZodInner,
|
|
177
|
+
getZodPipeOut,
|
|
178
|
+
getZodArrayElement,
|
|
179
|
+
getZodObjectShape,
|
|
180
|
+
getZodEnumValues,
|
|
181
|
+
getZodLiteralValue,
|
|
182
|
+
isZodInt,
|
|
183
|
+
isZodCoerced,
|
|
184
|
+
zodToJsonSchemaCompat,
|
|
185
|
+
ensureZodCompat,
|
|
186
|
+
};
|
|
187
|
+
|
package/index.d.ts
CHANGED
|
@@ -5075,6 +5075,11 @@ export interface DefineApiOptions<
|
|
|
5075
5075
|
middleware?: (string | import('express').RequestHandler | [string, Record<string, unknown>])[];
|
|
5076
5076
|
flag?: string | { key: string; statusCode?: number; message?: string };
|
|
5077
5077
|
schema?: TSchema;
|
|
5078
|
+
validateFields?: boolean | string[];
|
|
5079
|
+
validate?: (
|
|
5080
|
+
req: ApiRequest<TBody, TParams, TQuery, THeaders>,
|
|
5081
|
+
ctx: { body: TBody; fields: string[]; req: ApiRequest<TBody, TParams, TQuery, THeaders>; res: ApiResponse<TBody, TParams, TQuery, THeaders> }
|
|
5082
|
+
) => Promise<Record<string, string> | null | undefined> | Record<string, string> | null | undefined;
|
|
5078
5083
|
response?: Record<number | string, unknown>;
|
|
5079
5084
|
handler: (
|
|
5080
5085
|
req: ApiRequest<TBody, TParams, TQuery, THeaders>,
|
|
@@ -5094,6 +5099,16 @@ export function defineApi<
|
|
|
5094
5099
|
>(
|
|
5095
5100
|
config: {
|
|
5096
5101
|
schema: TSchema;
|
|
5102
|
+
validateFields?: boolean | string[];
|
|
5103
|
+
validate?: (
|
|
5104
|
+
req: ApiRequest<ApiNoInfer<TBody>, ApiNoInfer<TParams>, ApiNoInfer<TQuery>, ApiNoInfer<THeaders>>,
|
|
5105
|
+
ctx: {
|
|
5106
|
+
body: ApiNoInfer<TBody>;
|
|
5107
|
+
fields: string[];
|
|
5108
|
+
req: ApiRequest<ApiNoInfer<TBody>, ApiNoInfer<TParams>, ApiNoInfer<TQuery>, ApiNoInfer<THeaders>>;
|
|
5109
|
+
res: ApiResponse<ApiNoInfer<TBody>, ApiNoInfer<TParams>, ApiNoInfer<TQuery>, ApiNoInfer<THeaders>>;
|
|
5110
|
+
}
|
|
5111
|
+
) => Promise<Record<string, string> | null | undefined> | Record<string, string> | null | undefined;
|
|
5097
5112
|
description?: string;
|
|
5098
5113
|
tags?: string[];
|
|
5099
5114
|
middleware?: (string | import('express').RequestHandler | [string, Record<string, unknown>])[];
|
|
@@ -6367,6 +6382,9 @@ export class ClientRequestError<TData = unknown> extends Error {
|
|
|
6367
6382
|
readonly isTimedOut: boolean;
|
|
6368
6383
|
readonly isDropped: boolean;
|
|
6369
6384
|
readonly isSuperseded: boolean;
|
|
6385
|
+
readonly dispatched: boolean;
|
|
6386
|
+
readonly outcomeUnknown: boolean;
|
|
6387
|
+
readonly outcome: 'unknown' | 'failed' | 'not_sent';
|
|
6370
6388
|
readonly cause?: unknown;
|
|
6371
6389
|
constructor(options: {
|
|
6372
6390
|
message: string;
|
|
@@ -6383,6 +6401,9 @@ export class ClientRequestError<TData = unknown> extends Error {
|
|
|
6383
6401
|
isTimedOut?: boolean;
|
|
6384
6402
|
isDropped?: boolean;
|
|
6385
6403
|
isSuperseded?: boolean;
|
|
6404
|
+
dispatched?: boolean;
|
|
6405
|
+
outcomeUnknown?: boolean;
|
|
6406
|
+
outcome?: 'unknown' | 'failed' | 'not_sent';
|
|
6386
6407
|
});
|
|
6387
6408
|
}
|
|
6388
6409
|
|
|
@@ -6411,6 +6432,12 @@ export interface ClientRequestCallOptions<
|
|
|
6411
6432
|
timeout?: number;
|
|
6412
6433
|
retry?: boolean | number | ClientRetryOptions;
|
|
6413
6434
|
retryMutations?: boolean;
|
|
6435
|
+
onUploadProgress?: (progress: {
|
|
6436
|
+
loaded: number;
|
|
6437
|
+
total: number;
|
|
6438
|
+
percent: number | null;
|
|
6439
|
+
lengthComputable: boolean;
|
|
6440
|
+
}) => void;
|
|
6414
6441
|
}
|
|
6415
6442
|
|
|
6416
6443
|
export interface AppApiRouteMap {
|
|
@@ -6440,6 +6467,8 @@ export interface ClientTaskOptions {
|
|
|
6440
6467
|
concurrency?: ClientConcurrencyPolicy;
|
|
6441
6468
|
policy?: ClientConcurrencyPolicy;
|
|
6442
6469
|
key?: string;
|
|
6470
|
+
mutation?: boolean;
|
|
6471
|
+
kind?: 'query' | 'mutation';
|
|
6443
6472
|
staleTime?: number | string;
|
|
6444
6473
|
refetchOnMount?: ClientRefetchPolicy;
|
|
6445
6474
|
refetchOnVisit?: ClientRefetchPolicy;
|
|
@@ -6714,6 +6743,8 @@ export interface ClientFormSubmitResult<TData = any> {
|
|
|
6714
6743
|
hookError?: Error | null;
|
|
6715
6744
|
error?: ClientRequestError | Error;
|
|
6716
6745
|
aborted?: boolean;
|
|
6746
|
+
outcomeUnknown?: boolean;
|
|
6747
|
+
outcome?: 'unknown' | 'failed' | 'not_sent';
|
|
6717
6748
|
}
|
|
6718
6749
|
|
|
6719
6750
|
export interface ClientFormHookMeta<
|
|
@@ -6738,6 +6769,9 @@ export interface ClientFormHandle<
|
|
|
6738
6769
|
readonly services: ClientServicesRegistry<TServices>;
|
|
6739
6770
|
readonly pending: boolean;
|
|
6740
6771
|
readonly dirty: boolean;
|
|
6772
|
+
readonly touched: boolean;
|
|
6773
|
+
readonly touchedFields: string[];
|
|
6774
|
+
readonly submitted: boolean;
|
|
6741
6775
|
readonly inputRevision: number;
|
|
6742
6776
|
readonly draftKey: string;
|
|
6743
6777
|
readonly baseline: TValues;
|
|
@@ -6746,6 +6780,34 @@ export interface ClientFormHandle<
|
|
|
6746
6780
|
readonly lastError: ClientRequestError | Error | null;
|
|
6747
6781
|
readonly fieldErrors: Partial<Record<keyof TValues & string, string>> & Record<string, string>;
|
|
6748
6782
|
disabled: boolean;
|
|
6783
|
+
isTouched(fieldName?: keyof TValues & string | string): boolean;
|
|
6784
|
+
touch(fieldName?: keyof TValues & string | string): this;
|
|
6785
|
+
untouch(fieldName?: keyof TValues & string | string): this;
|
|
6786
|
+
validateField(
|
|
6787
|
+
fieldName: (keyof TValues & string) | string,
|
|
6788
|
+
options?: { url?: string; method?: string; contextFields?: string[] | '*' }
|
|
6789
|
+
): Promise<{
|
|
6790
|
+
ok: boolean;
|
|
6791
|
+
valid?: boolean;
|
|
6792
|
+
stale?: boolean;
|
|
6793
|
+
canceled?: boolean;
|
|
6794
|
+
networkError?: boolean;
|
|
6795
|
+
errors?: Record<string, string>;
|
|
6796
|
+
error?: Error;
|
|
6797
|
+
}>;
|
|
6798
|
+
readonly uploads: Array<{
|
|
6799
|
+
field: string;
|
|
6800
|
+
name: string;
|
|
6801
|
+
size: number;
|
|
6802
|
+
status: 'uploading' | 'done' | 'error' | 'canceled';
|
|
6803
|
+
loaded: number;
|
|
6804
|
+
total: number;
|
|
6805
|
+
percent: number | null;
|
|
6806
|
+
error: string | null;
|
|
6807
|
+
}>;
|
|
6808
|
+
cancelUpload(): boolean;
|
|
6809
|
+
createPreviewUrl(file: File | Blob, fieldName?: string): string | null;
|
|
6810
|
+
revokePreviewUrls(fieldName?: string): void;
|
|
6749
6811
|
values(): TValues;
|
|
6750
6812
|
read(submitter?: HTMLElement | null): TValues;
|
|
6751
6813
|
fill(values: Partial<TValues> & Record<string, unknown>, options?: ClientFormFillOptions): this;
|
|
@@ -6801,10 +6863,12 @@ export interface ControllerFormsRegistry<TServices = AppClientServices> {
|
|
|
6801
6863
|
|
|
6802
6864
|
export interface FragmentRefreshResult {
|
|
6803
6865
|
ok: boolean;
|
|
6866
|
+
partial?: boolean;
|
|
6804
6867
|
refreshed: string[];
|
|
6805
6868
|
errors?: Error[];
|
|
6806
6869
|
skipped?: boolean;
|
|
6807
6870
|
canceled?: boolean;
|
|
6871
|
+
superseded?: boolean;
|
|
6808
6872
|
error?: Error | null;
|
|
6809
6873
|
timings?: {
|
|
6810
6874
|
loadMs: number;
|
|
@@ -6816,6 +6880,12 @@ export interface FragmentRefreshOptions {
|
|
|
6816
6880
|
replaceInput?: boolean;
|
|
6817
6881
|
timeout?: number;
|
|
6818
6882
|
throwOnError?: boolean;
|
|
6883
|
+
mode?: 'replace' | 'append' | 'prepend';
|
|
6884
|
+
limit?: number;
|
|
6885
|
+
target?: string;
|
|
6886
|
+
removeKeys?: string[];
|
|
6887
|
+
onBeforePatch?: (el: Element, inst: any) => void;
|
|
6888
|
+
onAfterPatch?: (el: Element, inst: any) => void;
|
|
6819
6889
|
onSuccess?: (result: FragmentRefreshResult) => void | Promise<void>;
|
|
6820
6890
|
onFailure?: (error: Error) => void | Promise<void>;
|
|
6821
6891
|
onError?: (error: Error) => void | Promise<void>;
|
|
@@ -6825,6 +6895,7 @@ export interface FragmentRefreshOptions {
|
|
|
6825
6895
|
|
|
6826
6896
|
export interface ControllerFragmentHandle<TInput extends Record<string, any> = Record<string, any>> {
|
|
6827
6897
|
readonly input: TInput;
|
|
6898
|
+
readonly inputs: Record<string, TInput>;
|
|
6828
6899
|
refresh(
|
|
6829
6900
|
nextInput?: Partial<TInput> & Record<string, unknown>,
|
|
6830
6901
|
options?: FragmentRefreshOptions
|
|
@@ -6836,10 +6907,17 @@ export interface ControllerErrorMeta {
|
|
|
6836
6907
|
task?: string;
|
|
6837
6908
|
event?: Event;
|
|
6838
6909
|
actionEl?: HTMLElement | null;
|
|
6839
|
-
phase?: 'action' | 'task' | 'connect' | 'disconnect';
|
|
6910
|
+
phase?: 'action' | 'task' | 'connect' | 'disconnect' | 'beforePatch' | 'afterPatch';
|
|
6840
6911
|
[key: string]: unknown;
|
|
6841
6912
|
}
|
|
6842
6913
|
|
|
6914
|
+
export interface FragmentPatchHookMeta {
|
|
6915
|
+
fragmentId?: string | null;
|
|
6916
|
+
fragmentName?: string | null;
|
|
6917
|
+
isFragmentRoot?: boolean;
|
|
6918
|
+
isDescendant?: boolean;
|
|
6919
|
+
}
|
|
6920
|
+
|
|
6843
6921
|
export type WatchSource<T = any> =
|
|
6844
6922
|
| (() => T)
|
|
6845
6923
|
| string
|
|
@@ -6988,6 +7066,22 @@ export interface ControllerDefinition<
|
|
|
6988
7066
|
TServices
|
|
6989
7067
|
>
|
|
6990
7068
|
) => void;
|
|
7069
|
+
beforePatch?: (
|
|
7070
|
+
ctx: ControllerContext<
|
|
7071
|
+
[TState][TState extends any ? 0 : never] & { readonly [K in keyof TComputed]: ReturnType<TComputed[K]> },
|
|
7072
|
+
Record<string, ClientTask>,
|
|
7073
|
+
TServices
|
|
7074
|
+
>,
|
|
7075
|
+
meta: FragmentPatchHookMeta
|
|
7076
|
+
) => void;
|
|
7077
|
+
afterPatch?: (
|
|
7078
|
+
ctx: ControllerContext<
|
|
7079
|
+
[TState][TState extends any ? 0 : never] & { readonly [K in keyof TComputed]: ReturnType<TComputed[K]> },
|
|
7080
|
+
Record<string, ClientTask>,
|
|
7081
|
+
TServices
|
|
7082
|
+
>,
|
|
7083
|
+
meta: FragmentPatchHookMeta
|
|
7084
|
+
) => void | Promise<void>;
|
|
6991
7085
|
onError?: ControllerErrorHandler<
|
|
6992
7086
|
[TState][TState extends any ? 0 : never] & { readonly [K in keyof TComputed]: ReturnType<TComputed[K]> },
|
|
6993
7087
|
Record<string, ClientTask>,
|
|
@@ -7052,6 +7146,9 @@ export interface FragmentDefinition<TInput = Record<string, unknown>, TData = Re
|
|
|
7052
7146
|
refetchOnVisit?: ClientRefetchPolicy;
|
|
7053
7147
|
refetchInterval?: number | string | false;
|
|
7054
7148
|
refetchIntervalInBackground?: boolean;
|
|
7149
|
+
lazy?: boolean;
|
|
7150
|
+
placeholder?: string | ((ctx: { input: TInput; instanceId: string; name: string }) => string);
|
|
7151
|
+
placeholderTemplate?: string;
|
|
7055
7152
|
load?: (context: FragmentLoaderContext<TInput>) => TData | Promise<TData>;
|
|
7056
7153
|
}
|
|
7057
7154
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webspresso",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.113",
|
|
4
4
|
"description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"bench:ci:local": "npm run bench:baseline && npm run bench:ci",
|
|
101
101
|
"bench:profile": "softscope --top 30 node scripts/profile-bench.mjs",
|
|
102
102
|
"bench:profile:ai": "softscope --top 40 -o softscope-bench.ai node scripts/profile-bench.mjs",
|
|
103
|
-
"check:types": "tsc --project tests/ts-smoke/tsconfig.json && tsc --noEmit --skipLibCheck false --types node index.d.ts",
|
|
103
|
+
"check:types": "tsc --project tests/ts-smoke/tsconfig.json && tsc --noEmit --skipLibCheck false --esModuleInterop --types node index.d.ts",
|
|
104
104
|
"rebuild:native": "npm rebuild better-sqlite3 bcrypt sharp",
|
|
105
105
|
"docs:dev": "docboot dev ./docs",
|
|
106
106
|
"docs:build": "docboot build ./docs",
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
"qs": "^6.17.1",
|
|
149
149
|
"sanitize-html": "^2.18.1",
|
|
150
150
|
"yaml": "^2.8.3",
|
|
151
|
-
"zod": "^
|
|
151
|
+
"zod": "^4.6.7",
|
|
152
152
|
"zod-to-json-schema": "^3.25.2"
|
|
153
153
|
},
|
|
154
154
|
"peerDependencies": {
|
|
@@ -218,7 +218,7 @@
|
|
|
218
218
|
"@swup/scripts-plugin": "^2.1.0",
|
|
219
219
|
"@types/express": "^5.0.0",
|
|
220
220
|
"@types/node": "^20.14.0",
|
|
221
|
-
"@vitest/coverage-v8": "^4.1.
|
|
221
|
+
"@vitest/coverage-v8": "^4.1.12",
|
|
222
222
|
"bcrypt": "^6.0.0",
|
|
223
223
|
"better-sqlite3": "^11.10.0",
|
|
224
224
|
"chokidar": "^4.0.3",
|
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
"dotenv": "^16.3.1",
|
|
227
227
|
"exceljs": "^4.4.0",
|
|
228
228
|
"express-rate-limit": "^8.4.1",
|
|
229
|
-
"hono": "^4.13.
|
|
229
|
+
"hono": "^4.13.16",
|
|
230
230
|
"mjml": "^5.4.0",
|
|
231
231
|
"nodemailer": "^10.0.10",
|
|
232
232
|
"release-it": "^20.2.1",
|
|
@@ -235,14 +235,14 @@
|
|
|
235
235
|
"supertest": "^6.3.4",
|
|
236
236
|
"swup": "^4.8.3",
|
|
237
237
|
"typescript": "~5.6.3",
|
|
238
|
-
"vitest": "^4.1.
|
|
238
|
+
"vitest": "^4.1.12",
|
|
239
239
|
"vitest-doctor": "^0.3.0"
|
|
240
240
|
},
|
|
241
241
|
"engines": {
|
|
242
242
|
"node": ">=20.0.0"
|
|
243
243
|
},
|
|
244
244
|
"overrides": {
|
|
245
|
-
"basic-ftp": "^6.2.
|
|
245
|
+
"basic-ftp": "^6.2.4",
|
|
246
246
|
"chokidar": "^4.0.3",
|
|
247
247
|
"katex": "^0.18.2",
|
|
248
248
|
"tar": "^7.5.11",
|
|
@@ -251,7 +251,8 @@
|
|
|
251
251
|
"js-yaml": "^5.4.2",
|
|
252
252
|
"qs": "^6.17.1",
|
|
253
253
|
"sanitize-html": "^2.18.1",
|
|
254
|
-
"vite": "^8.3.5"
|
|
254
|
+
"vite": "^8.3.5",
|
|
255
|
+
"minimatch@>=7.0.0 <=11.0.0": "^11.0.8"
|
|
255
256
|
},
|
|
256
257
|
"allowScripts": {
|
|
257
258
|
"bcrypt@6.0.0": true,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module plugins/mcp/providers/services
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const { zodToJsonSchemaCompat } = require('../../../core/validation/zod-introspect');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Normalizes a dot-separated service name to an MCP-safe tool name
|
|
@@ -99,7 +99,7 @@ function convertZodToInputSchema(schema) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
try {
|
|
102
|
-
const rawSchema =
|
|
102
|
+
const rawSchema = zodToJsonSchemaCompat(schema, {
|
|
103
103
|
$refStrategy: 'none',
|
|
104
104
|
removeAdditionalStrategy: 'strict',
|
|
105
105
|
});
|
package/src/api/define-api.js
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* @param {Array<string|Function>} [config.middleware] - Named or functional middlewares
|
|
14
14
|
* @param {string|{ key: string, statusCode?: number, message?: string }} [config.flag] - Feature flag required to access this endpoint
|
|
15
15
|
* @param {Object|Function} [config.schema] - Request schemas (params, query, body, headers)
|
|
16
|
+
* @param {boolean|string[]} [config.validateFields] - Opt-in field-level validation without running mutation handler
|
|
17
|
+
* @param {Function} [config.validate] - Optional async read-only validation hook: async (ctx, fields) => ({ field: 'msg' })
|
|
16
18
|
* @param {Object} [config.response] - Expected response schemas by status code
|
|
17
19
|
* @param {Function} config.handler - Request handler: async (req, ctx) => data
|
|
18
20
|
* @returns {Object}
|
|
@@ -94,8 +94,12 @@ function translateSingleIssue(issue, t) {
|
|
|
94
94
|
|
|
95
95
|
const pathKey = Array.isArray(issue.path) && issue.path.length > 0 ? issue.path.join('.') : '';
|
|
96
96
|
const field = Array.isArray(issue.path) && issue.path.length > 0 ? issue.path[issue.path.length - 1] : '';
|
|
97
|
-
const isRequired = (issue.code === 'invalid_type' && (
|
|
98
|
-
|
|
97
|
+
const isRequired = (issue.code === 'invalid_type' && (
|
|
98
|
+
issue.received === 'undefined' ||
|
|
99
|
+
issue.input === undefined ||
|
|
100
|
+
issue.message === 'Required' ||
|
|
101
|
+
/received undefined$/i.test(issue.message || '')
|
|
102
|
+
)) || issue.message === 'Required';
|
|
99
103
|
|
|
100
104
|
const params = Object.assign(
|
|
101
105
|
{
|
package/src/bundle/index.js
CHANGED
|
@@ -944,17 +944,34 @@ function buildBundleFallback({ cwd, entryPoints, outdir, publicDir, manifestPath
|
|
|
944
944
|
const emittedChunksBySource = new Map(); // absSourcePath -> { absOut, relFromOutdir, relOutput }
|
|
945
945
|
|
|
946
946
|
function transpileSource(absFile, rawCode) {
|
|
947
|
+
let code = rawCode;
|
|
947
948
|
if ((absFile.endsWith('.ts') || absFile.endsWith('.tsx')) && ts) {
|
|
948
|
-
const transpiled = ts.transpileModule(
|
|
949
|
+
const transpiled = ts.transpileModule(code, {
|
|
949
950
|
compilerOptions: {
|
|
950
951
|
module: ts.ModuleKind.ESNext,
|
|
951
952
|
target: ts.ScriptTarget.ES2020,
|
|
952
953
|
},
|
|
953
954
|
fileName: absFile,
|
|
954
955
|
});
|
|
955
|
-
|
|
956
|
+
code = transpiled.outputText;
|
|
956
957
|
}
|
|
957
|
-
return
|
|
958
|
+
return code.replace(/\bprocess\.env\.NODE_ENV\b/g, '"production"');
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function extractCjsExportNames(code) {
|
|
962
|
+
const match = code.match(/module\.exports\s*=\s*\{([\s\S]*?)\};?\s*$/);
|
|
963
|
+
if (!match) return [];
|
|
964
|
+
const body = match[1].replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
|
965
|
+
const names = new Set();
|
|
966
|
+
const propRegex = /^\s*(?:get\s+|set\s+)?([A-Za-z_$][A-Za-z0-9_$]*)\s*(?:,|:|\()/gm;
|
|
967
|
+
let m;
|
|
968
|
+
while ((m = propRegex.exec(body)) !== null) {
|
|
969
|
+
const name = m[1];
|
|
970
|
+
if (name && name !== 'get' && name !== 'set' && name !== 'function') {
|
|
971
|
+
names.add(name);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return Array.from(names);
|
|
958
975
|
}
|
|
959
976
|
|
|
960
977
|
function emitChunkModule(absModulePath) {
|
|
@@ -1038,6 +1055,53 @@ function buildBundleFallback({ cwd, entryPoints, outdir, publicDir, manifestPath
|
|
|
1038
1055
|
}
|
|
1039
1056
|
);
|
|
1040
1057
|
|
|
1058
|
+
// 3. Transform CommonJS modules (`module.exports = ...` / `require('./...')`) into native ESM chunks
|
|
1059
|
+
if (/\bmodule\.exports\b/.test(rewritten)) {
|
|
1060
|
+
const cjsImports = [];
|
|
1061
|
+
let depSeq = 0;
|
|
1062
|
+
const depVarBySpec = new Map();
|
|
1063
|
+
const exportNames = extractCjsExportNames(rewritten);
|
|
1064
|
+
|
|
1065
|
+
rewritten = rewritten.replace(
|
|
1066
|
+
/\b(?:reqFn|require)\s*\(\s*(['"])(\.[^'"]+)\1\s*\)/g,
|
|
1067
|
+
(fullMatch, quote, spec) => {
|
|
1068
|
+
if (spec === './debug' || spec === './devtool' || spec === './server-fragments') {
|
|
1069
|
+
return '{}';
|
|
1070
|
+
}
|
|
1071
|
+
const resolved = resolveLocalModuleFile(importerAbsPath, spec);
|
|
1072
|
+
if (!resolved) return fullMatch;
|
|
1073
|
+
const chunkInfo = emitChunkModule(resolved);
|
|
1074
|
+
if (!chunkInfo || !chunkInfo.absOut) return fullMatch;
|
|
1075
|
+
let relSpec = path.relative(emitterOutDir, chunkInfo.absOut).replace(/\\/g, '/');
|
|
1076
|
+
if (!relSpec.startsWith('.')) relSpec = './' + relSpec;
|
|
1077
|
+
let depVar = depVarBySpec.get(relSpec);
|
|
1078
|
+
if (!depVar) {
|
|
1079
|
+
depVar = `__cjs_dep_${++depSeq}`;
|
|
1080
|
+
depVarBySpec.set(relSpec, depVar);
|
|
1081
|
+
cjsImports.push(`import ${depVar} from ${quote}${relSpec}${quote};`);
|
|
1082
|
+
collectedImports.push({ path: chunkInfo.relOutput, kind: 'import-statement' });
|
|
1083
|
+
}
|
|
1084
|
+
return depVar;
|
|
1085
|
+
}
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
const strippedBody = rewritten.replace(/^\s*['"]use strict['"];?\s*/, '');
|
|
1089
|
+
const namedExportLines = exportNames.map(
|
|
1090
|
+
(name) => `export const ${name} = __cjs_exports.${name};`
|
|
1091
|
+
);
|
|
1092
|
+
rewritten = [
|
|
1093
|
+
...cjsImports,
|
|
1094
|
+
'const __cjs_exports = (() => {',
|
|
1095
|
+
'const module = { exports: {} };',
|
|
1096
|
+
'const exports = module.exports;',
|
|
1097
|
+
strippedBody,
|
|
1098
|
+
'return module.exports;',
|
|
1099
|
+
'})();',
|
|
1100
|
+
'export default __cjs_exports;',
|
|
1101
|
+
...namedExportLines,
|
|
1102
|
+
].join('\n');
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1041
1105
|
return rewritten;
|
|
1042
1106
|
}
|
|
1043
1107
|
|