stitchkit 0.72.5 → 0.74.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.
@@ -72,6 +72,62 @@ function cancellationError(cause) {
72
72
  return new RequestCancellationError(cause);
73
73
  }
74
74
 
75
+ // src/browser/http.ts
76
+ import ky, {
77
+ isHTTPError,
78
+ isNetworkError,
79
+ isTimeoutError
80
+ } from "ky";
81
+
82
+ // src/browser/request-id.ts
83
+ var REQUEST_ID_HEADER = "x-request-id";
84
+ function responseTraceId(response) {
85
+ return response?.headers.get(REQUEST_ID_HEADER) ?? undefined;
86
+ }
87
+
88
+ // src/browser/http.ts
89
+ var API_ERROR_BRAND = Symbol.for("stitchkit.ApiError");
90
+ function messageForCode(code, message) {
91
+ return message !== undefined && message.length > 0 ? message : `${code} (no message supplied)`;
92
+ }
93
+
94
+ class ApiError extends Error {
95
+ code;
96
+ status;
97
+ details;
98
+ hint;
99
+ traceId;
100
+ constructor(code, status = 0, details, message, hint, traceId, options) {
101
+ super(messageForCode(code, message), options);
102
+ this.code = code;
103
+ this.status = status;
104
+ this.details = details;
105
+ this.hint = hint;
106
+ this.traceId = traceId;
107
+ this.name = "ApiError";
108
+ Object.defineProperty(this, API_ERROR_BRAND, { value: true });
109
+ }
110
+ static is(error) {
111
+ return typeof error === "object" && error !== null && API_ERROR_BRAND in error;
112
+ }
113
+ }
114
+ function refuseLocally(path, message) {
115
+ return new ApiError("VALIDATION_ERROR", 0, { issues: [{ path, code: "invalid_type", message }] }, message);
116
+ }
117
+ function parseApiErrorBody(body) {
118
+ if (!isRecord(body) || !isRecord(body.error))
119
+ return null;
120
+ const error = body.error;
121
+ if (typeof error.code !== "string")
122
+ return null;
123
+ return {
124
+ code: error.code,
125
+ message: typeof error.message === "string" ? error.message : undefined,
126
+ details: error.details,
127
+ hint: typeof error.hint === "string" ? error.hint : undefined
128
+ };
129
+ }
130
+
75
131
  // src/browser/client-multipart.ts
76
132
  function isFileDescriptor(value) {
77
133
  return typeof value === "object" && value !== null && !(value instanceof Blob) && "uri" in value && typeof value.uri === "string" && "name" in value && typeof value.name === "string" && "type" in value && typeof value.type === "string";
@@ -96,24 +152,26 @@ function buildMultipartForm(descriptor, values) {
96
152
  for (const [field, policy] of Object.entries(descriptor.files)) {
97
153
  const value = values[field];
98
154
  if (value === undefined) {
99
- if (policy.required !== false)
100
- throw new Error(`Missing multipart file field: ${field}`);
155
+ if (policy.required !== false) {
156
+ throw refuseLocally(field, `Missing multipart file field: ${field}`);
157
+ }
101
158
  continue;
102
159
  }
103
160
  if (policy.multiple === true) {
104
161
  if (!Array.isArray(value) || value.length === 0) {
105
- throw new Error(`Multipart file field "${field}" must be a non-empty array`);
162
+ throw refuseLocally(field, `Multipart file field "${field}" must be a non-empty array`);
106
163
  }
107
164
  for (const file of value) {
108
165
  if (!isMultipartFile(file)) {
109
- throw new Error(`Invalid multipart file field: ${field}`);
166
+ throw refuseLocally(field, `Invalid multipart file field: ${field}`);
110
167
  }
111
168
  appendMultipartFile(formData, field, file);
112
169
  }
113
170
  continue;
114
171
  }
115
- if (!isMultipartFile(value))
116
- throw new Error(`Invalid multipart file field: ${field}`);
172
+ if (!isMultipartFile(value)) {
173
+ throw refuseLocally(field, `Invalid multipart file field: ${field}`);
174
+ }
117
175
  appendMultipartFile(formData, field, value);
118
176
  }
119
177
  appendFormFields(formData, values, fileFields);
@@ -136,7 +194,7 @@ function collectQueryParams(args, endpoint) {
136
194
  continue;
137
195
  }
138
196
  const what = Array.isArray(value) ? "an array with non-primitive items" : typeof value === "object" ? "a nested object" : `a ${typeof value}`;
139
- throw new Error(`${endpoint.method} ${endpoint.path}: input field "${key}" is ${what} — it cannot ` + "travel as a query parameter. GET / DELETE input must be flat (string / number / " + "boolean, or an array of string / number); flatten the field or move the " + "operation to a body verb (POST).");
197
+ throw refuseLocally(key, `${endpoint.method} ${endpoint.path}: input field "${key}" is ${what} — it cannot ` + "travel as a query parameter. GET / DELETE input must be flat (string / number / " + "boolean, or an array of string / number); flatten the field or move the " + "operation to a body verb (POST).");
140
198
  }
141
199
  return hasParams ? params : undefined;
142
200
  }
@@ -155,7 +213,7 @@ function resolvePathPrefix(config, args) {
155
213
  const keys = config.stripPrefixKeys ?? [];
156
214
  if (!hasStringKeys(args, keys)) {
157
215
  const missing = keys.find((key) => typeof args[key] !== "string");
158
- throw new Error(`Missing path prefix key: ${missing}`);
216
+ throw refuseLocally(String(missing), `Missing path prefix key: ${missing}`);
159
217
  }
160
218
  return config.pathPrefix(args);
161
219
  }
@@ -172,7 +230,7 @@ function fillPathParams(path, args) {
172
230
  let filled = path.replace(/:(\w+)/g, (_, key) => {
173
231
  const value = args[key];
174
232
  if (value === undefined || value === null) {
175
- throw new Error(`Missing path param: ${key}`);
233
+ throw refuseLocally(String(key), `Missing path param: ${key}`);
176
234
  }
177
235
  return encodeURIComponent(String(value));
178
236
  });
@@ -180,7 +238,7 @@ function fillPathParams(path, args) {
180
238
  return filled;
181
239
  const wildcardValue = args[wildcard.name];
182
240
  if (wildcardValue === undefined || wildcardValue === null) {
183
- throw new Error(`Missing path param: ${wildcard.name}`);
241
+ throw refuseLocally(wildcard.name, `Missing path param: ${wildcard.name}`);
184
242
  }
185
243
  const remainder = String(wildcardValue).split("/").map((segment) => encodeURIComponent(segment)).join("/");
186
244
  filled = `${filled.slice(0, -(wildcard.name.length + 1))}${remainder}`;
@@ -232,56 +290,6 @@ function joinClientBaseUrl(baseUrl, relativeUrl) {
232
290
  const path = relativeUrl.startsWith("/") ? relativeUrl : `/${relativeUrl}`;
233
291
  return `${base}${path}`;
234
292
  }
235
-
236
- // src/browser/http.ts
237
- import ky, {
238
- isHTTPError,
239
- isNetworkError,
240
- isTimeoutError
241
- } from "ky";
242
-
243
- // src/browser/request-id.ts
244
- var REQUEST_ID_HEADER = "x-request-id";
245
- function responseTraceId(response) {
246
- return response?.headers.get(REQUEST_ID_HEADER) ?? undefined;
247
- }
248
-
249
- // src/browser/http.ts
250
- var API_ERROR_BRAND = Symbol.for("stitchkit.ApiError");
251
-
252
- class ApiError extends Error {
253
- code;
254
- status;
255
- details;
256
- hint;
257
- traceId;
258
- constructor(code, status = 0, details, message, hint, traceId, options) {
259
- super(message ?? `API Error: ${code}`, options);
260
- this.code = code;
261
- this.status = status;
262
- this.details = details;
263
- this.hint = hint;
264
- this.traceId = traceId;
265
- this.name = "ApiError";
266
- Object.defineProperty(this, API_ERROR_BRAND, { value: true });
267
- }
268
- static is(error) {
269
- return typeof error === "object" && error !== null && API_ERROR_BRAND in error;
270
- }
271
- }
272
- function parseApiErrorBody(body) {
273
- if (!isRecord(body) || !isRecord(body.error))
274
- return null;
275
- const error = body.error;
276
- if (typeof error.code !== "string")
277
- return null;
278
- return {
279
- code: error.code,
280
- message: typeof error.message === "string" ? error.message : undefined,
281
- details: error.details,
282
- hint: typeof error.hint === "string" ? error.hint : undefined
283
- };
284
- }
285
293
  // src/browser/stream.ts
286
294
  function parseFailure(raw, error, onParseError) {
287
295
  const failure = error instanceof Error ? error : new Error(String(error));
@@ -462,16 +470,35 @@ function setClientMethod(target, key, method) {
462
470
  function createEndpointMethod(endpoint, execute, contractConfig) {
463
471
  const hasScopedArguments = (contractConfig?.stripPrefixKeys?.length ?? 0) > 0;
464
472
  if (endpointHasArguments(endpoint) || hasScopedArguments) {
465
- const method2 = (requestArgs) => execute(readClientRequestArgs(requestArgs), undefined);
473
+ const method2 = (requestArgs) => settle(execute, readClientRequestArgs(requestArgs), undefined);
466
474
  return Object.assign(method2, {
467
- withOptions: (requestArgs, options) => execute(readClientRequestArgs(requestArgs), readClientRequestOptions(options))
475
+ withOptions: (...args) => {
476
+ refuseExtraWithOptionsArguments(endpoint, args.length, 2);
477
+ return settle(execute, readClientRequestArgs(args[0]), readClientRequestOptions(args[1]));
478
+ }
468
479
  });
469
480
  }
470
- const method = () => execute({}, undefined);
481
+ const method = () => settle(execute, {}, undefined);
471
482
  return Object.assign(method, {
472
- withOptions: (options) => execute({}, readClientRequestOptions(options))
483
+ withOptions: (...args) => {
484
+ refuseExtraWithOptionsArguments(endpoint, args.length, 1);
485
+ return settle(execute, {}, readClientRequestOptions(args[0]));
486
+ }
473
487
  });
474
488
  }
489
+ function settle(execute, requestArgs, options) {
490
+ try {
491
+ return execute(requestArgs, options);
492
+ } catch (error) {
493
+ return Promise.reject(error);
494
+ }
495
+ }
496
+ function refuseExtraWithOptionsArguments(endpoint, received, expected) {
497
+ if (received <= expected)
498
+ return;
499
+ const shape = expected === 1 ? "withOptions(options)" : "withOptions(args, options)";
500
+ throw new TypeError(`${endpoint.method} ${endpoint.path}: this endpoint declares ${expected === 1 ? "no input" : "an input"}, so its method is ${shape} — it received ${received} arguments. ` + "An extra argument here is dropped, and a request options object in the dropped position " + "sends the request without them: an abort signal placed there never reaches the server.");
501
+ }
475
502
  function createHttpExecutor(endpoint, prefix, client, config) {
476
503
  const httpMethod = endpoint.method.toLowerCase();
477
504
  return (requestArgs, options) => {
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export type { BindRealtimeClientOptions, BoundRealtimeClient, RealtimeClient, Re
6
6
  export { bindRealtimeClient, createRealtimeClient, createSocketIOClient, } from './browser/socket-io.js';
7
7
  export { type ParseNDJSONOptions, type ParseSSEOptions, parseNDJSON, parseSSE, } from './browser/stream.js';
8
8
  export * from './contract/index.js';
9
+ export { formatZodError, type ZodIssueSummary, zodIssues } from './internal/errors.js';
9
10
  export type { StitchLogger } from './logger.js';
10
11
  export { childSpan, createTraceContext, formatTraceparent, parseTraceparent, type TraceContext, } from './observability/trace.js';
11
12
  export * from './realtime/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,+BAA+B,EAC/B,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,wBAAwB,GAC9B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,mBAAmB,EACnB,aAAa,EACb,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,yBAAyB,EACzB,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,WAAW,EACX,QAAQ,GACT,MAAM,kBAAkB,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,+BAA+B,EAC/B,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,wBAAwB,GAC9B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,aAAa,EAClB,mBAAmB,EACnB,aAAa,EACb,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,yBAAyB,EACzB,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,WAAW,EACX,QAAQ,GACT,MAAM,kBAAkB,CAAC;AAC1B,cAAc,YAAY,CAAC;AAK3B,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACpF,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC"}