ts-procedures 5.3.0 → 5.4.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 (60) hide show
  1. package/README.md +90 -0
  2. package/agent_config/claude-code/agents/ts-procedures-architect.md +15 -0
  3. package/agent_config/claude-code/skills/guide/anti-patterns.md +106 -0
  4. package/agent_config/claude-code/skills/guide/api-reference.md +150 -4
  5. package/agent_config/claude-code/skills/guide/patterns.md +155 -0
  6. package/agent_config/claude-code/skills/review/checklist.md +22 -0
  7. package/agent_config/claude-code/skills/scaffold/SKILL.md +3 -1
  8. package/agent_config/claude-code/skills/scaffold/templates/hono-api.md +169 -0
  9. package/agent_config/copilot/copilot-instructions.md +35 -0
  10. package/agent_config/cursor/cursorrules +35 -0
  11. package/build/implementations/http/hono-api/index.d.ts +102 -0
  12. package/build/implementations/http/hono-api/index.js +339 -0
  13. package/build/implementations/http/hono-api/index.js.map +1 -0
  14. package/build/implementations/http/hono-api/index.test.d.ts +1 -0
  15. package/build/implementations/http/hono-api/index.test.js +983 -0
  16. package/build/implementations/http/hono-api/index.test.js.map +1 -0
  17. package/build/implementations/http/hono-api/types.d.ts +13 -0
  18. package/build/implementations/http/hono-api/types.js +2 -0
  19. package/build/implementations/http/hono-api/types.js.map +1 -0
  20. package/build/implementations/types.d.ts +44 -0
  21. package/build/index.d.ts +28 -6
  22. package/build/index.js +28 -0
  23. package/build/index.js.map +1 -1
  24. package/build/schema/compute-schema.d.ts +5 -0
  25. package/build/schema/compute-schema.js +8 -1
  26. package/build/schema/compute-schema.js.map +1 -1
  27. package/build/schema/parser.d.ts +6 -5
  28. package/build/schema/parser.js +54 -0
  29. package/build/schema/parser.js.map +1 -1
  30. package/package.json +8 -4
  31. package/src/errors.test.ts +0 -163
  32. package/src/errors.ts +0 -107
  33. package/src/exports.ts +0 -7
  34. package/src/implementations/http/README.md +0 -217
  35. package/src/implementations/http/express-rpc/README.md +0 -281
  36. package/src/implementations/http/express-rpc/index.test.ts +0 -957
  37. package/src/implementations/http/express-rpc/index.ts +0 -265
  38. package/src/implementations/http/express-rpc/types.ts +0 -16
  39. package/src/implementations/http/hono-rpc/README.md +0 -358
  40. package/src/implementations/http/hono-rpc/index.test.ts +0 -1075
  41. package/src/implementations/http/hono-rpc/index.ts +0 -237
  42. package/src/implementations/http/hono-rpc/types.ts +0 -16
  43. package/src/implementations/http/hono-stream/README.md +0 -526
  44. package/src/implementations/http/hono-stream/index.test.ts +0 -1676
  45. package/src/implementations/http/hono-stream/index.ts +0 -435
  46. package/src/implementations/http/hono-stream/types.ts +0 -29
  47. package/src/implementations/types.ts +0 -75
  48. package/src/index.test.ts +0 -1194
  49. package/src/index.ts +0 -435
  50. package/src/schema/compute-schema.test.ts +0 -128
  51. package/src/schema/compute-schema.ts +0 -67
  52. package/src/schema/extract-json-schema.test.ts +0 -25
  53. package/src/schema/extract-json-schema.ts +0 -15
  54. package/src/schema/parser.test.ts +0 -182
  55. package/src/schema/parser.ts +0 -148
  56. package/src/schema/resolve-schema-lib.test.ts +0 -19
  57. package/src/schema/resolve-schema-lib.ts +0 -29
  58. package/src/schema/types.ts +0 -20
  59. package/src/stack-utils.test.ts +0 -94
  60. package/src/stack-utils.ts +0 -129
@@ -1,182 +0,0 @@
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
-
6
- describe('schemaParser', () => {
7
- test('it parses params to json-schema', async () => {
8
- let done: () => void = () => void 0
9
- const promise = new Promise<void>((r) => {
10
- done = r
11
- })
12
-
13
- const params = v.object({
14
- name: v.string(),
15
- age: v.number(),
16
- })
17
-
18
- const result = schemaParser(
19
- {
20
- params: params,
21
- },
22
- (errors) => {
23
- throw new Error(JSON.stringify(errors))
24
- },
25
- )
26
-
27
- expect(result.jsonSchema.params).toEqual(
28
- extractSingleJsonSchema(params)?.schema,
29
- )
30
-
31
- done()
32
-
33
- await promise
34
- await promise
35
- })
36
-
37
- test('it parses params and generates a validator function', async () => {
38
- let done: () => void = () => void 0
39
- const promise = new Promise<void>((r) => {
40
- done = r
41
- })
42
-
43
- const params = v.object({
44
- name: v.string(),
45
- age: v.number().required(),
46
- })
47
-
48
- const result = schemaParser(
49
- {
50
- params: params,
51
- },
52
- (errors) => {
53
- throw new Error(JSON.stringify(errors))
54
- },
55
- )
56
-
57
- expect(
58
- result.validation.params?.({
59
- name: 'John',
60
- age: 30,
61
- })?.errors,
62
- ).toBeUndefined()
63
-
64
- expect(
65
- result.validation.params?.({
66
- name: { name: '' },
67
- age: 'poop',
68
- })?.errors,
69
- ).toBeDefined()
70
-
71
- expect(
72
- result.validation.params?.({
73
- name: { name: '' },
74
- age: 'poop',
75
- })?.errors?.length,
76
- ).toEqual(2)
77
-
78
- done()
79
-
80
- await promise
81
- })
82
-
83
- test('it parses returnType to json-schema', async () => {
84
- let done: () => void = () => void 0
85
- const promise = new Promise<void>((r) => {
86
- done = r
87
- })
88
-
89
- const returnType = v.object({
90
- name: v.string(),
91
- age: v.number(),
92
- })
93
-
94
- const result = schemaParser(
95
- {
96
- returnType: returnType,
97
- },
98
- (errors) => {
99
- throw new Error(JSON.stringify(errors))
100
- },
101
- )
102
-
103
- expect(result.jsonSchema.returnType).toEqual(
104
- extractSingleJsonSchema(returnType)?.schema,
105
- )
106
-
107
- done()
108
-
109
- await promise
110
- })
111
-
112
- test('it throws a meaningful error to the dev', async () => {
113
- schemaParser(
114
- // invalid params schema
115
- { params: { test: Type.String() } },
116
- (errors) => {
117
- expect(errors.params).toMatch(/Error extracting json schema schema.params/)
118
- },
119
- )
120
-
121
- schemaParser(
122
- // invalid returnType schema
123
- { returnType: 'string value' },
124
- (errors) => {
125
- expect(errors.returnType).toMatch(/Error extracting json schema schema.returnType/)
126
- },
127
- )
128
- })
129
-
130
- test('it parses multiple schemas correct', async () => {
131
- const schema = schemaParser(
132
- {
133
- params: Type.Object({ a: Type.String() }),
134
- returnType: Type.Object({ b: Type.Null() }),
135
- },
136
- (error) => {
137
- throw new Error(JSON.stringify(error))
138
- },
139
- )
140
-
141
- const schema2= schemaParser(
142
- {
143
- params: Type.Object({ c: Type.String() }),
144
- returnType: Type.Object({ d: Type.Number() }),
145
- },
146
- (error) => {
147
- throw new Error(JSON.stringify(error))
148
- },
149
- )
150
-
151
- expect(schema.validation.params?.({}).errors?.[0]?.message).toMatch(/must have required property 'a'/)
152
- expect(schema2.validation.params?.({ c: 'test' })
153
- ).toMatchObject({})
154
- expect(schema.validation.params?.({}).errors?.[0]?.message).toMatch(/must have required property 'a'/)
155
- })
156
-
157
- test('validation returns error if validator fails to initialize', async () => {
158
- // Create a schema that will pass extraction but creates a validation function
159
- // that handles the uninitialized case gracefully
160
- let parseErrorCalled = false
161
-
162
- const result = schemaParser(
163
- {
164
- // Using a valid schema to get through extraction, but we'll test the guard
165
- params: Type.Object({ name: Type.String() }),
166
- },
167
- () => {
168
- parseErrorCalled = true
169
- },
170
- )
171
-
172
- // The validator should be initialized for valid schemas
173
- expect(result.validation.params).toBeDefined()
174
-
175
- // Test that the validation function works correctly
176
- const validResult = result.validation.params?.({ name: 'test' })
177
- expect(validResult?.errors).toBeUndefined()
178
-
179
- const invalidResult = result.validation.params?.({})
180
- expect(invalidResult?.errors).toBeDefined()
181
- })
182
- })
@@ -1,148 +0,0 @@
1
- import { default as addFormats } from 'ajv-formats'
2
- import * as AJV from 'ajv'
3
- import { extractJsonSchema } from './extract-json-schema.js'
4
- import { TJSONSchema } from './types.js'
5
-
6
- export type TSchemaParsed = {
7
- jsonSchema: { params?: TJSONSchema; returnType?: TJSONSchema; yieldType?: TJSONSchema }
8
- validation: {
9
- params?: (params: any) => { errors?: TSchemaValidationError[] }
10
- yield?: (value: any) => { errors?: TSchemaValidationError[] }
11
- }
12
- }
13
-
14
- export type TSchemaValidationError = AJV.ErrorObject
15
-
16
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
17
- // @ts-expect-error
18
- const ajv = addFormats(
19
- new AJV.Ajv({
20
- allErrors: true,
21
- coerceTypes: true,
22
- removeAdditional: true,
23
- })
24
- )
25
-
26
- export function schemaParser(
27
- schema: { params?: unknown; returnType?: unknown; yieldType?: unknown },
28
- onParseError: (errors: { params?: string; returnType?: string; yieldType?: string }) => void
29
- ): TSchemaParsed {
30
- const jsonSchema: TSchemaParsed['jsonSchema'] = {}
31
- const validation: TSchemaParsed['validation'] = {}
32
-
33
- if (schema.params) {
34
- try {
35
- const extracted = extractJsonSchema(schema.params as TJSONSchema)
36
-
37
- if (extracted) {
38
- jsonSchema.params = extracted
39
- }
40
- } catch (e: any) {
41
- onParseError({
42
- params: `Error extracting json schema schema.params - ${e.message}`,
43
- })
44
- }
45
-
46
- if (!jsonSchema.params) {
47
- onParseError({
48
- params: `Error extracting json schema schema.params - schema.params might be empty or it is not a valid suretype or typebox type`,
49
- })
50
- } else {
51
- let paramsValidator: AJV.ValidateFunction | undefined
52
-
53
- try {
54
- paramsValidator = ajv.compile(jsonSchema.params as TJSONSchema)
55
- } catch (e: any) {
56
- onParseError({
57
- params: `Error compiling schema.params for validator - ${e.message}`,
58
- })
59
- }
60
-
61
- validation.params = (params: any) => {
62
- if (!paramsValidator) {
63
- return { errors: [{ message: 'Validator not initialized', keyword: 'internal' } as TSchemaValidationError] }
64
- }
65
-
66
- const valid = paramsValidator(params)
67
-
68
- if (!valid) {
69
- const errors = paramsValidator.errors
70
-
71
- return {
72
- errors: errors?.length ? errors : undefined,
73
- }
74
- }
75
-
76
- return {}
77
- }
78
- }
79
- }
80
-
81
- if (schema.returnType) {
82
- try {
83
- const extracted = extractJsonSchema(schema.returnType as TJSONSchema)
84
-
85
- jsonSchema.returnType = extracted
86
- } catch (e: any) {
87
- onParseError({
88
- returnType: `Error extracting json schema schema.returnType - ${e.message}`,
89
- })
90
- }
91
-
92
- if (!jsonSchema.returnType) {
93
- onParseError({
94
- returnType: `Error extracting json schema schema.returnType - schema.returnType might be empty or it is not a valid suretype or typebox type`,
95
- })
96
- }
97
- }
98
-
99
- if (schema.yieldType) {
100
- try {
101
- const extracted = extractJsonSchema(schema.yieldType as TJSONSchema)
102
-
103
- if (extracted) {
104
- jsonSchema.yieldType = extracted
105
- }
106
- } catch (e: any) {
107
- onParseError({
108
- yieldType: `Error extracting json schema schema.yieldType - ${e.message}`,
109
- })
110
- }
111
-
112
- if (!jsonSchema.yieldType) {
113
- onParseError({
114
- yieldType: `Error extracting json schema schema.yieldType - schema.yieldType might be empty or it is not a valid suretype or typebox type`,
115
- })
116
- } else {
117
- let yieldValidator: AJV.ValidateFunction | undefined
118
-
119
- try {
120
- yieldValidator = ajv.compile(jsonSchema.yieldType as TJSONSchema)
121
- } catch (e: any) {
122
- onParseError({
123
- yieldType: `Error compiling schema.yieldType for validator - ${e.message}`,
124
- })
125
- }
126
-
127
- validation.yield = (value: any) => {
128
- if (!yieldValidator) {
129
- return { errors: [{ message: 'Validator not initialized', keyword: 'internal' } as TSchemaValidationError] }
130
- }
131
-
132
- const valid = yieldValidator(value)
133
-
134
- if (!valid) {
135
- const errors = yieldValidator.errors
136
-
137
- return {
138
- errors: errors?.length ? errors : undefined,
139
- }
140
- }
141
-
142
- return {}
143
- }
144
- }
145
- }
146
-
147
- return { jsonSchema, validation }
148
- }
@@ -1,19 +0,0 @@
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
-
6
- describe('lib schema resolvers', () => {
7
- const typebox = Type.Object({ name: Type.String() })
8
- const suretype = v.object({ name: v.string() })
9
-
10
- test('it recognizes TypeBox schema', async () => {
11
- expect(isTypeboxSchema(typebox)).toBe(true)
12
- expect(isTypeboxSchema(suretype)).toBe(false)
13
- })
14
-
15
- test('it recognizes Suretype schema', async () => {
16
- expect(isSuretypeSchema(suretype)).toBe(true)
17
- expect(isSuretypeSchema(typebox)).toBe(false)
18
- })
19
- })
@@ -1,29 +0,0 @@
1
- import { CoreValidator } from 'suretype'
2
- import { Type } from 'typebox'
3
-
4
- export type IsTypeboxSchema<TSchema> = TSchema extends {
5
- static: unknown
6
- params: unknown
7
- }
8
- ? true
9
- : false
10
-
11
- export function isTypeboxSchema(schema: any): schema is Type.TSchema {
12
- return (
13
- // typebox v1
14
- (typeof schema === 'object' && '~kind' in schema) ||
15
- // @sinclair/typebox v0.3x
16
- (typeof schema === 'object' && Symbol.for('TypeBox.Kind') in schema)
17
- )
18
- }
19
-
20
- export type IsSuretypeSchema<TSchema> = TSchema extends {
21
- required: () => object
22
- nullable?: never
23
- }
24
- ? true
25
- : false
26
-
27
- export function isSuretypeSchema(schema: any): schema is CoreValidator<any> {
28
- return typeof schema === 'object' && 'getJsonSchemaObject' in schema
29
- }
@@ -1,20 +0,0 @@
1
- import { CoreValidator, TypeOf } from 'suretype'
2
- import { Static, TSchema } from 'typebox'
3
-
4
- // Determine if the generic "SchemaLibType" is Suretype's CoreValidator or Typebox's TSchema
5
- export type TSchemaLib<SchemaLibType> =
6
- SchemaLibType extends CoreValidator<any>
7
- ? TypeOf<SchemaLibType>
8
- : SchemaLibType extends TSchema
9
- ? Static<SchemaLibType>
10
- : unknown
11
-
12
- // AsyncGenerator type extraction for streaming procedures
13
- export type TSchemaLibGenerator<TYield, TReturn = void> =
14
- AsyncGenerator<TSchemaLib<TYield>, TSchemaLib<TReturn>, unknown>
15
-
16
- export type TJSONSchema = Record<string, any>
17
-
18
- export type Prettify<TObject> = {
19
- [Key in keyof TObject]: TObject[Key]
20
- } & {}
@@ -1,94 +0,0 @@
1
- import { describe, expect, test } from 'vitest'
2
- import { captureDefinitionInfo, formatDefinitionInfo, DefinitionInfo } from './stack-utils.js'
3
-
4
- describe('Stack Utils', () => {
5
- describe('captureDefinitionInfo', () => {
6
- test('returns definition info with definedAt', () => {
7
- const info = captureDefinitionInfo()
8
-
9
- // Should capture the call site in this test file
10
- expect(info).toBeDefined()
11
- expect(info.definitionStack).toBeDefined()
12
- expect(typeof info.definitionStack).toBe('string')
13
- })
14
-
15
- test('definedAt contains file, line, column when available', () => {
16
- const info = captureDefinitionInfo()
17
-
18
- // The definedAt should be present since we're calling from user code (test file)
19
- if (info.definedAt) {
20
- expect(info.definedAt.file).toBeDefined()
21
- expect(typeof info.definedAt.file).toBe('string')
22
- expect(info.definedAt.line).toBeDefined()
23
- expect(typeof info.definedAt.line).toBe('number')
24
- expect(info.definedAt.line).toBeGreaterThan(0)
25
- expect(info.definedAt.column).toBeDefined()
26
- expect(typeof info.definedAt.column).toBe('number')
27
- expect(info.definedAt.column).toBeGreaterThan(0)
28
- expect(info.definedAt.raw).toBeDefined()
29
- expect(typeof info.definedAt.raw).toBe('string')
30
- }
31
- })
32
-
33
- test('definitionStack contains Error stack trace', () => {
34
- const info = captureDefinitionInfo()
35
-
36
- expect(info.definitionStack).toContain('Error')
37
- expect(info.definitionStack).toContain('at ')
38
- })
39
- })
40
-
41
- describe('formatDefinitionInfo', () => {
42
- test('returns undefined when definedAt is not present', () => {
43
- const info: DefinitionInfo = {}
44
- const result = formatDefinitionInfo(info, 'TestProcedure')
45
-
46
- expect(result).toBeUndefined()
47
- })
48
-
49
- test('returns formatted string when definedAt is present', () => {
50
- const info: DefinitionInfo = {
51
- definedAt: {
52
- file: '/app/procedures/test.ts',
53
- line: 42,
54
- column: 5,
55
- raw: 'at Object.<anonymous> (/app/procedures/test.ts:42:5)',
56
- },
57
- }
58
- const result = formatDefinitionInfo(info, 'TestProcedure')
59
-
60
- expect(result).toBeDefined()
61
- expect(result).toContain('--- Procedure "TestProcedure" defined at ---')
62
- expect(result).toContain('/app/procedures/test.ts:42:5')
63
- })
64
-
65
- test('includes procedure name in formatted output', () => {
66
- const info: DefinitionInfo = {
67
- definedAt: {
68
- file: '/path/to/file.ts',
69
- line: 10,
70
- column: 3,
71
- raw: 'at /path/to/file.ts:10:3',
72
- },
73
- }
74
- const result = formatDefinitionInfo(info, 'MyCustomProcedure')
75
-
76
- expect(result).toContain('"MyCustomProcedure"')
77
- })
78
- })
79
-
80
- describe('integration with procedure creation', () => {
81
- test('captures location from calling code', () => {
82
- // Helper to simulate what happens in Create()
83
- function simulateCreate() {
84
- return captureDefinitionInfo()
85
- }
86
-
87
- const info = simulateCreate()
88
-
89
- // Should have captured the location of the simulateCreate() call
90
- expect(info).toBeDefined()
91
- expect(info.definitionStack).toBeDefined()
92
- })
93
- })
94
- })
@@ -1,129 +0,0 @@
1
- /**
2
- * Represents a specific location in source code where a procedure was defined.
3
- */
4
- export type DefinitionLocation = {
5
- file: string
6
- line: number
7
- column: number
8
- raw: string
9
- }
10
-
11
- /**
12
- * Contains information about where a procedure was defined.
13
- */
14
- export type DefinitionInfo = {
15
- definedAt?: DefinitionLocation
16
- definitionStack?: string
17
- }
18
-
19
- /**
20
- * Internal ts-procedures files that should be skipped when finding user code.
21
- * Only skip the core library files, not test files or user code.
22
- */
23
- const INTERNAL_FILES = [
24
- '/index.ts',
25
- '/index.js',
26
- '/errors.ts',
27
- '/errors.js',
28
- '/stack-utils.ts',
29
- '/stack-utils.js',
30
- '/compute-schema.ts',
31
- '/compute-schema.js',
32
- '/parser.ts',
33
- '/parser.js',
34
- ]
35
-
36
- /**
37
- * Captures the stack trace at the call site and extracts the definition location.
38
- * Finds the first stack frame outside of ts-procedures internal files.
39
- */
40
- export function captureDefinitionInfo(): DefinitionInfo {
41
- const err = new Error()
42
- const stack = err.stack
43
-
44
- if (!stack) {
45
- return {}
46
- }
47
-
48
- const lines = stack.split('\n')
49
-
50
- // Find the first frame that's not from ts-procedures internals
51
- // Skip the first line (Error message) and frames from this module
52
- let userFrame: string | undefined
53
-
54
- for (let i = 1; i < lines.length; i++) {
55
- const rawLine = lines[i]
56
- if (!rawLine) continue
57
- const line = rawLine.trim()
58
-
59
- // Skip empty or invalid frames
60
- if (!line.startsWith('at ')) {
61
- continue
62
- }
63
-
64
- // Skip frames from ts-procedures internal source files
65
- const isInternalFile = INTERNAL_FILES.some(file => line.includes(file))
66
- if (isInternalFile) {
67
- continue
68
- }
69
-
70
- // Skip frames from ts-procedures in node_modules (when used as a dependency)
71
- if (line.includes('/node_modules/ts-procedures/') || line.includes('\\node_modules\\ts-procedures\\')) {
72
- continue
73
- }
74
-
75
- // Skip internal node frames
76
- if (line.includes('node:') || line.startsWith('at Module.') || line.startsWith('at Object.<anonymous> (node:')) {
77
- continue
78
- }
79
-
80
- userFrame = line
81
- break
82
- }
83
-
84
- if (!userFrame) {
85
- return { definitionStack: stack }
86
- }
87
-
88
- const definedAt = parseStackFrame(userFrame)
89
-
90
- return {
91
- definedAt,
92
- definitionStack: stack,
93
- }
94
- }
95
-
96
- /**
97
- * Parses a V8 stack frame line to extract file, line, and column info.
98
- * Handles formats like:
99
- * - "at Object.<anonymous> (/path/to/file.ts:10:5)"
100
- * - "at functionName (/path/to/file.ts:10:5)"
101
- * - "at /path/to/file.ts:10:5"
102
- */
103
- function parseStackFrame(frame: string): DefinitionLocation | undefined {
104
- // Match patterns like "(path:line:column)" or just "path:line:column"
105
- const match = frame.match(/\(([^)]+):(\d+):(\d+)\)$/) || frame.match(/at ([^:]+):(\d+):(\d+)$/)
106
-
107
- if (match && match[1] && match[2] && match[3]) {
108
- return {
109
- file: match[1],
110
- line: parseInt(match[2], 10),
111
- column: parseInt(match[3], 10),
112
- raw: frame,
113
- }
114
- }
115
-
116
- return undefined
117
- }
118
-
119
- /**
120
- * Formats definition info for appending to error stacks.
121
- */
122
- export function formatDefinitionInfo(info: DefinitionInfo, procedureName: string): string | undefined {
123
- if (!info.definedAt) {
124
- return undefined
125
- }
126
-
127
- const { file, line, column } = info.definedAt
128
- return `\n--- Procedure "${procedureName}" defined at ---\n at ${file}:${line}:${column}`
129
- }