@pikku/cli 0.7.0 → 0.7.2

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 (99) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/bin/pikku-all.ts +203 -0
  3. package/bin/pikku-channels-map.ts +55 -0
  4. package/bin/pikku-channels.ts +63 -0
  5. package/bin/pikku-fetch.ts +55 -0
  6. package/bin/pikku-function-types.ts +84 -0
  7. package/bin/pikku-functions.ts +35 -0
  8. package/bin/pikku-http-map.ts +56 -0
  9. package/bin/pikku-http-routes.ts +63 -0
  10. package/bin/pikku-nextjs.test.ts +279 -0
  11. package/bin/pikku-nextjs.ts +152 -0
  12. package/bin/pikku-openapi.ts +76 -0
  13. package/bin/pikku-rpc.ts +22 -0
  14. package/bin/pikku-scheduler.ts +64 -0
  15. package/bin/pikku-schemas.ts +57 -0
  16. package/bin/pikku-websocket.ts +58 -0
  17. package/bin/pikku.ts +26 -0
  18. package/dist/bin/pikku-all.js +3 -0
  19. package/dist/bin/pikku-functions.d.ts +0 -2
  20. package/dist/bin/pikku-functions.js +2 -17
  21. package/dist/bin/pikku-http-map.js +1 -1
  22. package/dist/bin/pikku-openapi.js +2 -2
  23. package/dist/bin/pikku-rpc.d.ts +3 -0
  24. package/dist/bin/pikku-rpc.js +8 -0
  25. package/dist/bin/pikku-schemas.js +2 -2
  26. package/dist/bin/pikku.js +0 -0
  27. package/dist/src/openapi-spec-generator.d.ts +2 -2
  28. package/dist/src/openapi-spec-generator.js +14 -5
  29. package/dist/src/pikku-cli-config.d.ts +1 -0
  30. package/dist/src/pikku-cli-config.js +3 -0
  31. package/dist/src/schema-generator.d.ts +3 -3
  32. package/dist/src/schema-generator.js +15 -34
  33. package/dist/src/serialize-typed-function-map.d.ts +2 -1
  34. package/dist/src/serialize-typed-function-map.js +6 -4
  35. package/dist/src/serialize-typed-http-map.d.ts +2 -1
  36. package/dist/src/serialize-typed-http-map.js +10 -4
  37. package/dist/tsconfig.tsbuildinfo +1 -0
  38. package/package.json +3 -3
  39. package/src/inspector-glob.ts +28 -0
  40. package/src/openapi-spec-generator.ts +246 -0
  41. package/src/pikku-cli-config.ts +240 -0
  42. package/src/schema-generator.ts +118 -0
  43. package/{dist/src/events/http/serialize-fetch-wrapper.js → src/serialize-fetch-wrapper.ts} +4 -4
  44. package/src/serialize-import-map.ts +34 -0
  45. package/{dist/src/nextjs/serialize-nextjs-backend-wrapper.js → src/serialize-nextjs-backend-wrapper.ts} +11 -4
  46. package/{dist/src/nextjs/serialize-nextjs-http-wrapper.js → src/serialize-nextjs-http-wrapper.ts} +7 -4
  47. package/{dist/src/core/serialize-pikku-types.js → src/serialize-pikku-types.ts} +46 -35
  48. package/src/serialize-scheduler-meta.ts +18 -0
  49. package/src/serialize-typed-channel-map.ts +138 -0
  50. package/src/serialize-typed-function-map.ts +161 -0
  51. package/src/serialize-typed-http-map.ts +167 -0
  52. package/{dist/src/channels/serialize-websocket-wrapper.js → src/serialize-websocket-wrapper.ts} +4 -4
  53. package/src/utils.ts +284 -0
  54. package/tsconfig.json +21 -0
  55. package/dist/bin/pikku-http.d.ts +0 -5
  56. package/dist/bin/pikku-http.js +0 -27
  57. package/dist/bin/pikku-routes-map.d.ts +0 -5
  58. package/dist/bin/pikku-routes-map.js +0 -23
  59. package/dist/src/channels/serialize-channels.d.ts +0 -3
  60. package/dist/src/channels/serialize-channels.js +0 -19
  61. package/dist/src/channels/serialize-typed-channel-map.d.ts +0 -3
  62. package/dist/src/channels/serialize-typed-channel-map.js +0 -93
  63. package/dist/src/channels/serialize-websocket-wrapper.d.ts +0 -1
  64. package/dist/src/core/serialize-import-map.d.ts +0 -2
  65. package/dist/src/core/serialize-import-map.js +0 -24
  66. package/dist/src/core/serialize-pikku-types.d.ts +0 -4
  67. package/dist/src/events/channels/serialize-channels.d.ts +0 -3
  68. package/dist/src/events/channels/serialize-channels.js +0 -19
  69. package/dist/src/events/channels/serialize-typed-channel-map.d.ts +0 -3
  70. package/dist/src/events/channels/serialize-typed-channel-map.js +0 -90
  71. package/dist/src/events/channels/serialize-websocket-wrapper.d.ts +0 -1
  72. package/dist/src/events/channels/serialize-websocket-wrapper.js +0 -61
  73. package/dist/src/events/http/serialize-fetch-wrapper.d.ts +0 -1
  74. package/dist/src/events/http/serialize-route-imports.d.ts +0 -1
  75. package/dist/src/events/http/serialize-route-imports.js +0 -13
  76. package/dist/src/events/http/serialize-route-meta.d.ts +0 -2
  77. package/dist/src/events/http/serialize-route-meta.js +0 -6
  78. package/dist/src/events/http/serialize-typed-route-map.d.ts +0 -4
  79. package/dist/src/events/http/serialize-typed-route-map.js +0 -107
  80. package/dist/src/events/scheduler/serialize-schedulers.d.ts +0 -3
  81. package/dist/src/events/scheduler/serialize-schedulers.js +0 -23
  82. package/dist/src/http/serialize-fetch-wrapper.d.ts +0 -1
  83. package/dist/src/http/serialize-fetch-wrapper.js +0 -67
  84. package/dist/src/http/serialize-route-imports.d.ts +0 -1
  85. package/dist/src/http/serialize-route-imports.js +0 -13
  86. package/dist/src/http/serialize-route-meta.d.ts +0 -2
  87. package/dist/src/http/serialize-route-meta.js +0 -6
  88. package/dist/src/http/serialize-typed-route-map.d.ts +0 -4
  89. package/dist/src/http/serialize-typed-route-map.js +0 -107
  90. package/dist/src/nextjs/serialize-nextjs-backend-wrapper.d.ts +0 -1
  91. package/dist/src/nextjs/serialize-nextjs-http-wrapper.d.ts +0 -1
  92. package/dist/src/openapi/openapi-spec-generator.d.ts +0 -79
  93. package/dist/src/openapi/openapi-spec-generator.js +0 -136
  94. package/dist/src/scheduler/serialize-schedulers.d.ts +0 -3
  95. package/dist/src/scheduler/serialize-schedulers.js +0 -23
  96. package/dist/src/schema/schema-generator.d.ts +0 -5
  97. package/dist/src/schema/schema-generator.js +0 -89
  98. package/dist/src/serialize-typed-route-map.d.ts +0 -4
  99. package/dist/src/serialize-typed-route-map.js +0 -107
@@ -1,14 +1,21 @@
1
1
  /**
2
2
  *
3
3
  */
4
- export const serializePikkuTypes = (userSessionTypeImport, userSessionTypeName, singletonServicesTypeImport, singletonServicesTypeName, sessionServicesTypeImport, servicesTypeName) => {
5
- return `/**
4
+ export const serializePikkuTypes = (
5
+ userSessionTypeImport: string,
6
+ userSessionTypeName: string,
7
+ singletonServicesTypeImport: string,
8
+ singletonServicesTypeName: string,
9
+ sessionServicesTypeImport: string,
10
+ servicesTypeName: string
11
+ ) => {
12
+ return `/**
6
13
  * This is used to provide the application types in the typescript project
7
14
  */
8
15
 
9
- import { CorePermissionGroup, CoreAPIPermission, PikkuMiddleware, MakeRequired } from '@pikku/core'
16
+ import { CoreAPIPermission, PikkuMiddleware } from '@pikku/core'
10
17
  import { CoreAPIFunction, CoreAPIFunctionSessionless } from '@pikku/core/function'
11
- import { CoreHTTPFunctionRoute, AssertRouteParams, addRoute as addCoreHTTP } from '@pikku/core/http'
18
+ import { CoreHTTPFunctionRoute, AssertRouteParams, addHTTPRoute as addCoreHTTPRoute } from '@pikku/core/http'
12
19
  import { CoreScheduledTask, addScheduledTask as addCoreScheduledTask } from '@pikku/core/scheduler'
13
20
  import { CoreAPIChannel, PikkuChannel, addChannel as addCoreChannel } from '@pikku/core/channel'
14
21
 
@@ -19,14 +26,30 @@ ${sessionServicesTypeImport}
19
26
  export type APIPermission<In = unknown, RequiredServices extends ${singletonServicesTypeName} = ${singletonServicesTypeName}> = CoreAPIPermission<In, RequiredServices, ${userSessionTypeName}>
20
27
  export type APIMiddleware<RequiredServices extends ${singletonServicesTypeName} = ${singletonServicesTypeName}> = PikkuMiddleware<RequiredServices, ${userSessionTypeName}>
21
28
 
22
- type APIFunctionSessionless<In = unknown, Out = never, Channel extends boolean = false, RequiredServices extends Services = Services & (Channel extends true ? { channel: PikkuChannel<unknown, Out> } : { channel?: PikkuChannel<unknown, Out> })> = CoreAPIFunctionSessionless<In, Out, Channel, RequiredServices, ${userSessionTypeName}>
23
- type APIFunction<In = unknown, Out = never, Channel extends boolean = false, RequiredServices extends Services = Services & (Channel extends true ? { channel: PikkuChannel<unknown, Out> } : { channel?: PikkuChannel<unknown, Out> })> = CoreAPIFunction<In, Out, Channel, RequiredServices, ${userSessionTypeName}>
24
- type APIRoute<In, Out, Route extends string> = CoreHTTPFunctionRoute<In, Out, Route, APIFunction<In, Out>, APIFunctionSessionless<In, Out>, CorePermissionGroup<APIPermission>, APIMiddleware>
29
+ type APIFunctionSessionless<
30
+ In = unknown,
31
+ Out = never,
32
+ ChannelData = null, // null means optional channel
33
+ RequiredServices extends Services = Services & (
34
+ [ChannelData] extends [null]
35
+ ? { channel?: PikkuChannel<unknown, Out> } // Optional channel
36
+ : { channel: PikkuChannel<ChannelData, Out> } // Required channel with any data type
37
+ )
38
+ > = CoreAPIFunctionSessionless<In, Out, ChannelData, RequiredServices, ${userSessionTypeName}>
25
39
 
26
- export type ChannelConnection<Out = unknown, ChannelData = unknown, RequiredServices extends ${servicesTypeName} = ${servicesTypeName}> = (services: MakeRequired<RequiredServices, 'userSession'>, channel: PikkuChannel<ChannelData, Out>) => Promise<void>
27
- export type ChannelDisconnection<ChannelData = unknown, RequiredServices extends ${servicesTypeName} = ${servicesTypeName}> = (services: MakeRequired<RequiredServices, 'userSession'>, channel: PikkuChannel<ChannelData, never>) => Promise<void>
28
- type APIChannel<ChannelData, Channel extends string> = CoreAPIChannel<ChannelData, Channel, APIFunction<any, any, false> | APIFunction<any, any, true>, APIPermission>
40
+ type APIFunction<
41
+ In = unknown,
42
+ Out = never,
43
+ ChannelData = null, // null means optional channel
44
+ RequiredServices extends Services = Services & (
45
+ [ChannelData] extends [null]
46
+ ? { channel?: PikkuChannel<unknown, Out> } // Optional channel
47
+ : { channel: PikkuChannel<ChannelData, Out> } // Required channel with any data type
48
+ )
49
+ > = CoreAPIFunction<In, Out, ChannelData, RequiredServices, ${userSessionTypeName}>
29
50
 
51
+ type APIRoute<In, Out, Route extends string> = CoreHTTPFunctionRoute<In, Out, Route, APIFunction<In, Out>, APIFunctionSessionless<In, Out>, APIPermission<In>, APIMiddleware>
52
+ type APIChannel<ChannelData, Channel extends string> = CoreAPIChannel<ChannelData, Channel, APIFunction<void, unknown> | APIFunction<void, unknown, ChannelData>, APIFunction<void, void> | APIFunction<void, void, ChannelData>, APIFunction<any, any> | APIFunction<any, any, ChannelData>, APIPermission>
30
53
  type ScheduledTask = CoreScheduledTask<APIFunctionSessionless<void, void>, ${userSessionTypeName}>
31
54
 
32
55
  export const pikkuFunc = <In, Out = unknown>(
@@ -57,45 +80,33 @@ export const pikkuSessionlessFunc = <In, Out = unknown>(
57
80
  return typeof func === 'function' ? func : func.func
58
81
  }
59
82
 
60
- export const pikkuChannelConnection = <In, Out = unknown>(
83
+ export const pikkuChannelConnectionFunc = <Out = unknown, ChannelData = unknown>(
61
84
  func:
62
- | ChannelConnection<In, Out>
63
- | {
64
- func: ChannelConnection<In, Out>
65
- auth?: true
66
- name?: string
67
- }
85
+ | APIFunctionSessionless<void, Out, ChannelData>
68
86
  | {
69
- func: ChannelConnection<In, Out>
70
- auth: false
87
+ func: APIFunctionSessionless<void, Out, ChannelData>
71
88
  name?: string
72
89
  }
73
90
  ) => {
74
91
  return typeof func === 'function' ? func : func.func
75
92
  }
76
93
 
77
- export const pikkuChannelDisconnection = <In>(
94
+ export const pikkuChannelDisconnectionFunc = <ChannelData = unknown>(
78
95
  func:
79
- | ChannelDisconnection<In>
96
+ | APIFunctionSessionless<void, void, ChannelData>
80
97
  | {
81
- func: ChannelDisconnection<In>
82
- auth?: true
83
- name?: string
84
- }
85
- | {
86
- func: ChannelDisconnection<In>
87
- auth: false
98
+ func: APIFunction<void, void, ChannelData>
88
99
  name?: string
89
100
  }
90
101
  ) => {
91
102
  return typeof func === 'function' ? func : func.func
92
103
  }
93
104
 
94
- export const pikkuChannelFunc = <In, Out = unknown>(
105
+ export const pikkuChannelFunc = <In = unknown, Out = unknown, ChannelData = unknown>(
95
106
  func:
96
- | APIFunctionSessionless<In, Out, true>
107
+ | APIFunctionSessionless<In, Out, ChannelData>
97
108
  | {
98
- func: APIFunctionSessionless<In, Out, true>
109
+ func: APIFunctionSessionless<In, Out, ChannelData>
99
110
  name?: string
100
111
  }
101
112
  ) => {
@@ -119,14 +130,14 @@ export const addChannel = <ChannelData, Channel extends string>(
119
130
  addCoreChannel(channel as any) // TODO
120
131
  }
121
132
 
122
- export const addRoute = <In, Out, Route extends string>(
133
+ export const addHTTPRoute = <In, Out, Route extends string>(
123
134
  route: APIRoute<In, Out, Route> & AssertRouteParams<In, Route>
124
135
  ) => {
125
- addCoreHTTP(route)
136
+ addCoreHTTPRoute(route)
126
137
  }
127
138
 
128
139
  export const addScheduledTask = (task: ScheduledTask) => {
129
140
  addCoreScheduledTask(task as any) // TODO
130
141
  }
131
- `;
132
- };
142
+ `
143
+ }
@@ -0,0 +1,18 @@
1
+ import { ScheduledTasksMeta } from '@pikku/core/scheduler'
2
+
3
+ export const serializeSchedulerMeta = (
4
+ scheduledTasksMeta: ScheduledTasksMeta
5
+ ) => {
6
+ const serializedOutput: string[] = []
7
+ serializedOutput.push("import { pikkuState } from '@pikku/core'")
8
+ serializedOutput.push(
9
+ `pikkuState('scheduler', 'meta', ${JSON.stringify(scheduledTasksMeta, null, 2)})`
10
+ )
11
+ const scheduledTasksMetaValues = Object.values(scheduledTasksMeta)
12
+ if (scheduledTasksMetaValues.length > 0) {
13
+ serializedOutput.push(
14
+ `export type ScheduledTaskNames = '${scheduledTasksMetaValues.map((s) => s.name).join("' | '")}'`
15
+ )
16
+ }
17
+ return serializedOutput.join('\n')
18
+ }
@@ -0,0 +1,138 @@
1
+ import { ChannelsMeta } from '@pikku/core/channel'
2
+ import { serializeImportMap } from './serialize-import-map.js'
3
+ import { TypesMap } from '@pikku/inspector'
4
+ import { generateCustomTypes } from './serialize-typed-http-map.js'
5
+
6
+ export const serializeTypedChannelsMap = (
7
+ relativeToPath: string,
8
+ packageMappings: Record<string, string>,
9
+ typesMap: TypesMap,
10
+ channelsMeta: ChannelsMeta
11
+ ): string => {
12
+ const { channels, requiredTypes } = generateChannels(channelsMeta)
13
+ typesMap.customTypes.forEach(({ references }) => {
14
+ for (const reference of references) {
15
+ requiredTypes.add(reference)
16
+ }
17
+ })
18
+ const imports = serializeImportMap(
19
+ relativeToPath,
20
+ packageMappings,
21
+ typesMap,
22
+ requiredTypes
23
+ )
24
+ const serializedCustomTypes = generateCustomTypes(typesMap, requiredTypes)
25
+ return `/**
26
+ * This provides the structure needed for TypeScript to be aware of channels
27
+ */
28
+
29
+ ${imports}
30
+ ${serializedCustomTypes}
31
+
32
+ interface ChannelHandler<I, O> {
33
+ input: I;
34
+ output: O;
35
+ }
36
+
37
+ ${channels}
38
+
39
+ export type ChannelDefaultHandlerOf<Channel extends keyof ChannelsMap> =
40
+ ChannelsMap[Channel]['defaultMessage'] extends { input: infer I; output: infer O }
41
+ ? ChannelHandler<I, O>
42
+ : never;
43
+
44
+ export type ChannelRouteHandlerOf<
45
+ Channel extends keyof ChannelsMap,
46
+ Route extends keyof ChannelsMap[Channel]['routes'],
47
+ Method extends keyof ChannelsMap[Channel]['routes'][Route],
48
+ > =
49
+ ChannelsMap[Channel]['routes'][Route][Method] extends { input: infer I; output: infer O }
50
+ ? ChannelHandler<I, O>
51
+ : never;
52
+ `
53
+ }
54
+
55
+ function generateChannels(channelsMeta: ChannelsMeta) {
56
+ const requiredTypes = new Set<string>()
57
+ const channelsObject: Record<
58
+ string,
59
+ {
60
+ message: { inputs: string[] | null; outputs: string[] | null } | null
61
+ routes: Record<
62
+ string,
63
+ Record<
64
+ string,
65
+ {
66
+ inputTypes: string[] | null
67
+ outputTypes: string[] | null
68
+ }
69
+ >
70
+ >
71
+ }
72
+ > = {}
73
+
74
+ for (const meta of Object.values(channelsMeta)) {
75
+ const { name, messageRoutes, message } = meta
76
+
77
+ if (!channelsObject[name]) {
78
+ channelsObject[name] = { message, routes: {} }
79
+ }
80
+
81
+ for (const [key, route] of Object.entries(messageRoutes)) {
82
+ if (!channelsObject[name].routes[key]) {
83
+ channelsObject[name].routes[key] = {}
84
+ }
85
+ for (const [method, { inputs, outputs }] of Object.entries(route)) {
86
+ const inputTypes = inputs || null
87
+ const outputTypes = outputs || null
88
+ channelsObject[name].routes[key][method] = {
89
+ inputTypes,
90
+ outputTypes,
91
+ }
92
+ inputTypes?.forEach((type) => requiredTypes.add(type))
93
+ outputTypes?.forEach((type) => requiredTypes.add(type))
94
+ }
95
+ }
96
+ }
97
+
98
+ let routesStr = 'export type ChannelsMap = {\n'
99
+
100
+ for (const [channelPath, { routes, message }] of Object.entries(
101
+ channelsObject
102
+ )) {
103
+ routesStr += ` readonly '${channelPath}': {\n`
104
+
105
+ // Add `routes` object
106
+ routesStr += ` readonly routes: {\n`
107
+ for (const [key, methods] of Object.entries(routes)) {
108
+ routesStr += ` readonly ${key}: {\n`
109
+ for (const [method, handler] of Object.entries(methods)) {
110
+ routesStr += ` readonly ${method}: ChannelHandler<${
111
+ formatTypeArray(handler.inputTypes) || 'void'
112
+ }, ${formatTypeArray(handler.outputTypes) || 'never'}>,\n`
113
+ }
114
+ routesStr += ' },\n'
115
+ }
116
+ routesStr += ' },\n'
117
+
118
+ // Add `defaultMessage` outside `routes`
119
+ if (message) {
120
+ routesStr += ` readonly defaultMessage: ChannelHandler<${formatTypeArray(
121
+ message.inputs
122
+ )}, ${formatTypeArray(message.outputs)}>,\n`
123
+ } else {
124
+ routesStr += ` readonly defaultMessage: never,\n`
125
+ }
126
+
127
+ routesStr += ' },\n'
128
+ }
129
+
130
+ routesStr += '};'
131
+
132
+ return { channels: routesStr, requiredTypes }
133
+ }
134
+
135
+ // Utility to format type arrays
136
+ function formatTypeArray(types: string[] | null): string {
137
+ return types ? types.join(' | ') : 'null'
138
+ }
@@ -0,0 +1,161 @@
1
+ import { HTTPRoutesMeta } from '@pikku/core/http'
2
+ import { serializeImportMap } from './serialize-import-map.js'
3
+ import { MetaInputTypes, TypesMap } from '@pikku/inspector'
4
+ import { FunctionsMeta } from '@pikku/core'
5
+
6
+ export const serializeTypedRoutesMap = (
7
+ relativeToPath: string,
8
+ packageMappings: Record<string, string>,
9
+ typesMap: TypesMap,
10
+ functionsMeta: FunctionsMeta,
11
+ routesMeta: HTTPRoutesMeta,
12
+ metaTypes: MetaInputTypes
13
+ ) => {
14
+ const requiredTypes = new Set<string>()
15
+ const serializedCustomTypes = generateCustomTypes(typesMap, requiredTypes)
16
+ const serializedMetaTypes = generateMetaTypes(metaTypes, typesMap)
17
+ const serializedRoutes = generateRoutes(
18
+ functionsMeta,
19
+ routesMeta,
20
+ typesMap,
21
+ requiredTypes
22
+ )
23
+
24
+ const serializedImportMap = serializeImportMap(
25
+ relativeToPath,
26
+ packageMappings,
27
+ typesMap,
28
+ requiredTypes
29
+ )
30
+
31
+ return `/**
32
+ * This provides the structure needed for typescript to be aware of routes and their return types
33
+ */
34
+
35
+ ${serializedImportMap}
36
+ ${serializedCustomTypes}
37
+ ${serializedMetaTypes}
38
+
39
+ interface RouteHandler<I, O> {
40
+ input: I;
41
+ output: O;
42
+ }
43
+
44
+ ${serializedRoutes}
45
+
46
+ export type RouteHandlerOf<Route extends keyof RoutesMap, Method extends keyof RoutesMap[Route]> =
47
+ RoutesMap[Route][Method] extends { input: infer I; output: infer O }
48
+ ? RouteHandler<I, O>
49
+ : never;
50
+
51
+ export type RoutesWithMethod<Method extends string> = {
52
+ [Route in keyof RoutesMap]: Method extends keyof RoutesMap[Route] ? Route : never;
53
+ }[keyof RoutesMap];
54
+ `
55
+ }
56
+
57
+ export function generateCustomTypes(
58
+ typesMap: TypesMap,
59
+ requiredTypes: Set<string>
60
+ ) {
61
+ return `
62
+ // Custom types are those that are defined directly within generics
63
+ // or are broken into simpler types
64
+ ${Array.from(typesMap.customTypes.entries())
65
+ .map(([name, { type, references }]) => {
66
+ references.forEach((name) => {
67
+ const originalName = typesMap.getTypeMeta(name).originalName
68
+ requiredTypes.add(originalName)
69
+ })
70
+ return `export type ${name} = ${type}`
71
+ })
72
+ .join('\n')}`
73
+ }
74
+
75
+ function generateRoutes(
76
+ functionsMeta: FunctionsMeta,
77
+ routesMeta: HTTPRoutesMeta,
78
+ typesMap: TypesMap,
79
+ requiredTypes: Set<string>
80
+ ) {
81
+ // Initialize an object to collect routes
82
+ const routesObj: Record<
83
+ string,
84
+ Record<string, { inputType: string; outputType: string }>
85
+ > = {}
86
+
87
+ for (const meta of routesMeta) {
88
+ const { route, method, pikkuFuncName } = meta
89
+ const input = functionsMeta[pikkuFuncName]?.inputs?.[0]
90
+ const output = functionsMeta[pikkuFuncName]?.outputs?.[0]
91
+
92
+ // Initialize the route entry if it doesn't exist
93
+ if (!routesObj[route]) {
94
+ routesObj[route] = {}
95
+ }
96
+
97
+ // Store the input and output types separately for RouteHandler
98
+ const inputType = input ? typesMap.getTypeMeta(input).uniqueName : 'null'
99
+ const outputType = output ? typesMap.getTypeMeta(output).uniqueName : 'null'
100
+
101
+ requiredTypes.add(inputType)
102
+ requiredTypes.add(outputType)
103
+
104
+ // Add method entry
105
+ routesObj[route][method] = {
106
+ inputType,
107
+ outputType,
108
+ }
109
+ }
110
+
111
+ // Build the routes object as a string
112
+ let routesStr = 'export type RoutesMap = {\n'
113
+
114
+ for (const [routePath, methods] of Object.entries(routesObj)) {
115
+ routesStr += ` readonly '${routePath}': {\n`
116
+ for (const [method, handler] of Object.entries(methods)) {
117
+ routesStr += ` readonly ${method.toUpperCase()}: RouteHandler<${handler.inputType}, ${handler.outputType}>,\n`
118
+ }
119
+ routesStr += ' },\n'
120
+ }
121
+
122
+ routesStr += '};'
123
+
124
+ return routesStr
125
+ }
126
+
127
+ const generateMetaTypes = (metaTypes: MetaInputTypes, typesMap: TypesMap) => {
128
+ const nameToTypeMap = Array.from(metaTypes.entries()).reduce<
129
+ Map<string, string>
130
+ >((result, [_name, { query, body, params }]) => {
131
+ const { uniqueName } = typesMap.getTypeMeta(_name)
132
+ const queryType =
133
+ query && query.length > 0
134
+ ? `Pick<${uniqueName}, '${query?.join("' | '")}'>`
135
+ : undefined
136
+ if (queryType) {
137
+ result.set(`${uniqueName}Query`, queryType)
138
+ }
139
+ const paramsType =
140
+ params && params.length > 0
141
+ ? `Pick<${uniqueName}, '${params.join("' | '")}'>`
142
+ : undefined
143
+ if (paramsType) {
144
+ result.set(`${uniqueName}Params`, paramsType)
145
+ }
146
+ const bodyType =
147
+ (body && body.length > 0) || (params && params.length > 0)
148
+ ? `Omit<${uniqueName}, '${[...new Set([...(query || []), ...(params || [])])].join("' | '")}'>`
149
+ : uniqueName!
150
+ if (bodyType) {
151
+ result.set(`${uniqueName}Body`, bodyType)
152
+ }
153
+ return result
154
+ }, new Map())
155
+
156
+ return `
157
+ // The '& {}' is a workaround for not directly refering to a type since it confuses typescript
158
+ ${Array.from(nameToTypeMap.entries())
159
+ .map(([name, type]) => `export type ${name} = ${type} & {}`)
160
+ .join('\n')}`
161
+ }
@@ -0,0 +1,167 @@
1
+ import { HTTPRoutesMeta } from '@pikku/core/http'
2
+ import { serializeImportMap } from './serialize-import-map.js'
3
+ import { MetaInputTypes, TypesMap } from '@pikku/inspector'
4
+ import { FunctionsMeta } from '@pikku/core'
5
+
6
+ export const serializeTypedRoutesMap = (
7
+ relativeToPath: string,
8
+ packageMappings: Record<string, string>,
9
+ typesMap: TypesMap,
10
+ functionsMeta: FunctionsMeta,
11
+ routesMeta: HTTPRoutesMeta,
12
+ metaTypes: MetaInputTypes
13
+ ) => {
14
+ const requiredTypes = new Set<string>()
15
+ const serializedCustomTypes = generateCustomTypes(typesMap, requiredTypes)
16
+ const serializedMetaTypes = generateMetaTypes(metaTypes, typesMap)
17
+ const serializedRoutes = generateRoutes(
18
+ routesMeta,
19
+ functionsMeta,
20
+ typesMap,
21
+ requiredTypes
22
+ )
23
+
24
+ const serializedImportMap = serializeImportMap(
25
+ relativeToPath,
26
+ packageMappings,
27
+ typesMap,
28
+ requiredTypes
29
+ )
30
+
31
+ return `/**
32
+ * This provides the structure needed for typescript to be aware of routes and their return types
33
+ */
34
+
35
+ ${serializedImportMap}
36
+ ${serializedCustomTypes}
37
+ ${serializedMetaTypes}
38
+
39
+ interface RouteHandler<I, O> {
40
+ input: I;
41
+ output: O;
42
+ }
43
+
44
+ ${serializedRoutes}
45
+
46
+ export type RouteHandlerOf<Route extends keyof RoutesMap, Method extends keyof RoutesMap[Route]> =
47
+ RoutesMap[Route][Method] extends { input: infer I; output: infer O }
48
+ ? RouteHandler<I, O>
49
+ : never;
50
+
51
+ export type RoutesWithMethod<Method extends string> = {
52
+ [Route in keyof RoutesMap]: Method extends keyof RoutesMap[Route] ? Route : never;
53
+ }[keyof RoutesMap];
54
+ `
55
+ }
56
+
57
+ export function generateCustomTypes(
58
+ typesMap: TypesMap,
59
+ requiredTypes: Set<string>
60
+ ) {
61
+ return `
62
+ // Custom types are those that are defined directly within generics
63
+ // or are broken into simpler types
64
+ ${Array.from(typesMap.customTypes.entries())
65
+ .map(([name, { type, references }]) => {
66
+ references.forEach((name) => {
67
+ const originalName = typesMap.getTypeMeta(name).originalName
68
+ requiredTypes.add(originalName)
69
+ })
70
+ return `export type ${name} = ${type}`
71
+ })
72
+ .join('\n')}`
73
+ }
74
+
75
+ function generateRoutes(
76
+ routesMeta: HTTPRoutesMeta,
77
+ functionsMeta: FunctionsMeta,
78
+ typesMap: TypesMap,
79
+ requiredTypes: Set<string>
80
+ ) {
81
+ // Initialize an object to collect routes
82
+ const routesObj: Record<
83
+ string,
84
+ Record<string, { inputType: string; outputType: string }>
85
+ > = {}
86
+
87
+ for (const meta of routesMeta) {
88
+ const { route, method, pikkuFuncName } = meta
89
+ const functionMeta = functionsMeta[pikkuFuncName]
90
+ if (!functionMeta) {
91
+ throw new Error(
92
+ `Function ${pikkuFuncName} not found in functionsMeta. Please check your configuration.`
93
+ )
94
+ }
95
+ const input = functionMeta.inputs ? functionMeta.inputs[0] : undefined
96
+ const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined
97
+
98
+ // Initialize the route entry if it doesn't exist
99
+ if (!routesObj[route]) {
100
+ routesObj[route] = {}
101
+ }
102
+
103
+ // Store the input and output types separately for RouteHandler
104
+ const inputType = input ? typesMap.getTypeMeta(input).uniqueName : 'null'
105
+ const outputType = output ? typesMap.getTypeMeta(output).uniqueName : 'null'
106
+
107
+ requiredTypes.add(inputType)
108
+ requiredTypes.add(outputType)
109
+
110
+ // Add method entry
111
+ routesObj[route][method] = {
112
+ inputType,
113
+ outputType,
114
+ }
115
+ }
116
+
117
+ // Build the routes object as a string
118
+ let routesStr = 'export type RoutesMap = {\n'
119
+
120
+ for (const [routePath, methods] of Object.entries(routesObj)) {
121
+ routesStr += ` readonly '${routePath}': {\n`
122
+ for (const [method, handler] of Object.entries(methods)) {
123
+ routesStr += ` readonly ${method.toUpperCase()}: RouteHandler<${handler.inputType}, ${handler.outputType}>,\n`
124
+ }
125
+ routesStr += ' },\n'
126
+ }
127
+
128
+ routesStr += '};'
129
+
130
+ return routesStr
131
+ }
132
+
133
+ const generateMetaTypes = (metaTypes: MetaInputTypes, typesMap: TypesMap) => {
134
+ const nameToTypeMap = Array.from(metaTypes.entries()).reduce<
135
+ Map<string, string>
136
+ >((result, [_name, { query, body, params }]) => {
137
+ const { uniqueName } = typesMap.getTypeMeta(_name)
138
+ const queryType =
139
+ query && query.length > 0
140
+ ? `Pick<${uniqueName}, '${query?.join("' | '")}'>`
141
+ : undefined
142
+ if (queryType) {
143
+ result.set(`${uniqueName}Query`, queryType)
144
+ }
145
+ const paramsType =
146
+ params && params.length > 0
147
+ ? `Pick<${uniqueName}, '${params.join("' | '")}'>`
148
+ : undefined
149
+ if (paramsType) {
150
+ result.set(`${uniqueName}Params`, paramsType)
151
+ }
152
+ const bodyType =
153
+ (body && body.length > 0) || (params && params.length > 0)
154
+ ? `Omit<${uniqueName}, '${[...new Set([...(query || []), ...(params || [])])].join("' | '")}'>`
155
+ : uniqueName!
156
+ if (bodyType) {
157
+ result.set(`${uniqueName}Body`, bodyType)
158
+ }
159
+ return result
160
+ }, new Map())
161
+
162
+ return `
163
+ // The '& {}' is a workaround for not directly refering to a type since it confuses typescript
164
+ ${Array.from(nameToTypeMap.entries())
165
+ .map(([name, type]) => `export type ${name} = ${type} & {}`)
166
+ .join('\n')}`
167
+ }
@@ -1,5 +1,5 @@
1
- export const serializeWebsocketWrapper = (channelsMapPath) => {
2
- return `import { CorePikkuWebsocket, CorePikkuRouteHandler } from '@pikku/websocket'
1
+ export const serializeWebsocketWrapper = (channelsMapPath: string) => {
2
+ return `import { CorePikkuWebsocket, CorePikkuRouteHandler } from '@pikku/websocket'
3
3
  import { ChannelDefaultHandlerOf, ChannelRouteHandlerOf, ChannelsMap } from '${channelsMapPath}';
4
4
 
5
5
  class PikkuWebSocketRoute<Channel extends keyof ChannelsMap, Route extends keyof ChannelsMap[Channel]['routes']> extends CorePikkuRouteHandler {
@@ -57,5 +57,5 @@ export class PikkuWebSocket<Channel extends keyof ChannelsMap> extends CorePikku
57
57
  super.send(data)
58
58
  }
59
59
  }
60
- `;
61
- };
60
+ `
61
+ }