@routepact/core 0.1.2 → 0.1.4

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
@@ -8,82 +8,77 @@ Shared types and utilities for defining type-safe route pacts. This package is t
8
8
  npm install @routepact/core zod
9
9
  ```
10
10
 
11
- ## Defining a route spec
11
+ ## Defining a pact
12
12
 
13
- A `RoutePact` describes a single endpoint: its path, HTTP method, and optional Zod schemas for request body, response body, and response metadata.
13
+ A pact describes a single endpoint: its path, HTTP method, and optional Zod schemas for the request body, response body, query parameters, path parameters, and response metadata.
14
+
15
+ Use `definePact` to get full type inference while keeping literal types (exact path strings, method literals):
14
16
 
15
17
  ```ts
16
18
  import { z } from "zod";
17
- import type { RoutePact } from "@routepact/core";
19
+ import { definePact } from "@routepact/core";
18
20
 
19
21
  export const PostPacts = {
20
- list: {
22
+ list: definePact({
21
23
  method: "get",
22
24
  path: "/posts",
23
- validation: {
24
- response: z.object({ id: z.string(), title: z.string() }).array(),
25
- meta: z.object({ total: z.number(), page: z.number() }),
26
- },
27
- },
28
- getById: {
25
+ response: z.object({
26
+ items: z.object({ id: z.string(), title: z.string() }).array(),
27
+ total: z.number(),
28
+ page: z.number(),
29
+ }),
30
+ query: z.object({ page: z.string().optional(), limit: z.string().optional() }),
31
+ }),
32
+ getById: definePact({
29
33
  method: "get",
30
34
  path: "/posts/:id",
31
- validation: {
32
- response: z.object({
33
- id: z.string(),
34
- title: z.string(),
35
- body: z.string(),
36
- }),
37
- },
38
- },
39
- create: {
35
+ response: z.object({
36
+ id: z.string(),
37
+ title: z.string(),
38
+ body: z.string(),
39
+ }),
40
+ }),
41
+ create: definePact({
40
42
  method: "post",
41
43
  path: "/posts",
42
- validation: {
43
- request: z.object({ title: z.string(), body: z.string() }),
44
- response: z.object({
45
- id: z.string(),
46
- title: z.string(),
47
- body: z.string(),
48
- }),
49
- },
50
- },
51
- update: {
44
+ request: z.object({ title: z.string(), body: z.string() }),
45
+ response: z.object({
46
+ id: z.string(),
47
+ title: z.string(),
48
+ body: z.string(),
49
+ }),
50
+ }),
51
+ update: definePact({
52
52
  method: "patch",
53
53
  path: "/posts/:id",
54
- validation: {
55
- request: z.object({
56
- title: z.string().optional(),
57
- body: z.string().optional(),
58
- }),
59
- response: z.object({
60
- id: z.string(),
61
- title: z.string(),
62
- body: z.string(),
63
- }),
64
- },
65
- },
66
- delete: {
54
+ request: z.object({
55
+ title: z.string().optional(),
56
+ body: z.string().optional(),
57
+ }),
58
+ response: z.object({
59
+ id: z.string(),
60
+ title: z.string(),
61
+ body: z.string(),
62
+ }),
63
+ }),
64
+ delete: definePact({
67
65
  method: "delete",
68
66
  path: "/posts/:id",
69
- validation: {},
70
- },
71
- } satisfies Record<string, RoutePact>;
67
+ }),
68
+ };
72
69
  ```
73
70
 
74
- Using `satisfies Record<string, RoutePact>` lets TypeScript keep the literal types (exact path strings, exact method literals) while still validating the shape.
75
-
76
- ## Validation rules
71
+ ## Validation schemas
77
72
 
78
- | Field | Required | Description |
79
- | --------------------- | ------------------------------- | -------------------------------------------- |
80
- | `validation.request` | Only for `post`, `patch`, `put` | Zod schema for `req.body.resource` |
81
- | `validation.response` | Optional | Zod schema for `res.json({ resource: ... })` |
82
- | `validation.meta` | Optional | Zod schema for `res.json({ meta: ... })` |
73
+ | Field | Required for | Description |
74
+ | ---------- | ------------------------------- | ----------------------------------------------------------------- |
75
+ | `request` | Only for `post`, `patch`, `put` | Zod schema for `req.body` on the server |
76
+ | `response` | Optional | Zod schema for the full response body sent via `res.json(...)` |
77
+ | `query` | Optional | Zod schema for query parameters typed on both server and client |
83
78
 
84
79
  ## Path parameters
85
80
 
86
- Parameters in the path are extracted as a type-safe object. If a path contains `:id`, the `params` option is required and typed as `{ id: string }`.
81
+ Parameters in the path (`:param`) are extracted as a type-safe object. If a path contains `:id`, the `params` option is required and typed as `{ id: string }`.
87
82
 
88
83
  ```ts
89
84
  // TypeScript requires params when the path has parameters
@@ -93,71 +88,34 @@ await request(PostPacts.getById, { params: { id: "abc" } });
93
88
  await request(PostPacts.list);
94
89
  ```
95
90
 
96
- ## API response shape
97
-
98
- All endpoints follow the same envelope:
99
-
100
- ```ts
101
- // Response body sent from the server
102
- interface ApiResponse<TResource, TMeta> {
103
- resource: TResource;
104
- meta?: TMeta;
105
- message?: string;
106
- }
107
-
108
- // Request body sent to the server
109
- interface ApiRequest<TResource> {
110
- resource: TResource;
111
- }
112
- ```
113
-
114
- ## Utilities
115
-
116
- ### `buildEndpoint(spec, options)`
117
-
118
- Interpolates path params and appends a query string.
119
-
120
- ```ts
121
- import { buildEndpoint } from "@routepact/core";
122
-
123
- buildEndpoint(PostPacts.getById, { params: { id: "42" } });
124
- // → "/posts/42"
125
-
126
- buildEndpoint(PostPacts.list, { queries: { page: "2", limit: "10" } });
127
- // → "/posts?page=2&limit=10"
128
- ```
91
+ On the server, `req.parsedParams` is automatically populated from `req.params` and typed as `{ [key: string]: string }` based on the path string — no Zod schema needed.
129
92
 
130
- ### `exhaustiveGuard(value)`
93
+ ## Query parameters
131
94
 
132
- A compile-time exhaustiveness check for `switch` statements. Throws at runtime if an unhandled branch is reached.
95
+ Add a `query` Zod schema to make query parameters type-safe on both server and client. If any field in the schema is required, TypeScript will require the `queries` option at the call site:
133
96
 
134
97
  ```ts
135
- import { exhaustiveGuard, type HttpMethod } from "@routepact/core";
136
-
137
- function handle(method: HttpMethod) {
138
- switch (method) {
139
- case "get":
140
- return "GET";
141
- case "post":
142
- return "POST";
143
- // ... etc
144
- default:
145
- exhaustiveGuard(method); // TypeScript errors if a case is missing
146
- }
147
- }
98
+ const list = definePact({
99
+ method: "get",
100
+ path: "/posts",
101
+ query: z.object({ page: z.string().optional(), sort: z.string() }), // sort is required
102
+ });
103
+
104
+ // TypeScript requires queries.sort
105
+ await request(list, { queries: { sort: "createdAt" } });
148
106
  ```
149
107
 
150
108
  ## Type reference
151
109
 
152
- | Type | Description |
153
- | ----------------------------- | ----------------------------------------------------------------- |
154
- | `RoutePact` | Base type for a single endpoint definition |
155
- | `RouteValidation` | The `validation` field of a spec |
156
- | `HttpMethod` | `"get" \| "post" \| "patch" \| "put" \| "delete"` |
157
- | `RouteOptions<TPact>` | Inferred options (params, payload, queries) for a spec |
158
- | `RouteOptionsRequired<TPact>` | `true` if options are required (has params or request schema) |
159
- | `RequestResult<TPact>` | Inferred return type when calling a spec from the client |
160
- | `ExtractParams<TPath>` | Extracts parameter names from a path string |
161
- | `ExpectedParams<TPath>` | Maps extracted param names to `string` values |
162
- | `ApiRequest<T>` | `{ resource: T }` request body envelope |
163
- | `ApiResponse<T, M>` | `{ resource: T; meta?: M; message?: string }` — response envelope |
110
+ | Type | Description |
111
+ | ----------------------------- | ---------------------------------------------------------------------------------- |
112
+ | `RoutePact` | Generic type for a single pact definition |
113
+ | `AnyRoutePact` | Widened pact type used internally and in package integrations |
114
+ | `AnyRouteValidation` | The set of optional Zod schemas that can be attached to a pact |
115
+ | `HttpMethod` | `"get" \| "post" \| "patch" \| "put" \| "delete"` |
116
+ | `RouteOptions<TPact>` | Inferred call-site options (params, payload, queries) for a pact |
117
+ | `RouteOptionsRequired<TPact>` | `true` if options are required (pact has params or a request schema) |
118
+ | `QueryOption<TPact>` | Inferred `queries` option required if the query schema has required fields |
119
+ | `PactResponse<TPact>` | Inferred return type when calling a pact from the client — `z.infer<response>` or `undefined` |
120
+ | `ExtractParams<TPath>` | Extracts parameter names from a path string |
121
+ | `ExpectedParams<TPath>` | Maps extracted param names to `string` values |
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export * from "./api.js";
2
1
  export * from "./types.js";
3
2
  export * from "./utils.js";
4
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- export * from "./api.js";
2
1
  export * from "./types.js";
3
2
  export * from "./utils.js";
4
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
package/dist/types.d.ts CHANGED
@@ -1,41 +1,49 @@
1
1
  import type z from "zod/v4";
2
- import type { ApiResponse } from "./api.js";
3
2
  export type HttpMethod = "get" | "post" | "patch" | "put" | "delete";
4
- export type RouteValidation = {
3
+ export type QueryParam = Record<string, string | number | boolean | undefined>;
4
+ export type AnyRouteValidation = {
5
5
  response?: z.ZodType<unknown>;
6
6
  request?: z.ZodType<unknown>;
7
- meta?: z.ZodType<unknown>;
8
- };
9
- export type RoutePact = {
10
- path: string;
11
- method: HttpMethod;
12
- validation: RouteValidation;
7
+ query?: z.ZodType<QueryParam>;
8
+ params?: z.ZodType<Record<string, unknown>>;
13
9
  };
10
+ export type RoutePact<TPath extends string, TMethod extends HttpMethod, TValidation extends AnyRouteValidation> = {
11
+ path: TPath;
12
+ method: TMethod;
13
+ } & TValidation;
14
+ export type AnyRoutePact = RoutePact<string, HttpMethod, AnyRouteValidation>;
14
15
  export type ExtractParams<T extends string> = T extends `${string}/:${infer Param}/${infer Rest}` ? Param | ExtractParams<`/${Rest}`> : T extends `${string}:${infer Param}` ? Param : never;
15
16
  export type ExpectedParams<T extends string> = {
16
17
  [Key in ExtractParams<T>]: string;
17
18
  };
18
- type ParamsOption<TPath extends string> = [ExtractParams<TPath>] extends [never] ? {
19
- params?: never;
20
- } : {
19
+ type RequiredKeys<T> = {
20
+ [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
21
+ }[keyof T];
22
+ type RequiredOnly<T> = Pick<T, RequiredKeys<T>>;
23
+ type HasParams<TPath extends string> = [ExtractParams<TPath>] extends [never] ? false : true;
24
+ type ParamsOption<TPath extends string> = HasParams<TPath> extends true ? {
21
25
  params: ExpectedParams<TPath>;
26
+ } : {
27
+ params?: never;
22
28
  };
23
- type PayloadOption<TPact extends RoutePact> = TPact["method"] extends "post" | "patch" | "put" ? TPact["validation"]["request"] extends z.ZodType<unknown> ? {
24
- payload: z.infer<TPact["validation"]["request"]>;
29
+ type PayloadOption<TPact extends AnyRoutePact> = TPact["method"] extends "post" | "patch" | "put" ? TPact["request"] extends z.ZodType<unknown> ? {
30
+ payload: z.infer<TPact["request"]>;
25
31
  } : {
26
32
  payload?: never;
27
33
  } : {
28
34
  payload?: never;
29
35
  };
30
- export type RouteOptions<TPact extends RoutePact> = ParamsOption<TPact["path"]> & PayloadOption<TPact> & {
31
- queries?: Record<string, string>;
36
+ export type QueryOption<TPact extends AnyRoutePact> = TPact["query"] extends z.ZodType<infer TQuery> ? {} extends RequiredOnly<TQuery> ? {
37
+ queries?: TQuery;
38
+ } : {
39
+ queries: TQuery;
40
+ } : {
41
+ queries?: never;
32
42
  };
33
- export type RouteOptionsRequired<TPact extends RoutePact> = TPact["validation"]["request"] extends z.ZodType<unknown> ? true : [ExtractParams<TPact["path"]>] extends [never] ? false : true;
34
- export type PactResponse<TPact extends RoutePact> = ApiResponse<TPact["validation"] extends {
35
- response: z.ZodType<unknown>;
36
- } ? z.infer<TPact["validation"]["response"]> : undefined, TPact["validation"] extends {
43
+ export type RouteOptions<TPact extends AnyRoutePact> = ParamsOption<TPact["path"]> & PayloadOption<TPact> & QueryOption<TPact>;
44
+ export type RouteOptionsRequired<TPact extends AnyRoutePact> = {} extends RouteOptions<TPact> ? false : true;
45
+ export type PactResponse<TPact extends AnyRoutePact> = TPact extends {
37
46
  response: z.ZodType<unknown>;
38
- meta: z.ZodType<unknown>;
39
- } ? z.infer<TPact["validation"]["meta"]> : undefined>;
47
+ } ? z.infer<TPact["response"]> : undefined;
40
48
  export {};
41
49
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,22 @@
1
- import type { RouteOptions, RoutePact } from "./types.js";
1
+ import type { AnyRoutePact, AnyRouteValidation, HttpMethod, RouteOptions, RoutePact } from "./types.js";
2
2
  export declare function exhaustiveGuard(_value: never): never;
3
- export declare function buildEndpoint<TPact extends RoutePact>(pact: TPact, options?: RouteOptions<TPact>): string;
3
+ /**
4
+ * Builds a full endpoint path from a pact, substituting path parameters and
5
+ * appending a query string. Path param values are URL-encoded automatically.
6
+ * Throws if a required path parameter is missing from `options.params`.
7
+ */
8
+ export declare function buildEndpoint<TPact extends AnyRoutePact>(pact: TPact, options?: RouteOptions<TPact>): string;
9
+ /**
10
+ * Defines a type-safe route pact
11
+ *
12
+ * @example
13
+ * const getUser = definePact({
14
+ * path: "/users/:id",
15
+ * method: "get",
16
+ * response: z.object({ id: z.string(), name: z.string() }),
17
+ * params: z.object({ param: z.string() }),
18
+ * query: z.object({ query: z.string() }),
19
+ * });
20
+ */
21
+ export declare function definePact<const TPath extends string, TMethod extends HttpMethod, TValidation extends AnyRouteValidation>(pact: RoutePact<TPath, TMethod, TValidation>): RoutePact<TPath, TMethod, TValidation>;
4
22
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE1E,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,GAAG,KAAK,CAIpD;AAED,wBAAgB,aAAa,CAAC,KAAK,SAAS,SAAS,EACnD,IAAI,EAAE,KAAK,EACX,OAAO,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,GAC5B,MAAM,CAUR"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAElB,UAAU,EACV,YAAY,EACZ,SAAS,EACV,MAAM,YAAY,CAAC;AAEpB,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,GAAG,KAAK,CAIpD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,SAAS,YAAY,EACtD,IAAI,EAAE,KAAK,EACX,OAAO,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,GAC5B,MAAM,CAiBR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CACxB,KAAK,CAAC,KAAK,SAAS,MAAM,EAC1B,OAAO,SAAS,UAAU,EAC1B,WAAW,SAAS,kBAAkB,EAEtC,IAAI,EAAE,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,GAC3C,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,CAAC,CAExC"}
package/dist/utils.js CHANGED
@@ -1,14 +1,37 @@
1
1
  export function exhaustiveGuard(_value) {
2
2
  throw new Error(`Reached forbidden guard function with unexpected value: ${JSON.stringify(_value)}`);
3
3
  }
4
+ /**
5
+ * Builds a full endpoint path from a pact, substituting path parameters and
6
+ * appending a query string. Path param values are URL-encoded automatically.
7
+ * Throws if a required path parameter is missing from `options.params`.
8
+ */
4
9
  export function buildEndpoint(pact, options) {
5
- const replacedEndpoint = pact.path.replace(/:(\w+)/g, (match, key) => {
10
+ const replacedEndpoint = pact.path.replace(/:(\w+)/g, (_match, key) => {
6
11
  if (options?.params && key in options.params) {
7
- return options.params[key];
12
+ return encodeURIComponent(options.params[key]);
8
13
  }
9
- return match;
14
+ throw new Error(`Missing required path parameter: ${key}`);
10
15
  });
11
- const queryString = new URLSearchParams(options?.queries).toString();
16
+ const queryEntries = Object.entries(options?.queries ?? {})
17
+ .filter(([, value]) => value !== undefined)
18
+ .map(([key, value]) => [key, String(value)]);
19
+ const queryString = new URLSearchParams(queryEntries).toString();
12
20
  return queryString ? `${replacedEndpoint}?${queryString}` : replacedEndpoint;
13
21
  }
22
+ /**
23
+ * Defines a type-safe route pact
24
+ *
25
+ * @example
26
+ * const getUser = definePact({
27
+ * path: "/users/:id",
28
+ * method: "get",
29
+ * response: z.object({ id: z.string(), name: z.string() }),
30
+ * params: z.object({ param: z.string() }),
31
+ * query: z.object({ query: z.string() }),
32
+ * });
33
+ */
34
+ export function definePact(pact) {
35
+ return pact;
36
+ }
14
37
  //# sourceMappingURL=utils.js.map
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAAC,MAAa;IAC3C,MAAM,IAAI,KAAK,CACb,2DAA2D,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CACpF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,IAAW,EACX,OAA6B;IAE7B,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACnE,IAAI,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YAC7C,OAAO,OAAO,CAAC,MAAM,CAAC,GAA0C,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACrE,OAAO,WAAW,CAAC,CAAC,CAAC,GAAG,gBAAgB,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC;AAC/E,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,eAAe,CAAC,MAAa;IAC3C,MAAM,IAAI,KAAK,CACb,2DAA2D,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CACpF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAW,EACX,OAA6B;IAE7B,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACpE,IAAI,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YAC7C,OAAO,kBAAkB,CACvB,OAAO,CAAC,MAAM,CAAC,GAA0C,CAAC,CAC3D,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,oCAAoC,GAAG,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC;SACxD,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;SAC1C,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE/C,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,CAAC;IAEjE,OAAO,WAAW,CAAC,CAAC,CAAC,GAAG,gBAAgB,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC;AAC/E,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAKxB,IAA4C;IAE5C,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routepact/core",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Type-safe route spec definitions shared between server and client",
5
5
  "type": "module",
6
6
  "repository": "gitlab:mr5k/routepact",