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.
- package/README.md +13 -3
- package/build/errors.d.ts +16 -0
- package/build/errors.js +37 -0
- package/build/errors.js.map +1 -0
- package/build/exports.d.ts +6 -0
- package/build/exports.js +7 -0
- package/build/exports.js.map +1 -0
- package/build/implementations/http/client/index.d.ts +1 -0
- package/build/implementations/http/client/index.js +2 -0
- package/build/implementations/http/client/index.js.map +1 -0
- package/build/implementations/http/express/example/factories.d.ts +97 -0
- package/build/implementations/http/express/example/factories.js +4 -0
- package/build/implementations/http/express/example/factories.js.map +1 -0
- package/build/implementations/http/express/example/procedures/auth.d.ts +1 -0
- package/build/implementations/http/express/example/procedures/auth.js +22 -0
- package/build/implementations/http/express/example/procedures/auth.js.map +1 -0
- package/build/implementations/http/express/example/procedures/users.d.ts +1 -0
- package/build/implementations/http/express/example/procedures/users.js +30 -0
- package/build/implementations/http/express/example/procedures/users.js.map +1 -0
- package/build/implementations/http/express/example/server.d.ts +3 -0
- package/build/implementations/http/express/example/server.js +49 -0
- package/build/implementations/http/express/example/server.js.map +1 -0
- package/build/implementations/http/express/example/server.test.d.ts +1 -0
- package/build/implementations/http/express/example/server.test.js +110 -0
- package/build/implementations/http/express/example/server.test.js.map +1 -0
- package/build/implementations/http/express/index.d.ts +34 -0
- package/build/implementations/http/express/index.js +75 -0
- package/build/implementations/http/express/index.js.map +1 -0
- package/build/implementations/http/express/index.test.d.ts +1 -0
- package/build/implementations/http/express/index.test.js +329 -0
- package/build/implementations/http/express/index.test.js.map +1 -0
- package/build/index.d.ts +71 -0
- package/build/index.js +80 -0
- package/build/index.js.map +1 -0
- package/build/index.test.d.ts +1 -0
- package/build/index.test.js +249 -0
- package/build/index.test.js.map +1 -0
- package/build/schema/compute-schema.d.ts +23 -0
- package/build/schema/compute-schema.js +28 -0
- package/build/schema/compute-schema.js.map +1 -0
- package/build/schema/compute-schema.test.d.ts +1 -0
- package/build/schema/compute-schema.test.js +107 -0
- package/build/schema/compute-schema.test.js.map +1 -0
- package/build/schema/extract-json-schema.d.ts +2 -0
- package/build/schema/extract-json-schema.js +12 -0
- package/build/schema/extract-json-schema.js.map +1 -0
- package/build/schema/extract-json-schema.test.d.ts +1 -0
- package/build/schema/extract-json-schema.test.js +23 -0
- package/build/schema/extract-json-schema.test.js.map +1 -0
- package/build/schema/parser.d.ts +21 -0
- package/build/schema/parser.js +71 -0
- package/build/schema/parser.js.map +1 -0
- package/build/schema/parser.test.d.ts +1 -0
- package/build/schema/parser.test.js +102 -0
- package/build/schema/parser.test.js.map +1 -0
- package/build/schema/resolve-schema-lib.d.ts +12 -0
- package/build/schema/resolve-schema-lib.js +11 -0
- package/build/schema/resolve-schema-lib.js.map +1 -0
- package/build/schema/resolve-schema-lib.test.d.ts +1 -0
- package/build/schema/resolve-schema-lib.test.js +17 -0
- package/build/schema/resolve-schema-lib.test.js.map +1 -0
- package/build/schema/types.d.ts +7 -0
- package/build/schema/types.js +2 -0
- package/build/schema/types.js.map +1 -0
- package/package.json +22 -9
- package/src/implementations/http/client/index.ts +0 -0
- package/src/implementations/http/express/README.md +351 -0
- package/src/implementations/http/express/example/factories.ts +25 -0
- package/src/implementations/http/express/example/procedures/auth.ts +24 -0
- package/src/implementations/http/express/example/procedures/users.ts +32 -0
- package/src/implementations/http/express/example/server.test.ts +133 -0
- package/src/implementations/http/express/example/server.ts +67 -0
- package/src/implementations/http/express/index.test.ts +526 -0
- package/src/implementations/http/express/index.ts +108 -0
- package/src/index.test.ts +4 -2
- package/src/index.ts +9 -17
- package/src/schema/parser.ts +5 -4
- 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 @@
|
|
|
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.
|
|
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": "
|
|
6
|
-
"types": "
|
|
5
|
+
"main": "build/exports.js",
|
|
6
|
+
"types": "build/exports.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./
|
|
11
|
-
"import": "./
|
|
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
|
-
"
|
|
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
|
-
"
|
|
40
|
-
"
|
|
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
|
+
})
|