@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.
- package/cli.schema.json +1 -1
- package/console-app/assets/{index-DvrDbftC.css → index-BpY2pSuA.css} +10 -1
- package/console-app/assets/index-DXLy-_D4.js +717 -0
- package/console-app/index.html +2 -2
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +11 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +29 -2
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +274 -123
- package/dist/.pikku/function/pikku-functions.gen.js +5 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings-meta.gen.json +62 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +2 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.js +2 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +2 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +2 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +7 -6
- package/dist/.pikku/pikku-services.gen.js +5 -4
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +12 -4
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +27 -7
- package/dist/.pikku/schemas/schemas/DevInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/GraphStarterInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/GraphStarterOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/WorkflowRunStatus.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/WorkflowRunnerInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/WorkflowStarterInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/WorkflowStarterOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/WorkflowStatusCheckerInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/WorkflowStatusStreamFullInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/WorkflowStatusStreamInput.schema.json +1 -0
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +911 -0
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +5 -2
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.d.ts +2 -4
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +5 -2
- package/dist/src/cli.wiring.js +25 -0
- package/dist/src/deploy/analyzer/analyzer.d.ts +1 -0
- package/dist/src/deploy/analyzer/analyzer.js +18 -15
- package/dist/src/deploy/codegen/per-unit-codegen.js +3 -3
- package/dist/src/functions/commands/all.js +2 -186
- package/dist/src/functions/commands/bootstrap.js +10 -10
- package/dist/src/functions/commands/console.js +1 -1
- package/dist/src/functions/commands/dev.d.ts +13 -0
- package/dist/src/functions/commands/dev.js +187 -0
- package/dist/src/functions/commands/versions-update.js +7 -3
- package/dist/src/functions/commands/watch.js +1 -1
- package/dist/src/functions/wirings/rpc/pikku-command-react-query.d.ts +1 -0
- package/dist/src/functions/wirings/rpc/pikku-command-react-query.js +33 -0
- package/dist/src/functions/wirings/rpc/serialize-react-query-hooks.d.ts +1 -0
- package/dist/src/functions/wirings/rpc/serialize-react-query-hooks.js +108 -0
- package/dist/src/functions/wirings/rpc/serialize-rpc-wrapper.js +3 -3
- package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.js +6 -4
- package/dist/src/functions/wirings/workflow/serialize-workflow-routes.js +128 -29
- package/dist/src/functions/workflows/all.workflow.d.ts +1 -0
- package/dist/src/functions/workflows/all.workflow.js +207 -0
- package/dist/src/scaffold/rpc-remote.gen.js +1 -1
- package/dist/src/scaffold/workflow-routes.gen.d.ts +84 -0
- package/dist/src/scaffold/workflow-routes.gen.js +197 -0
- package/dist/src/services.js +2 -0
- package/dist/src/utils/pikku-cli-config.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -4
- package/console-app/assets/index-CzMWJFqj.js +0 -700
- package/dist/.pikku/agent/pikku-agent-wirings-meta.gen.d.ts +0 -1
- package/dist/.pikku/agent/pikku-agent-wirings-meta.gen.js +0 -6
- package/dist/.pikku/agent/pikku-agent-wirings-meta.gen.json +0 -3
- package/dist/.pikku/agent/pikku-agent-wirings.gen.d.ts +0 -4
- package/dist/.pikku/agent/pikku-agent-wirings.gen.js +0 -4
- package/dist/.pikku/channel/pikku-channels-meta.gen.d.ts +0 -1
- package/dist/.pikku/channel/pikku-channels-meta.gen.js +0 -6
- package/dist/.pikku/channel/pikku-channels-meta.gen.json +0 -1
- package/dist/.pikku/channel/pikku-channels.gen.d.ts +0 -4
- package/dist/.pikku/channel/pikku-channels.gen.js +0 -5
- package/dist/.pikku/mcp/pikku-mcp-wirings-meta.gen.d.ts +0 -1
- package/dist/.pikku/mcp/pikku-mcp-wirings-meta.gen.js +0 -8
- package/dist/.pikku/mcp/pikku-mcp-wirings-meta.gen.json +0 -5
- package/dist/.pikku/mcp/pikku-mcp-wirings.gen.d.ts +0 -4
- package/dist/.pikku/mcp/pikku-mcp-wirings.gen.js +0 -5
- package/dist/.pikku/pikku-websocket.gen.d.ts +0 -45
- package/dist/.pikku/pikku-websocket.gen.js +0 -63
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.d.ts +0 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +0 -6
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.json +0 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +0 -4
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +0 -5
- package/dist/.pikku/rpc/pikku-remote-rpc-workers.gen.d.ts +0 -17
- package/dist/.pikku/rpc/pikku-remote-rpc-workers.gen.js +0 -25
- package/dist/.pikku/scheduler/pikku-schedulers-wirings-meta.gen.d.ts +0 -1
- package/dist/.pikku/scheduler/pikku-schedulers-wirings-meta.gen.js +0 -6
- package/dist/.pikku/scheduler/pikku-schedulers-wirings-meta.gen.json +0 -1
- package/dist/.pikku/scheduler/pikku-schedulers-wirings.gen.d.ts +0 -4
- 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
|
-
|
|
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
|
|
15
|
-
|
|
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'
|
|
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
|
|
63
|
-
return
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
62
|
+
unknown
|
|
55
63
|
>({
|
|
56
64
|
auth: ${authFlag},
|
|
57
|
-
func: async ({
|
|
58
|
-
|
|
65
|
+
func: async ({ workflowRunService }, { runId }, { channel }) => {
|
|
66
|
+
assertWorkflowRunService(workflowRunService)
|
|
67
|
+
if (!channel) return
|
|
68
|
+
|
|
59
69
|
const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
|
|
60
|
-
|
|
61
|
-
const maxWaitMs = 300_000
|
|
70
|
+
let lastHash = ''
|
|
62
71
|
|
|
63
|
-
const
|
|
64
|
-
|
|
72
|
+
const poll = async () => {
|
|
73
|
+
const run = await workflowRunService.getRun(runId)
|
|
74
|
+
if (!run) {
|
|
75
|
+
channel.close()
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
65
78
|
|
|
66
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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">;
|