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.
@@ -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.112",
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": "^3.23.0",
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.11",
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.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.11",
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.1",
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 { zodToJsonSchema } = require('zod-to-json-schema');
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 = zodToJsonSchema(schema, {
102
+ const rawSchema = zodToJsonSchemaCompat(schema, {
103
103
  $refStrategy: 'none',
104
104
  removeAdditionalStrategy: 'strict',
105
105
  });
@@ -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' && (issue.received === 'undefined' || issue.message === 'Required'))
98
- || issue.message === 'Required';
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
  {
@@ -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(rawCode, {
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
- return transpiled.outputText;
956
+ code = transpiled.outputText;
956
957
  }
957
- return rawCode;
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