@pikku/cli 0.12.17 → 0.12.20

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 (131) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/{index-DvrDbftC.css → index-BpY2pSuA.css} +10 -1
  3. package/console-app/assets/index-DXLy-_D4.js +717 -0
  4. package/console-app/index.html +2 -2
  5. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  7. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-channel.js +11 -1
  9. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  10. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  12. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  13. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +29 -2
  15. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  16. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  17. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  18. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  19. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  20. package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
  21. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  22. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  23. package/dist/.pikku/function/pikku-functions-meta.gen.json +274 -123
  24. package/dist/.pikku/function/pikku-functions.gen.js +5 -1
  25. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  26. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  27. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  28. package/dist/.pikku/http/pikku-http-wirings-meta.gen.json +62 -1
  29. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +2 -1
  30. package/dist/.pikku/http/pikku-http-wirings.gen.js +2 -1
  31. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  32. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  33. package/dist/.pikku/pikku-bootstrap.gen.d.ts +2 -1
  34. package/dist/.pikku/pikku-bootstrap.gen.js +2 -1
  35. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  36. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  37. package/dist/.pikku/pikku-services.gen.d.ts +7 -6
  38. package/dist/.pikku/pikku-services.gen.js +5 -4
  39. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  40. package/dist/.pikku/pikku-types.gen.js +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  42. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  43. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  44. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +12 -4
  45. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  46. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  47. package/dist/.pikku/schemas/register.gen.js +27 -7
  48. package/dist/.pikku/schemas/schemas/DevInput.schema.json +1 -0
  49. package/dist/.pikku/schemas/schemas/GraphStarterInput.schema.json +1 -0
  50. package/dist/.pikku/schemas/schemas/GraphStarterOutput.schema.json +1 -0
  51. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  52. package/dist/.pikku/schemas/schemas/WorkflowRunStatus.schema.json +1 -0
  53. package/dist/.pikku/schemas/schemas/WorkflowRunnerInput.schema.json +1 -0
  54. package/dist/.pikku/schemas/schemas/WorkflowStarterInput.schema.json +1 -0
  55. package/dist/.pikku/schemas/schemas/WorkflowStarterOutput.schema.json +1 -0
  56. package/dist/.pikku/schemas/schemas/WorkflowStatusCheckerInput.schema.json +1 -0
  57. package/dist/.pikku/schemas/schemas/WorkflowStatusStreamFullInput.schema.json +1 -0
  58. package/dist/.pikku/schemas/schemas/WorkflowStatusStreamInput.schema.json +1 -0
  59. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  60. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  61. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  62. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  63. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  64. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  65. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  66. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  67. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  68. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  69. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +911 -0
  70. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  71. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  72. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +5 -2
  73. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.d.ts +2 -4
  74. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +5 -2
  75. package/dist/src/cli.wiring.js +25 -0
  76. package/dist/src/deploy/analyzer/analyzer.d.ts +1 -0
  77. package/dist/src/deploy/analyzer/analyzer.js +18 -15
  78. package/dist/src/deploy/codegen/per-unit-codegen.js +3 -3
  79. package/dist/src/functions/commands/all.js +2 -186
  80. package/dist/src/functions/commands/bootstrap.js +10 -10
  81. package/dist/src/functions/commands/console.js +1 -1
  82. package/dist/src/functions/commands/dev.d.ts +13 -0
  83. package/dist/src/functions/commands/dev.js +187 -0
  84. package/dist/src/functions/commands/versions-update.js +7 -3
  85. package/dist/src/functions/commands/watch.js +1 -1
  86. package/dist/src/functions/wirings/rpc/pikku-command-react-query.d.ts +1 -0
  87. package/dist/src/functions/wirings/rpc/pikku-command-react-query.js +33 -0
  88. package/dist/src/functions/wirings/rpc/serialize-react-query-hooks.d.ts +1 -0
  89. package/dist/src/functions/wirings/rpc/serialize-react-query-hooks.js +108 -0
  90. package/dist/src/functions/wirings/rpc/serialize-rpc-wrapper.js +3 -3
  91. package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.js +6 -4
  92. package/dist/src/functions/wirings/workflow/serialize-workflow-routes.js +128 -29
  93. package/dist/src/functions/workflows/all.workflow.d.ts +1 -0
  94. package/dist/src/functions/workflows/all.workflow.js +207 -0
  95. package/dist/src/scaffold/rpc-remote.gen.js +1 -1
  96. package/dist/src/scaffold/workflow-routes.gen.d.ts +84 -0
  97. package/dist/src/scaffold/workflow-routes.gen.js +197 -0
  98. package/dist/src/services.js +2 -0
  99. package/dist/src/utils/pikku-cli-config.js +1 -0
  100. package/dist/tsconfig.tsbuildinfo +1 -1
  101. package/package.json +8 -4
  102. package/console-app/assets/index-CzMWJFqj.js +0 -700
  103. package/dist/.pikku/agent/pikku-agent-wirings-meta.gen.d.ts +0 -1
  104. package/dist/.pikku/agent/pikku-agent-wirings-meta.gen.js +0 -6
  105. package/dist/.pikku/agent/pikku-agent-wirings-meta.gen.json +0 -3
  106. package/dist/.pikku/agent/pikku-agent-wirings.gen.d.ts +0 -4
  107. package/dist/.pikku/agent/pikku-agent-wirings.gen.js +0 -4
  108. package/dist/.pikku/channel/pikku-channels-meta.gen.d.ts +0 -1
  109. package/dist/.pikku/channel/pikku-channels-meta.gen.js +0 -6
  110. package/dist/.pikku/channel/pikku-channels-meta.gen.json +0 -1
  111. package/dist/.pikku/channel/pikku-channels.gen.d.ts +0 -4
  112. package/dist/.pikku/channel/pikku-channels.gen.js +0 -5
  113. package/dist/.pikku/mcp/pikku-mcp-wirings-meta.gen.d.ts +0 -1
  114. package/dist/.pikku/mcp/pikku-mcp-wirings-meta.gen.js +0 -8
  115. package/dist/.pikku/mcp/pikku-mcp-wirings-meta.gen.json +0 -5
  116. package/dist/.pikku/mcp/pikku-mcp-wirings.gen.d.ts +0 -4
  117. package/dist/.pikku/mcp/pikku-mcp-wirings.gen.js +0 -5
  118. package/dist/.pikku/pikku-websocket.gen.d.ts +0 -45
  119. package/dist/.pikku/pikku-websocket.gen.js +0 -63
  120. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.d.ts +0 -1
  121. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +0 -6
  122. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.json +0 -1
  123. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +0 -4
  124. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +0 -5
  125. package/dist/.pikku/rpc/pikku-remote-rpc-workers.gen.d.ts +0 -17
  126. package/dist/.pikku/rpc/pikku-remote-rpc-workers.gen.js +0 -25
  127. package/dist/.pikku/scheduler/pikku-schedulers-wirings-meta.gen.d.ts +0 -1
  128. package/dist/.pikku/scheduler/pikku-schedulers-wirings-meta.gen.js +0 -6
  129. package/dist/.pikku/scheduler/pikku-schedulers-wirings-meta.gen.json +0 -1
  130. package/dist/.pikku/scheduler/pikku-schedulers-wirings.gen.d.ts +0 -4
  131. package/dist/.pikku/scheduler/pikku-schedulers-wirings.gen.js +0 -5
@@ -0,0 +1,187 @@
1
+ import { createServer } from 'http';
2
+ import { join, resolve } from 'path';
3
+ import { Readable } from 'stream';
4
+ import { pikkuSessionlessFunc } from '#pikku';
5
+ import chokidar from 'chokidar';
6
+ import { pikkuDevReloader } from '@pikku/core/dev';
7
+ import { ConsoleLogger, InMemoryQueueService, InMemoryWorkflowService, InMemoryTriggerService, InMemoryAIRunStateService, } from '@pikku/core/services';
8
+ import { stopSingletonServices } from '@pikku/core';
9
+ import { pikkuState } from '@pikku/core/internal';
10
+ import { fetchData, PikkuFetchHTTPResponse, logRoutes, } from '@pikku/core/http';
11
+ import { compileAllSchemas } from '@pikku/core/schema';
12
+ import { pikkuWebsocketHandler } from '@pikku/ws';
13
+ import { WebSocketServer } from 'ws';
14
+ import { InMemorySchedulerService } from '@pikku/schedule';
15
+ function incomingMessageToRequest(req) {
16
+ const url = new URL(req.url || '/', 'http://localhost');
17
+ const method = req.method ? req.method.toUpperCase() : 'GET';
18
+ const headers = new Headers();
19
+ for (const [key, value] of Object.entries(req.headers)) {
20
+ if (value) {
21
+ headers.set(key, Array.isArray(value) ? value.join(', ') : value);
22
+ }
23
+ }
24
+ let body = null;
25
+ if (method !== 'GET' && method !== 'HEAD') {
26
+ body = Readable.toWeb(req);
27
+ }
28
+ return new Request(url.toString(), {
29
+ method,
30
+ headers,
31
+ body,
32
+ // @ts-ignore - duplex is needed for streaming body in Node.js
33
+ duplex: 'half',
34
+ });
35
+ }
36
+ async function writeResponse(nodeRes, webResponse) {
37
+ const headers = {};
38
+ webResponse.headers.forEach((value, name) => {
39
+ const lower = name.toLowerCase();
40
+ if (lower === 'set-cookie') {
41
+ const existing = headers[lower];
42
+ if (Array.isArray(existing)) {
43
+ existing.push(value);
44
+ }
45
+ else {
46
+ headers[lower] = [value];
47
+ }
48
+ }
49
+ else {
50
+ headers[lower] = value;
51
+ }
52
+ });
53
+ nodeRes.writeHead(webResponse.status, headers);
54
+ if (webResponse.body) {
55
+ const reader = webResponse.body.getReader();
56
+ try {
57
+ while (true) {
58
+ const { done, value } = await reader.read();
59
+ if (done)
60
+ break;
61
+ nodeRes.write(value);
62
+ }
63
+ }
64
+ finally {
65
+ reader.releaseLock();
66
+ }
67
+ }
68
+ nodeRes.end();
69
+ }
70
+ export const dev = pikkuSessionlessFunc({
71
+ remote: true,
72
+ func: async ({ logger, config, getInspectorState }, { port, watch, hmr }, { rpc }) => {
73
+ const resolvedPort = parseInt(port || '3000', 10);
74
+ const hostname = 'localhost';
75
+ const enableWatch = watch !== false;
76
+ const enableHmr = hmr !== false;
77
+ await rpc.invoke('all');
78
+ const inspectorState = await getInspectorState(true);
79
+ const { pikkuConfigFactory, singletonServicesFactory } = inspectorState.filesAndMethods;
80
+ if (!pikkuConfigFactory || !singletonServicesFactory) {
81
+ logger.error('createConfig and createSingletonServices must be defined in your project');
82
+ return;
83
+ }
84
+ const pikkuDir = resolve(config.rootDir, config.outDir);
85
+ const bootstrapPath = join(pikkuDir, 'pikku-bootstrap.gen.js');
86
+ await import(bootstrapPath);
87
+ const configModule = await import(pikkuConfigFactory.file);
88
+ const servicesModule = await import(singletonServicesFactory.file);
89
+ const userCreateConfig = configModule[pikkuConfigFactory.variable];
90
+ const userCreateSingletonServices = servicesModule[singletonServicesFactory.variable];
91
+ const userConfig = await userCreateConfig();
92
+ const schedulerService = new InMemorySchedulerService();
93
+ const inMemoryServices = {
94
+ logger: new ConsoleLogger(),
95
+ schedulerService,
96
+ queueService: new InMemoryQueueService(),
97
+ workflowService: new InMemoryWorkflowService(),
98
+ triggerService: new InMemoryTriggerService(),
99
+ aiRunStateService: new InMemoryAIRunStateService(),
100
+ };
101
+ const singletonServices = await userCreateSingletonServices(userConfig, inMemoryServices);
102
+ pikkuState(null, 'package', 'singletonServices', singletonServices);
103
+ compileAllSchemas(logger);
104
+ logRoutes(logger);
105
+ const server = createServer(async (req, res) => {
106
+ const request = incomingMessageToRequest(req);
107
+ const pikkuResponse = new PikkuFetchHTTPResponse();
108
+ await fetchData(request, pikkuResponse, { respondWith404: true });
109
+ const response = pikkuResponse.toResponse();
110
+ await writeResponse(res, response);
111
+ });
112
+ const wss = new WebSocketServer({ noServer: true });
113
+ pikkuWebsocketHandler({ server, wss, logger });
114
+ await schedulerService.start();
115
+ await new Promise((resolve) => {
116
+ server.listen(resolvedPort, hostname, () => {
117
+ logger.info(`Dev server running at http://${hostname}:${resolvedPort}`);
118
+ resolve();
119
+ });
120
+ });
121
+ let configWatcher;
122
+ let watcher;
123
+ process.once('SIGINT', async () => {
124
+ logger.info('Stopping dev server...');
125
+ try {
126
+ await stopSingletonServices();
127
+ await configWatcher?.close();
128
+ await watcher?.close();
129
+ await new Promise((resolve, reject) => wss.close((err) => (err ? reject(err) : resolve())));
130
+ await new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve())));
131
+ }
132
+ finally {
133
+ process.exit(0);
134
+ }
135
+ });
136
+ if (enableHmr) {
137
+ await pikkuDevReloader({
138
+ srcDirectories: config.srcDirectories,
139
+ logger,
140
+ });
141
+ }
142
+ if (enableWatch) {
143
+ const genIgnore = /\.gen\.tsx?$/;
144
+ configWatcher = chokidar.watch(config.srcDirectories, {
145
+ ignoreInitial: true,
146
+ ignored: genIgnore,
147
+ });
148
+ const generatorWatcher = () => {
149
+ watcher?.close();
150
+ logger.info(`• Watching directories: \n - ${config.srcDirectories.join('\n - ')}`);
151
+ watcher = chokidar.watch(config.srcDirectories, {
152
+ ignoreInitial: true,
153
+ ignored: genIgnore,
154
+ });
155
+ watcher.on('ready', async () => {
156
+ const handle = async () => {
157
+ try {
158
+ const start = Date.now();
159
+ await rpc.invoke('all');
160
+ logger.info({
161
+ message: `✓ Generated in ${Date.now() - start}ms`,
162
+ type: 'timing',
163
+ });
164
+ }
165
+ catch (err) {
166
+ logger.error(`Error running watch: ${err}`);
167
+ }
168
+ };
169
+ await handle();
170
+ let timeout;
171
+ const deduped = (_file) => {
172
+ if (timeout) {
173
+ clearTimeout(timeout);
174
+ }
175
+ timeout = setTimeout(handle, 10);
176
+ };
177
+ watcher?.on('change', deduped);
178
+ watcher?.on('add', deduped);
179
+ watcher?.on('unlink', deduped);
180
+ });
181
+ };
182
+ configWatcher.on('ready', generatorWatcher);
183
+ configWatcher.on('change', generatorWatcher);
184
+ }
185
+ await new Promise(() => { });
186
+ },
187
+ });
@@ -7,12 +7,16 @@ export const pikkuVersionsUpdate = pikkuSessionlessFunc({
7
7
  const manifestPath = join(config.rootDir, 'versions.pikku.json');
8
8
  const visitState = await getInspectorState();
9
9
  if (!visitState.manifest.initial) {
10
- throw new Error(`Version manifest not found at ${manifestPath}. Run 'pikku versions init' to create one.`);
10
+ logger.warn(`Run 'pikku versions init' to enable contract versioning.`);
11
+ return;
11
12
  }
12
13
  const immutabilityErrors = visitState.manifest.errors.filter((e) => e.code === ErrorCode.FUNCTION_VERSION_MODIFIED);
13
14
  if (immutabilityErrors.length > 0) {
14
- const messages = immutabilityErrors.map((e) => `[${e.code}] ${e.message}`);
15
- throw new Error(messages.join('\n'));
15
+ for (const e of immutabilityErrors) {
16
+ logger.warn(`[${e.code}] ${e.message}`);
17
+ }
18
+ logger.warn(`Contract drift detected — version manifest not updated. Run 'pikku versions check' to inspect, or bump versions via code and re-run.`);
19
+ return;
16
20
  }
17
21
  await saveManifest(manifestPath, visitState.manifest.current);
18
22
  logger.debug(`Version manifest updated at ${manifestPath}`);
@@ -26,7 +26,7 @@ export const watch = pikkuSessionlessFunc({
26
26
  const handle = async () => {
27
27
  try {
28
28
  const start = Date.now();
29
- await rpc.invoke('all', null);
29
+ await rpc.invoke('all');
30
30
  logger.info({
31
31
  message: `✓ Generated in ${Date.now() - start}ms`,
32
32
  type: 'timing',
@@ -0,0 +1 @@
1
+ export declare const pikkuReactQuery: import("#pikku").PikkuFunctionConfig<void, void, "session" | "rpc", import("#pikku").PikkuFunctionSessionless<void, void, "session" | "rpc", import("#pikku").Services> | import("#pikku").PikkuFunction<void, void, "session" | "rpc", import("#pikku").Services>, undefined, undefined>;
@@ -0,0 +1,33 @@
1
+ import { pikkuSessionlessFunc } from '#pikku';
2
+ import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
3
+ import { writeFileInDir } from '../../../utils/file-writer.js';
4
+ import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
+ import { serializeReactQueryHooks } from './serialize-react-query-hooks.js';
6
+ export const pikkuReactQuery = pikkuSessionlessFunc({
7
+ func: async ({ logger, config, getInspectorState }) => {
8
+ const reactQueryFile = config.clientFiles?.reactQueryFile;
9
+ const { rpcMapDeclarationFile, workflowMapDeclarationFile, packageMappings, } = config;
10
+ if (!reactQueryFile) {
11
+ logger.debug({
12
+ message: "Skipping generating React Query hooks since reactQueryFile isn't set in the pikku config.",
13
+ type: 'skip',
14
+ });
15
+ return;
16
+ }
17
+ const { workflows } = await getInspectorState();
18
+ const hasWorkflows = Object.keys(workflows?.meta ?? {}).length > 0;
19
+ const rpcMapPath = getFileImportRelativePath(reactQueryFile, rpcMapDeclarationFile, packageMappings);
20
+ let workflowMapPath;
21
+ if (hasWorkflows) {
22
+ workflowMapPath = getFileImportRelativePath(reactQueryFile, workflowMapDeclarationFile, packageMappings);
23
+ }
24
+ const content = serializeReactQueryHooks(rpcMapPath, workflowMapPath);
25
+ await writeFileInDir(logger, reactQueryFile, content);
26
+ },
27
+ middleware: [
28
+ logCommandInfoAndTime({
29
+ commandStart: 'Generating React Query hooks',
30
+ commandEnd: 'Generated React Query hooks',
31
+ }),
32
+ ],
33
+ });
@@ -0,0 +1 @@
1
+ export declare const serializeReactQueryHooks: (rpcMapPath: string, workflowMapPath?: string) => string;
@@ -0,0 +1,108 @@
1
+ export const serializeReactQueryHooks = (rpcMapPath, workflowMapPath) => {
2
+ const workflowImport = workflowMapPath
3
+ ? `\nimport type { FlattenedWorkflowMap } from '${workflowMapPath}'`
4
+ : '';
5
+ const workflowHooks = workflowMapPath
6
+ ? `
7
+ export const useRunWorkflow = <Name extends keyof FlattenedWorkflowMap>(
8
+ name: Name,
9
+ options?: Omit<UseMutationOptions<FlattenedWorkflowMap[Name]['output'], Error, FlattenedWorkflowMap[Name]['input']>, 'mutationFn'>
10
+ ) => {
11
+ const rpc = usePikkuRPC<{ runWorkflow: <N extends keyof FlattenedWorkflowMap>(name: N, input: FlattenedWorkflowMap[N]['input']) => Promise<FlattenedWorkflowMap[N]['output']> }>()
12
+ return useMutation<FlattenedWorkflowMap[Name]['output'], Error, FlattenedWorkflowMap[Name]['input']>({
13
+ mutationFn: (input) => rpc.runWorkflow(name, input),
14
+ ...options,
15
+ })
16
+ }
17
+
18
+ export const useStartWorkflow = <Name extends keyof FlattenedWorkflowMap>(
19
+ name: Name,
20
+ options?: Omit<UseMutationOptions<{ runId: string }, Error, FlattenedWorkflowMap[Name]['input']>, 'mutationFn'>
21
+ ) => {
22
+ const rpc = usePikkuRPC<{ startWorkflow: <N extends keyof FlattenedWorkflowMap>(name: N, input: FlattenedWorkflowMap[N]['input']) => Promise<{ runId: string }> }>()
23
+ return useMutation<{ runId: string }, Error, FlattenedWorkflowMap[Name]['input']>({
24
+ mutationFn: (input) => rpc.startWorkflow(name, input),
25
+ ...options,
26
+ })
27
+ }
28
+
29
+ type WorkflowRunStatus = {
30
+ id: string
31
+ status: 'running' | 'suspended' | 'completed' | 'failed' | 'cancelled'
32
+ output?: unknown
33
+ error?: { message?: string }
34
+ }
35
+
36
+ export const useWorkflowStatus = (
37
+ workflowName: keyof FlattenedWorkflowMap & string,
38
+ runId?: string,
39
+ options?: Omit<UseQueryOptions<WorkflowRunStatus, Error>, 'queryKey' | 'queryFn' | 'enabled'>
40
+ ) => {
41
+ const rpc = usePikkuRPC<{ workflowStatus: (name: string, runId: string) => Promise<WorkflowRunStatus> }>()
42
+ return useQuery<WorkflowRunStatus, Error>({
43
+ queryKey: ['workflowStatus', workflowName, runId],
44
+ queryFn: () => {
45
+ if (!runId) throw new Error('runId is required')
46
+ return rpc.workflowStatus(workflowName, runId)
47
+ },
48
+ enabled: !!runId,
49
+ ...options,
50
+ })
51
+ }
52
+ `
53
+ : '';
54
+ return `import { useQuery, useInfiniteQuery, useMutation, type UseQueryOptions, type UseInfiniteQueryOptions, type UseMutationOptions, type InfiniteData } from '@tanstack/react-query'
55
+ import { usePikkuRPC } from '@pikku/react'
56
+ import type { FlattenedRPCMap } from '${rpcMapPath}'${workflowImport}
57
+
58
+ type RPCInvoke = <Name extends keyof FlattenedRPCMap>(name: Name, data: FlattenedRPCMap[Name]['input']) => Promise<FlattenedRPCMap[Name]['output']>
59
+
60
+ export const usePikkuQuery = <Name extends keyof FlattenedRPCMap>(
61
+ name: Name,
62
+ data: FlattenedRPCMap[Name]['input'],
63
+ options?: Omit<UseQueryOptions<FlattenedRPCMap[Name]['output'], Error>, 'queryKey' | 'queryFn'>
64
+ ) => {
65
+ const rpc = usePikkuRPC<{ invoke: RPCInvoke }>()
66
+ return useQuery<FlattenedRPCMap[Name]['output'], Error>({
67
+ queryKey: [name, data],
68
+ queryFn: () => rpc.invoke(name, data),
69
+ ...options,
70
+ })
71
+ }
72
+
73
+ export const usePikkuMutation = <Name extends keyof FlattenedRPCMap>(
74
+ name: Name,
75
+ options?: Omit<UseMutationOptions<FlattenedRPCMap[Name]['output'], Error, FlattenedRPCMap[Name]['input']>, 'mutationFn'>
76
+ ) => {
77
+ const rpc = usePikkuRPC<{ invoke: RPCInvoke }>()
78
+ return useMutation<FlattenedRPCMap[Name]['output'], Error, FlattenedRPCMap[Name]['input']>({
79
+ mutationFn: (data) => rpc.invoke(name, data),
80
+ ...options,
81
+ })
82
+ }
83
+
84
+ type PaginatedKeys = {
85
+ [K in keyof FlattenedRPCMap]: FlattenedRPCMap[K]['output'] extends { nextCursor?: string | null } ? K : never
86
+ }[keyof FlattenedRPCMap]
87
+
88
+ type InfiniteOpts<Name extends PaginatedKeys> = Omit<
89
+ UseInfiniteQueryOptions<FlattenedRPCMap[Name]['output'], Error, InfiniteData<FlattenedRPCMap[Name]['output'], string | undefined>, readonly unknown[], string | undefined>,
90
+ 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'
91
+ >
92
+
93
+ export const usePikkuInfiniteQuery = <Name extends PaginatedKeys>(
94
+ name: Name,
95
+ data: Omit<FlattenedRPCMap[Name]['input'], 'nextCursor'>,
96
+ options?: InfiniteOpts<Name>
97
+ ) => {
98
+ const rpc = usePikkuRPC<{ invoke: RPCInvoke }>()
99
+ return useInfiniteQuery({
100
+ queryKey: [name, data] as const,
101
+ queryFn: ({ pageParam }: { pageParam: string | undefined }) => rpc.invoke(name, { ...data, nextCursor: pageParam } as FlattenedRPCMap[Name]['input']),
102
+ initialPageParam: undefined as string | undefined,
103
+ getNextPageParam: (lastPage: FlattenedRPCMap[Name]['output']) => (lastPage as { nextCursor?: string }).nextCursor ?? undefined,
104
+ ...options,
105
+ })
106
+ }
107
+ ${workflowHooks}`;
108
+ };
@@ -59,9 +59,9 @@ export class PikkuRPC {
59
59
  * @param data - The data to pass to the server function
60
60
  * @returns A promise that resolves with the function's return value
61
61
  */
62
- invoke: RPCInvoke = async (rpcName, data) => {
63
- return await this.pikkuFetch.post(\`${globalHTTPPrefix}/rpc/\${String(rpcName)}\` as never, { rpcName: String(rpcName), data }) as any
64
- }
62
+ invoke = ((rpcName: string, data?: unknown) => {
63
+ return this.pikkuFetch.post(\`${globalHTTPPrefix}/rpc/\${String(rpcName)}\` as never, { rpcName: String(rpcName), data }) as any
64
+ }) as RPCInvoke
65
65
 
66
66
  /**
67
67
  * Starts a workflow by name with the given input.
@@ -41,13 +41,15 @@ ${addonImports}
41
41
  ${mergedRPCMap}
42
42
 
43
43
  export type RPCInvoke = <Name extends keyof FlattenedRPCMap>(
44
- name: Name,
45
- data: FlattenedRPCMap[Name]['input']
44
+ ...args: FlattenedRPCMap[Name]['input'] extends void | null
45
+ ? [name: Name]
46
+ : [name: Name, data: FlattenedRPCMap[Name]['input']]
46
47
  ) => Promise<FlattenedRPCMap[Name]['output']>
47
48
 
48
49
  export type RPCRemote = <Name extends keyof FlattenedRPCMap>(
49
- name: Name,
50
- data: FlattenedRPCMap[Name]['input']
50
+ ...args: FlattenedRPCMap[Name]['input'] extends void | null
51
+ ? [name: Name]
52
+ : [name: Name, data: FlattenedRPCMap[Name]['input']]
51
53
  ) => Promise<FlattenedRPCMap[Name]['output']>
52
54
 
53
55
  ${workflowMapPath ? `import type { FlattenedWorkflowMap } from '${workflowMapPath}'` : `type FlattenedWorkflowMap = {}`}
@@ -15,13 +15,17 @@ function assertWorkflowService(workflowService: unknown): asserts workflowServic
15
15
  if (!workflowService) throw new MissingServiceError('workflowService is required')
16
16
  }
17
17
 
18
+ function assertWorkflowRunService(workflowRunService: unknown): asserts workflowRunService {
19
+ if (!workflowRunService) throw new MissingServiceError('workflowRunService is required')
20
+ }
21
+
18
22
  export const workflowStarter = pikkuSessionlessFunc<
19
23
  { workflowName: string; data?: unknown },
20
24
  { runId: string }
21
25
  >({
22
26
  auth: ${authFlag},
23
27
  func: async (_services, { workflowName, data }, { rpc }) => {
24
- return await rpc.startWorkflow(workflowName as any, data ?? {})
28
+ return await rpc.startWorkflow(workflowName as any, (data ?? {}) as any)
25
29
  },
26
30
  })
27
31
 
@@ -49,48 +53,137 @@ export const workflowStatusChecker = pikkuSessionlessFunc<
49
53
  },
50
54
  })
51
55
 
56
+ /**
57
+ * Minimal workflow status stream — sends step names and statuses only.
58
+ * Use this for user-facing frontends where internal details should not be exposed.
59
+ */
52
60
  export const workflowStatusStream = pikkuSessionlessFunc<
53
61
  { workflowName: string; runId: string },
54
- WorkflowRunStatus
62
+ unknown
55
63
  >({
56
64
  auth: ${authFlag},
57
- func: async ({ workflowService }, { runId }, { channel }) => {
58
- assertWorkflowService(workflowService)
65
+ func: async ({ workflowRunService }, { runId }, { channel }) => {
66
+ assertWorkflowRunService(workflowRunService)
67
+ if (!channel) return
68
+
59
69
  const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
60
- const pollIntervalMs = 1000
61
- const maxWaitMs = 300_000
70
+ let lastHash = ''
62
71
 
63
- const startTime = Date.now()
64
- let lastStatusJson = ''
72
+ const poll = async () => {
73
+ const run = await workflowRunService.getRun(runId)
74
+ if (!run) {
75
+ channel.close()
76
+ return false
77
+ }
65
78
 
66
- while (Date.now() - startTime < maxWaitMs) {
67
- const status = await workflowService.getRunStatus(runId)
68
- if (!status) throw new Error(\`Run not found: \${runId}\`)
79
+ const steps = await workflowRunService.getRunSteps(runId)
69
80
 
70
- const statusJson = JSON.stringify(status)
71
- if (statusJson !== lastStatusJson) {
72
- lastStatusJson = statusJson
73
- if (channel) {
74
- channel.send(status)
75
- }
81
+ const hash = JSON.stringify({
82
+ s: run.status,
83
+ steps: steps.map((s: { stepName: string; status: string }) => [s.stepName, s.status]),
84
+ })
85
+
86
+ if (hash !== lastHash) {
87
+ lastHash = hash
88
+ channel.send({
89
+ type: 'update',
90
+ status: run.status,
91
+ steps: steps.map((s: { stepName: string; status: string }) => ({
92
+ stepName: s.stepName,
93
+ status: s.status,
94
+ })),
95
+ })
96
+ }
97
+
98
+ if (terminalStatuses.has(run.status)) {
99
+ channel.send({ type: 'done' })
100
+ channel.close()
101
+ return false
76
102
  }
103
+ return true
104
+ }
105
+
106
+ const shouldContinue = await poll()
107
+ if (!shouldContinue) return
77
108
 
78
- if (terminalStatuses.has(status.status)) {
79
- if (channel) {
80
- channel.close()
109
+ await new Promise<void>((resolve) => {
110
+ const interval = setInterval(async () => {
111
+ const cont = await poll()
112
+ if (!cont) {
113
+ clearInterval(interval)
114
+ resolve()
81
115
  }
82
- return status
116
+ }, 500)
117
+ })
118
+ },
119
+ })
120
+
121
+ /**
122
+ * Full workflow status stream — includes output, error, and child run IDs.
123
+ * Use this for admin consoles and internal tooling.
124
+ */
125
+ export const workflowStatusStreamFull = pikkuSessionlessFunc<
126
+ { workflowName: string; runId: string },
127
+ unknown
128
+ >({
129
+ auth: ${authFlag},
130
+ func: async ({ workflowRunService }, { runId }, { channel }) => {
131
+ assertWorkflowRunService(workflowRunService)
132
+ if (!channel) return
133
+
134
+ const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
135
+ let lastHash = ''
136
+
137
+ const poll = async () => {
138
+ const run = await workflowRunService.getRun(runId)
139
+ if (!run) {
140
+ channel.close()
141
+ return false
83
142
  }
84
143
 
85
- await new Promise((r) => setTimeout(r, pollIntervalMs))
86
- }
144
+ const steps = await workflowRunService.getRunSteps(runId)
145
+
146
+ const hash = JSON.stringify({
147
+ s: run.status,
148
+ o: run.output,
149
+ steps: steps.map((s: { stepName: string; status: string }) => [s.stepName, s.status]),
150
+ })
87
151
 
88
- // Timeout return last known status
89
- const finalStatus = await workflowService.getRunStatus(runId)
90
- if (channel) {
91
- channel.close()
152
+ if (hash !== lastHash) {
153
+ lastHash = hash
154
+ channel.send({
155
+ type: 'update',
156
+ status: run.status,
157
+ output: run.output,
158
+ error: run.error,
159
+ steps: steps.map((s: { stepName: string; status: string; childRunId?: string }) => ({
160
+ stepName: s.stepName,
161
+ status: s.status,
162
+ ...(s.childRunId ? { childRunId: s.childRunId } : {}),
163
+ })),
164
+ })
165
+ }
166
+
167
+ if (terminalStatuses.has(run.status)) {
168
+ channel.send({ type: 'done' })
169
+ channel.close()
170
+ return false
171
+ }
172
+ return true
92
173
  }
93
- return finalStatus as WorkflowRunStatus
174
+
175
+ const shouldContinue = await poll()
176
+ if (!shouldContinue) return
177
+
178
+ await new Promise<void>((resolve) => {
179
+ const interval = setInterval(async () => {
180
+ const cont = await poll()
181
+ if (!cont) {
182
+ clearInterval(interval)
183
+ resolve()
184
+ }
185
+ }, 500)
186
+ })
94
187
  },
95
188
  })
96
189
 
@@ -100,7 +193,7 @@ export const graphStarter = pikkuSessionlessFunc<
100
193
  >({
101
194
  auth: ${authFlag},
102
195
  func: async (_services, { workflowName, nodeId, data }, { rpc }) => {
103
- return await rpc.startWorkflow(workflowName as any, data ?? {}, { startNode: nodeId })
196
+ return await rpc.startWorkflow(workflowName as any, (data ?? {}) as any, { startNode: nodeId })
104
197
  },
105
198
  })
106
199
 
@@ -128,6 +221,12 @@ wireHTTPRoutes({
128
221
  sse: true,
129
222
  func: workflowStatusStream,
130
223
  },
224
+ workflowStatusStreamFull: {
225
+ route: '/workflow/:workflowName/status/:runId/stream/full',
226
+ method: 'get',
227
+ sse: true,
228
+ func: workflowStatusStreamFull,
229
+ },
131
230
  graphStart: {
132
231
  route: '/workflow/:workflowName/graph/:nodeId',
133
232
  method: 'post',
@@ -0,0 +1 @@
1
+ export declare const allWorkflow: import("../../../.pikku/pikku-types.gen.js").PikkuFunctionConfig<void, void, "workflow">;