@pikku/core 0.12.14 → 0.12.16

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 (136) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/errors/errors.d.ts +4 -0
  3. package/dist/errors/errors.js +12 -0
  4. package/dist/function/function-runner.js +2 -1
  5. package/dist/function/functions.types.js +1 -0
  6. package/dist/function/index.d.ts +2 -1
  7. package/dist/function/index.js +1 -0
  8. package/dist/handle-error.d.ts +2 -2
  9. package/dist/handle-error.js +8 -8
  10. package/dist/index.d.ts +2 -3
  11. package/dist/index.js +1 -2
  12. package/dist/middleware/index.d.ts +3 -0
  13. package/dist/middleware/index.js +3 -0
  14. package/dist/middleware/remote-auth.js +5 -2
  15. package/dist/middleware/telemetry.d.ts +47 -0
  16. package/dist/middleware/telemetry.js +106 -0
  17. package/dist/middleware-runner.js +31 -4
  18. package/dist/permissions.d.ts +13 -1
  19. package/dist/permissions.js +51 -0
  20. package/dist/pikku-state.d.ts +0 -1
  21. package/dist/pikku-state.js +1 -4
  22. package/dist/remote.d.ts +10 -0
  23. package/dist/remote.js +32 -0
  24. package/dist/schema.js +2 -0
  25. package/dist/services/deployment-service.d.ts +12 -1
  26. package/dist/services/in-memory-queue-service.d.ts +7 -0
  27. package/dist/services/in-memory-queue-service.js +28 -0
  28. package/dist/services/index.d.ts +4 -1
  29. package/dist/services/index.js +2 -1
  30. package/dist/services/logger-console.d.ts +26 -40
  31. package/dist/services/logger-console.js +102 -46
  32. package/dist/services/logger.d.ts +5 -0
  33. package/dist/services/meta-service.d.ts +175 -0
  34. package/dist/services/meta-service.js +310 -0
  35. package/dist/services/workflow-service.d.ts +6 -1
  36. package/dist/testing/service-tests.js +0 -8
  37. package/dist/types/core.types.d.ts +21 -0
  38. package/dist/types/core.types.js +7 -1
  39. package/dist/types/state.types.d.ts +2 -2
  40. package/dist/wirings/ai-agent/ai-agent-prepare.js +42 -21
  41. package/dist/wirings/ai-agent/ai-agent-registry.js +2 -2
  42. package/dist/wirings/ai-agent/ai-agent-runner.js +18 -1
  43. package/dist/wirings/ai-agent/ai-agent-stream.js +1 -0
  44. package/dist/wirings/ai-agent/ai-agent.types.d.ts +5 -3
  45. package/dist/wirings/channel/channel-runner.js +3 -3
  46. package/dist/wirings/cli/cli-runner.js +5 -3
  47. package/dist/wirings/cli/command-parser.js +7 -3
  48. package/dist/wirings/http/http-runner.d.ts +1 -1
  49. package/dist/wirings/http/http-runner.js +23 -11
  50. package/dist/wirings/http/http.types.d.ts +3 -0
  51. package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
  52. package/dist/wirings/http/pikku-fetch-http-response.js +3 -0
  53. package/dist/wirings/mcp/mcp-runner.js +5 -3
  54. package/dist/wirings/mcp/mcp.types.d.ts +1 -1
  55. package/dist/wirings/queue/queue-runner.d.ts +3 -1
  56. package/dist/wirings/queue/queue-runner.js +7 -3
  57. package/dist/wirings/rpc/rpc-runner.js +56 -90
  58. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -1
  59. package/dist/wirings/scheduler/scheduler-runner.js +9 -5
  60. package/dist/wirings/trigger/trigger-runner.js +4 -2
  61. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +1 -1
  62. package/dist/wirings/workflow/dsl/workflow-runner.js +6 -9
  63. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
  64. package/dist/wirings/workflow/graph/graph-runner.js +18 -4
  65. package/dist/wirings/workflow/index.d.ts +4 -2
  66. package/dist/wirings/workflow/index.js +4 -2
  67. package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -4
  68. package/dist/wirings/workflow/pikku-workflow-service.js +216 -24
  69. package/dist/wirings/workflow/workflow-queue-workers.d.ts +40 -0
  70. package/dist/wirings/workflow/workflow-queue-workers.js +41 -0
  71. package/dist/wirings/workflow/workflow.types.d.ts +17 -1
  72. package/package.json +4 -1
  73. package/src/errors/errors.ts +12 -0
  74. package/src/function/function-runner.ts +5 -4
  75. package/src/function/functions.types.ts +1 -0
  76. package/src/function/index.ts +10 -0
  77. package/src/handle-error.test.ts +2 -2
  78. package/src/handle-error.ts +8 -8
  79. package/src/index.ts +2 -2
  80. package/src/middleware/index.ts +3 -0
  81. package/src/middleware/remote-auth.test.ts +4 -4
  82. package/src/middleware/remote-auth.ts +7 -2
  83. package/src/middleware/telemetry.ts +110 -0
  84. package/src/middleware-runner.test.ts +149 -1
  85. package/src/middleware-runner.ts +48 -4
  86. package/src/permissions.ts +70 -0
  87. package/src/pikku-state.test.ts +0 -26
  88. package/src/pikku-state.ts +1 -5
  89. package/src/remote.ts +46 -0
  90. package/src/schema.ts +1 -0
  91. package/src/services/deployment-service.ts +17 -1
  92. package/src/services/in-memory-queue-service.ts +46 -0
  93. package/src/services/index.ts +21 -1
  94. package/src/services/logger-console.ts +127 -47
  95. package/src/services/logger.ts +6 -0
  96. package/src/services/meta-service.ts +523 -0
  97. package/src/services/workflow-service.ts +8 -0
  98. package/src/testing/service-tests.ts +0 -10
  99. package/src/types/core.types.ts +36 -1
  100. package/src/types/state.types.ts +2 -2
  101. package/src/wirings/ai-agent/ai-agent-prepare.ts +51 -40
  102. package/src/wirings/ai-agent/ai-agent-registry.ts +3 -3
  103. package/src/wirings/ai-agent/ai-agent-runner.ts +16 -1
  104. package/src/wirings/ai-agent/ai-agent-stream.ts +8 -0
  105. package/src/wirings/ai-agent/ai-agent.types.ts +5 -3
  106. package/src/wirings/channel/channel-runner.ts +4 -5
  107. package/src/wirings/channel/local/local-channel-runner.test.ts +5 -1
  108. package/src/wirings/cli/cli-runner.test.ts +8 -7
  109. package/src/wirings/cli/cli-runner.ts +6 -4
  110. package/src/wirings/cli/command-parser.ts +8 -3
  111. package/src/wirings/http/http-runner.ts +27 -11
  112. package/src/wirings/http/http.types.ts +3 -0
  113. package/src/wirings/http/pikku-fetch-http-response.ts +4 -0
  114. package/src/wirings/mcp/mcp-runner.ts +7 -9
  115. package/src/wirings/mcp/mcp.types.ts +1 -1
  116. package/src/wirings/queue/queue-runner.test.ts +5 -11
  117. package/src/wirings/queue/queue-runner.ts +11 -3
  118. package/src/wirings/rpc/rpc-runner.ts +82 -115
  119. package/src/wirings/scheduler/scheduler-runner.test.ts +5 -11
  120. package/src/wirings/scheduler/scheduler-runner.ts +13 -5
  121. package/src/wirings/trigger/trigger-runner.test.ts +10 -11
  122. package/src/wirings/trigger/trigger-runner.ts +6 -4
  123. package/src/wirings/workflow/dsl/workflow-runner.ts +11 -10
  124. package/src/wirings/workflow/graph/graph-runner.ts +19 -4
  125. package/src/wirings/workflow/index.ts +17 -6
  126. package/src/wirings/workflow/pikku-workflow-service.ts +284 -24
  127. package/src/wirings/workflow/workflow-queue-workers.ts +85 -0
  128. package/src/wirings/workflow/workflow.types.ts +16 -1
  129. package/tsconfig.tsbuildinfo +1 -1
  130. package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +0 -6
  131. package/dist/wirings/ai-agent/agent-dynamic-workflow.js +0 -468
  132. package/dist/wirings/workflow/workflow-helpers.d.ts +0 -36
  133. package/dist/wirings/workflow/workflow-helpers.js +0 -38
  134. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -610
  135. package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
  136. package/src/wirings/workflow/workflow-helpers.ts +0 -79
@@ -1,4 +1,4 @@
1
- import { NotFoundError, PikkuMissingMetaError } from '../../errors/errors.js';
1
+ import { NotFoundError } from '../../errors/errors.js';
2
2
  import { addFunction } from '../../function/function-runner.js';
3
3
  import { pikkuState, getSingletonServices } from '../../pikku-state.js';
4
4
  import { coerceTopLevelDataFromSchema, validateSchema } from '../../schema.js';
@@ -12,9 +12,9 @@ export const wireChannel = (channel) => {
12
12
  const channelsMeta = pikkuState(null, 'channel', 'meta');
13
13
  const channelMeta = channelsMeta[channel.name];
14
14
  if (!channelMeta) {
15
- throw new PikkuMissingMetaError(`Missing generated metadata for channel '${channel.name}'`);
15
+ console.warn(`[pikku] Skipping channel '${channel.name}' — metadata not found. Consider moving this wiring to its own file.`);
16
+ return;
16
17
  }
17
- pikkuState(null, 'channel', 'channels').set(channel.name, channel);
18
18
  // Register onConnect function if provided
19
19
  if (channel.onConnect && channelMeta.connect) {
20
20
  addFunction(channelMeta.connect.pikkuFuncId, channel.onConnect);
@@ -1,4 +1,4 @@
1
- import { NotFoundError, PikkuMissingMetaError } from '../../errors/errors.js';
1
+ import { NotFoundError } from '../../errors/errors.js';
2
2
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
3
3
  import { pikkuState } from '../../pikku-state.js';
4
4
  import { PikkuSessionService, createMiddlewareSessionWireProps, } from '../../services/user-session-service.js';
@@ -29,7 +29,8 @@ export const wireCLI = (cli) => {
29
29
  // Get the existing metadata that was generated during inspection
30
30
  const cliMeta = pikkuState(null, 'cli', 'meta') || {};
31
31
  if (!cliMeta.programs?.[cli.program]) {
32
- throw new PikkuMissingMetaError(`CLI metadata not found for program '${cli.program}'`);
32
+ console.warn(`[pikku] Skipping CLI program '${cli.program}' — metadata not found. Consider moving this wiring to its own file.`);
33
+ return;
33
34
  }
34
35
  // Get existing programs state and add this program
35
36
  const programs = pikkuState(null, 'cli', 'programs') || {};
@@ -322,8 +323,9 @@ export async function executeCLI({ programName, args, createConfig, createSingle
322
323
  const config = createConfig
323
324
  ? await createConfig(new LocalVariablesService(), data)
324
325
  : {};
325
- // Create services with config
326
+ // Create services with config and register in global state
326
327
  const singletonServices = await createSingletonServices(config);
328
+ pikkuState(null, 'package', 'singletonServices', singletonServices);
327
329
  // Execute the command
328
330
  await runCLICommand({
329
331
  program: programName,
@@ -1,3 +1,7 @@
1
+ /** Convert kebab-case to camelCase: "from-plan" → "fromPlan" */
2
+ function toCamelCase(str) {
3
+ return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
4
+ }
1
5
  /**
2
6
  * Parses raw CLI arguments into structured data for a specific program
3
7
  */
@@ -81,11 +85,11 @@ export function parseCLIArguments(args, programName, allMeta) {
81
85
  while (currentIndex < args.length) {
82
86
  const arg = args[currentIndex];
83
87
  if (arg.startsWith('--')) {
84
- // Long option
88
+ // Long option (--from-plan → fromPlan)
85
89
  const equalIndex = arg.indexOf('=');
86
90
  if (equalIndex > 0) {
87
91
  // --option=value format
88
- const key = arg.slice(2, equalIndex);
92
+ const key = toCamelCase(arg.slice(2, equalIndex));
89
93
  const optionDef = availableOptions[key];
90
94
  // Unknown options are allowed for forward compatibility
91
95
  const value = arg.slice(equalIndex + 1);
@@ -93,7 +97,7 @@ export function parseCLIArguments(args, programName, allMeta) {
93
97
  }
94
98
  else {
95
99
  // --option value format
96
- const key = arg.slice(2);
100
+ const key = toCamelCase(arg.slice(2));
97
101
  const optionDef = availableOptions[key];
98
102
  // Unknown options are allowed for forward compatibility
99
103
  if (optionDef && optionDef.array) {
@@ -136,4 +136,4 @@ export declare const pikkuFetch: <In, Out>(request: Request | PikkuHTTPRequest,
136
136
  * @param {RunHTTPWiringOptions} options - Options such as singleton services, session handling, and error configuration.
137
137
  * @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
138
138
  */
139
- export declare const fetchData: <In, Out>(request: Request | PikkuHTTPRequest, response: PikkuHTTPResponse, { skipUserSession, respondWith404, logWarningsForStatusCodes, coerceDataFromSchema, bubbleErrors, exposeErrors, generateRequestId, }?: RunHTTPWiringOptions) => Promise<Out | void>;
139
+ export declare const fetchData: <In, Out>(request: Request | PikkuHTTPRequest, response: PikkuHTTPResponse, { skipUserSession, respondWith404, logWarningsForStatusCodes, coerceDataFromSchema, bubbleErrors, exposeErrors, generateRequestId, traceId: externalTraceId, }?: RunHTTPWiringOptions) => Promise<Out | void>;
@@ -1,4 +1,4 @@
1
- import { NotFoundError, PikkuMissingMetaError } from '../../errors/errors.js';
1
+ import { NotFoundError } from '../../errors/errors.js';
2
2
  import { closeWireServices, createWeakUID, isSerializable, } from '../../utils.js';
3
3
  import { getSingletonServices, getCreateWireServices, } from '../../pikku-state.js';
4
4
  import { PikkuSessionService } from '../../services/user-session-service.js';
@@ -112,7 +112,12 @@ export const wireHTTP = (httpWiring) => {
112
112
  const httpMeta = pikkuState(null, 'http', 'meta');
113
113
  const routeMeta = httpMeta[httpWiring.method][httpWiring.route];
114
114
  if (!routeMeta) {
115
- throw new PikkuMissingMetaError(`Missing generated metadata for HTTP route '${httpWiring.method.toUpperCase()} ${httpWiring.route}'`);
115
+ // In deploy units with filtered metadata, wiring files may include
116
+ // routes for functions not in this unit. This happens when multiple
117
+ // wirings share a file — split them into separate files for better
118
+ // tree-shaking.
119
+ console.warn(`[pikku] Skipping HTTP route '${httpWiring.method.toUpperCase()} ${httpWiring.route}' — metadata not found. Consider moving this wiring to its own file.`);
120
+ return;
116
121
  }
117
122
  if (httpWiring.func) {
118
123
  addFunction(routeMeta.pikkuFuncId, httpWiring.func);
@@ -251,6 +256,7 @@ const executeRoute = async (services, matchedRoute, http, options) => {
251
256
  };
252
257
  }
253
258
  const wire = {
259
+ traceId: requestId,
254
260
  http,
255
261
  channel,
256
262
  session: userSession.get(),
@@ -345,19 +351,25 @@ export const pikkuFetch = async (request, params = {}) => {
345
351
  * @param {RunHTTPWiringOptions} options - Options such as singleton services, session handling, and error configuration.
346
352
  * @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
347
353
  */
348
- export const fetchData = async (request, response, { skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, exposeErrors = false, generateRequestId, } = {}) => {
354
+ export const fetchData = async (request, response, { skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, exposeErrors = false, generateRequestId, traceId: externalTraceId, } = {}) => {
349
355
  const singletonServices = getSingletonServices();
350
356
  const createWireServices = getCreateWireServices();
351
357
  let wireServices;
352
358
  let result;
353
359
  // Combine the request and response into one wire object
354
360
  const pikkuRequest = request instanceof Request ? new PikkuFetchHTTPRequest(request) : request;
355
- let requestId = null;
356
- try {
357
- requestId = pikkuRequest.header('x-request-id');
361
+ // Resolve traceId: external (e.g. CF-Ray) > x-request-id header > generated
362
+ let requestId = externalTraceId ?? null;
363
+ if (!requestId) {
364
+ try {
365
+ requestId = pikkuRequest.header('x-request-id');
366
+ }
367
+ catch { }
358
368
  }
359
- catch { }
360
369
  requestId = requestId || generateRequestId?.() || createWeakUID();
370
+ // Scoped logger for HTTP runner internal logging (error handling, route matching)
371
+ // Functions/middleware receive singletonServices.logger directly for compatibility
372
+ const scopedLogger = singletonServices.logger.scope?.(requestId) ?? singletonServices.logger;
361
373
  const http = createHTTPWire(pikkuRequest, response);
362
374
  const apiType = http.request.method();
363
375
  const apiRoute = http.request.path();
@@ -375,7 +387,7 @@ export const fetchData = async (request, response, { skipUserSession = false, re
375
387
  response.status(204).json(undefined);
376
388
  return;
377
389
  }
378
- singletonServices.logger.info({
390
+ scopedLogger.info({
379
391
  message: 'Route not found',
380
392
  apiRoute,
381
393
  apiType,
@@ -395,7 +407,7 @@ export const fetchData = async (request, response, { skipUserSession = false, re
395
407
  catch (e) {
396
408
  if (matchedRoute?.route.sse) {
397
409
  // For SSE routes, send error through the stream since the response is already in stream mode
398
- singletonServices.logger.error(e instanceof Error ? e.message : e);
410
+ scopedLogger.error(e instanceof Error ? e.message : e);
399
411
  try {
400
412
  const errorResponse = getErrorResponse(e);
401
413
  response.arrayBuffer(JSON.stringify({
@@ -405,12 +417,12 @@ export const fetchData = async (request, response, { skipUserSession = false, re
405
417
  response.arrayBuffer(JSON.stringify({ type: 'done' }));
406
418
  }
407
419
  catch (streamErr) {
408
- singletonServices.logger.error(`SSE error while sending error payload: ${streamErr instanceof Error ? streamErr.message : String(streamErr)}`);
420
+ scopedLogger.error(`SSE error while sending error payload: ${streamErr instanceof Error ? streamErr.message : String(streamErr)}`);
409
421
  }
410
422
  response.close?.();
411
423
  }
412
424
  else {
413
- handleHTTPError(e, http, requestId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors, exposeErrors);
425
+ handleHTTPError(e, http, requestId, scopedLogger, logWarningsForStatusCodes, respondWith404, bubbleErrors, exposeErrors);
414
426
  }
415
427
  }
416
428
  finally {
@@ -17,6 +17,8 @@ export type RunHTTPWiringOptions = Partial<{
17
17
  bubbleErrors: boolean;
18
18
  exposeErrors: boolean;
19
19
  generateRequestId: () => string;
20
+ /** Pre-resolved trace ID (e.g. CF-Ray). Falls back to x-request-id header or generated ID. */
21
+ traceId: string;
20
22
  }>;
21
23
  /**
22
24
  * Represents the HTTP methods supported for API HTTP wirings.
@@ -165,6 +167,7 @@ export interface PikkuHTTPRequest<In = unknown> {
165
167
  query(): PikkuQuery;
166
168
  }
167
169
  export interface PikkuHTTPResponse<Out = unknown> {
170
+ readonly statusCode: number;
168
171
  status(code: number): this;
169
172
  cookie(name: string, value: string | null, options: SerializeOptions): this;
170
173
  header(name: string, value: string | string[]): this;
@@ -3,6 +3,7 @@ import type { SerializeOptions as CookieSerializeOptions } from 'cookie';
3
3
  export declare class PikkuFetchHTTPResponse implements PikkuHTTPResponse {
4
4
  #private;
5
5
  setMode(mode: 'stream'): void;
6
+ get statusCode(): number;
6
7
  status(code: number): this;
7
8
  cookie(name: string, value: string, flags: CookieSerializeOptions): this;
8
9
  header(name: string, value: string | string[]): this;
@@ -13,6 +13,9 @@ export class PikkuFetchHTTPResponse {
13
13
  this.#body = this.createStream();
14
14
  }
15
15
  }
16
+ get statusCode() {
17
+ return this.#statusCode;
18
+ }
16
19
  status(code) {
17
20
  this.#statusCode = code;
18
21
  return this;
@@ -3,7 +3,7 @@ import { closeWireServices } from '../../utils.js';
3
3
  import { pikkuState, getSingletonServices, getCreateWireServices, } from '../../pikku-state.js';
4
4
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
5
5
  import { resolveNamespace } from '../rpc/rpc-runner.js';
6
- import { BadRequestError, NotFoundError, PikkuMissingMetaError, } from '../../errors/errors.js';
6
+ import { BadRequestError, NotFoundError } from '../../errors/errors.js';
7
7
  import { PikkuSessionService, createMiddlewareSessionWireProps, } from '../../services/user-session-service.js';
8
8
  export class MCPError extends Error {
9
9
  error;
@@ -18,7 +18,8 @@ export const wireMCPResource = (mcpResource) => {
18
18
  const resourcesMeta = pikkuState(null, 'mcp', 'resourcesMeta');
19
19
  const mcpResourceMeta = resourcesMeta[mcpResource.uri];
20
20
  if (!mcpResourceMeta) {
21
- throw new PikkuMissingMetaError(`Missing generated metadata for MCP resource '${mcpResource.uri}'`);
21
+ console.warn(`[pikku] Skipping MCP resource '${mcpResource.uri}' — metadata not found. Consider moving this wiring to its own file.`);
22
+ return;
22
23
  }
23
24
  addFunction(mcpResourceMeta.pikkuFuncId, mcpResource.func);
24
25
  const resources = pikkuState(null, 'mcp', 'resources');
@@ -31,7 +32,8 @@ export const wireMCPPrompt = (mcpPrompt) => {
31
32
  const promptsMeta = pikkuState(null, 'mcp', 'promptsMeta');
32
33
  const mcpPromptMeta = promptsMeta[mcpPrompt.name];
33
34
  if (!mcpPromptMeta) {
34
- throw new PikkuMissingMetaError(`Missing generated metadata for MCP prompt '${mcpPrompt.name}'`);
35
+ console.warn(`[pikku] Skipping MCP prompt '${mcpPrompt.name}' — metadata not found. Consider moving this wiring to its own file.`);
36
+ return;
35
37
  }
36
38
  addFunction(mcpPromptMeta.pikkuFuncId, mcpPrompt.func);
37
39
  const prompts = pikkuState(null, 'mcp', 'prompts');
@@ -80,7 +80,7 @@ export type CoreMCPResource<PikkuFunctionConfig = CorePikkuFunctionConfig<CorePi
80
80
  export type CoreMCPTool<PikkuFunctionConfig = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>>, PikkuPermission = CorePikkuPermission<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
81
81
  name: string;
82
82
  title?: string;
83
- description: string;
83
+ description?: string;
84
84
  summary?: string;
85
85
  errors?: string[];
86
86
  func: PikkuFunctionConfig;
@@ -28,7 +28,9 @@ export declare function removeQueueWorker(name: string): Promise<void>;
28
28
  /**
29
29
  * Process a single queue job - this function is called by queue consumers
30
30
  */
31
- export declare function runQueueJob({ job, updateProgress, }: {
31
+ export declare function runQueueJob({ job, updateProgress, traceId, }: {
32
32
  job: QueueJob;
33
33
  updateProgress?: (progress: number | string | object) => Promise<void>;
34
+ /** Pre-resolved trace ID (e.g. from queue message metadata) */
35
+ traceId?: string;
34
36
  }): Promise<void>;
@@ -36,7 +36,8 @@ export const wireQueueWorker = (queueWorker) => {
36
36
  const meta = pikkuState(null, 'queue', 'meta');
37
37
  const processorMeta = meta[queueWorker.name];
38
38
  if (!processorMeta) {
39
- throw new PikkuMissingMetaError(`Missing generated metadata for queue worker '${queueWorker.name}'`);
39
+ console.warn(`[pikku] Skipping queue worker '${queueWorker.name}' — metadata not found. Consider moving this wiring to its own file.`);
40
+ return;
40
41
  }
41
42
  // Register the function with pikku
42
43
  addFunction(processorMeta.pikkuFuncId, {
@@ -70,10 +71,12 @@ export async function removeQueueWorker(name) {
70
71
  /**
71
72
  * Process a single queue job - this function is called by queue consumers
72
73
  */
73
- export async function runQueueJob({ job, updateProgress, }) {
74
+ export async function runQueueJob({ job, updateProgress, traceId, }) {
74
75
  const singletonServices = getSingletonServices();
75
76
  const createWireServices = getCreateWireServices();
76
- const logger = singletonServices.logger;
77
+ const resolvedTraceId = traceId ?? `q-${job.id}`;
78
+ const logger = singletonServices.logger.scope?.(resolvedTraceId) ??
79
+ singletonServices.logger;
77
80
  const meta = pikkuState(null, 'queue', 'meta');
78
81
  const processorMeta = meta[job.queueName];
79
82
  if (!processorMeta) {
@@ -105,6 +108,7 @@ export async function runQueueJob({ job, updateProgress, }) {
105
108
  try {
106
109
  logger.info(`Processing job ${job.id} in queue ${job.queueName}`);
107
110
  const wire = {
111
+ traceId: resolvedTraceId,
108
112
  queue,
109
113
  };
110
114
  // Execute the pikku function with the job data
@@ -3,7 +3,6 @@ import { pikkuState } from '../../pikku-state.js';
3
3
  import { ForbiddenError } from '../../errors/errors.js';
4
4
  import { PikkuError, addError } from '../../errors/error-handler.js';
5
5
  import { parseVersionedId } from '../../version.js';
6
- import { encryptJSON } from '../../crypto-utils.js';
7
6
  export class RPCNotFoundError extends PikkuError {
8
7
  rpcName;
9
8
  constructor(rpcName) {
@@ -95,17 +94,38 @@ export class ContextAwareRPCService {
95
94
  }
96
95
  : undefined,
97
96
  };
98
- // Check if it's a namespaced function call (e.g., 'stripe:createCharge')
97
+ // Check addon namespace first (e.g. 'stripe:createCharge')
99
98
  if (funcName.includes(':')) {
100
- return this.invokeAddonFunction(funcName, data, updatedWire);
99
+ try {
100
+ return await this.invokeAddonFunction(funcName, data, updatedWire);
101
+ }
102
+ catch (addonErr) {
103
+ if (!(addonErr instanceof RPCNotFoundError))
104
+ throw addonErr;
105
+ // Not an addon — fall through to local lookup
106
+ }
107
+ }
108
+ // Try local function, then fall back to deployment service (remote)
109
+ try {
110
+ return await runPikkuFunc('rpc', funcName, getPikkuFunctionName(funcName), {
111
+ auth: this.options.requiresAuth,
112
+ singletonServices: this.services,
113
+ data: () => data,
114
+ wire: updatedWire,
115
+ });
116
+ }
117
+ catch (e) {
118
+ if (e instanceof RPCNotFoundError) {
119
+ // Fall back to deployment service (e.g. CF service binding, Lambda Invoke)
120
+ if (this.services.deploymentService) {
121
+ const session = this.wire.getSession && typeof this.wire.getSession === 'function'
122
+ ? await this.wire.getSession()
123
+ : this.wire.session;
124
+ return this.services.deploymentService.invoke(funcName, data, session, this.wire.traceId);
125
+ }
126
+ }
127
+ throw e;
101
128
  }
102
- // Main package function
103
- return runPikkuFunc('rpc', funcName, getPikkuFunctionName(funcName), {
104
- auth: this.options.requiresAuth,
105
- singletonServices: this.services,
106
- data: () => data,
107
- wire: updatedWire,
108
- });
109
129
  }
110
130
  /**
111
131
  * Invoke a function from an addon package
@@ -117,16 +137,14 @@ export class ContextAwareRPCService {
117
137
  // Resolve namespace to package name
118
138
  const resolved = resolveNamespace(namespacedFunction);
119
139
  if (!resolved) {
120
- throw new Error(`Unknown namespace in function reference: ${namespacedFunction}. ` +
121
- `Make sure the package is registered in addons config.`);
140
+ throw new RPCNotFoundError(namespacedFunction);
122
141
  }
123
142
  // Get the function meta from the addon package
124
143
  // Addon packages use function meta, not RPC meta
125
144
  const addonFunctionMeta = pikkuState(resolved.package, 'function', 'meta');
126
145
  const funcMeta = addonFunctionMeta[resolved.function];
127
146
  if (!funcMeta) {
128
- throw new Error(`Function '${resolved.function}' not found in package '${resolved.package}'. ` +
129
- `Available functions: ${Object.keys(addonFunctionMeta).join(', ') || '(none)'}`);
147
+ throw new RPCNotFoundError(namespacedFunction);
130
148
  }
131
149
  const funcName = funcMeta.pikkuFuncId || resolved.function;
132
150
  const auth = resolved.addonConfig?.auth ?? this.options.requiresAuth;
@@ -162,12 +180,23 @@ export class ContextAwareRPCService {
162
180
  if (rpcName.includes(':')) {
163
181
  return this.invokeAddonFunction(rpcName, data, mergedWire);
164
182
  }
165
- return runPikkuFunc('rpc', rpcName, getPikkuFunctionName(rpcName), {
166
- auth: this.options.requiresAuth,
167
- singletonServices: this.services,
168
- data: () => data,
169
- wire: mergedWire,
170
- });
183
+ try {
184
+ return await runPikkuFunc('rpc', rpcName, getPikkuFunctionName(rpcName), {
185
+ auth: this.options.requiresAuth,
186
+ singletonServices: this.services,
187
+ data: () => data,
188
+ wire: mergedWire,
189
+ });
190
+ }
191
+ catch (e) {
192
+ if (e instanceof RPCNotFoundError && this.services.deploymentService) {
193
+ const session = this.wire.getSession && typeof this.wire.getSession === 'function'
194
+ ? await this.wire.getSession()
195
+ : this.wire.session;
196
+ return this.services.deploymentService.invoke(rpcName, data, session, this.wire.traceId);
197
+ }
198
+ throw e;
199
+ }
171
200
  }
172
201
  async startWorkflow(workflowName, input, options) {
173
202
  if (!this.services.workflowService) {
@@ -227,77 +256,14 @@ export class ContextAwareRPCService {
227
256
  };
228
257
  }
229
258
  async remote(funcName, data) {
230
- let endpoint;
231
- const headers = {
232
- 'Content-Type': 'application/json',
233
- };
234
- const colonIndex = funcName.indexOf(':');
235
- if (colonIndex !== -1) {
236
- const namespace = funcName.substring(0, colonIndex);
237
- const addons = pikkuState(null, 'addons', 'packages');
238
- const pkgConfig = addons.get(namespace);
239
- endpoint = pkgConfig?.rpcEndpoint;
240
- }
241
- if (!endpoint && this.services.deploymentService) {
242
- const deployments = await this.services.deploymentService.findFunction(funcName);
243
- if (deployments.length > 0) {
244
- endpoint = deployments[0].endpoint;
245
- }
246
- }
247
- if (!endpoint) {
248
- throw new Error(`No endpoint configured for remote RPC: ${funcName}. ` +
249
- `Configure rpcEndpoint in addons config or set up a DeploymentService.`);
250
- }
251
- let secret;
252
- if (this.services.secrets) {
253
- try {
254
- secret = await this.services.secrets.getSecret('PIKKU_REMOTE_SECRET');
255
- }
256
- catch { }
257
- }
258
- if (secret) {
259
- if (!this.services.jwt) {
260
- throw new Error('PIKKU_REMOTE_SECRET is set but JWT service is not available');
261
- }
262
- const session = this.wire.getSession && typeof this.wire.getSession === 'function'
263
- ? await this.wire.getSession()
264
- : this.wire.session;
265
- const sessionEnc = session
266
- ? await encryptJSON(secret, { session })
267
- : undefined;
268
- const token = await this.services.jwt.encode({ value: 5, unit: 'minute' }, {
269
- aud: 'pikku-remote',
270
- fn: funcName,
271
- iat: Date.now(),
272
- session: sessionEnc,
273
- });
274
- headers.Authorization = `Bearer ${token}`;
275
- }
276
- const url = `${endpoint}/rpc/${encodeURIComponent(funcName)}`;
277
- const timeoutMs = 30_000;
278
- const response = await fetch(url, {
279
- method: 'POST',
280
- headers,
281
- body: JSON.stringify({ data }),
282
- signal: AbortSignal.timeout(timeoutMs),
283
- });
284
- if (!response.ok) {
285
- let errorBody;
286
- try {
287
- errorBody = JSON.stringify(await response.json());
288
- }
289
- catch {
290
- errorBody = await response.text();
291
- }
292
- throw new Error(`Remote RPC call failed: ${response.status} ${response.statusText}. ${errorBody}`);
293
- }
294
- try {
295
- return (await response.json());
296
- }
297
- catch {
298
- const text = await response.text();
299
- throw new Error(`Remote RPC call returned non-JSON response: ${response.status} ${response.statusText}. ${text}`);
259
+ if (!this.services.deploymentService) {
260
+ throw new Error(`No DeploymentService configured for remote RPC: ${funcName}. ` +
261
+ `Set up a DeploymentService to enable remote function calls.`);
300
262
  }
263
+ const session = this.wire.getSession && typeof this.wire.getSession === 'function'
264
+ ? await this.wire.getSession()
265
+ : this.wire.session;
266
+ return this.services.deploymentService.invoke(funcName, data, session, this.wire.traceId);
301
267
  }
302
268
  }
303
269
  // RPC Service class for the global interface
@@ -4,7 +4,9 @@ import type { CorePikkuFunctionConfig, CorePikkuFunctionSessionless } from '../.
4
4
  export type RunScheduledTasksParams = {
5
5
  name: string;
6
6
  session?: CoreUserSession;
7
+ /** Pre-resolved trace ID */
8
+ traceId?: string;
7
9
  };
8
10
  export declare const wireScheduler: <PikkuFunctionConfig extends CorePikkuFunctionConfig<CorePikkuFunctionSessionless<void, void>>>(scheduledTask: CoreScheduledTask<PikkuFunctionConfig>) => void;
9
- export declare function runScheduledTask({ name, session, }: RunScheduledTasksParams): Promise<void>;
11
+ export declare function runScheduledTask({ name, session, traceId, }: RunScheduledTasksParams): Promise<void>;
10
12
  export declare const getScheduledTasks: () => Map<string, CoreScheduledTask>;
@@ -1,5 +1,4 @@
1
1
  import { getErrorResponse, PikkuError } from '../../errors/error-handler.js';
2
- import { PikkuMissingMetaError } from '../../errors/errors.js';
3
2
  import { getSingletonServices, getCreateWireServices, pikkuState, } from '../../pikku-state.js';
4
3
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
5
4
  import { PikkuSessionService, createMiddlewareSessionWireProps, } from '../../services/user-session-service.js';
@@ -7,7 +6,8 @@ export const wireScheduler = (scheduledTask) => {
7
6
  const meta = pikkuState(null, 'scheduler', 'meta');
8
7
  const taskMeta = meta[scheduledTask.name];
9
8
  if (!taskMeta) {
10
- throw new PikkuMissingMetaError(`Missing generated metadata for scheduled task '${scheduledTask.name}'`);
9
+ console.warn(`[pikku] Skipping scheduled task '${scheduledTask.name}' — metadata not found. Consider moving this wiring to its own file.`);
10
+ return;
11
11
  }
12
12
  addFunction(taskMeta.pikkuFuncId, {
13
13
  func: scheduledTask.func.func,
@@ -33,9 +33,12 @@ class ScheduledTaskSkippedError extends PikkuError {
33
33
  this.name = 'ScheduledTaskSkippedError';
34
34
  }
35
35
  }
36
- export async function runScheduledTask({ name, session, }) {
36
+ export async function runScheduledTask({ name, session, traceId, }) {
37
37
  const singletonServices = getSingletonServices();
38
38
  const createWireServices = getCreateWireServices();
39
+ const resolvedTraceId = traceId ?? `cron-${name}-${Date.now()}`;
40
+ const scopedLogger = singletonServices.logger.scope?.(resolvedTraceId) ??
41
+ singletonServices.logger;
39
42
  const task = pikkuState(null, 'scheduler', 'tasks').get(name);
40
43
  const meta = pikkuState(null, 'scheduler', 'meta')[name];
41
44
  const userSession = new PikkuSessionService();
@@ -50,6 +53,7 @@ export async function runScheduledTask({ name, session, }) {
50
53
  }
51
54
  // Create the scheduled task wire object
52
55
  const wire = {
56
+ traceId: resolvedTraceId,
53
57
  scheduledTask: {
54
58
  name,
55
59
  schedule: task.schedule,
@@ -61,7 +65,7 @@ export async function runScheduledTask({ name, session, }) {
61
65
  ...createMiddlewareSessionWireProps(userSession),
62
66
  };
63
67
  try {
64
- singletonServices.logger.info(`Running schedule task: ${name} | schedule: ${task.schedule}`);
68
+ scopedLogger.info(`Running schedule task: ${name} | schedule: ${task.schedule}`);
65
69
  await runPikkuFunc('scheduler', meta.name, meta.pikkuFuncId, {
66
70
  singletonServices,
67
71
  createWireServices,
@@ -78,7 +82,7 @@ export async function runScheduledTask({ name, session, }) {
78
82
  catch (e) {
79
83
  const errorResponse = getErrorResponse(e);
80
84
  if (errorResponse != null) {
81
- singletonServices.logger.error(e);
85
+ scopedLogger.error(e);
82
86
  }
83
87
  throw e;
84
88
  }
@@ -10,7 +10,8 @@ export const wireTrigger = (trigger) => {
10
10
  const meta = pikkuState(null, 'trigger', 'meta');
11
11
  const triggerMeta = meta[trigger.name];
12
12
  if (!triggerMeta) {
13
- throw new PikkuMissingMetaError(`Missing generated metadata for trigger '${trigger.name}'`);
13
+ console.warn(`[pikku] Skipping trigger '${trigger.name}' — metadata not found. Consider moving this wiring to its own file.`);
14
+ return;
14
15
  }
15
16
  addFunction(triggerMeta.pikkuFuncId, trigger.func);
16
17
  const triggers = pikkuState(null, 'trigger', 'triggers');
@@ -25,7 +26,8 @@ export const wireTriggerSource = (source) => {
25
26
  const sourceMeta = pikkuState(null, 'trigger', 'sourceMeta');
26
27
  const triggerSourceMeta = sourceMeta[source.name];
27
28
  if (!triggerSourceMeta) {
28
- throw new PikkuMissingMetaError(`Missing generated metadata for trigger source '${source.name}'`);
29
+ console.warn(`[pikku] Skipping trigger source '${source.name}' — metadata not found. Consider moving this wiring to its own file.`);
30
+ return;
29
31
  }
30
32
  const triggerSources = pikkuState(null, 'trigger', 'triggerSources');
31
33
  if (triggerSources.has(source.name)) {
@@ -2,4 +2,4 @@
2
2
  * Add a workflow to the system
3
3
  * This is called by the generated workflow wirings
4
4
  */
5
- export declare const addWorkflow: (workflowName: string, workflowFunc: any) => void;
5
+ export declare const addWorkflow: (workflowName: string, workflowFunc: any, packageName?: string | null) => void;
@@ -1,23 +1,20 @@
1
1
  import { pikkuState } from '../../../pikku-state.js';
2
2
  import { addFunction } from '../../../function/function-runner.js';
3
- import { PikkuMissingMetaError } from '../../../errors/errors.js';
4
3
  /**
5
4
  * Add a workflow to the system
6
5
  * This is called by the generated workflow wirings
7
6
  */
8
- export const addWorkflow = (workflowName, workflowFunc) => {
9
- // Get workflow metadata from inspector
10
- const meta = pikkuState(null, 'workflows', 'meta');
7
+ export const addWorkflow = (workflowName, workflowFunc, packageName = null) => {
8
+ const meta = pikkuState(packageName, 'workflows', 'meta');
11
9
  const workflowMeta = meta[workflowName];
12
10
  if (!workflowMeta) {
13
- throw new PikkuMissingMetaError(`Missing generated metadata for workflow '${workflowName}'`);
11
+ console.warn(`[pikku] Skipping workflow '${workflowName}' — metadata not found. Consider moving this wiring to its own file.`);
12
+ return;
14
13
  }
15
- // Store workflow definition in state
16
- const registrations = pikkuState(null, 'workflows', 'registrations');
14
+ const registrations = pikkuState(packageName, 'workflows', 'registrations');
17
15
  registrations.set(workflowName, {
18
16
  name: workflowName,
19
17
  func: workflowFunc,
20
18
  });
21
- // Register the function with pikku
22
- addFunction(workflowMeta.pikkuFuncId, workflowFunc);
19
+ addFunction(workflowMeta.pikkuFuncId, workflowFunc, packageName);
23
20
  };
@@ -11,6 +11,6 @@ export declare function continueGraph(workflowService: PikkuWorkflowService, run
11
11
  export declare function executeGraphStep(workflowService: PikkuWorkflowService, rpcService: any, runId: string, stepId: string, nodeId: string, rpcName: string, data: any, graphName: string): Promise<any>;
12
12
  export declare function onGraphNodeComplete(workflowService: PikkuWorkflowService, runId: string, graphName: string): Promise<void>;
13
13
  export declare function runFromMeta(workflowService: PikkuWorkflowService, runId: string, meta: WorkflowRuntimeMeta, _rpcService: any): Promise<void>;
14
- export declare function runWorkflowGraph(workflowService: PikkuWorkflowService, graphName: string, triggerInput: any, rpcService?: any, inline?: boolean, startNode?: string, wire?: WorkflowRunWire): Promise<{
14
+ export declare function runWorkflowGraph(workflowService: PikkuWorkflowService, graphName: string, triggerInput: any, rpcService?: any, inline?: boolean, startNode?: string, wire?: WorkflowRunWire, overrideMeta?: WorkflowRuntimeMeta): Promise<{
15
15
  runId: string;
16
16
  }>;