@pikku/cli 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/bin/pikku-all.ts +10 -3
  3. package/bin/pikku-rpc.ts +28 -0
  4. package/bin/pikku.ts +4 -0
  5. package/cli.schema.json +4 -0
  6. package/dist/bin/pikku-all.js +7 -3
  7. package/dist/bin/pikku-rpc.d.ts +4 -0
  8. package/dist/bin/pikku-rpc.js +19 -0
  9. package/dist/bin/pikku.js +4 -0
  10. package/dist/src/pikku-cli-config.d.ts +3 -0
  11. package/dist/src/pikku-cli-config.js +15 -4
  12. package/dist/src/serialize-import-map.js +62 -1
  13. package/dist/src/serialize-pikku-types.js +77 -5
  14. package/dist/src/utils.js +24 -2
  15. package/dist/src/wirings/functions/pikku-command-function-types.js +2 -2
  16. package/dist/src/wirings/functions/pikku-command-functions.d.ts +0 -4
  17. package/dist/src/wirings/functions/pikku-command-functions.js +10 -31
  18. package/dist/src/wirings/functions/pikku-command-services.js +1 -1
  19. package/dist/src/wirings/functions/pikku-function-types.js +2 -2
  20. package/dist/src/wirings/functions/serialize-function-imports.d.ts +6 -0
  21. package/dist/src/wirings/functions/serialize-function-imports.js +59 -0
  22. package/dist/src/wirings/rpc/pikku-command-rpc-client.js +2 -2
  23. package/dist/src/wirings/rpc/pikku-command-rpc-map.d.ts +2 -1
  24. package/dist/src/wirings/rpc/pikku-command-rpc-map.js +9 -3
  25. package/dist/src/wirings/rpc/pikku-command-rpc.js +7 -2
  26. package/dist/src/wirings/rpc/serialize-typed-rpc-map.d.ts +1 -2
  27. package/dist/src/wirings/rpc/serialize-typed-rpc-map.js +2 -1
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +3 -3
  30. package/src/pikku-cli-config.ts +35 -5
  31. package/src/serialize-import-map.ts +67 -2
  32. package/src/serialize-pikku-types.ts +77 -5
  33. package/src/utils.ts +31 -2
  34. package/src/wirings/functions/pikku-command-function-types.ts +6 -2
  35. package/src/wirings/functions/pikku-command-functions.ts +28 -64
  36. package/src/wirings/functions/pikku-command-services.ts +1 -1
  37. package/src/wirings/functions/pikku-function-types.ts +6 -2
  38. package/src/wirings/functions/serialize-function-imports.ts +97 -0
  39. package/src/wirings/rpc/pikku-command-rpc-client.ts +8 -4
  40. package/src/wirings/rpc/pikku-command-rpc-map.ts +27 -4
  41. package/src/wirings/rpc/pikku-command-rpc.ts +16 -6
  42. package/src/wirings/rpc/serialize-typed-rpc-map.ts +4 -5
  43. package/dist/src/wirings/functions/pikku-functions.d.ts +0 -6
  44. package/dist/src/wirings/functions/pikku-functions.js +0 -35
  45. package/dist/src/wirings/rpc/pikku-rpc.d.ts +0 -2
  46. package/dist/src/wirings/rpc/pikku-rpc.js +0 -6
  47. package/src/wirings/functions/pikku-functions.ts +0 -84
  48. package/src/wirings/rpc/pikku-rpc.ts +0 -22
@@ -0,0 +1,59 @@
1
+ import { getFileImportRelativePath } from '../../utils.js';
2
+ export const serializeFunctionImports = (outputPath, functionsMap, functionsMeta, packageMappings = {}) => {
3
+ const serializedImports = [
4
+ `/* Import and register functions used by RPCs */`,
5
+ `import { addFunction } from '@pikku/core'`,
6
+ ];
7
+ const serializedRegistrations = [];
8
+ // Sort by function name for consistent output
9
+ const sortedEntries = Array.from(functionsMap.entries()).sort((a, b) => a[0].localeCompare(b[0]));
10
+ for (const [name, { path, exportedName }] of sortedEntries) {
11
+ const filePath = getFileImportRelativePath(outputPath, path, packageMappings);
12
+ // Find the function metadata to check if it's a direct function
13
+ // The functionsMeta is keyed by the function name (exported name)
14
+ const funcMeta = functionsMeta[name];
15
+ const isDirectFunction = funcMeta?.isDirectFunction ?? false;
16
+ // For directly exported functions, we can just import and register them
17
+ if (name === exportedName) {
18
+ serializedImports.push(`import { ${exportedName} } from '${filePath}'`);
19
+ if (isDirectFunction) {
20
+ // Direct function: pikkuFunc(fn) - needs to be wrapped
21
+ serializedRegistrations.push(`addFunction('${name}', { func: ${exportedName} })`);
22
+ }
23
+ else {
24
+ // Object format: pikkuFunc({ func: fn }) - can be used directly
25
+ serializedRegistrations.push(`addFunction('${name}', ${exportedName} as any) // TODO`);
26
+ }
27
+ }
28
+ // For renamed functions, we need to import and alias them
29
+ else {
30
+ serializedImports.push(`import { ${exportedName} as ${name} } from '${filePath}'`);
31
+ if (isDirectFunction) {
32
+ // Direct function: pikkuFunc(fn) - needs to be wrapped
33
+ serializedRegistrations.push(`addFunction('${name}', { func: ${name} })`);
34
+ }
35
+ else {
36
+ // Object format: pikkuFunc({ func: fn }) - can be used directly
37
+ serializedRegistrations.push(`addFunction('${name}', ${name} as any) // TODO`);
38
+ }
39
+ }
40
+ }
41
+ // Add a blank line between imports and registrations
42
+ if (serializedImports.length > 0 && serializedRegistrations.length > 0) {
43
+ serializedImports.push('');
44
+ }
45
+ // Combine the imports and registrations
46
+ return [...serializedImports, ...serializedRegistrations].join('\n');
47
+ };
48
+ export const generateRuntimeMeta = (functions) => {
49
+ const runtimeMeta = {};
50
+ for (const [key, { pikkuFuncName, inputSchemaName, outputSchemaName, expose },] of Object.entries(functions)) {
51
+ runtimeMeta[key] = {
52
+ pikkuFuncName,
53
+ inputSchemaName,
54
+ outputSchemaName,
55
+ expose,
56
+ };
57
+ }
58
+ return runtimeMeta;
59
+ };
@@ -1,7 +1,7 @@
1
1
  import { serializeRPCWrapper } from './serialize-rpc-wrapper.js';
2
2
  import { getFileImportRelativePath, logCommandInfoAndTime, writeFileInDir, } from '../../utils.js';
3
- export const pikkuRPCClient = async (logger, { rpcWiringsFile, rpcMapDeclarationFile, packageMappings }) => {
4
- return await logCommandInfoAndTime(logger, 'Generating RPC wrapper', 'Generated RPC wrapper', [
3
+ export const pikkuRPCClient = async (logger, { rpcWiringsFile, rpcMapDeclarationFile, rpcInternalMapDeclarationFile, packageMappings, }) => {
4
+ return await logCommandInfoAndTime(logger, 'Generating RPC wrappers', 'Generated RPC wrappers', [
5
5
  rpcWiringsFile === undefined || rpcWiringsFile === null,
6
6
  "rpcWiringsFile isn't set in the pikku config",
7
7
  ], async () => {
@@ -1,2 +1,3 @@
1
1
  import { PikkuCommand } from '../../types.js';
2
- export declare const pikkuRPCMap: PikkuCommand;
2
+ export declare const pikkuRPCInternalMap: PikkuCommand;
3
+ export declare const pikkuRPCExposedMap: PikkuCommand;
@@ -1,8 +1,14 @@
1
1
  import { logCommandInfoAndTime, writeFileInDir } from '../../utils.js';
2
2
  import { serializeTypedRPCMap } from './serialize-typed-rpc-map.js';
3
- export const pikkuRPCMap = async (logger, { rpcMapDeclarationFile, packageMappings }, { functions, rpc }) => {
4
- return await logCommandInfoAndTime(logger, 'Creating RPC map', 'Created RPC map', [false], async () => {
5
- const content = serializeTypedRPCMap(rpcMapDeclarationFile, packageMappings, functions.typesMap, functions.meta, rpc.meta);
3
+ export const pikkuRPCInternalMap = async (logger, { rpcInternalMapDeclarationFile, packageMappings }, { functions, rpc }) => {
4
+ return await logCommandInfoAndTime(logger, 'Creating RPC internal map', 'Created RPC internal map', [false], async () => {
5
+ const content = serializeTypedRPCMap(rpcInternalMapDeclarationFile, packageMappings, functions.typesMap, functions.meta, rpc.internalMeta);
6
+ await writeFileInDir(logger, rpcInternalMapDeclarationFile, content);
7
+ });
8
+ };
9
+ export const pikkuRPCExposedMap = async (logger, { rpcMapDeclarationFile, packageMappings }, { functions, rpc }) => {
10
+ return await logCommandInfoAndTime(logger, 'Creating RPC external map', 'Created RPC external map', [false], async () => {
11
+ const content = serializeTypedRPCMap(rpcMapDeclarationFile, packageMappings, functions.typesMap, functions.meta, rpc.exposedMeta);
6
12
  await writeFileInDir(logger, rpcMapDeclarationFile, content);
7
13
  });
8
14
  };
@@ -1,6 +1,11 @@
1
1
  import { logCommandInfoAndTime, writeFileInDir } from '../../utils.js';
2
- export const pikkuRPC = async (logger, { rpcWiringMetaFile }, { rpc }) => {
2
+ export const pikkuRPC = async (logger, { rpcInternalWiringMetaFile, rpcWiringMetaFile }, { rpc }) => {
3
3
  return await logCommandInfoAndTime(logger, 'Finding RPCs tasks', 'Found RPCs', [false], async () => {
4
- await writeFileInDir(logger, rpcWiringMetaFile, `import { pikkuState } from '@pikku/core'\npikkuState('rpc', 'meta', ${JSON.stringify(rpc.meta, null, 2)})`);
4
+ if (rpc.internalFiles.size > 0) {
5
+ await writeFileInDir(logger, rpcInternalWiringMetaFile, `import { pikkuState } from '@pikku/core'\npikkuState('rpc', 'meta', ${JSON.stringify(rpc.internalMeta, null, 2)})`);
6
+ }
7
+ if (rpc.exposedFiles.size > 0) {
8
+ await writeFileInDir(logger, rpcWiringMetaFile, `import { pikkuState } from '@pikku/core'\npikkuState('rpc', 'meta', ${JSON.stringify(rpc.exposedFiles, null, 2)})`);
9
+ }
5
10
  });
6
11
  };
@@ -1,4 +1,3 @@
1
- import type { RPCMeta } from '@pikku/core/rpc';
2
1
  import { TypesMap } from '@pikku/inspector';
3
2
  import { FunctionsMeta } from '@pikku/core';
4
- export declare const serializeTypedRPCMap: (relativeToPath: string, packageMappings: Record<string, string>, typesMap: TypesMap, functionsMeta: FunctionsMeta, rpcMeta: Record<string, RPCMeta>) => string;
3
+ export declare const serializeTypedRPCMap: (relativeToPath: string, packageMappings: Record<string, string>, typesMap: TypesMap, functionsMeta: FunctionsMeta, rpcMeta: Record<string, string>) => string;
@@ -31,6 +31,7 @@ export type TypedPikkuRPC = {
31
31
  depth: number;
32
32
  global: boolean;
33
33
  invoke: RPCInvoke;
34
+ invokeExposed: (name: string, data: any) => Promise<any>
34
35
  }
35
36
  `;
36
37
  };
@@ -38,7 +39,7 @@ function generateRPCs(rpcMeta, functionsMeta, typesMap, requiredTypes) {
38
39
  // Initialize an object to collect RPCs
39
40
  const rpcsObj = {};
40
41
  // Iterate through RPC metadata
41
- for (const [funcName, { pikkuFuncName }] of Object.entries(rpcMeta)) {
42
+ for (const [funcName, pikkuFuncName] of Object.entries(rpcMeta)) {
42
43
  const functionMeta = functionsMeta[pikkuFuncName];
43
44
  if (!functionMeta) {
44
45
  throw new Error(`Function ${funcName} not found in functionsMeta. Please check your configuration.`);
@@ -1 +1 @@
1
- {"root":["../bin/pikku-all.ts","../bin/pikku-fetch.ts","../bin/pikku-nextjs.ts","../bin/pikku-openapi.ts","../bin/pikku-queue-service.ts","../bin/pikku-schemas.ts","../bin/pikku-websocket.ts","../bin/pikku.ts","../src/inspector-glob.ts","../src/pikku-cli-config.ts","../src/pikku-command-schemas.ts","../src/schema-generator.ts","../src/schemas.ts","../src/serialize-import-map.ts","../src/serialize-pikku-types.ts","../src/types.ts","../src/utils.ts","../src/runtimes/nextjs/pikku-command-nextjs.ts","../src/runtimes/nextjs/serialize-nextjs-backend-wrapper.ts","../src/runtimes/nextjs/serialize-nextjs-http-wrapper.ts","../src/wirings/channels/pikku-channels.ts","../src/wirings/channels/pikku-command-channels-map.ts","../src/wirings/channels/pikku-command-channels.ts","../src/wirings/channels/pikku-command-websocket-typed.ts","../src/wirings/channels/serialize-typed-channel-map.ts","../src/wirings/channels/serialize-websocket-wrapper.ts","../src/wirings/fetch/index.ts","../src/wirings/functions/pikku-command-function-types.ts","../src/wirings/functions/pikku-command-functions.ts","../src/wirings/functions/pikku-command-services.ts","../src/wirings/functions/pikku-function-types.ts","../src/wirings/functions/pikku-functions.ts","../src/wirings/http/openapi-spec-generator.ts","../src/wirings/http/pikku-command-http-map.ts","../src/wirings/http/pikku-command-http-routes.ts","../src/wirings/http/pikku-command-openapi.ts","../src/wirings/http/pikku-http-routes.ts","../src/wirings/http/serialize-fetch-wrapper.ts","../src/wirings/http/serialize-typed-http-map.ts","../src/wirings/mcp/pikku-command-mcp-json.ts","../src/wirings/mcp/pikku-command-mcp.ts","../src/wirings/mcp/serialize-mcp-json.ts","../src/wirings/queue/pikku-command-queue-map.ts","../src/wirings/queue/pikku-command-queue-service.ts","../src/wirings/queue/pikku-command-queue.ts","../src/wirings/queue/pikku-queue-map.ts","../src/wirings/queue/pikku-queue.ts","../src/wirings/queue/serialize-queue-map.ts","../src/wirings/queue/serialize-queue-meta.ts","../src/wirings/queue/serialize-queue-wrapper.ts","../src/wirings/rpc/pikku-command-rpc-client.ts","../src/wirings/rpc/pikku-command-rpc-map.ts","../src/wirings/rpc/pikku-command-rpc.ts","../src/wirings/rpc/pikku-rpc.ts","../src/wirings/rpc/serialize-rpc-wrapper.ts","../src/wirings/rpc/serialize-typed-rpc-map.ts","../src/wirings/scheduler/pikku-command-scheduler.ts","../src/wirings/scheduler/serialize-scheduler-meta.ts"],"version":"5.9.2"}
1
+ {"root":["../bin/pikku-all.ts","../bin/pikku-fetch.ts","../bin/pikku-nextjs.ts","../bin/pikku-openapi.ts","../bin/pikku-queue-service.ts","../bin/pikku-rpc.ts","../bin/pikku-schemas.ts","../bin/pikku-websocket.ts","../bin/pikku.ts","../src/inspector-glob.ts","../src/pikku-cli-config.ts","../src/pikku-command-schemas.ts","../src/schema-generator.ts","../src/schemas.ts","../src/serialize-import-map.ts","../src/serialize-pikku-types.ts","../src/types.ts","../src/utils.ts","../src/runtimes/nextjs/pikku-command-nextjs.ts","../src/runtimes/nextjs/serialize-nextjs-backend-wrapper.ts","../src/runtimes/nextjs/serialize-nextjs-http-wrapper.ts","../src/wirings/channels/pikku-channels.ts","../src/wirings/channels/pikku-command-channels-map.ts","../src/wirings/channels/pikku-command-channels.ts","../src/wirings/channels/pikku-command-websocket-typed.ts","../src/wirings/channels/serialize-typed-channel-map.ts","../src/wirings/channels/serialize-websocket-wrapper.ts","../src/wirings/fetch/index.ts","../src/wirings/functions/pikku-command-function-types.ts","../src/wirings/functions/pikku-command-functions.ts","../src/wirings/functions/pikku-command-services.ts","../src/wirings/functions/pikku-function-types.ts","../src/wirings/functions/serialize-function-imports.ts","../src/wirings/http/openapi-spec-generator.ts","../src/wirings/http/pikku-command-http-map.ts","../src/wirings/http/pikku-command-http-routes.ts","../src/wirings/http/pikku-command-openapi.ts","../src/wirings/http/pikku-http-routes.ts","../src/wirings/http/serialize-fetch-wrapper.ts","../src/wirings/http/serialize-typed-http-map.ts","../src/wirings/mcp/pikku-command-mcp-json.ts","../src/wirings/mcp/pikku-command-mcp.ts","../src/wirings/mcp/serialize-mcp-json.ts","../src/wirings/queue/pikku-command-queue-map.ts","../src/wirings/queue/pikku-command-queue-service.ts","../src/wirings/queue/pikku-command-queue.ts","../src/wirings/queue/pikku-queue-map.ts","../src/wirings/queue/pikku-queue.ts","../src/wirings/queue/serialize-queue-map.ts","../src/wirings/queue/serialize-queue-meta.ts","../src/wirings/queue/serialize-queue-wrapper.ts","../src/wirings/rpc/pikku-command-rpc-client.ts","../src/wirings/rpc/pikku-command-rpc-map.ts","../src/wirings/rpc/pikku-command-rpc.ts","../src/wirings/rpc/serialize-rpc-wrapper.ts","../src/wirings/rpc/serialize-typed-rpc-map.ts","../src/wirings/scheduler/pikku-command-scheduler.ts","../src/wirings/scheduler/serialize-scheduler-meta.ts"],"version":"5.9.2"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/cli",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -22,8 +22,8 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@openapi-contrib/json-schema-to-openapi-schema": "^4.0.2",
25
- "@pikku/core": "^0.9.2",
26
- "@pikku/inspector": "^0.9.2",
25
+ "@pikku/core": "^0.9.4",
26
+ "@pikku/inspector": "^0.9.3",
27
27
  "@types/cookie": "^1.0.0",
28
28
  "@types/uuid": "^10.0.0",
29
29
  "chalk": "^5.5.0",
@@ -15,6 +15,7 @@ export interface PikkuCLICoreOutputFiles {
15
15
  // Function definitions
16
16
  functionsFile: string
17
17
  functionsMetaFile: string
18
+ functionsMetaMinFile: string
18
19
 
19
20
  // HTTP
20
21
  httpWiringsFile: string
@@ -26,7 +27,11 @@ export interface PikkuCLICoreOutputFiles {
26
27
  channelsWiringMetaFile: string
27
28
  channelsMapDeclarationFile: string
28
29
 
29
- // RPC
30
+ // RPC Internal
31
+ rpcInternalWiringMetaFile: string
32
+ rpcInternalMapDeclarationFile: string
33
+
34
+ // RPC Exposed
30
35
  rpcWiringMetaFile: string
31
36
  rpcMapDeclarationFile: string
32
37
 
@@ -166,7 +171,8 @@ const _getPikkuCLIConfig = async (
166
171
  const functionDir = join(result.outDir, 'function')
167
172
  const httpDir = join(result.outDir, 'http')
168
173
  const channelDir = join(result.outDir, 'channel')
169
- const rpcDir = join(result.outDir, 'rpc')
174
+ const internalRPCDirectory = join(result.outDir, 'rpc-internal')
175
+ const externalRPCDirectory = join(result.outDir, 'rpc')
170
176
  const schedulerDir = join(result.outDir, 'scheduler')
171
177
  const queueDir = join(result.outDir, 'queue')
172
178
  const mcpDir = join(result.outDir, 'mcp')
@@ -187,6 +193,12 @@ const _getPikkuCLIConfig = async (
187
193
  'pikku-functions-meta.gen.ts'
188
194
  )
189
195
  }
196
+ if (!result.functionsMetaMinFile) {
197
+ result.functionsMetaMinFile = join(
198
+ functionDir,
199
+ 'pikku-functions-meta.min.gen.ts'
200
+ )
201
+ }
190
202
  if (!result.typesDeclarationFile) {
191
203
  result.typesDeclarationFile = join(result.outDir, 'pikku-types.gen.ts')
192
204
  }
@@ -225,13 +237,31 @@ const _getPikkuCLIConfig = async (
225
237
  )
226
238
  }
227
239
 
228
- // RPC
240
+ // Internal
241
+ if (!result.rpcInternalWiringMetaFile) {
242
+ result.rpcInternalWiringMetaFile = join(
243
+ internalRPCDirectory,
244
+ 'pikku-rpc-wirings-meta.internal.gen.ts'
245
+ )
246
+ }
247
+
248
+ if (!result.rpcInternalMapDeclarationFile) {
249
+ result.rpcInternalMapDeclarationFile = join(
250
+ internalRPCDirectory,
251
+ 'pikku-rpc-wirings-map.internal.gen.d.ts'
252
+ )
253
+ }
254
+
255
+ // External
229
256
  if (!result.rpcWiringMetaFile) {
230
- result.rpcWiringMetaFile = join(rpcDir, 'pikku-rpc-wirings-meta.gen.ts')
257
+ result.rpcWiringMetaFile = join(
258
+ externalRPCDirectory,
259
+ 'pikku-rpc-wirings-meta.gen.ts'
260
+ )
231
261
  }
232
262
  if (!result.rpcMapDeclarationFile) {
233
263
  result.rpcMapDeclarationFile = join(
234
- rpcDir,
264
+ externalRPCDirectory,
235
265
  'pikku-rpc-wirings-map.gen.d.ts'
236
266
  )
237
267
  }
@@ -9,8 +9,73 @@ export const serializeImportMap = (
9
9
  ) => {
10
10
  const paths = new Map<string, string[]>()
11
11
  Array.from(requiredTypes).forEach((requiredType) => {
12
- const { originalName, uniqueName, path } =
13
- typesMap.getTypeMeta(requiredType)
12
+ let originalName, uniqueName, path
13
+
14
+ try {
15
+ const typeMeta = typesMap.getTypeMeta(requiredType)
16
+ originalName = typeMeta.originalName
17
+ uniqueName = typeMeta.uniqueName
18
+ path = typeMeta.path
19
+ } catch (e) {
20
+ // Handle missing types by trying to find a suitable import path
21
+ // Look through all existing types in the map to find a path that might contain this type
22
+ let foundPath: string | null = null
23
+
24
+ // Get all unique paths from the typesMap
25
+ const allPaths = new Set<string>()
26
+ typesMap.customTypes.forEach(({ type, references }) => {
27
+ references.forEach((ref) => {
28
+ try {
29
+ const refMeta = typesMap.getTypeMeta(ref)
30
+ if (refMeta.path) {
31
+ allPaths.add(refMeta.path)
32
+ }
33
+ } catch {
34
+ // Continue
35
+ }
36
+ })
37
+ })
38
+
39
+ // Also check direct types in the map
40
+ try {
41
+ const mapEntries = (typesMap as any).map?.entries?.() || []
42
+ for (const [_, typeMeta] of mapEntries) {
43
+ if (typeMeta.path) {
44
+ allPaths.add(typeMeta.path)
45
+ }
46
+ }
47
+ } catch {
48
+ // Continue
49
+ }
50
+
51
+ // For PascalCase types, prefer paths that look like type definition files
52
+ if (/^[A-Z]/.test(requiredType)) {
53
+ for (const candidatePath of allPaths) {
54
+ if (
55
+ candidatePath.includes('types') ||
56
+ candidatePath.includes('.d.')
57
+ ) {
58
+ foundPath = candidatePath
59
+ break
60
+ }
61
+ }
62
+
63
+ // If no types file found, use the first available path
64
+ if (!foundPath && allPaths.size > 0) {
65
+ foundPath = Array.from(allPaths)[0] || null
66
+ }
67
+ }
68
+
69
+ if (foundPath) {
70
+ originalName = requiredType
71
+ uniqueName = requiredType
72
+ path = foundPath
73
+ } else {
74
+ // No suitable path found, skip
75
+ return
76
+ }
77
+ }
78
+
14
79
  if (!path) {
15
80
  // This is a custom type that exists in file, so we don't need to import it
16
81
  return
@@ -13,7 +13,7 @@ export const serializePikkuTypes = (
13
13
  * This is used to provide the application types in the typescript project
14
14
  */
15
15
 
16
- import { CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuMiddleware, addHTTPMiddleware } from '@pikku/core'
16
+ import { CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuMiddleware, addHTTPMiddleware, addMiddleware, addMiddlewareForTags, addPermission } from '@pikku/core'
17
17
  import { CorePikkuFunction, CorePikkuFunctionSessionless } from '@pikku/core/function'
18
18
  import { CoreHTTPFunctionWiring, AssertHTTPWiringParams, wireHTTP as wireHTTPCore } from '@pikku/core/http'
19
19
  import { CoreScheduledTask, wireScheduler as wireSchedulerCore } from '@pikku/core/scheduler'
@@ -58,7 +58,7 @@ type PikkuFunctionSessionless<
58
58
  Out = never,
59
59
  ChannelData = null, // null means optional channel
60
60
  MCPData = null, // null means optional MCP
61
- RequiredServices extends Services = Services &
61
+ RequiredServices extends Services = Omit<Services, 'rpc'> &
62
62
  { rpc: TypedPikkuRPC } & (
63
63
  [ChannelData] extends [null]
64
64
  ? { channel?: PikkuChannel<unknown, Out> } // Optional channel
@@ -84,7 +84,7 @@ type PikkuFunction<
84
84
  Out = never,
85
85
  ChannelData = null, // null means optional channel
86
86
  MCPData = null, // null means optional MCP
87
- RequiredServices extends Services = Services &
87
+ RequiredServices extends Services = Omit<Services, 'rpc'> &
88
88
  { rpc: TypedPikkuRPC } & (
89
89
  [ChannelData] extends [null]
90
90
  ? { channel?: PikkuChannel<unknown, Out> } // Optional channel
@@ -118,7 +118,7 @@ type ChannelWiring<ChannelData, Channel extends string> = CoreChannel<ChannelDat
118
118
  * Type definition for scheduled tasks that run at specified intervals.
119
119
  * These are sessionless functions that execute based on cron expressions.
120
120
  */
121
- type SchedulerWiring = CoreScheduledTask<PikkuFunctionSessionless<void, void>>
121
+ type SchedulerWiring = CoreScheduledTask<PikkuFunctionSessionless<void, void>, PikkuMiddleware>
122
122
 
123
123
  /**
124
124
  * Type definition for queue workers that process background jobs.
@@ -201,7 +201,7 @@ export const pikkuFunc = <In, Out = unknown>(
201
201
  export const pikkuSessionlessFunc = <In, Out = unknown>(
202
202
  func:
203
203
  | PikkuFunctionSessionless<In, Out>
204
- | CorePikkuFunctionConfig<PikkuFunctionSessionless<In, Out>, PikkuPermission<In>>
204
+ | CorePikkuFunctionConfig<PikkuFunctionSessionless<In, Out>, PikkuPermission<In>, PikkuMiddleware>
205
205
  ) => {
206
206
  return typeof func === 'function' ? func : func.func
207
207
  }
@@ -351,6 +351,78 @@ export const wireChannel = <ChannelData, Channel extends string>(
351
351
  */
352
352
  export { addHTTPMiddleware }
353
353
 
354
+ /**
355
+ * Adds global middleware for a specific tag.
356
+ *
357
+ * This function allows you to register middleware that will be applied to
358
+ * any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
359
+ *
360
+ * @param tag - The tag that the middleware should apply to.
361
+ * @param middleware - The middleware array to apply for the specified tag.
362
+ *
363
+ * @throws Error if middleware for the tag already exists.
364
+ *
365
+ * @example
366
+ * \`\`\`typescript
367
+ * // Add admin middleware for admin endpoints
368
+ * addMiddleware('admin', [adminMiddleware])
369
+ *
370
+ * // Add authentication middleware for auth endpoints
371
+ * addMiddleware('auth', [authMiddleware])
372
+ *
373
+ * // Add logging middleware for all API endpoints
374
+ * addMiddleware('api', [loggingMiddleware])
375
+ * \`\`\`
376
+ */
377
+ export { addMiddleware }
378
+
379
+ /**
380
+ * Combines tag-based middleware with wiring-specific middleware.
381
+ *
382
+ * This helper function gets middleware for tags and combines it with any
383
+ * wiring-specific middleware, avoiding the need for manual spreading.
384
+ *
385
+ * @param wiringMiddleware - Wiring-specific middleware.
386
+ * @param tags - Array of tags to look up middleware for.
387
+ * @returns Combined array of tag-based and wiring-specific middleware.
388
+ *
389
+ * @example
390
+ * \`\`\`typescript
391
+ * // Instead of:
392
+ * const taggedMiddleware = getMiddlewareForTags(tags)
393
+ * const combined = [...taggedMiddleware, ...(middleware || [])]
394
+ *
395
+ * // Use:
396
+ * const combined = addMiddlewareForTags(middleware, tags)
397
+ * \`\`\`
398
+ */
399
+ export { addMiddlewareForTags }
400
+
401
+ /**
402
+ * Adds global permissions for a specific tag.
403
+ *
404
+ * This function allows you to register permissions that will be applied to
405
+ * any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
406
+ *
407
+ * @param tag - The tag that the permissions should apply to.
408
+ * @param permissions - The permissions array to apply for the specified tag.
409
+ *
410
+ * @throws Error if permissions for the tag already exist.
411
+ *
412
+ * @example
413
+ * \`\`\`typescript
414
+ * // Add admin permissions for admin endpoints
415
+ * addPermission('admin', [adminPermission])
416
+ *
417
+ * // Add authentication permissions for auth endpoints
418
+ * addPermission('auth', [authPermission])
419
+ *
420
+ * // Add read permissions for all API endpoints
421
+ * addPermission('api', [readPermission])
422
+ * \`\`\`
423
+ */
424
+ export { addPermission }
425
+
354
426
  /**
355
427
  * Registers an HTTP wiring with the Pikku framework.
356
428
  *
package/src/utils.ts CHANGED
@@ -371,9 +371,38 @@ export function generateCustomTypes(
371
371
  ${Array.from(typesMap.customTypes.entries())
372
372
  .map(([name, { type, references }]) => {
373
373
  references.forEach((name) => {
374
- const originalName = typesMap.getTypeMeta(name).originalName
375
- requiredTypes.add(originalName)
374
+ requiredTypes.add(name)
376
375
  })
376
+
377
+ // Extract type names from the type string that might not be in references
378
+ const typeString = type
379
+ // Use regex to extract potential type names (PascalCase identifiers)
380
+ const typeNameRegex = /\b[A-Z][a-zA-Z0-9]*\b/g
381
+ const potentialTypes = typeString.match(typeNameRegex) || []
382
+
383
+ potentialTypes.forEach((typeName) => {
384
+ // Skip string literals and common keywords
385
+ if (
386
+ typeString.includes(`"${typeName}"`) ||
387
+ ['Pick', 'Omit', 'Partial', 'Required', 'Record', 'Readonly'].includes(
388
+ typeName
389
+ )
390
+ ) {
391
+ return
392
+ }
393
+
394
+ // Try to find this type in the typesMap and add it if found
395
+ try {
396
+ const typeMeta = typesMap.getTypeMeta(typeName)
397
+ if (typeMeta.path) {
398
+ requiredTypes.add(typeName)
399
+ }
400
+ } catch (e) {
401
+ // Type not found in map, but add it anyway for fallback resolution
402
+ requiredTypes.add(typeName)
403
+ }
404
+ })
405
+
377
406
  return `export type ${name} = ${type}`
378
407
  })
379
408
  .join('\n')}`
@@ -9,7 +9,11 @@ import { PikkuCommand } from '../../types.js'
9
9
 
10
10
  export const pikkuFunctionTypes: PikkuCommand = async (
11
11
  logger,
12
- { typesDeclarationFile: typesFile, packageMappings, rpcMapDeclarationFile },
12
+ {
13
+ typesDeclarationFile: typesFile,
14
+ packageMappings,
15
+ rpcInternalMapDeclarationFile,
16
+ },
13
17
  visitState,
14
18
  options = {}
15
19
  ) => {
@@ -39,7 +43,7 @@ export const pikkuFunctionTypes: PikkuCommand = async (
39
43
  `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(typesFile, singletonServicesType.typePath, packageMappings)}'`,
40
44
  singletonServicesType.type,
41
45
  `import type { ${sessionServicesType.type} } from '${getFileImportRelativePath(typesFile, sessionServicesType.typePath, packageMappings)}'`,
42
- `import type { TypedPikkuRPC } from '${getFileImportRelativePath(typesFile, rpcMapDeclarationFile, packageMappings)}'`
46
+ `import type { TypedPikkuRPC } from '${getFileImportRelativePath(typesFile, rpcInternalMapDeclarationFile, packageMappings)}'`
43
47
  )
44
48
 
45
49
  await writeFileInDir(logger, typesFile, content)
@@ -1,63 +1,14 @@
1
- import {
2
- getFileImportRelativePath,
3
- logCommandInfoAndTime,
4
- writeFileInDir,
5
- } from '../../utils.js'
1
+ import { logCommandInfoAndTime, writeFileInDir } from '../../utils.js'
6
2
  import { PikkuCommand } from '../../types.js'
7
-
8
- export const serializeFunctionImports = (
9
- outputPath: string,
10
- functionsMap: Map<string, { path: string; exportedName: string }>,
11
- packageMappings: Record<string, string> = {}
12
- ) => {
13
- const serializedImports: string[] = [
14
- `/* Import and register RPCs */`,
15
- `import { addFunction } from '@pikku/core'`,
16
- ]
17
-
18
- const serializedRegistrations: string[] = []
19
-
20
- // Sort by function name for consistent output
21
- const sortedEntries = Array.from(functionsMap.entries()).sort((a, b) =>
22
- a[0].localeCompare(b[0])
23
- )
24
-
25
- for (const [name, { path, exportedName }] of sortedEntries) {
26
- const filePath = getFileImportRelativePath(
27
- outputPath,
28
- path,
29
- packageMappings
30
- )
31
-
32
- // For directly exported functions, we can just import and register them
33
- if (name === exportedName) {
34
- serializedImports.push(`import { ${exportedName} } from '${filePath}'`)
35
- serializedRegistrations.push(
36
- `addFunction('${name}', { func: ${exportedName} })`
37
- )
38
- }
39
- // For renamed functions, we need to import and alias them
40
- else {
41
- serializedImports.push(
42
- `import { ${exportedName} as ${name} } from '${filePath}'`
43
- )
44
- serializedRegistrations.push(`addFunction('${name}', ${name})`)
45
- }
46
- }
47
-
48
- // Add a blank line between imports and registrations
49
- if (serializedImports.length > 0 && serializedRegistrations.length > 0) {
50
- serializedImports.push('')
51
- }
52
-
53
- // Combine the imports and registrations
54
- return [...serializedImports, ...serializedRegistrations].join('\n')
55
- }
3
+ import {
4
+ generateRuntimeMeta,
5
+ serializeFunctionImports,
6
+ } from './serialize-function-imports.js'
56
7
 
57
8
  export const pikkuFunctions: PikkuCommand = async (
58
9
  logger,
59
- { functionsMetaFile, functionsFile, packageMappings },
60
- { functions }
10
+ { functionsMetaFile, functionsMetaMinFile, functionsFile, packageMappings },
11
+ { functions, rpc }
61
12
  ) => {
62
13
  return await logCommandInfoAndTime(
63
14
  logger,
@@ -65,20 +16,33 @@ export const pikkuFunctions: PikkuCommand = async (
65
16
  'Serialized Pikku functions',
66
17
  [false],
67
18
  async () => {
19
+ // Generate full metadata
68
20
  await writeFileInDir(
69
21
  logger,
70
- functionsFile,
71
- serializeFunctionImports(
72
- functionsFile,
73
- functions.files,
74
- packageMappings
75
- )
22
+ functionsMetaFile,
23
+ `import { pikkuState } from '@pikku/core'\npikkuState('function', 'meta', ${JSON.stringify(functions.meta, null, 2)})`
76
24
  )
25
+
26
+ // Generate minimal metadata (runtime)
27
+ const runtimeMeta = generateRuntimeMeta(functions.meta)
77
28
  await writeFileInDir(
78
29
  logger,
79
- functionsMetaFile,
80
- `import { pikkuState } from '@pikku/core'\npikkuState('function', 'meta', ${JSON.stringify(functions.meta, null, 2)})`
30
+ functionsMetaMinFile,
31
+ `import { pikkuState } from '@pikku/core'\npikkuState('function', 'meta', ${JSON.stringify(runtimeMeta, null, 2)})`
81
32
  )
33
+
34
+ if (rpc.exposedFiles.size > 0 || rpc.internalFiles.size > 0) {
35
+ await writeFileInDir(
36
+ logger,
37
+ functionsFile,
38
+ serializeFunctionImports(
39
+ functionsFile,
40
+ rpc.internalFiles,
41
+ functions.meta,
42
+ packageMappings
43
+ )
44
+ )
45
+ }
82
46
  }
83
47
  )
84
48
  }
@@ -95,7 +95,7 @@ export const pikkuServices: PikkuCommand = async (
95
95
  logger,
96
96
  'Generating Pikku services map',
97
97
  'Generated Pikku services map',
98
- [visitState.functions.files.size === 0],
98
+ [false],
99
99
  async () => {
100
100
  const { sessionServicesType, singletonServicesType } =
101
101
  await getPikkuFilesAndMethods(
@@ -9,7 +9,11 @@ import { PikkuCommand } from '../../types.js'
9
9
 
10
10
  export const pikkuFunctionTypes: PikkuCommand = async (
11
11
  logger,
12
- { typesDeclarationFile: typesFile, packageMappings, rpcMapDeclarationFile },
12
+ {
13
+ typesDeclarationFile: typesFile,
14
+ packageMappings,
15
+ rpcInternalMapDeclarationFile,
16
+ },
13
17
  visitState,
14
18
  options = {}
15
19
  ) => {
@@ -39,7 +43,7 @@ export const pikkuFunctionTypes: PikkuCommand = async (
39
43
  `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(typesFile, singletonServicesType.typePath, packageMappings)}'`,
40
44
  singletonServicesType.type,
41
45
  `import type { ${sessionServicesType.type} } from '${getFileImportRelativePath(typesFile, sessionServicesType.typePath, packageMappings)}'`,
42
- `import type { TypedPikkuRPC } from '${getFileImportRelativePath(typesFile, rpcMapDeclarationFile, packageMappings)}'`
46
+ `import type { TypedPikkuRPC } from '${getFileImportRelativePath(typesFile, rpcInternalMapDeclarationFile, packageMappings)}'`
43
47
  )
44
48
  await writeFileInDir(logger, typesFile, content)
45
49
  }