@b3-business/cherry 0.2.7 → 0.3.1

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/README.md CHANGED
@@ -10,11 +10,9 @@ A tree-shakeable, minimal API client factory. Import only the routes you need
10
10
 
11
11
  ---
12
12
 
13
- ## Latest Changelog - 0.2.7
13
+ ## Latest Changelog - 0.3.1
14
14
 
15
- - README: Reconcile documentation with actual API (`createCherryClient`, `route`, `path` tagged template)
16
- - README: Remove unimplemented OpenAPI generator section
17
- - Example: JSONPlaceholder now uses sub-namespaced routes (`posts.list`, `users.get`)
15
+ - Fix: Add `@types/bun` to monorepo root for editor TypeScript support
18
16
 
19
17
  See [CHANGELOG.md](https://github.com/b3-business/cherry/blob/main/packages/cherry/CHANGELOG.md) for full history.
20
18
 
@@ -139,20 +137,163 @@ const zones = await cf.call(listZones, { account_id: "abc" });
139
137
 
140
138
  ## Features
141
139
 
142
- ### Dynamic Path Parameters
140
+ ### Path Parameters
141
+
142
+ Use `path` tagged template with `param()` markers for dynamic URL segments:
143
143
 
144
144
  ```ts
145
- export const getZone = route({
145
+ import { route, path, param } from "@b3b/cherry";
146
+
147
+ export const getUser = route({
148
+ method: "GET",
149
+ path: path`/users/${param("id")}`,
150
+ pathParams: v.object({ id: v.number() }),
151
+ response: UserSchema,
152
+ });
153
+
154
+ // Multiple params
155
+ export const getComment = route({
146
156
  method: "GET",
147
- path: path`/zones/${param("zone_id")}`,
148
- pathParams: v.object({ zone_id: v.string() }),
149
- response: v.object({ /* ... */ }),
157
+ path: path`/posts/${param("postId")}/comments/${param("commentId")}`,
158
+ pathParams: v.object({ postId: v.number(), commentId: v.number() }),
159
+ response: CommentSchema,
150
160
  });
151
161
  ```
152
162
 
163
+ ### Optional Path Parameters
164
+
165
+ Use `optional()` for optional URL segments:
166
+
167
+ ```ts
168
+ import { route, path, optional } from "@b3b/cherry";
169
+
170
+ export const getApiResource = route({
171
+ method: "GET",
172
+ path: path`/api${optional("version")}/resource`,
173
+ pathParams: v.object({ version: v.optional(v.string()) }),
174
+ response: ResourceSchema,
175
+ });
176
+ ```
177
+
178
+ ### Query Parameters
179
+
180
+ Define query string parameters with their own schema:
181
+
182
+ ```ts
183
+ export const listUsers = route({
184
+ method: "GET",
185
+ path: path`/users`,
186
+ queryParams: v.object({
187
+ page: v.optional(v.number()),
188
+ limit: v.optional(v.number()),
189
+ search: v.optional(v.string()),
190
+ }),
191
+ response: v.array(UserSchema),
192
+ });
193
+
194
+ // Usage: client.listUsers({ page: 1, limit: 10, search: "john" })
195
+ // → GET /users?page=1&limit=10&search=john
196
+ ```
197
+
198
+ ### Query Array Formats
199
+
200
+ Configure how arrays are serialized in query strings:
201
+
202
+ ```ts
203
+ export const filterItems = route({
204
+ method: "GET",
205
+ path: path`/items`,
206
+ queryParams: v.object({ tags: v.array(v.string()) }),
207
+ queryParamOptions: { arrayFormat: "comma" }, // or "repeat" | "brackets" | "json"
208
+ response: v.array(ItemSchema),
209
+ });
210
+
211
+ // arrayFormat examples for tags=["a","b"]:
212
+ // "repeat" → ?tags=a&tags=b (default)
213
+ // "comma" → ?tags=a,b
214
+ // "brackets" → ?tags[]=a&tags[]=b
215
+ // "json" → ?tags=["a","b"]
216
+ ```
217
+
218
+ ### Body Parameters
219
+
220
+ Define request body schema for POST/PUT/PATCH:
221
+
222
+ ```ts
223
+ export const createPost = route({
224
+ method: "POST",
225
+ path: path`/posts`,
226
+ bodyParams: v.object({
227
+ title: v.string(),
228
+ body: v.string(),
229
+ userId: v.number(),
230
+ }),
231
+ response: PostSchema,
232
+ });
233
+ ```
234
+
235
+ ### Namespaced Routes
236
+
237
+ Group related routes into nested objects for better organization:
238
+
239
+ ```ts
240
+ // routes.ts
241
+ export const posts = {
242
+ list: route({ method: "GET", path: path`/posts`, response: v.array(PostSchema) }),
243
+ get: route({ method: "GET", path: path`/posts/${param("id")}`, pathParams: v.object({ id: v.number() }), response: PostSchema }),
244
+ create: route({ method: "POST", path: path`/posts`, bodyParams: PostInputSchema, response: PostSchema }),
245
+ };
246
+
247
+ export const users = {
248
+ list: route({ method: "GET", path: path`/users`, response: v.array(UserSchema) }),
249
+ get: route({ method: "GET", path: path`/users/${param("id")}`, pathParams: v.object({ id: v.number() }), response: UserSchema }),
250
+ };
251
+
252
+ // client.ts
253
+ const api = createCherryClient({
254
+ baseUrl: "https://api.example.com",
255
+ routes: { posts, users },
256
+ });
257
+
258
+ // Usage with namespacing
259
+ await api.posts.list({});
260
+ await api.posts.get({ id: 1 });
261
+ await api.users.get({ id: 42 });
262
+ ```
263
+
264
+ ### Dynamic Headers
265
+
266
+ Provide headers dynamically (supports async for token refresh):
267
+
268
+ ```ts
269
+ const client = createCherryClient({
270
+ baseUrl: "https://api.example.com",
271
+ headers: async () => ({
272
+ Authorization: `Bearer ${await getAccessToken()}`,
273
+ "X-Request-ID": crypto.randomUUID(),
274
+ }),
275
+ routes: { /* ... */ },
276
+ });
277
+ ```
278
+
279
+ ### Generic `call()` Method
280
+
281
+ Call any route without registering it in the client:
282
+
283
+ ```ts
284
+ import { createCherryClient } from "@b3b/cherry";
285
+ import { getUser, listPosts } from "./routes";
286
+
287
+ const client = createCherryClient({ baseUrl: "https://api.example.com" });
288
+
289
+ // Works with any route - useful for one-off calls or dynamic route selection
290
+ const user = await client.call(getUser, { id: 1 });
291
+ const posts = await client.call(listPosts, {});
292
+ ```
293
+
153
294
  ### Custom Fetcher
154
295
 
155
- Replace the underlying fetch logic for logging, retries, auth refresh, etc.
296
+ Replace the underlying fetch logic for logging, retries, auth refresh, etc.:
156
297
 
157
298
  ```ts
158
299
  createCherryClient({
@@ -196,6 +337,101 @@ createCherryClient({
196
337
  });
197
338
  ```
198
339
 
340
+ ### Railway-Oriented Error Handling
341
+
342
+ All client methods return `ResultAsync<T, CherryError>` from [neverthrow](https://github.com/supermacro/neverthrow):
343
+
344
+ ```ts
345
+ const result = await client.posts.get({ id: 1 });
346
+
347
+ // Pattern 1: Check and unwrap
348
+ if (result.isOk()) {
349
+ console.log(result.value); // typed as Post
350
+ } else {
351
+ console.error(result.error); // typed as CherryError
352
+ }
353
+
354
+ // Pattern 2: Map/chain operations
355
+ const title = await client.posts.get({ id: 1 })
356
+ .map(post => post.title)
357
+ .unwrapOr("Unknown");
358
+
359
+ // Pattern 3: Match both cases
360
+ result.match(
361
+ (post) => console.log(`Got: ${post.title}`),
362
+ (error) => console.error(`Failed: ${error.message}`),
363
+ );
364
+ ```
365
+
366
+ ### Typed Error Hierarchy
367
+
368
+ All errors extend `CherryError` with `type` and `retryable` properties:
369
+
370
+ ```ts
371
+ import {
372
+ CherryError,
373
+ HttpError, // HTTP 4xx/5xx (retryable for 5xx and 429)
374
+ ValidationError, // Valibot schema validation failed
375
+ NetworkError, // fetch() threw (always retryable)
376
+ SerializationError, // JSON serialization failed
377
+ UnknownCherryError, // Catch-all
378
+ isCherryError,
379
+ } from "@b3b/cherry";
380
+
381
+ const result = await client.posts.get({ id: 1 });
382
+
383
+ if (result.isErr()) {
384
+ const err = result.error;
385
+
386
+ if (err instanceof HttpError) {
387
+ console.log(err.status, err.statusText, err.body);
388
+ if (err.retryable) { /* retry logic */ }
389
+ }
390
+
391
+ if (err instanceof ValidationError) {
392
+ console.log(err.target); // "request" or "response"
393
+ console.log(err.issues); // Valibot issues array
394
+ }
395
+ }
396
+ ```
397
+
398
+ ### Type Inference Utilities
399
+
400
+ Extract input/output types from route definitions:
401
+
402
+ ```ts
403
+ import type { InferRouteInput, InferRouteOutput } from "@b3b/cherry";
404
+ import { getUser } from "./routes";
405
+
406
+ type GetUserInput = InferRouteInput<typeof getUser>;
407
+ // { id: number }
408
+
409
+ type GetUserOutput = InferRouteOutput<typeof getUser>;
410
+ // { id: number; name: string; email: string; ... }
411
+ ```
412
+
413
+ ### Route Validation at Definition Time
414
+
415
+ Routes validate configuration immediately — catch mistakes during development:
416
+
417
+ ```ts
418
+ // ❌ Throws: "Route has path params [id] but no pathParams schema"
419
+ const bad = route({
420
+ method: "GET",
421
+ path: path`/users/${param("id")}`,
422
+ response: UserSchema,
423
+ // Missing pathParams!
424
+ });
425
+
426
+ // ❌ Throws: "Path param ':userId' not found in pathParams schema"
427
+ const mismatch = route({
428
+ method: "GET",
429
+ path: path`/users/${param("userId")}`,
430
+ pathParams: v.object({ id: v.number() }), // Wrong key!
431
+ response: UserSchema,
432
+ });
433
+ ```
434
+
199
435
  ---
200
436
 
201
437
  ## Design Principles
package/dist/index.d.ts CHANGED
@@ -54,6 +54,7 @@ declare function isCherryError(error: unknown): error is CherryError;
54
54
  declare function cherryErr<T>(error: CherryError): ResultAsync<T, CherryError>;
55
55
  //#endregion
56
56
  //#region src/types.d.ts
57
+ type AnySchema = BaseSchema<any, any, any>;
57
58
  /** HTTP methods supported by Cherry */
58
59
  type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
59
60
  /** Path template result from path() tagged template */
@@ -62,7 +63,7 @@ type PathTemplate = {
62
63
  paramNames: string[];
63
64
  };
64
65
  /** Route definition with separated parameter schemas */
65
- type CherryRoute<TPathParams extends BaseSchema<any, any, any> | undefined = undefined, TQueryParams extends BaseSchema<any, any, any> | undefined = undefined, TBodyParams extends BaseSchema<any, any, any> | undefined = undefined, TResponse extends BaseSchema<any, any, any> = BaseSchema<any, any, any>> = {
66
+ type CherryRoute<TPathParams extends AnySchema | undefined = undefined, TQueryParams extends AnySchema | undefined = undefined, TBodyParams extends AnySchema | undefined = undefined, TResponse extends AnySchema = AnySchema> = {
66
67
  method: HttpMethod;
67
68
  path: PathTemplate;
68
69
  pathParams?: TPathParams;
@@ -77,11 +78,18 @@ type QueryParamOptions = {
77
78
  arrayFormat?: "repeat" | "comma" | "brackets" | "json";
78
79
  customSerializer?: (params: Record<string, unknown>) => string;
79
80
  };
81
+ type AnyCherryRoute = CherryRoute<any, any, any, any>;
80
82
  type Prettify<T> = { [K in keyof T]: T[K] } & {};
81
- /** Infer combined input params from a route */
82
- type InferRouteInput<T> = T extends CherryRoute<infer TPath, infer TQuery, infer TBody, any> ? Prettify<(TPath extends BaseSchema<any, any, any> ? InferInput<TPath> : {}) & (TQuery extends BaseSchema<any, any, any> ? InferInput<TQuery> : {}) & (TBody extends BaseSchema<any, any, any> ? InferInput<TBody> : {})> : never;
83
+ type IsEmptyObject<T> = keyof T extends never ? true : false;
84
+ type InferSchemaInput<T> = T extends AnySchema ? InferInput<T> : {};
85
+ type BuildRouteInputFromProps<TPathParams, TQueryParams, TBodyParams> = InferSchemaInput<TPathParams> & InferSchemaInput<TQueryParams> & InferSchemaInput<TBodyParams>;
86
+ type InferRouteInput<T> = T extends {
87
+ pathParams?: infer TPath;
88
+ queryParams?: infer TQuery;
89
+ bodyParams?: infer TBody;
90
+ } ? IsEmptyObject<BuildRouteInputFromProps<TPath, TQuery, TBody>> extends true ? void : Prettify<BuildRouteInputFromProps<TPath, TQuery, TBody>> : never;
83
91
  /** Infer response output from a route */
84
- type InferRouteOutput<T extends CherryRoute<any, any, any, any>> = T["response"] extends BaseSchema<any, any, any> ? InferOutput<T["response"]> : never;
92
+ type InferRouteOutput<T extends AnyCherryRoute> = T["response"] extends AnySchema ? InferOutput<T["response"]> : never;
85
93
  /** Cherry result type - always ResultAsync */
86
94
  type CherryResult<T> = ResultAsync<T, CherryError>;
87
95
  /** Fetcher request shape (extensible for middleware) */
@@ -93,7 +101,7 @@ type FetchRequest = {
93
101
  type Fetcher = (req: FetchRequest) => Promise<Response>;
94
102
  /** Route tree (supports namespacing via nested objects) */
95
103
  type RouteTree = {
96
- [key: string]: CherryRoute<any, any, any, any> | RouteTree;
104
+ [key: string]: AnyCherryRoute | RouteTree;
97
105
  };
98
106
  /** Client configuration */
99
107
  type ClientConfig<TRoutes extends RouteTree | undefined = undefined> = {
@@ -103,27 +111,17 @@ type ClientConfig<TRoutes extends RouteTree | undefined = undefined> = {
103
111
  routes?: TRoutes;
104
112
  };
105
113
  type Client<TRoutes extends RouteTree | undefined = undefined> = {
106
- call: <T extends CherryRoute<any, any, any, any>>(route: T, params: InferRouteInput<T>) => CherryResult<InferRouteOutput<T>>;
114
+ call: <T extends AnyCherryRoute>(route: T, ...args: InferRouteInput<T> extends void ? [] : [params: InferRouteInput<T>]) => CherryResult<InferRouteOutput<T>>;
107
115
  } & (TRoutes extends RouteTree ? RoutesToClient<TRoutes> : {});
108
116
  /** Convert a nested route tree into a nested client method tree */
109
- type RoutesToClient<TRoutes extends RouteTree> = { [K in keyof TRoutes]: TRoutes[K] extends CherryRoute<any, any, any, any> ? (params: InferRouteInput<TRoutes[K]>) => CherryResult<InferRouteOutput<TRoutes[K]>> : TRoutes[K] extends RouteTree ? RoutesToClient<TRoutes[K]> : never };
117
+ type RoutesToClient<TRoutes extends RouteTree> = { [K in keyof TRoutes]: TRoutes[K] extends AnyCherryRoute ? InferRouteInput<TRoutes[K]> extends void ? () => CherryResult<InferRouteOutput<TRoutes[K]>> : (params: InferRouteInput<TRoutes[K]>) => CherryResult<InferRouteOutput<TRoutes[K]>> : TRoutes[K] extends RouteTree ? RoutesToClient<TRoutes[K]> : never };
110
118
  //#endregion
111
119
  //#region src/cherry_client.d.ts
112
120
  declare function serializeQueryParams(params: Record<string, unknown>, options?: QueryParamOptions): string;
113
121
  declare function createCherryClient<TRoutes extends RouteTree | undefined = undefined>(config: ClientConfig<TRoutes>): Client<TRoutes>;
114
122
  //#endregion
115
123
  //#region src/route.d.ts
116
- type RouteConfig<TPathParams extends v.BaseSchema<any, any, any> | undefined, TQueryParams extends v.BaseSchema<any, any, any> | undefined, TBodyParams extends v.BaseSchema<any, any, any> | undefined, TResponse extends v.BaseSchema<any, any, any>> = {
117
- method: HttpMethod;
118
- path: PathTemplate;
119
- pathParams?: TPathParams;
120
- queryParams?: TQueryParams;
121
- bodyParams?: TBodyParams;
122
- response: TResponse;
123
- queryParamOptions?: QueryParamOptions;
124
- description?: string;
125
- };
126
- declare function route<TPathParams extends v.BaseSchema<any, any, any> | undefined, TQueryParams extends v.BaseSchema<any, any, any> | undefined, TBodyParams extends v.BaseSchema<any, any, any> | undefined, TResponse extends v.BaseSchema<any, any, any>>(config: RouteConfig<TPathParams, TQueryParams, TBodyParams, TResponse>): CherryRoute<TPathParams, TQueryParams, TBodyParams, TResponse>;
124
+ declare function route<TPathParams extends v.BaseSchema<any, any, any> | undefined = undefined, TQueryParams extends v.BaseSchema<any, any, any> | undefined = undefined, TBodyParams extends v.BaseSchema<any, any, any> | undefined = undefined, TResponse extends v.BaseSchema<any, any, any> = v.BaseSchema<any, any, any>>(config: CherryRoute<TPathParams, TQueryParams, TBodyParams, TResponse>): CherryRoute<TPathParams, TQueryParams, TBodyParams, TResponse>;
127
125
  //#endregion
128
126
  //#region src/path.d.ts
129
127
  /** Branded type for path parameter markers */
@@ -166,5 +164,5 @@ declare function optional<T extends string>(name: T): OptionalParam<T>;
166
164
  */
167
165
  declare function path(strings: TemplateStringsArray, ...params: AnyPathParam[]): PathTemplate;
168
166
  //#endregion
169
- export { type AnyPathParam, CherryError, type CherryResult, type CherryRoute, type Client, type ClientConfig, type FetchRequest, type Fetcher, HttpError, type HttpMethod, type InferRouteInput, type InferRouteOutput, NetworkError, type OptionalParam, type PathParam, type PathTemplate, type QueryParamOptions, type RouteConfig, type RouteTree, type RoutesToClient, SerializationError, UnknownCherryError, ValidationError, cherryErr, createCherryClient, isCherryError, optional, param, path, route, serializeQueryParams };
167
+ export { type AnyPathParam, CherryError, type CherryResult, type CherryRoute, type Client, type ClientConfig, type FetchRequest, type Fetcher, HttpError, type HttpMethod, type InferRouteInput, type InferRouteOutput, NetworkError, type OptionalParam, type PathParam, type PathTemplate, type QueryParamOptions, type RouteTree, type RoutesToClient, SerializationError, UnknownCherryError, ValidationError, cherryErr, createCherryClient, isCherryError, optional, param, path, route, serializeQueryParams };
170
168
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/cherry_client.ts","../src/route.ts","../src/path.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/cherry_client.ts","../src/route.ts","../src/path.ts"],"sourcesContent":[],"mappings":";;;;;;uBAGsB,WAAA,SAAoB,KAAA;;;EAApB,WAAA,CAAA,OAAY,EAAA,MAAQ,EAAA,OA2B7B,CA3BkC,EAAA;IAWlC,KAAA,CAAA,EAAU,OAAA;EAgBV,CAAA;AAcb;AAUA;AAca,cAtDA,SAAA,SAAkB,WAAA,CAsDoB;EAUnC,SAAA,MAAA,EAAa,MAAA;EAKb,SAAA,UAAS,EAAA,MAAA;EAAW,SAAA,IAAA,CAAA,EAAA,OAAA,GAAA,SAAA;EAA0B,SAAA,IAAA,GAAA,WAAA;EAAG,SAAA,SAAA,EAAA,OAAA;EAAf,WAAA,CAAA,MAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,IAAA,CAAA,EAAA,OAAA,GAAA,SAAA,EAAA,KAAA,CAAA,EAAA,OAAA;;;cArDrC,eAAA,SAAwB,WAAA;;EC1BhC,SAAA,MAAS,EAAA,OAAG,EAAA;EAGL,SAAA,IAAU,GAAA,iBAAA;EAGV,SAAA,SAAY,GAAA,KAAA;EAMZ,WAAA,CAAA,MAAW,EAAA,SAAA,GAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EAAA,EAAA,KAAA,CAAA,EAAA,OAAA;;;AAGD,cDyBT,YAAA,SAAqB,WAAA,CCzBZ;EACF,SAAA,IAAA,GAAA,cAAA;EAAY,SAAA,SAAA,GAAA,IAAA;EAEtB,WAAA,CAAA,KAAA,CAAA,EAAA,OAAA;;;AAGM,cD6BH,kBAAA,SAA2B,WAAA,CC7BxB;EACD,SAAA,MAAA,EAAA,OAAA,GAAA,MAAA;EACH,SAAA,GAAA,EAAA,MAAA;EACU,SAAA,IAAA,GAAA,oBAAA;EAAiB,SAAA,SAAA,GAAA,KAAA;EAK3B,WAAA,CAAA,MAAA,EAAiB,OAAA,GAAA,MAEC,EAAM,GAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,OAAA;AAClC;AAE+B;AAEA,cD4BpB,kBAAA,SAA2B,WAAA,CC5BP;EAAI,SAAA,IAAA,GAAA,oBAAA;EAAE,SAAA,SAAA,GAAA,KAAA;EAAC,WAAA,CAAA,KAAA,CAAA,EAAA,OAAA;AAAA;AAET;AAEJ,iBDkCX,aAAA,CClCW,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IDkC6B,WClC7B;;AAAiC,iBDuC5C,SCvC4C,CAAA,CAAA,CAAA,CAAA,KAAA,EDuCxB,WCvCwB,CAAA,EDuCV,WCvCU,CDuCE,CCvCF,EDuCK,WCvCL,CAAA;;;KAxCvD,SAAA,GAAY;;ADDK,KCIV,UAAA,GDJsB,KAAQ,GAAA,MAAK,GAAA,KAAA,GAAA,OAAA,GAAA,QAAA;AAW/C;AAgBa,KCpBD,YAAA,GDoBiB;EAchB,QAAA,EAAA,MAAa;EAUb,UAAA,EAAA,MAAA,EAAA;AAcb,CAAA;AAUA;AAKgB,KCnEJ,WDmEa,CAAA,oBClEH,SDkEG,GAAA,SAAA,GAAA,SAAA,EAAA,qBCjEF,SDiEE,GAAA,SAAA,GAAA,SAAA,EAAA,oBChEH,SDgEG,GAAA,SAAA,GAAA,SAAA,EAAA,kBC/DL,SD+DK,GC/DO,SD+DP,CAAA,GAAA;EAAW,MAAA,EC7D1B,UD6D0B;EAA0B,IAAA,EC5DtD,YD4DsD;EAAG,UAAA,CAAA,EC3DlD,WD2DkD;EAAf,WAAA,CAAA,EC1DlC,YD0DkC;EAAW,UAAA,CAAA,ECzD9C,WDyD8C;YCxDjD;sBACU;;AA1BsB,CAAA;AAK5C;AAGY,KAuBA,iBAAA,GAvBY;EAMZ,WAAA,CAAA,EAAW,QAAA,GAAA,OAAA,GAAA,UAAA,GAAA,MAAA;EACD,gBAAA,CAAA,EAAA,CAAA,MAAA,EAkBQ,MAlBR,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,GAAA,MAAA;CACC;KAoBlB,cAAA,GAAiB,WAnBA,CAAA,GAAA,EAAA,GAAA,EAAA,GAAA,EAAA,GAAA,CAAA;KAqBjB,QApBe,CAAA,CAAA,CAAA,GAAA,QAAY,MAoBC,CApBD,GAoBK,CApBL,CAoBO,CApBP,CAAA,EAEtB,GAAA,CAAA,CAAA;KAoBL,aAnBG,CAAA,CAAA,CAAA,GAAA,MAmBsB,CAnBtB,SAAA,KAAA,GAAA,IAAA,GAAA,KAAA;KAqBH,gBApBU,CAAA,CAAA,CAAA,GAoBY,CApBZ,SAoBsB,SApBtB,GAoBkC,UApBlC,CAoB6C,CApB7C,CAAA,GAAA,CAAA,CAAA;KAsBV,wBArBW,CAAA,WAAA,EAAA,YAAA,EAAA,WAAA,CAAA,GAyBZ,gBAzBY,CAyBK,WAzBL,CAAA,GA0BZ,gBA1BY,CA0BK,YA1BL,CAAA,GA2BZ,gBA3BY,CA2BK,WA3BL,CAAA;AACD,KA4BH,eA5BG,CAAA,CAAA,CAAA,GA6Bb,CA7Ba,SAAA;EACH,UAAA,CAAA,EAAA,KAAA,MAAA;EACU,WAAA,CAAA,EAAA,KAAA,OAAA;EAAiB,UAAA,CAAA,EAAA,KAAA,MAAA;AAKvC,CAAA,GAuBM,aAvBM,CAuBQ,wBArBgB,CAqBS,KArBT,EAqBgB,MArBhB,EAqBwB,KArBxB,CAAA,CAAA,SAAA,IAAA,GAAA,IAAA,GAuB5B,QAvB4B,CAuBnB,wBAvBmB,CAuBM,KAvBN,EAuBa,MAvBb,EAuBqB,KAvBrB,CAAA,CAAA,GAAA,KAAA;AAClC;AAIG,KAsBO,gBAtBC,CAAA,UAsB0B,cAtB1B,CAAA,GAuBX,CAvBW,CAAA,UAAA,CAAA,SAuBW,SAvBX,GAuBuB,WAvBvB,CAuBmC,CAvBnC,CAAA,UAAA,CAAA,CAAA,GAAA,KAAA;;AAAwB,KA0BzB,YA1ByB,CAAA,CAAA,CAAA,GA0BP,WA1BO,CA0BK,CA1BL,EA0BQ,WA1BR,CAAA;;AAAG,KA6B5B,YAAA,GA7B4B;EAEnC,GAAA,EAAA,MAAA;EAEA,IAAA,EA2BG,WA3BH;CAAsB;;AAAiC,KA+BhD,OAAA,GA/BgD,CAAA,GAAA,EA+BhC,YA/BgC,EAAA,GA+Bf,OA/Be,CA+BP,QA/BO,CAAA;;AAAD,KAkC/C,SAAA,GAlC+C;EAEtD,CAAA,GAAA,EAAA,MAAA,CAAA,EAiCY,cAjCY,GAiCK,SAjCL;CAIR;;AACA,KAgCT,YAhCS,CAAA,gBAgCoB,SAhCpB,GAAA,SAAA,GAAA,SAAA,CAAA,GAAA;EAAjB,OAAA,EAAA,MAAA;EACiB,OAAA,CAAA,EAAA,GAAA,GAiCH,MAjCG,CAAA,MAAA,EAAA,MAAA,CAAA,GAiCsB,OAjCtB,CAiC8B,MAjC9B,CAAA,MAAA,EAAA,MAAA,CAAA,CAAA;EAAjB,OAAA,CAAA,EAkCQ,OAlCR;EAAgB,MAAA,CAAA,EAmCT,OAnCS;AAEpB,CAAA;AACE,KAmCU,MAnCV,CAAA,gBAmCiC,SAnCjC,GAAA,SAAA,GAAA,SAAA,CAAA,GAAA;EAC2C,IAAA,EAAA,CAAA,UAmC1B,cAnC0B,CAAA,CAAA,KAAA,EAoClC,CApCkC,EAAA,GAAA,IAAA,EAqChC,eArCgC,CAqChB,CArCgB,CAAA,SAAA,IAAA,GAAA,EAAA,GAAA,CAAA,MAAA,EAqCgB,eArChB,CAqCgC,CArChC,CAAA,CAAA,EAAA,GAsCtC,YAtCsC,CAsCzB,gBAtCyB,CAsCR,CAtCQ,CAAA,CAAA;CAAO,GAAA,CAuC/C,OAvC+C,SAuC/B,SAvC+B,GAuCnB,cAvCmB,CAuCJ,OAvCI,CAAA,GAAA,CAAA,CAAA,CAAA;;AAAhC,KA0CR,cA1CQ,CAAA,gBA0CuB,SA1CvB,CAAA,GAAA,QAAd,MA2CQ,OA3CR,GA2CkB,OA3ClB,CA2C0B,CA3C1B,CAAA,SA2CqC,cA3CrC,GA4CA,eA5CA,CA4CgB,OA5ChB,CA4CwB,CA5CxB,CAAA,CAAA,SAAA,IAAA,GAAA,GAAA,GA6CQ,YA7CR,CA6CqB,gBA7CrB,CA6CsC,OA7CtC,CA6C8C,CA7C9C,CAAA,CAAA,CAAA,GAAA,CAAA,MAAA,EA8CW,eA9CX,CA8C2B,OA9C3B,CA8CmC,CA9CnC,CAAA,CAAA,EAAA,GA8C2C,YA9C3C,CA8CwD,gBA9CxD,CA8CyE,OA9CzE,CA8CiF,CA9CjF,CAAA,CAAA,CAAA,GA+CA,OA/CA,CA+CQ,CA/CR,CAAA,SA+CmB,SA/CnB,GAgDE,cAhDF,CAgDiB,OAhDjB,CAgDyB,CAhDzB,CAAA,CAAA,GAAA,KAAA,EAEoC;;;iBCvC1B,oBAAA,SACN,mCACE;iBA0CI,mCAAmC,2CACzC,aAAa,WACpB,OAAO;;;iBC5DM,0BACM,CAAA,CAAE,wEACD,CAAA,CAAE,uEACH,CAAA,CAAE,qEACJ,CAAA,CAAE,4BAA4B,CAAA,CAAE,mCAE1C,YAAY,aAAa,cAAc,aAAa,aAC3D,YAAY,aAAa,cAAc,aAAa;;;;cCRzC;KACF;YACA,cAAA,GAAiB;AJH7B,CAAA;AAWA;AAgBA,cIpBc,kBJoBe,EAAQ,OAAA,MAAW;AAcnC,KIjCD,aJiCc,CAAA,UAAQ,MAAA,GAAW,MAAA,CAAA,GAAA,MAAA,GAAA;EAUhC,UI1CD,kBAAA,CJ0CoB,EI1CC,CJ0CD;AAchC,CAAA;AAUA;AAKgB,KInEJ,YAAA,GAAe,SJmEF,CAAA,MAAA,CAAA,GInEsB,aJmEtB,CAAA,MAAA,CAAA;;AAAqC,iBIhE9C,KJgE8C,CAAA,UAAA,MAAA,CAAA,CAAA,IAAA,EIhEhB,CJgEgB,CAAA,EIhEZ,SJgEY,CIhEF,CJgEE,CAAA;;AAAZ,iBI3DlC,QJ2DkC,CAAA,UAAA,MAAA,CAAA,CAAA,IAAA,EI3DD,CJ2DC,CAAA,EI3DG,aJ2DH,CI3DiB,CJ2DjB,CAAA;;;;;ACjFN;AAK5C;AAGA;AAMA;;;;;;;;;;;;;;AAiBA;AAKK,iBGYW,IAAA,CHZG,OAAG,EGaX,oBHbsB,EAAA,GAAA,MAAA,EGcpB,YHdoB,EAAA,CAAA,EGe9B,YHf8B"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["route","result"],"sources":["../src/errors.ts","../src/cherry_client.ts","../src/route.ts","../src/path.ts"],"sourcesContent":["import { errAsync, ResultAsync } from \"neverthrow\";\n\n/** Base error class for all Cherry errors */\nexport abstract class CherryError extends Error {\n abstract readonly type: string;\n abstract readonly retryable: boolean;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = this.constructor.name;\n }\n}\n\n/** HTTP response errors (4xx, 5xx) */\nexport class HttpError extends CherryError {\n readonly type = \"HttpError\";\n readonly retryable: boolean;\n\n constructor(\n public readonly status: number,\n public readonly statusText: string,\n public readonly body?: unknown,\n cause?: unknown,\n ) {\n super(`HTTP ${status}: ${statusText}`, { cause });\n this.retryable = status >= 500 || status === 429;\n }\n}\n\n/** Valibot validation errors */\nexport class ValidationError extends CherryError {\n readonly type = \"ValidationError\";\n readonly retryable = false;\n\n constructor(\n public readonly target: \"request\" | \"response\",\n public readonly issues: unknown[],\n cause?: unknown,\n ) {\n super(`Validation failed for ${target}`, { cause });\n }\n}\n\n/** Network/fetch errors */\nexport class NetworkError extends CherryError {\n readonly type = \"NetworkError\";\n readonly retryable = true;\n\n constructor(cause?: unknown) {\n super(`Network error`, { cause });\n }\n}\n\n/** Serialization errors (e.g., circular references, BigInt in JSON) */\nexport class SerializationError extends CherryError {\n readonly type = \"SerializationError\";\n readonly retryable = false;\n\n constructor(\n public readonly target: \"query\" | \"body\",\n public readonly key: string,\n cause?: unknown,\n ) {\n super(`Failed to serialize ${target} parameter \"${key}\"`, { cause });\n }\n}\n\n/** Catch-all for unexpected errors */\nexport class UnknownCherryError extends CherryError {\n readonly type = \"UnknownCherryError\";\n readonly retryable = false;\n\n constructor(cause?: unknown) {\n super(`Unknown error`, { cause });\n }\n}\n\n/** Type guard for CherryError */\nexport function isCherryError(error: unknown): error is CherryError {\n return error instanceof CherryError;\n}\n\n/** Helper to create error ResultAsync */\nexport function cherryErr<T>(error: CherryError): ResultAsync<T, CherryError> {\n return errAsync(error);\n}\n","import { ResultAsync } from \"neverthrow\";\nimport * as v from \"valibot\";\nimport type {\n CherryRoute,\n CherryResult,\n InferRouteInput,\n InferRouteOutput,\n Fetcher,\n FetchRequest,\n ClientConfig,\n Client,\n RouteTree,\n RoutesToClient,\n QueryParamOptions,\n} from \"./types\";\nimport { HttpError, ValidationError, NetworkError, SerializationError, UnknownCherryError } from \"./errors\";\n\nconst defaultFetcher: Fetcher = (req) => fetch(req.url, req.init);\n\nexport function serializeQueryParams(\n params: Record<string, unknown>,\n options?: QueryParamOptions,\n): string {\n if (options?.customSerializer) {\n return options.customSerializer(params);\n }\n\n const searchParams = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n switch (options?.arrayFormat ?? \"repeat\") {\n case \"repeat\":\n for (const item of value) {\n searchParams.append(key, String(item));\n }\n break;\n case \"comma\":\n searchParams.set(key, value.join(\",\"));\n break;\n case \"brackets\":\n for (const item of value) {\n searchParams.append(`${key}[]`, String(item));\n }\n break;\n case \"json\":\n try {\n searchParams.set(key, JSON.stringify(value));\n } catch (error) {\n throw new SerializationError(\"query\", key, error);\n }\n break;\n }\n } else {\n searchParams.set(key, String(value));\n }\n }\n\n return searchParams.toString();\n}\n\nexport function createCherryClient<TRoutes extends RouteTree | undefined = undefined>(\n config: ClientConfig<TRoutes>,\n): Client<TRoutes> {\n const fetcher = config.fetcher ?? defaultFetcher;\n\n function call<T extends CherryRoute<any, any, any, any>>(\n route: T,\n params: InferRouteInput<T>,\n ): CherryResult<InferRouteOutput<T>> {\n return ResultAsync.fromPromise(executeRoute(route, params), (error) => {\n if (error instanceof HttpError) return error;\n if (error instanceof ValidationError) return error;\n if (error instanceof NetworkError) return error;\n if (error instanceof SerializationError) return error;\n return new UnknownCherryError(error);\n });\n }\n\n async function executeRoute<T extends CherryRoute<any, any, any, any>>(\n route: T,\n params: InferRouteInput<T>,\n ): Promise<InferRouteOutput<T>> {\n let pathParams: Record<string, unknown> = {};\n if (route.pathParams) {\n const result = v.safeParse(route.pathParams, params);\n if (!result.success) throw new ValidationError(\"request\", result.issues);\n pathParams = result.output;\n }\n\n let queryParams: Record<string, unknown> = {};\n if (route.queryParams) {\n const result = v.safeParse(route.queryParams, params);\n if (!result.success) throw new ValidationError(\"request\", result.issues);\n queryParams = result.output;\n }\n\n let bodyParams: Record<string, unknown> | undefined;\n if (route.bodyParams) {\n const result = v.safeParse(route.bodyParams, params);\n if (!result.success) throw new ValidationError(\"request\", result.issues);\n bodyParams = result.output;\n }\n\n let url = route.path.template;\n for (const [key, value] of Object.entries(pathParams)) {\n url = url.replace(`:${key}`, encodeURIComponent(String(value)));\n }\n\n const fullUrl = new URL(url, config.baseUrl);\n\n if (Object.keys(queryParams).length > 0) {\n fullUrl.search = serializeQueryParams(queryParams, route.queryParamOptions);\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...(await config.headers?.()),\n };\n\n const init: RequestInit = {\n method: route.method,\n headers,\n };\n\n if (bodyParams && route.method !== \"GET\") {\n init.body = JSON.stringify(bodyParams);\n }\n\n const req: FetchRequest = {\n url: fullUrl.toString(),\n init,\n };\n\n let response: Response;\n try {\n response = await fetcher(req);\n } catch (error) {\n throw new NetworkError(error);\n }\n\n if (!response.ok) {\n const body = await response.text().catch(() => undefined);\n throw new HttpError(response.status, response.statusText, body);\n }\n\n const json = await response.json();\n const result = v.safeParse(route.response, json);\n if (!result.success) throw new ValidationError(\"response\", result.issues);\n\n return result.output;\n }\n\n function forgeRouteMethods<T extends RouteTree>(routes: T): RoutesToClient<T> {\n const out: any = {};\n\n for (const [key, value] of Object.entries(routes)) {\n if (value && typeof value === \"object\" && \"method\" in value && \"path\" in value) {\n out[key] = (params: any) => call(value as any, params);\n } else if (value && typeof value === \"object\") {\n out[key] = forgeRouteMethods(value as RouteTree);\n }\n }\n\n return out;\n }\n\n const routes = config.routes ? forgeRouteMethods(config.routes) : {};\n return { call, ...routes } as Client<TRoutes>;\n}\n","import * as v from \"valibot\";\nimport type {\n CherryRoute,\n HttpMethod,\n PathTemplate,\n QueryParamOptions,\n} from \"./types\";\n\nconst HttpMethodSchema = v.picklist([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\nexport type RouteConfig<\n TPathParams extends v.BaseSchema<any, any, any> | undefined,\n TQueryParams extends v.BaseSchema<any, any, any> | undefined,\n TBodyParams extends v.BaseSchema<any, any, any> | undefined,\n TResponse extends v.BaseSchema<any, any, any>,\n> = {\n method: HttpMethod;\n path: PathTemplate;\n pathParams?: TPathParams;\n queryParams?: TQueryParams;\n bodyParams?: TBodyParams;\n response: TResponse;\n queryParamOptions?: QueryParamOptions;\n description?: string;\n};\n\nexport function route<\n TPathParams extends v.BaseSchema<any, any, any> | undefined,\n TQueryParams extends v.BaseSchema<any, any, any> | undefined,\n TBodyParams extends v.BaseSchema<any, any, any> | undefined,\n TResponse extends v.BaseSchema<any, any, any>,\n>(\n config: RouteConfig<TPathParams, TQueryParams, TBodyParams, TResponse>,\n): CherryRoute<TPathParams, TQueryParams, TBodyParams, TResponse> {\n // validate HTTP method\n v.parse(HttpMethodSchema, config.method);\n\n // validate availability and content of pathParams schema, if needed\n if (config.path.paramNames.length > 0) {\n if (!config.pathParams) {\n throw new Error(\n `Route has path params [${config.path.paramNames.join(\", \")}] but no pathParams schema`,\n );\n }\n\n const schemaKeys = getSchemaKeys(config.pathParams);\n\n for (const paramName of config.path.paramNames) {\n if (!schemaKeys.includes(paramName)) {\n throw new Error(\n `Path param \":${paramName}\" not found in pathParams schema. ` +\n `Available: [${schemaKeys.join(\", \")}]`,\n );\n }\n }\n\n for (const schemaKey of schemaKeys) {\n if (!config.path.paramNames.includes(schemaKey)) {\n throw new Error(\n `pathParams schema key \"${schemaKey}\" not present in path template. ` +\n `Template params: [${config.path.paramNames.join(\", \")}]`,\n );\n }\n }\n }\n\n return config as CherryRoute<\n TPathParams,\n TQueryParams,\n TBodyParams,\n TResponse\n >;\n}\n\nfunction getSchemaKeys(schema: v.BaseSchema<any, any, any>): string[] {\n if (\n \"entries\" in schema &&\n typeof (schema as any).entries === \"object\" &&\n (schema as any).entries !== null\n ) {\n return Object.keys((schema as any).entries);\n }\n return [];\n}\n","// path.ts - Tagged template functions for type-safe path building\nimport type { PathTemplate } from \"./types\";\n\n/** Branded type for path parameter markers */\ndeclare const PathParamBrand: unique symbol;\nexport type PathParam<T extends string = string> = string & {\n readonly [PathParamBrand]: T;\n};\n\n/** Branded type for optional path parameter markers */\ndeclare const OptionalParamBrand: unique symbol;\nexport type OptionalParam<T extends string = string> = string & {\n readonly [OptionalParamBrand]: T;\n};\n\n/** Union type for any path param marker */\nexport type AnyPathParam = PathParam<string> | OptionalParam<string>;\n\n/** Create a path parameter marker */\nexport function param<T extends string>(name: T): PathParam<T> {\n return `:${name}` as PathParam<T>;\n}\n\n/** Create an optional path parameter marker */\nexport function optional<T extends string>(name: T): OptionalParam<T> {\n return `(:${name})` as OptionalParam<T>;\n}\n\n/**\n * Tagged template for building path templates.\n *\n * @example\n * ```ts\n * // Simple path with one param\n * const userPath = path`/users/${param(\"id\")}`;\n * // { template: \"/users/:id\", paramNames: [\"id\"] }\n *\n * // Multiple params\n * const postPath = path`/users/${param(\"userId\")}/posts/${param(\"postId\")}`;\n * // { template: \"/users/:userId/posts/:postId\", paramNames: [\"userId\", \"postId\"] }\n *\n * // Optional params\n * const versionedPath = path`/api${optional(\"version\")}/users`;\n * // { template: \"/api(:version)/users\", paramNames: [\"version\"] }\n *\n * // No params\n * const staticPath = path`/health`;\n * // { template: \"/health\", paramNames: [] }\n * ```\n */\nexport function path(\n strings: TemplateStringsArray,\n ...params: AnyPathParam[]\n): PathTemplate {\n const paramNames: string[] = [];\n let template = strings[0];\n\n for (let i = 0; i < params.length; i++) {\n const p = params[i];\n template += p + strings[i + 1];\n\n // Extract param name from `:name` or `(:name)`\n const match = p.match(/^\\(?:(\\w+)\\)?$/);\n if (match) {\n paramNames.push(match[1]);\n }\n }\n\n return { template, paramNames };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["route","result"],"sources":["../src/errors.ts","../src/cherry_client.ts","../src/route.ts","../src/path.ts"],"sourcesContent":["import { errAsync, ResultAsync } from \"neverthrow\";\n\n/** Base error class for all Cherry errors */\nexport abstract class CherryError extends Error {\n abstract readonly type: string;\n abstract readonly retryable: boolean;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = this.constructor.name;\n }\n}\n\n/** HTTP response errors (4xx, 5xx) */\nexport class HttpError extends CherryError {\n readonly type = \"HttpError\";\n readonly retryable: boolean;\n\n constructor(\n public readonly status: number,\n public readonly statusText: string,\n public readonly body?: unknown,\n cause?: unknown,\n ) {\n super(`HTTP ${status}: ${statusText}`, { cause });\n this.retryable = status >= 500 || status === 429;\n }\n}\n\n/** Valibot validation errors */\nexport class ValidationError extends CherryError {\n readonly type = \"ValidationError\";\n readonly retryable = false;\n\n constructor(\n public readonly target: \"request\" | \"response\",\n public readonly issues: unknown[],\n cause?: unknown,\n ) {\n super(`Validation failed for ${target}`, { cause });\n }\n}\n\n/** Network/fetch errors */\nexport class NetworkError extends CherryError {\n readonly type = \"NetworkError\";\n readonly retryable = true;\n\n constructor(cause?: unknown) {\n super(`Network error`, { cause });\n }\n}\n\n/** Serialization errors (e.g., circular references, BigInt in JSON) */\nexport class SerializationError extends CherryError {\n readonly type = \"SerializationError\";\n readonly retryable = false;\n\n constructor(\n public readonly target: \"query\" | \"body\",\n public readonly key: string,\n cause?: unknown,\n ) {\n super(`Failed to serialize ${target} parameter \"${key}\"`, { cause });\n }\n}\n\n/** Catch-all for unexpected errors */\nexport class UnknownCherryError extends CherryError {\n readonly type = \"UnknownCherryError\";\n readonly retryable = false;\n\n constructor(cause?: unknown) {\n super(`Unknown error`, { cause });\n }\n}\n\n/** Type guard for CherryError */\nexport function isCherryError(error: unknown): error is CherryError {\n return error instanceof CherryError;\n}\n\n/** Helper to create error ResultAsync */\nexport function cherryErr<T>(error: CherryError): ResultAsync<T, CherryError> {\n return errAsync(error);\n}\n","import { ResultAsync } from \"neverthrow\";\nimport * as v from \"valibot\";\nimport type {\n CherryRoute,\n CherryResult,\n InferRouteInput,\n InferRouteOutput,\n Fetcher,\n FetchRequest,\n ClientConfig,\n Client,\n RouteTree,\n RoutesToClient,\n QueryParamOptions,\n} from \"./types\";\nimport { HttpError, ValidationError, NetworkError, SerializationError, UnknownCherryError } from \"./errors\";\n\nconst defaultFetcher: Fetcher = (req) => fetch(req.url, req.init);\n\nexport function serializeQueryParams(\n params: Record<string, unknown>,\n options?: QueryParamOptions,\n): string {\n if (options?.customSerializer) {\n return options.customSerializer(params);\n }\n\n const searchParams = new URLSearchParams();\n\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n\n if (Array.isArray(value)) {\n switch (options?.arrayFormat ?? \"repeat\") {\n case \"repeat\":\n for (const item of value) {\n searchParams.append(key, String(item));\n }\n break;\n case \"comma\":\n searchParams.set(key, value.join(\",\"));\n break;\n case \"brackets\":\n for (const item of value) {\n searchParams.append(`${key}[]`, String(item));\n }\n break;\n case \"json\":\n try {\n searchParams.set(key, JSON.stringify(value));\n } catch (error) {\n throw new SerializationError(\"query\", key, error);\n }\n break;\n }\n } else {\n searchParams.set(key, String(value));\n }\n }\n\n return searchParams.toString();\n}\n\nexport function createCherryClient<TRoutes extends RouteTree | undefined = undefined>(\n config: ClientConfig<TRoutes>,\n): Client<TRoutes> {\n const fetcher = config.fetcher ?? defaultFetcher;\n\n function call<T extends CherryRoute<any, any, any, any>>(\n route: T,\n params: InferRouteInput<T>,\n ): CherryResult<InferRouteOutput<T>> {\n return ResultAsync.fromPromise(executeRoute(route, params), (error) => {\n if (error instanceof HttpError) return error;\n if (error instanceof ValidationError) return error;\n if (error instanceof NetworkError) return error;\n if (error instanceof SerializationError) return error;\n return new UnknownCherryError(error);\n });\n }\n\n async function executeRoute<T extends CherryRoute<any, any, any, any>>(\n route: T,\n params: InferRouteInput<T>,\n ): Promise<InferRouteOutput<T>> {\n let pathParams: Record<string, unknown> = {};\n if (route.pathParams) {\n const result = v.safeParse(route.pathParams, params);\n if (!result.success) throw new ValidationError(\"request\", result.issues);\n pathParams = result.output;\n }\n\n let queryParams: Record<string, unknown> = {};\n if (route.queryParams) {\n const result = v.safeParse(route.queryParams, params);\n if (!result.success) throw new ValidationError(\"request\", result.issues);\n queryParams = result.output;\n }\n\n let bodyParams: Record<string, unknown> | undefined;\n if (route.bodyParams) {\n const result = v.safeParse(route.bodyParams, params);\n if (!result.success) throw new ValidationError(\"request\", result.issues);\n bodyParams = result.output;\n }\n\n let url = route.path.template;\n for (const [key, value] of Object.entries(pathParams)) {\n url = url.replace(`:${key}`, encodeURIComponent(String(value)));\n }\n\n const fullUrl = new URL(url, config.baseUrl);\n\n if (Object.keys(queryParams).length > 0) {\n fullUrl.search = serializeQueryParams(queryParams, route.queryParamOptions);\n }\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...(await config.headers?.()),\n };\n\n const init: RequestInit = {\n method: route.method,\n headers,\n };\n\n if (bodyParams && route.method !== \"GET\") {\n init.body = JSON.stringify(bodyParams);\n }\n\n const req: FetchRequest = {\n url: fullUrl.toString(),\n init,\n };\n\n let response: Response;\n try {\n response = await fetcher(req);\n } catch (error) {\n throw new NetworkError(error);\n }\n\n if (!response.ok) {\n const body = await response.text().catch(() => undefined);\n throw new HttpError(response.status, response.statusText, body);\n }\n\n const json = await response.json();\n const result = v.safeParse(route.response, json);\n if (!result.success) throw new ValidationError(\"response\", result.issues);\n\n return result.output;\n }\n\n function forgeRouteMethods<T extends RouteTree>(routes: T): RoutesToClient<T> {\n const out: any = {};\n\n for (const [key, value] of Object.entries(routes)) {\n if (value && typeof value === \"object\" && \"method\" in value && \"path\" in value) {\n out[key] = (params: any) => call(value as any, params);\n } else if (value && typeof value === \"object\") {\n out[key] = forgeRouteMethods(value as RouteTree);\n }\n }\n\n return out;\n }\n\n const routes = config.routes ? forgeRouteMethods(config.routes) : {};\n return { call, ...routes } as Client<TRoutes>;\n}\n","import * as v from \"valibot\";\nimport type { CherryRoute } from \"./types\";\n\nconst HttpMethodSchema = v.picklist([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\nexport function route<\n TPathParams extends v.BaseSchema<any, any, any> | undefined = undefined,\n TQueryParams extends v.BaseSchema<any, any, any> | undefined = undefined,\n TBodyParams extends v.BaseSchema<any, any, any> | undefined = undefined,\n TResponse extends v.BaseSchema<any, any, any> = v.BaseSchema<any, any, any>,\n>(\n config: CherryRoute<TPathParams, TQueryParams, TBodyParams, TResponse>,\n): CherryRoute<TPathParams, TQueryParams, TBodyParams, TResponse> {\n // validate HTTP method\n v.parse(HttpMethodSchema, config.method);\n\n // validate availability and content of pathParams schema, if needed\n if (config.path.paramNames.length > 0) {\n if (!config.pathParams) {\n throw new Error(\n `Route has path params [${config.path.paramNames.join(\", \")}] but no pathParams schema`,\n );\n }\n\n const schemaKeys = getSchemaKeys(config.pathParams);\n\n for (const paramName of config.path.paramNames) {\n if (!schemaKeys.includes(paramName)) {\n throw new Error(\n `Path param \":${paramName}\" not found in pathParams schema. ` +\n `Available: [${schemaKeys.join(\", \")}]`,\n );\n }\n }\n\n for (const schemaKey of schemaKeys) {\n if (!config.path.paramNames.includes(schemaKey)) {\n throw new Error(\n `pathParams schema key \"${schemaKey}\" not present in path template. ` +\n `Template params: [${config.path.paramNames.join(\", \")}]`,\n );\n }\n }\n }\n\n return config as CherryRoute<\n TPathParams,\n TQueryParams,\n TBodyParams,\n TResponse\n >;\n}\n\nfunction getSchemaKeys(schema: v.BaseSchema<any, any, any>): string[] {\n if (\n \"entries\" in schema &&\n typeof (schema as any).entries === \"object\" &&\n (schema as any).entries !== null\n ) {\n return Object.keys((schema as any).entries);\n }\n return [];\n}\n","// path.ts - Tagged template functions for type-safe path building\nimport type { PathTemplate } from \"./types\";\n\n/** Branded type for path parameter markers */\ndeclare const PathParamBrand: unique symbol;\nexport type PathParam<T extends string = string> = string & {\n readonly [PathParamBrand]: T;\n};\n\n/** Branded type for optional path parameter markers */\ndeclare const OptionalParamBrand: unique symbol;\nexport type OptionalParam<T extends string = string> = string & {\n readonly [OptionalParamBrand]: T;\n};\n\n/** Union type for any path param marker */\nexport type AnyPathParam = PathParam<string> | OptionalParam<string>;\n\n/** Create a path parameter marker */\nexport function param<T extends string>(name: T): PathParam<T> {\n return `:${name}` as PathParam<T>;\n}\n\n/** Create an optional path parameter marker */\nexport function optional<T extends string>(name: T): OptionalParam<T> {\n return `(:${name})` as OptionalParam<T>;\n}\n\n/**\n * Tagged template for building path templates.\n *\n * @example\n * ```ts\n * // Simple path with one param\n * const userPath = path`/users/${param(\"id\")}`;\n * // { template: \"/users/:id\", paramNames: [\"id\"] }\n *\n * // Multiple params\n * const postPath = path`/users/${param(\"userId\")}/posts/${param(\"postId\")}`;\n * // { template: \"/users/:userId/posts/:postId\", paramNames: [\"userId\", \"postId\"] }\n *\n * // Optional params\n * const versionedPath = path`/api${optional(\"version\")}/users`;\n * // { template: \"/api(:version)/users\", paramNames: [\"version\"] }\n *\n * // No params\n * const staticPath = path`/health`;\n * // { template: \"/health\", paramNames: [] }\n * ```\n */\nexport function path(\n strings: TemplateStringsArray,\n ...params: AnyPathParam[]\n): PathTemplate {\n const paramNames: string[] = [];\n let template = strings[0];\n\n for (let i = 0; i < params.length; i++) {\n const p = params[i];\n template += p + strings[i + 1];\n\n // Extract param name from `:name` or `(:name)`\n const match = p.match(/^\\(?:(\\w+)\\)?$/);\n if (match) {\n paramNames.push(match[1]);\n }\n }\n\n return { template, paramNames };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3-business/cherry",
3
- "version": "0.2.7",
3
+ "version": "0.3.1",
4
4
  "description": "A tree-shakeable, minimal API client factory. Import only the routes you need — nothing more.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,25 +8,19 @@
8
8
  },
9
9
  "license": "MIT",
10
10
  "type": "module",
11
- "main": "./dist/index.js",
12
11
  "types": "./dist/index.d.ts",
13
12
  "exports": {
14
- ".": {
15
- "import": {
16
- "types": "./dist/index.d.ts",
17
- "default": "./dist/index.js"
18
- }
19
- }
13
+ ".": "./src/index.ts",
14
+ "./package.json": "./package.json"
20
15
  },
21
16
  "files": [
22
17
  "dist"
23
18
  ],
24
19
  "scripts": {
25
20
  "build": "tsdown",
26
- "check": "run-s lint typecheck typecheck-test",
21
+ "check": "run-s lint typecheck",
27
22
  "lint": "oxlint src test",
28
23
  "typecheck": "tsc --noEmit -p .",
29
- "typecheck-test": "tsc --noEmit -p tsconfig.test.json",
30
24
  "test": "bun test",
31
25
  "prepublishOnly": "npm run check && npm run test && npm run build",
32
26
  "dry-npm": "npm publish --dry-run --access public",
@@ -44,5 +38,11 @@
44
38
  "oxlint": "^1.36.0",
45
39
  "tsdown": "^0.19.0-beta.2",
46
40
  "typescript": "^5.9.3"
41
+ },
42
+ "publishConfig": {
43
+ "exports": {
44
+ ".": "./dist/index.js",
45
+ "./package.json": "./package.json"
46
+ }
47
47
  }
48
48
  }