ts-procedures 1.0.0 → 1.1.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.
Files changed (78) hide show
  1. package/README.md +13 -3
  2. package/build/errors.d.ts +16 -0
  3. package/build/errors.js +37 -0
  4. package/build/errors.js.map +1 -0
  5. package/build/exports.d.ts +6 -0
  6. package/build/exports.js +7 -0
  7. package/build/exports.js.map +1 -0
  8. package/build/implementations/http/client/index.d.ts +1 -0
  9. package/build/implementations/http/client/index.js +2 -0
  10. package/build/implementations/http/client/index.js.map +1 -0
  11. package/build/implementations/http/express/example/factories.d.ts +97 -0
  12. package/build/implementations/http/express/example/factories.js +4 -0
  13. package/build/implementations/http/express/example/factories.js.map +1 -0
  14. package/build/implementations/http/express/example/procedures/auth.d.ts +1 -0
  15. package/build/implementations/http/express/example/procedures/auth.js +22 -0
  16. package/build/implementations/http/express/example/procedures/auth.js.map +1 -0
  17. package/build/implementations/http/express/example/procedures/users.d.ts +1 -0
  18. package/build/implementations/http/express/example/procedures/users.js +30 -0
  19. package/build/implementations/http/express/example/procedures/users.js.map +1 -0
  20. package/build/implementations/http/express/example/server.d.ts +3 -0
  21. package/build/implementations/http/express/example/server.js +49 -0
  22. package/build/implementations/http/express/example/server.js.map +1 -0
  23. package/build/implementations/http/express/example/server.test.d.ts +1 -0
  24. package/build/implementations/http/express/example/server.test.js +110 -0
  25. package/build/implementations/http/express/example/server.test.js.map +1 -0
  26. package/build/implementations/http/express/index.d.ts +34 -0
  27. package/build/implementations/http/express/index.js +75 -0
  28. package/build/implementations/http/express/index.js.map +1 -0
  29. package/build/implementations/http/express/index.test.d.ts +1 -0
  30. package/build/implementations/http/express/index.test.js +329 -0
  31. package/build/implementations/http/express/index.test.js.map +1 -0
  32. package/build/index.d.ts +71 -0
  33. package/build/index.js +80 -0
  34. package/build/index.js.map +1 -0
  35. package/build/index.test.d.ts +1 -0
  36. package/build/index.test.js +249 -0
  37. package/build/index.test.js.map +1 -0
  38. package/build/schema/compute-schema.d.ts +23 -0
  39. package/build/schema/compute-schema.js +28 -0
  40. package/build/schema/compute-schema.js.map +1 -0
  41. package/build/schema/compute-schema.test.d.ts +1 -0
  42. package/build/schema/compute-schema.test.js +107 -0
  43. package/build/schema/compute-schema.test.js.map +1 -0
  44. package/build/schema/extract-json-schema.d.ts +2 -0
  45. package/build/schema/extract-json-schema.js +12 -0
  46. package/build/schema/extract-json-schema.js.map +1 -0
  47. package/build/schema/extract-json-schema.test.d.ts +1 -0
  48. package/build/schema/extract-json-schema.test.js +23 -0
  49. package/build/schema/extract-json-schema.test.js.map +1 -0
  50. package/build/schema/parser.d.ts +21 -0
  51. package/build/schema/parser.js +71 -0
  52. package/build/schema/parser.js.map +1 -0
  53. package/build/schema/parser.test.d.ts +1 -0
  54. package/build/schema/parser.test.js +102 -0
  55. package/build/schema/parser.test.js.map +1 -0
  56. package/build/schema/resolve-schema-lib.d.ts +12 -0
  57. package/build/schema/resolve-schema-lib.js +11 -0
  58. package/build/schema/resolve-schema-lib.js.map +1 -0
  59. package/build/schema/resolve-schema-lib.test.d.ts +1 -0
  60. package/build/schema/resolve-schema-lib.test.js +17 -0
  61. package/build/schema/resolve-schema-lib.test.js.map +1 -0
  62. package/build/schema/types.d.ts +7 -0
  63. package/build/schema/types.js +2 -0
  64. package/build/schema/types.js.map +1 -0
  65. package/package.json +22 -9
  66. package/src/implementations/http/client/index.ts +0 -0
  67. package/src/implementations/http/express/README.md +351 -0
  68. package/src/implementations/http/express/example/factories.ts +25 -0
  69. package/src/implementations/http/express/example/procedures/auth.ts +24 -0
  70. package/src/implementations/http/express/example/procedures/users.ts +32 -0
  71. package/src/implementations/http/express/example/server.test.ts +133 -0
  72. package/src/implementations/http/express/example/server.ts +67 -0
  73. package/src/implementations/http/express/index.test.ts +526 -0
  74. package/src/implementations/http/express/index.ts +108 -0
  75. package/src/index.test.ts +4 -2
  76. package/src/index.ts +9 -17
  77. package/src/schema/parser.ts +5 -4
  78. package/src/schema/types.ts +0 -1
@@ -0,0 +1,102 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { extractSingleJsonSchema, v } from 'suretype';
3
+ import { schemaParser } from './parser.js';
4
+ import { Type } from 'typebox';
5
+ describe('schemaParser', () => {
6
+ test('it parses params to json-schema', async () => {
7
+ let done = () => void 0;
8
+ const promise = new Promise((r) => {
9
+ done = r;
10
+ });
11
+ const params = v.object({
12
+ name: v.string(),
13
+ age: v.number(),
14
+ });
15
+ const result = schemaParser({
16
+ params: params,
17
+ }, (errors) => {
18
+ throw new Error(JSON.stringify(errors));
19
+ });
20
+ expect(result.jsonSchema.params).toEqual(extractSingleJsonSchema(params)?.schema);
21
+ done();
22
+ await promise;
23
+ await promise;
24
+ });
25
+ test('it parses params and generates a validator function', async () => {
26
+ let done = () => void 0;
27
+ const promise = new Promise((r) => {
28
+ done = r;
29
+ });
30
+ const params = v.object({
31
+ name: v.string(),
32
+ age: v.number().required(),
33
+ });
34
+ const result = schemaParser({
35
+ params: params,
36
+ }, (errors) => {
37
+ throw new Error(JSON.stringify(errors));
38
+ });
39
+ expect(result.validation.params?.({
40
+ name: 'John',
41
+ age: 30,
42
+ })?.errors).toBeUndefined();
43
+ expect(result.validation.params?.({
44
+ name: { name: '' },
45
+ age: 'poop',
46
+ })?.errors).toBeDefined();
47
+ expect(result.validation.params?.({
48
+ name: { name: '' },
49
+ age: 'poop',
50
+ })?.errors?.length).toEqual(2);
51
+ done();
52
+ await promise;
53
+ });
54
+ test('it parses returnType to json-schema', async () => {
55
+ let done = () => void 0;
56
+ const promise = new Promise((r) => {
57
+ done = r;
58
+ });
59
+ const returnType = v.object({
60
+ name: v.string(),
61
+ age: v.number(),
62
+ });
63
+ const result = schemaParser({
64
+ returnType: returnType,
65
+ }, (errors) => {
66
+ throw new Error(JSON.stringify(errors));
67
+ });
68
+ expect(result.jsonSchema.returnType).toEqual(extractSingleJsonSchema(returnType)?.schema);
69
+ done();
70
+ await promise;
71
+ });
72
+ test('it throws a meaningful error to the dev', async () => {
73
+ schemaParser(
74
+ // invalid params schema
75
+ { params: { test: Type.String() } }, (errors) => {
76
+ expect(errors.params).toMatch(/Error extracting json schema schema.params/);
77
+ });
78
+ schemaParser(
79
+ // invalid returnType schema
80
+ { returnType: 'string value' }, (errors) => {
81
+ expect(errors.returnType).toMatch(/Error extracting json schema schema.returnType/);
82
+ });
83
+ });
84
+ test('it parses multiple schemas correct', async () => {
85
+ const schema = schemaParser({
86
+ params: Type.Object({ a: Type.String() }),
87
+ returnType: Type.Object({ b: Type.Null() }),
88
+ }, (error) => {
89
+ throw new Error(JSON.stringify(error));
90
+ });
91
+ const schema2 = schemaParser({
92
+ params: Type.Object({ c: Type.String() }),
93
+ returnType: Type.Object({ d: Type.Number() }),
94
+ }, (error) => {
95
+ throw new Error(JSON.stringify(error));
96
+ });
97
+ expect(schema.validation.params?.({}).errors?.[0]?.message).toMatch(/must have required property 'a'/);
98
+ expect(schema2.validation.params?.({ c: 'test' })).toMatchObject({});
99
+ expect(schema.validation.params?.({}).errors?.[0]?.message).toMatch(/must have required property 'a'/);
100
+ });
101
+ });
102
+ //# sourceMappingURL=parser.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.test.js","sourceRoot":"","sources":["../../src/schema/parser.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { CoreValidator } from 'suretype';
2
+ import { Type } from 'typebox';
3
+ export type IsTypeboxSchema<TSchema> = TSchema extends {
4
+ static: unknown;
5
+ params: unknown;
6
+ } ? true : false;
7
+ export declare function isTypeboxSchema(schema: any): schema is Type.TSchema;
8
+ export type IsSuretypeSchema<TSchema> = TSchema extends {
9
+ required: () => object;
10
+ nullable?: never;
11
+ } ? true : false;
12
+ export declare function isSuretypeSchema(schema: any): schema is CoreValidator<any>;
@@ -0,0 +1,11 @@
1
+ export function isTypeboxSchema(schema) {
2
+ return (
3
+ // typebox v1
4
+ (typeof schema === 'object' && '~kind' in schema) ||
5
+ // @sinclair/typebox v0.3x
6
+ (typeof schema === 'object' && Symbol.for('TypeBox.Kind') in schema));
7
+ }
8
+ export function isSuretypeSchema(schema) {
9
+ return typeof schema === 'object' && 'getJsonSchemaObject' in schema;
10
+ }
11
+ //# sourceMappingURL=resolve-schema-lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-schema-lib.js","sourceRoot":"","sources":["../../src/schema/resolve-schema-lib.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,eAAe,CAAC,MAAW;IACzC,OAAO;IACL,cAAc;IACd,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,IAAI,MAAM,CAAC;QACjD,0BAA0B;QAC1B,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,MAAM,CAAC,CACrE,CAAA;AACH,CAAC;AASD,MAAM,UAAU,gBAAgB,CAAC,MAAW;IAC1C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,qBAAqB,IAAI,MAAM,CAAA;AACtE,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { isSuretypeSchema, isTypeboxSchema } from './resolve-schema-lib.js';
3
+ import { Type } from 'typebox';
4
+ import { v } from 'suretype';
5
+ describe('lib schema resolvers', () => {
6
+ const typebox = Type.Object({ name: Type.String() });
7
+ const suretype = v.object({ name: v.string() });
8
+ test('it recognizes TypeBox schema', async () => {
9
+ expect(isTypeboxSchema(typebox)).toBe(true);
10
+ expect(isTypeboxSchema(suretype)).toBe(false);
11
+ });
12
+ test('it recognizes Suretype schema', async () => {
13
+ expect(isSuretypeSchema(suretype)).toBe(true);
14
+ expect(isSuretypeSchema(typebox)).toBe(false);
15
+ });
16
+ });
17
+ //# sourceMappingURL=resolve-schema-lib.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-schema-lib.test.js","sourceRoot":"","sources":["../../src/schema/resolve-schema-lib.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC3E,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,EAAE,CAAC,EAAE,MAAM,UAAU,CAAA;AAE5B,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACpD,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAE/C,IAAI,CAAC,8BAA8B,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3C,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC7C,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,7 @@
1
+ import { CoreValidator, TypeOf } from 'suretype';
2
+ import { Static, TSchema } from 'typebox';
3
+ export type TSchemaLib<SchemaLibType> = SchemaLibType extends CoreValidator<any> ? TypeOf<SchemaLibType> : SchemaLibType extends TSchema ? Static<SchemaLibType> : unknown;
4
+ export type TJSONSchema = Record<string, any>;
5
+ export type Prettify<TObject> = {
6
+ [Key in keyof TObject]: TObject[Key];
7
+ } & {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/schema/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,27 +1,32 @@
1
1
  {
2
2
  "name": "ts-procedures",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.",
5
- "main": "dist/exports.js",
6
- "types": "dist/exports.d.ts",
5
+ "main": "build/exports.js",
6
+ "types": "build/exports.d.ts",
7
7
  "type": "module",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/exports.d.ts",
11
- "import": "./dist/exports.js"
10
+ "types": "./build/exports.d.ts",
11
+ "import": "./build/exports.js"
12
12
  },
13
- "./package.json": "./package.json"
13
+ "./package.json": "./package.json",
14
+ "./express": {
15
+ "types": "./build/implementations/http/express/index.d.ts",
16
+ "import": "./build/implementations/http/express/index.js"
17
+ }
14
18
  },
15
19
  "scripts": {
16
20
  "build": "tsc",
17
21
  "lint": "npx eslint src/ --quiet",
22
+ "prepublishOnly": "npm run lint && npm run build",
18
23
  "test": "vitest run"
19
24
  },
20
25
  "author": "coryrobinson42@gmail.com",
21
26
  "license": "MIT",
22
27
  "files": [
23
28
  "assets",
24
- "dist",
29
+ "build",
25
30
  "src"
26
31
  ],
27
32
  "keywords": [
@@ -33,15 +38,23 @@
33
38
  "api",
34
39
  "framework"
35
40
  ],
41
+ "optionalDependencies": {
42
+ "express": "^5.2.1"
43
+ },
36
44
  "dependencies": {
37
45
  "ajv": "^8.17.1",
38
46
  "ajv-formats": "^3.0.1",
39
- "typebox": "^1.0.30",
40
- "suretype": "^3.3.1"
47
+ "suretype": "^3.3.1",
48
+ "typebox": "^1.0.30"
41
49
  },
42
50
  "devDependencies": {
43
51
  "@eslint/js": "^9.17.0",
52
+ "@types/express": "^5.0.6",
53
+ "@types/supertest": "^6.0.3",
44
54
  "eslint": "^9.17.0",
55
+ "express": "^5.2.1",
56
+ "prettier": "^3.8.1",
57
+ "supertest": "^7.2.2",
45
58
  "suretype": "^3.3.1",
46
59
  "typebox": "^1.0.77",
47
60
  "typescript-eslint": "^8.53.0",
File without changes
@@ -0,0 +1,351 @@
1
+ # Express Integration Guide
2
+
3
+ ## Overview
4
+
5
+ `registerExpressRoutes` is a utility function that simplifies setting up an Express server with ts-procedures. It handles:
6
+
7
+ - **Automatic parameter merging** - Combines path params (`:id`), query params, and body into a single `params` object
8
+ - **Schema validation** - Validates merged params against your procedure's schema
9
+ - **Error handling** - Provides hooks for custom error responses
10
+ - **Context injection** - Creates procedure context from Express request/response
11
+
12
+ ## Quick Start
13
+
14
+ ```typescript
15
+ import express from 'express'
16
+ import { Procedures } from 'ts-procedures'
17
+ import { registerExpressRoutes } from 'ts-procedures/express'
18
+ import { Type } from 'typebox'
19
+
20
+ // Define extended config for HTTP routes
21
+ interface HTTPRouteConfig {
22
+ path: string
23
+ method: 'get' | 'post' | 'put' | 'delete' | 'patch'
24
+ }
25
+
26
+ // Create factory with route config
27
+ const { Create, getProcedures } = Procedures<{}, HTTPRouteConfig>()
28
+
29
+ // Define a procedure
30
+ Create('GetUser', {
31
+ path: '/users/:id',
32
+ method: 'get',
33
+ schema: {
34
+ params: Type.Object({ id: Type.String() }),
35
+ returnType: Type.Object({ id: Type.String(), name: Type.String() })
36
+ }
37
+ }, async (ctx, params) => {
38
+ return { id: params.id, name: 'John Doe' }
39
+ })
40
+
41
+ // Register routes
42
+ const app = express()
43
+ app.use(express.json())
44
+
45
+ registerExpressRoutes(
46
+ app,
47
+ { getContext: async (req, res) => ({}) },
48
+ getProcedures()
49
+ )
50
+
51
+ app.listen(3000)
52
+ ```
53
+
54
+ ## Architecture Pattern
55
+
56
+ ### Public vs Protected Factories
57
+
58
+ A common pattern is to separate procedures that require authentication from public endpoints:
59
+
60
+ ```typescript
61
+ // factories.ts
62
+ import { Procedures } from 'ts-procedures'
63
+
64
+ export interface ProceduresContext {
65
+ headers: Record<string, string>
66
+ ipAddress: string
67
+ requestId: string
68
+ }
69
+
70
+ export interface ProtectedContext extends ProceduresContext {
71
+ userId: string // Required - user must be authenticated
72
+ }
73
+
74
+ export interface PublicContext extends ProceduresContext {
75
+ userId?: undefined // Optional - may or may not be authenticated
76
+ }
77
+
78
+ export interface HTTPRouteConfig {
79
+ path: string
80
+ method: 'get' | 'post' | 'put' | 'delete' | 'patch'
81
+ }
82
+
83
+ export const ProtectedFactory = Procedures<ProtectedContext, HTTPRouteConfig>()
84
+ export const PublicFactory = Procedures<PublicContext, HTTPRouteConfig>()
85
+ ```
86
+
87
+ ### File Organization
88
+
89
+ ```
90
+ src/
91
+ ├── factories.ts # Context interfaces and factory setup
92
+ ├── procedures/
93
+ │ ├── auth.ts # Public endpoints (login, register)
94
+ │ └── users.ts # Protected endpoints (profile, settings)
95
+ └── server.ts # Express app and route registration
96
+ ```
97
+
98
+ ## Complete Example
99
+
100
+ ### factories.ts
101
+
102
+ ```typescript
103
+ import { Procedures } from 'ts-procedures'
104
+
105
+ export interface ProceduresContext {
106
+ headers: Record<string, string>
107
+ ipAddress: string
108
+ requestId: string
109
+ }
110
+
111
+ export interface ProtectedContext extends ProceduresContext {
112
+ userId: string
113
+ }
114
+
115
+ export interface PublicContext extends ProceduresContext {
116
+ userId?: undefined
117
+ }
118
+
119
+ export interface HTTPRouteConfig {
120
+ path: string
121
+ method: 'get' | 'post' | 'put' | 'delete' | 'patch'
122
+ }
123
+
124
+ export const ProtectedFactory = Procedures<ProtectedContext, HTTPRouteConfig>()
125
+ export const PublicFactory = Procedures<PublicContext, HTTPRouteConfig>()
126
+ ```
127
+
128
+ ### procedures/auth.ts
129
+
130
+ ```typescript
131
+ import { PublicFactory } from '../factories.js'
132
+ import { Type } from 'typebox'
133
+
134
+ PublicFactory.Create('Authenticate', {
135
+ path: '/authenticate',
136
+ method: 'post',
137
+ schema: {
138
+ params: Type.Object({
139
+ username: Type.String({ minLength: 3 }),
140
+ password: Type.String({ minLength: 6 }),
141
+ }),
142
+ returnType: Type.Object({
143
+ token: Type.String(),
144
+ })
145
+ },
146
+ description: 'Authenticate as a user and obtain a token',
147
+ }, async (ctx, params) => {
148
+ // Verify credentials and return token
149
+ return { token: 'jwt-token-here' }
150
+ })
151
+ ```
152
+
153
+ ### procedures/users.ts
154
+
155
+ ```typescript
156
+ import { ProtectedFactory } from '../factories.js'
157
+ import { Type } from 'typebox'
158
+
159
+ ProtectedFactory.Create('GetUserProfile', {
160
+ path: '/users/user-profile/:id',
161
+ method: 'get',
162
+ schema: {
163
+ params: Type.Object({
164
+ id: Type.String(), // Mapped from :id in path
165
+ }),
166
+ returnType: Type.Object({
167
+ user: Type.Object({
168
+ id: Type.String(),
169
+ name: Type.String(),
170
+ email: Type.String(),
171
+ }),
172
+ })
173
+ },
174
+ description: 'Get the profile of a specific user',
175
+ }, async (ctx, params) => {
176
+ // ctx.userId is guaranteed to exist (ProtectedContext)
177
+ return {
178
+ user: {
179
+ id: params.id,
180
+ name: 'Jane Doe',
181
+ email: 'jane@example.com'
182
+ }
183
+ }
184
+ })
185
+ ```
186
+
187
+ ### server.ts
188
+
189
+ ```typescript
190
+ import express from 'express'
191
+ import { PublicFactory, ProtectedFactory } from './factories.js'
192
+ import { registerExpressRoutes } from 'ts-procedures/express'
193
+
194
+ // Import procedures for side effects (registers with factories)
195
+ import './procedures/auth.js'
196
+ import './procedures/users.js'
197
+
198
+ const app = express()
199
+ app.use(express.json())
200
+
201
+ // Register public routes (no auth required)
202
+ registerExpressRoutes(
203
+ app,
204
+ {
205
+ getContext: async (req, res) => ({
206
+ ipAddress: req.ip || '',
207
+ requestId: req.headers['x-request-id']?.toString() || crypto.randomUUID(),
208
+ headers: req.headers as Record<string, string>,
209
+ })
210
+ },
211
+ PublicFactory.getProcedures()
212
+ )
213
+
214
+ // Register protected routes (auth required)
215
+ registerExpressRoutes(
216
+ app,
217
+ {
218
+ getContext: async (req, res) => {
219
+ const token = req.headers['authorization']?.replace('Bearer ', '')
220
+ const user = await validateToken(token) // Your auth logic
221
+
222
+ if (!user) {
223
+ res.status(401).json({ error: 'Unauthorized' })
224
+ throw new Error('Unauthorized')
225
+ }
226
+
227
+ return {
228
+ userId: user.id,
229
+ ipAddress: req.ip || '',
230
+ requestId: req.headers['x-request-id']?.toString() || crypto.randomUUID(),
231
+ headers: req.headers as Record<string, string>,
232
+ }
233
+ }
234
+ },
235
+ ProtectedFactory.getProcedures()
236
+ )
237
+
238
+ app.listen(3000)
239
+ ```
240
+
241
+ ## API Reference
242
+
243
+ ```typescript
244
+ registerExpressRoutes<ProceduresContext>(
245
+ app: express.Application,
246
+ callbacks: {
247
+ /** Create procedure context from Express request */
248
+ getContext: (req: express.Request, res: express.Response) => Promise<ProceduresContext>
249
+
250
+ /** Optional handler for procedure errors (default: 500 with error message) */
251
+ onHandlerError?: (error: Error, req: express.Request, res: express.Response) => void
252
+
253
+ /** Optional handler for validation errors (default: 422 with error details) */
254
+ onValidationError?: (
255
+ errors: Array<{ message: string; path: string[] }>,
256
+ req: express.Request,
257
+ res: express.Response
258
+ ) => void
259
+ },
260
+ procedures: Array<TProcedureRegistration<ProceduresContext, HTTPRouteConfig>>
261
+ ): void
262
+ ```
263
+
264
+ ## Features
265
+
266
+ ### Path Parameter Mapping
267
+
268
+ Path parameters defined with `:paramName` are automatically extracted and merged into the params object:
269
+
270
+ ```typescript
271
+ // Route: /users/:id/posts/:postId
272
+ // URL: /users/123/posts/456
273
+ // Params: { id: '123', postId: '456' }
274
+ ```
275
+
276
+ ### Parameter Merging
277
+
278
+ Parameters are merged in this order (later sources override earlier):
279
+
280
+ 1. Path parameters (`:id`)
281
+ 2. Query parameters (`?foo=bar`)
282
+ 3. Body parameters (JSON body)
283
+
284
+ ### Schema Validation
285
+
286
+ When validation fails, the default behavior returns a 422 response:
287
+
288
+ ```json
289
+ {
290
+ "error": "Validation Error",
291
+ "details": [
292
+ { "message": "must be string", "path": ["username"] }
293
+ ]
294
+ }
295
+ ```
296
+
297
+ ### Custom Error Handlers
298
+
299
+ ```typescript
300
+ registerExpressRoutes(
301
+ app,
302
+ {
303
+ getContext: async (req, res) => ({}),
304
+
305
+ onValidationError: (errors, req, res) => {
306
+ res.status(400).json({
307
+ code: 'INVALID_INPUT',
308
+ errors: errors.map(e => e.message)
309
+ })
310
+ },
311
+
312
+ onHandlerError: (error, req, res) => {
313
+ if (error instanceof ProcedureError) {
314
+ res.status(error.code || 500).json({ message: error.message })
315
+ } else {
316
+ res.status(500).json({ message: 'Internal Server Error' })
317
+ }
318
+ }
319
+ },
320
+ getProcedures()
321
+ )
322
+ ```
323
+
324
+ ### Authentication Pattern
325
+
326
+ The `getContext` callback is the ideal place to handle authentication:
327
+
328
+ ```typescript
329
+ getContext: async (req, res) => {
330
+ const token = req.headers['authorization']
331
+
332
+ if (!token) {
333
+ res.status(401).json({ error: 'No token provided' })
334
+ throw new Error('Unauthorized')
335
+ }
336
+
337
+ const user = await verifyToken(token)
338
+
339
+ if (!user) {
340
+ res.status(401).json({ error: 'Invalid token' })
341
+ throw new Error('Unauthorized')
342
+ }
343
+
344
+ return {
345
+ userId: user.id,
346
+ // ... other context
347
+ }
348
+ }
349
+ ```
350
+
351
+ When `getContext` throws, the route handler stops execution. This allows you to respond with an error and prevent the procedure from running.
@@ -0,0 +1,25 @@
1
+ import { Procedures } from '../../../../index.js'
2
+
3
+ export interface ProceduresContext {
4
+ headers: Record<string, string>
5
+ ipAddress: string
6
+ requestId: string
7
+ }
8
+
9
+ export interface ProtectedContext extends ProceduresContext {
10
+ userId: string
11
+ }
12
+
13
+ export interface PublicContext extends ProceduresContext {
14
+ // An authenticated client can call public endpoints so userId is optional
15
+ userId?: undefined
16
+ }
17
+
18
+ export const ProtectedFactory = Procedures<ProtectedContext,HTTPRouteConfig>()
19
+
20
+ export const PublicFactory = Procedures<PublicContext,HTTPRouteConfig>()
21
+
22
+ export interface HTTPRouteConfig {
23
+ path: string
24
+ method: 'get' | 'post' | 'put' | 'delete' | 'patch'
25
+ }
@@ -0,0 +1,24 @@
1
+ import { PublicFactory } from '../factories.js'
2
+ import { Type } from 'typebox'
3
+
4
+ PublicFactory.Create('Authenticate', {
5
+ path: '/authenticate',
6
+ method: 'post',
7
+ schema: {
8
+ params: Type.Object({
9
+ username: Type.String({ minLength: 3 }),
10
+ password: Type.String({ minLength: 6 }),
11
+ }),
12
+ returnType: Type.Object({
13
+ token: Type.String(),
14
+ })
15
+ },
16
+ description: 'Authenticate as a user and obtain a token',
17
+ },
18
+ async (ctx, params) => {
19
+ // In a real implementation, you would verify user credentials here, ie: ctx.services.userService.authenticate(params.username, params.password)
20
+
21
+ return {
22
+ token: 'fake-jwt-token',
23
+ }
24
+ })
@@ -0,0 +1,32 @@
1
+ import { ProtectedFactory } from '../factories.js'
2
+ import { Type } from 'typebox'
3
+
4
+ ProtectedFactory.Create('Get User Profile', {
5
+ path: '/users/user-profile/:id',
6
+ method: 'get',
7
+ schema: {
8
+ params: Type.Object({
9
+ // Our router in this example will map :id to this param
10
+ id: Type.String(),
11
+ }),
12
+ returnType: Type.Object({
13
+ user: Type.Object({
14
+ id: Type.String(),
15
+ name: Type.String(),
16
+ email: Type.String(),
17
+ }),
18
+ })
19
+ },
20
+ description: 'Get the profile of a specific user',
21
+ },
22
+ async (ctx, params) => {
23
+ // In a real implementation, you would fetch the user profile from a database or service here, ie: ctx.services.userService.getUserProfile(params.userId)
24
+
25
+ return {
26
+ user: {
27
+ id: params.id,
28
+ name: 'Jane Doe',
29
+ email: 'test@gmail.com'
30
+ },
31
+ }
32
+ })