@pikku/core 0.12.18 → 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/CHANGELOG.md +76 -14
- package/dist/function/function-runner.js +53 -3
- package/dist/function/functions.types.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/services/content-service.d.ts +66 -37
- package/dist/services/in-memory-workflow-service.d.ts +5 -2
- package/dist/services/in-memory-workflow-service.js +3 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/local-content.d.ts +10 -12
- package/dist/services/local-content.js +46 -37
- package/dist/services/workflow-service.d.ts +5 -2
- package/dist/wirings/cli/channel/cli-channel-runner.js +4 -2
- package/dist/wirings/cli/cli-runner.js +4 -2
- package/dist/wirings/rpc/rpc-runner.d.ts +3 -2
- package/dist/wirings/rpc/rpc-runner.js +32 -8
- package/dist/wirings/workflow/graph/graph-runner.js +4 -1
- package/dist/wirings/workflow/index.d.ts +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +5 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +6 -1
- package/dist/wirings/workflow/workflow.types.d.ts +14 -0
- package/package.json +1 -1
- package/src/function/function-runner.ts +68 -3
- package/src/function/functions.types.ts +5 -14
- package/src/index.ts +10 -1
- package/src/services/content-service.ts +79 -51
- package/src/services/in-memory-workflow-service.test.ts +21 -0
- package/src/services/in-memory-workflow-service.ts +8 -1
- package/src/services/index.ts +10 -1
- package/src/services/local-content.ts +68 -52
- package/src/services/workflow-service.ts +6 -1
- package/src/wirings/cli/channel/cli-channel-runner.ts +4 -2
- package/src/wirings/cli/cli-runner.ts +4 -2
- package/src/wirings/rpc/rpc-runner.ts +45 -18
- package/src/wirings/workflow/graph/graph-runner.ts +5 -1
- package/src/wirings/workflow/index.ts +1 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +13 -2
- package/src/wirings/workflow/workflow.types.ts +15 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
+
## 0.12.20
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 18acebe: feat(core): scope bare `rpc.invoke()` calls to the caller's addon package
|
|
6
|
+
|
|
7
|
+
Addon functions calling `rpc.invoke('foo')` (bare, no colon) previously only
|
|
8
|
+
resolved against root RPC meta and threw `RPCNotFoundError` for the addon's
|
|
9
|
+
own functions, forcing authors to prefix every call with their consumer-facing
|
|
10
|
+
namespace (`'cli:foo'`) — which couples the addon to its caller's `wireAddon({ name })`.
|
|
11
|
+
|
|
12
|
+
`ContextAwareRPCService` now carries an optional `packageName` passed through
|
|
13
|
+
from `runPikkuFunc` via `getContextRPCService`. For bare names from inside an
|
|
14
|
+
addon, resolution first checks the caller's package function meta, then falls
|
|
15
|
+
back to root. Applies to both `rpc.invoke()` and `rpc.rpcWithWire()`. Explicit
|
|
16
|
+
namespaced calls (`'stripe:createCharge'`) and root-namespace calls are unchanged.
|
|
17
|
+
|
|
18
|
+
- 66d1b4f: feat(content)!: bucket-aware ContentService with typed object args
|
|
19
|
+
|
|
20
|
+
BREAKING CHANGE: All `ContentService` methods now take object args with a
|
|
21
|
+
required `bucket` field. The interface is generic over `TBucket extends string`
|
|
22
|
+
so callers can constrain bucket names to a typed union.
|
|
23
|
+
|
|
24
|
+
Migration:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// Before
|
|
28
|
+
content.getUploadURL(fileKey, contentType)
|
|
29
|
+
content.signContentKey(key, expiresAt)
|
|
30
|
+
content.writeFile(assetKey, stream)
|
|
31
|
+
content.readFile(assetKey)
|
|
32
|
+
content.deleteFile(assetKey)
|
|
33
|
+
|
|
34
|
+
// After
|
|
35
|
+
content.getUploadURL({ bucket, fileKey, contentType })
|
|
36
|
+
content.signContentKey({ bucket, contentKey, dateLessThan: expiresAt })
|
|
37
|
+
content.writeFile({ bucket, key, stream })
|
|
38
|
+
content.readFile({ bucket, key })
|
|
39
|
+
content.deleteFile({ bucket, key })
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- New exported types: `SignContentKeyArgs`, `SignURLArgs`, `GetUploadURLArgs`,
|
|
43
|
+
`UploadURLResult`, `BucketKeyArgs`, `WriteFileArgs`, `CopyFileArgs`.
|
|
44
|
+
- `LocalContent` stores objects under `<base>/<bucket>/<key>`.
|
|
45
|
+
- `S3Content` and `B2Content` treat the logical bucket as a key prefix within
|
|
46
|
+
the configured underlying storage bucket.
|
|
47
|
+
- `workflow-screenshot` addon takes `bucket?` / `key?` input; default bucket
|
|
48
|
+
resolved from `PIKKU_WORKFLOW_SCREENSHOT_BUCKET` variable, no hardcoded
|
|
49
|
+
fallback.
|
|
50
|
+
|
|
51
|
+
- 3e35b99: feat(core): scope bare workflow names to the caller's addon package
|
|
52
|
+
|
|
53
|
+
Parallel to the RPC scoping fix for addon functions. Addon code calling
|
|
54
|
+
`services.workflowService.runToCompletion('myWorkflow', ...)` (bare name,
|
|
55
|
+
no colon) previously missed workflows registered under the addon's package
|
|
56
|
+
scope and threw `WorkflowNotFoundError`, forcing authors to hard-code
|
|
57
|
+
the consumer-facing namespace (`'cli:myWorkflow'`) — which couples the
|
|
58
|
+
addon to its caller's `wireAddon({ name })`.
|
|
59
|
+
|
|
60
|
+
`getOrCreatePackageSingletonServices` in the function-runner now wraps
|
|
61
|
+
the package's `workflowService` with a Proxy that auto-prefixes bare
|
|
62
|
+
workflow names on `startWorkflow` / `runToCompletion` with the addon's
|
|
63
|
+
consumer-defined namespace (looked up from `pikkuState(null, 'addons',
|
|
64
|
+
'packages')`). Explicit `'ns:name'` calls and root-namespace workflows
|
|
65
|
+
are unchanged.
|
|
66
|
+
|
|
67
|
+
## 0.12.19
|
|
68
|
+
|
|
69
|
+
### Patch Changes
|
|
70
|
+
|
|
71
|
+
- b9ed73e: Add deterministic workflow planned-step metadata support and SSE init stream payload generation.
|
|
72
|
+
- Persist `deterministic` and `plannedSteps` on workflow runs in core and service adapters.
|
|
73
|
+
- Expose planned-step metadata on workflow run status responses.
|
|
74
|
+
- Emit an initial `type: 'init'` SSE event for deterministic workflow streams before incremental updates.
|
|
75
|
+
- Add CLI tests covering serialized stream route output for init/update/done event behavior.
|
|
76
|
+
|
|
1
77
|
## 0.12.4
|
|
2
78
|
|
|
3
79
|
## 0.12.18
|
|
@@ -5,7 +81,6 @@
|
|
|
5
81
|
### Patch Changes
|
|
6
82
|
|
|
7
83
|
- 311c0c4: Unify session persistence through SessionStore, remove session blob from ChannelStore
|
|
8
|
-
|
|
9
84
|
- PikkuSessionService now persists sessions via SessionStore on set()/clear() instead of every function call
|
|
10
85
|
- ChannelStore no longer stores session data — maps channelId to pikkuUserId only
|
|
11
86
|
- ChannelStore API: setUserSession/getChannelAndSession replaced with setPikkuUserId/getChannel
|
|
@@ -50,14 +125,12 @@
|
|
|
50
125
|
### Patch Changes
|
|
51
126
|
|
|
52
127
|
- 9e8605f: Add Workers for Platforms dispatch namespace support and AI agent fixes.
|
|
53
|
-
|
|
54
128
|
- deploy-cloudflare: Thread dispatchNamespace through deploy pipeline, reads CF_DISPATCH_NAMESPACE env var
|
|
55
129
|
- core: Fix auth-gated tools visible to unauthenticated sessions (null session now hides permission-gated items)
|
|
56
130
|
- core: Recursive null stripping in AI agent tool call resume path
|
|
57
131
|
- ai-vercel: Handle anyOf/oneOf/array types when making optional fields nullable for strict providers
|
|
58
132
|
|
|
59
133
|
- 624097e: Add deploy pipeline with provider-agnostic architecture
|
|
60
|
-
|
|
61
134
|
- Add MetaService with explicit typed API, absorb WiringService reads
|
|
62
135
|
- Add deployment service, traceId propagation, scoped logger
|
|
63
136
|
- Rewrite analyzer: one function = one worker, gateways dispatch via RPC
|
|
@@ -80,7 +153,6 @@
|
|
|
80
153
|
### Patch Changes
|
|
81
154
|
|
|
82
155
|
- f85c234: Add unified credential system with per-user OAuth and AI agent pre-flight checks
|
|
83
|
-
|
|
84
156
|
- Unified CredentialService with lazy loading per user via pikkuUserId
|
|
85
157
|
- wire.getCredential() for typed single credential lookup
|
|
86
158
|
- MissingCredentialError with structured payload for client-side connect flows
|
|
@@ -187,7 +259,6 @@
|
|
|
187
259
|
- 7d369f3: Fix agent sub-agent tool execution failures: use UUID for sub-agent thread IDs (was exceeding varchar(36) DB column), and synthesize error results for failed tool calls in non-streaming run() to prevent "Tool result is missing" cascading errors.
|
|
188
260
|
- 508a796: Fix MCP server not exposing addon tools: resolve namespaced function IDs in MCP runner, load addon schemas after schema generation, and use resolveFunctionMeta for MCP JSON serialization
|
|
189
261
|
- ffe83af: Add Web Response passthrough support and fix close() flushing
|
|
190
|
-
|
|
191
262
|
- HTTP runner detects when a function returns a Web `Response` object and applies it directly via `applyWebResponse()`, enabling seamless integration with libraries like Auth.js
|
|
192
263
|
- Add `send()` method to `PikkuHTTPResponse` for setting body without Content-Type headers
|
|
193
264
|
- Add `headers()` method to `PikkuHTTPRequest` for retrieving all headers as a record
|
|
@@ -201,7 +272,6 @@
|
|
|
201
272
|
### Patch Changes
|
|
202
273
|
|
|
203
274
|
- cc4c9e9: Add gateway meta-wiring for messaging platforms:
|
|
204
|
-
|
|
205
275
|
- New `wireGateway()` API with three transport types: webhook, websocket, listener
|
|
206
276
|
- `GatewayAdapter` interface for platform-specific parse/send logic
|
|
207
277
|
- `PikkuGateway` wire object (`wire.gateway`) with senderId, platform, and send()
|
|
@@ -218,7 +288,6 @@
|
|
|
218
288
|
|
|
219
289
|
- 62a8725: Rename 'external' to 'addon' throughout the codebase. All types, functions, config keys, and CLI options previously named `external` or `External` are now named `addon` or `Addon` (e.g. `ExternalPackageConfig` → `AddonConfig`, `externalPackages` → `addons`, `function-external` → `function-addon`).
|
|
220
290
|
- a3bdb0d: Add AI middleware hooks for per-tool-call lifecycle and post-step observability:
|
|
221
|
-
|
|
222
291
|
- `beforeToolCall` / `afterToolCall`: per-tool-call hooks for logging, caching, input sanitization, and result transformation
|
|
223
292
|
- `afterStep`: post-step observation hook with full step context (text, toolCalls, toolResults, usage, finishReason)
|
|
224
293
|
- `onError`: error-specific hook for alerting and diagnostics (non-throwing, won't affect error flow)
|
|
@@ -317,18 +386,15 @@
|
|
|
317
386
|
- ea652dc: Refactor channel middleware handling and add lifecycle middleware support
|
|
318
387
|
|
|
319
388
|
**Breaking Changes:**
|
|
320
|
-
|
|
321
389
|
- Improved middleware resolution for channel message handlers to properly combine channel-level and message-level middleware
|
|
322
390
|
- Fixed cache key collisions when multiple message handlers use the same function
|
|
323
391
|
|
|
324
392
|
**New Features:**
|
|
325
|
-
|
|
326
393
|
- Add `runChannelLifecycleWithMiddleware` helper in `channel-common.ts` for consistent lifecycle function execution
|
|
327
394
|
- Support middleware on `onConnect` and `onDisconnect` lifecycle functions
|
|
328
395
|
- Channel-level middleware now properly applies to all messages in the channel
|
|
329
396
|
|
|
330
397
|
**Bug Fixes:**
|
|
331
|
-
|
|
332
398
|
- Fix middleware ordering: channel middleware → message middleware → inherited middleware
|
|
333
399
|
- Fix cache key generation to include routing information (prevents cache collisions)
|
|
334
400
|
- Properly detect wrapper objects vs direct function configs for message handlers
|
|
@@ -336,13 +402,11 @@
|
|
|
336
402
|
- 4349ec5: Add file-based storage implementations for serverless environments
|
|
337
403
|
|
|
338
404
|
**New Services:**
|
|
339
|
-
|
|
340
405
|
- Add `FileChannelStore` for file-based channel storage (suitable for AWS Lambda /tmp)
|
|
341
406
|
- Add `FileEventHubStore` for file-based event hub subscriptions
|
|
342
407
|
- Export new services in package.json for use in serverless runtimes
|
|
343
408
|
|
|
344
409
|
**Bug Fixes:**
|
|
345
|
-
|
|
346
410
|
- Fix serverless channel runner to handle disconnect gracefully when channel is already cleaned up
|
|
347
411
|
- Fix MCP runner to pass `mcp` service to functions and use correct function type
|
|
348
412
|
|
|
@@ -418,7 +482,6 @@ For complete details, see https://pikku.dev/changelogs/0_10_0.md
|
|
|
418
482
|
### Ordered Execution System
|
|
419
483
|
|
|
420
484
|
Both middleware and permissions now execute in a specific hierarchical order:
|
|
421
|
-
|
|
422
485
|
1. **Wiring Tags** - Tag-based middleware/permissions from wiring level (e.g., HTTP route tags)
|
|
423
486
|
2. **Wiring Middleware/Permissions** - Direct wiring-level middleware/permissions
|
|
424
487
|
3. **Function Middleware** - Function-level middleware
|
|
@@ -483,7 +546,6 @@ For complete details, see https://pikku.dev/changelogs/0_10_0.md
|
|
|
483
546
|
- 7c592b8: feat: support for required services and improved service configuration
|
|
484
547
|
|
|
485
548
|
This release includes several enhancements to service management and configuration:
|
|
486
|
-
|
|
487
549
|
- Added support for required services configuration
|
|
488
550
|
- Improved service discovery and registration
|
|
489
551
|
- Added typed RPC clients for service communication
|
|
@@ -37,6 +37,51 @@ async function resolveSession(wire, singletonServices, sessionService) {
|
|
|
37
37
|
* @param parentServices - The parent/caller's singleton services (used as base)
|
|
38
38
|
* @returns The package's singleton services
|
|
39
39
|
*/
|
|
40
|
+
/**
|
|
41
|
+
* Find the consumer-defined namespace (from wireAddon) for a given addon package.
|
|
42
|
+
* Returns null if the package isn't registered as an addon.
|
|
43
|
+
*/
|
|
44
|
+
const findAddonNamespaceForPackage = (packageName) => {
|
|
45
|
+
const addons = pikkuState(null, 'addons', 'packages');
|
|
46
|
+
if (!addons)
|
|
47
|
+
return null;
|
|
48
|
+
for (const [namespace, cfg] of addons.entries()) {
|
|
49
|
+
if (cfg?.package === packageName)
|
|
50
|
+
return namespace;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Wrap a workflow service so that bare workflow names passed from inside an
|
|
56
|
+
* addon function are auto-prefixed with the addon's consumer-facing namespace.
|
|
57
|
+
* Without this, `runToCompletion('myWorkflow')` from inside an addon misses
|
|
58
|
+
* the workflow registered under the addon's package scope and throws
|
|
59
|
+
* WorkflowNotFoundError — forcing addons to hardcode their consumer-defined
|
|
60
|
+
* namespace, which couples the addon to its caller.
|
|
61
|
+
*
|
|
62
|
+
* Explicit `'ns:name'` and bare names that already exist in root meta are
|
|
63
|
+
* unaffected; only bare names that would otherwise miss resolution get
|
|
64
|
+
* prefixed.
|
|
65
|
+
*/
|
|
66
|
+
const wrapWorkflowServiceForPackage = (service, packageName) => {
|
|
67
|
+
return new Proxy(service, {
|
|
68
|
+
get(target, prop, receiver) {
|
|
69
|
+
if (prop === 'startWorkflow' || prop === 'runToCompletion') {
|
|
70
|
+
const original = Reflect.get(target, prop, receiver);
|
|
71
|
+
return function (name, ...rest) {
|
|
72
|
+
if (typeof name === 'string' && !name.includes(':')) {
|
|
73
|
+
const namespace = findAddonNamespaceForPackage(packageName);
|
|
74
|
+
if (namespace) {
|
|
75
|
+
name = `${namespace}:${name}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return original.call(this, name, ...rest);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return Reflect.get(target, prop, receiver);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
};
|
|
40
85
|
const getOrCreatePackageSingletonServices = async (packageName, parentServices) => {
|
|
41
86
|
// Check if we already have cached singleton services for this package
|
|
42
87
|
const cachedServices = pikkuState(packageName, 'package', 'singletonServices');
|
|
@@ -56,6 +101,12 @@ const getOrCreatePackageSingletonServices = async (packageName, parentServices)
|
|
|
56
101
|
}
|
|
57
102
|
// Create singleton services for the package, passing parent services as existing
|
|
58
103
|
const packageServices = await factories.createSingletonServices(config, parentServices);
|
|
104
|
+
// Wrap workflowService so that bare names used inside the addon's functions
|
|
105
|
+
// resolve to workflows registered under the addon's package scope.
|
|
106
|
+
if (packageServices.workflowService &&
|
|
107
|
+
typeof packageServices.workflowService === 'object') {
|
|
108
|
+
packageServices.workflowService = wrapWorkflowServiceForPackage(packageServices.workflowService, packageName);
|
|
109
|
+
}
|
|
59
110
|
// Cache the services
|
|
60
111
|
pikkuState(packageName, 'package', 'singletonServices', packageServices);
|
|
61
112
|
return packageServices;
|
|
@@ -223,11 +274,10 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
|
|
|
223
274
|
const services = wireServices && Object.keys(wireServices).length > 0
|
|
224
275
|
? { ...resolvedSingletonServices, ...wireServices }
|
|
225
276
|
: resolvedSingletonServices;
|
|
277
|
+
const callerPackageName = packageName;
|
|
226
278
|
Object.defineProperty(resolvedWire, 'rpc', {
|
|
227
279
|
get() {
|
|
228
|
-
const rpc = rpcService.getContextRPCService(services, resolvedWire, {
|
|
229
|
-
sessionService,
|
|
230
|
-
});
|
|
280
|
+
const rpc = rpcService.getContextRPCService(services, resolvedWire, { sessionService }, 0, callerPackageName);
|
|
231
281
|
Object.defineProperty(resolvedWire, 'rpc', {
|
|
232
282
|
value: rpc,
|
|
233
283
|
writable: true,
|
|
@@ -146,6 +146,7 @@ export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any,
|
|
|
146
146
|
remote?: boolean;
|
|
147
147
|
mcp?: boolean;
|
|
148
148
|
readonly?: boolean;
|
|
149
|
+
deploy?: 'serverless' | 'server' | 'auto';
|
|
149
150
|
approvalRequired?: boolean;
|
|
150
151
|
approvalDescription?: any;
|
|
151
152
|
func: PikkuFunction;
|
package/dist/index.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export type { QueueService } from './wirings/queue/queue.types.js';
|
|
|
24
24
|
export type { JWTService } from './services/jwt-service.js';
|
|
25
25
|
export type { SecretService } from './services/secret-service.js';
|
|
26
26
|
export type { VariablesService } from './services/variables-service.js';
|
|
27
|
-
export type { ContentService } from './services/content-service.js';
|
|
27
|
+
export type { ContentService, SignContentKeyArgs, SignURLArgs, GetUploadURLArgs, UploadURLResult, BucketKeyArgs, WriteFileArgs, CopyFileArgs, } from './services/content-service.js';
|
|
28
28
|
export type { DeploymentService } from './services/deployment-service.js';
|
|
29
29
|
export type { WorkflowService } from './services/workflow-service.js';
|
|
30
30
|
export type { GatewayService } from './services/gateway-service.js';
|
|
@@ -1,60 +1,89 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Arguments for signing a content key into a time-limited URL.
|
|
3
|
+
*/
|
|
4
|
+
export interface SignContentKeyArgs<TBucket extends string = string> {
|
|
5
|
+
bucket: TBucket;
|
|
6
|
+
contentKey: string;
|
|
7
|
+
dateLessThan: Date;
|
|
8
|
+
dateGreaterThan?: Date;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Arguments for signing an arbitrary URL.
|
|
12
|
+
*/
|
|
13
|
+
export interface SignURLArgs {
|
|
14
|
+
url: string;
|
|
15
|
+
dateLessThan: Date;
|
|
16
|
+
dateGreaterThan?: Date;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Arguments for minting a presigned upload URL.
|
|
20
|
+
*/
|
|
21
|
+
export interface GetUploadURLArgs<TBucket extends string = string> {
|
|
22
|
+
bucket: TBucket;
|
|
23
|
+
fileKey: string;
|
|
24
|
+
contentType: string;
|
|
25
|
+
size?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Result of minting a presigned upload URL.
|
|
29
|
+
*/
|
|
30
|
+
export interface UploadURLResult {
|
|
31
|
+
uploadUrl: string;
|
|
32
|
+
assetKey: string;
|
|
33
|
+
uploadHeaders?: Record<string, string>;
|
|
34
|
+
uploadMethod?: 'PUT' | 'POST';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Arguments for an operation that targets a single object by key.
|
|
38
|
+
*/
|
|
39
|
+
export interface BucketKeyArgs<TBucket extends string = string> {
|
|
40
|
+
bucket: TBucket;
|
|
41
|
+
key: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Arguments for writing a stream to storage.
|
|
45
|
+
*/
|
|
46
|
+
export interface WriteFileArgs<TBucket extends string = string> extends BucketKeyArgs<TBucket> {
|
|
47
|
+
stream: ReadableStream | NodeJS.ReadableStream;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Arguments for copying a local file into storage.
|
|
51
|
+
*/
|
|
52
|
+
export interface CopyFileArgs<TBucket extends string = string> extends BucketKeyArgs<TBucket> {
|
|
53
|
+
fromAbsolutePath: string;
|
|
54
|
+
}
|
|
55
|
+
export interface ContentService<TBucket extends string = string> {
|
|
2
56
|
/**
|
|
3
57
|
* Signs a content key to generate a secure, time-limited access URL.
|
|
4
|
-
* @param contentKey - The key representing the content object.
|
|
5
|
-
* @param dateLessThan - The expiration time for the signed URL.
|
|
6
|
-
* @param dateGreaterThan - (Optional) Start time before which access is denied.
|
|
7
58
|
*/
|
|
8
|
-
signContentKey(
|
|
59
|
+
signContentKey(args: SignContentKeyArgs<TBucket>): Promise<string>;
|
|
9
60
|
/**
|
|
10
61
|
* Signs an arbitrary URL to generate a secure, time-limited access URL.
|
|
11
|
-
* @param url - The full URL that needs signing.
|
|
12
|
-
* @param dateLessThan - The expiration time for the signed URL.
|
|
13
|
-
* @param dateGreaterThan - (Optional) Start time before which access is denied.
|
|
14
62
|
*/
|
|
15
|
-
signURL(
|
|
63
|
+
signURL(args: SignURLArgs): Promise<string>;
|
|
16
64
|
/**
|
|
17
65
|
* Generates a signed URL for uploading a file directly to storage.
|
|
18
|
-
*
|
|
19
|
-
* @param contentType - The MIME type of the file.
|
|
20
|
-
* @returns A signed upload URL and the finalized asset key.
|
|
66
|
+
* Bucket policy (size limits, MIME allowlist) is enforced by the implementation.
|
|
21
67
|
*/
|
|
22
|
-
getUploadURL(
|
|
23
|
-
uploadUrl: string;
|
|
24
|
-
assetKey: string;
|
|
25
|
-
uploadHeaders?: Record<string, string>;
|
|
26
|
-
uploadMethod?: 'PUT' | 'POST';
|
|
27
|
-
}>;
|
|
68
|
+
getUploadURL(args: GetUploadURLArgs<TBucket>): Promise<UploadURLResult>;
|
|
28
69
|
/**
|
|
29
70
|
* Deletes a file from the storage backend.
|
|
30
|
-
* @param fileName - The name or key of the file to delete.
|
|
31
|
-
* @returns A boolean indicating success.
|
|
32
71
|
*/
|
|
33
|
-
deleteFile(
|
|
72
|
+
deleteFile(args: BucketKeyArgs<TBucket>): Promise<boolean>;
|
|
34
73
|
/**
|
|
35
|
-
* Uploads a file stream to storage under
|
|
36
|
-
* @param assetKey - The key where the file will be saved.
|
|
37
|
-
* @param stream - A readable stream of the file contents.
|
|
38
|
-
* @returns A boolean indicating success.
|
|
74
|
+
* Uploads a file stream to storage under the specified bucket + key.
|
|
39
75
|
*/
|
|
40
|
-
writeFile(
|
|
76
|
+
writeFile(args: WriteFileArgs<TBucket>): Promise<boolean>;
|
|
41
77
|
/**
|
|
42
|
-
* Copies a file from a local absolute path into storage
|
|
43
|
-
* @param assetKey - The destination key.
|
|
44
|
-
* @param fromAbsolutePath - The local absolute file path.
|
|
45
|
-
* @returns A boolean indicating success.
|
|
78
|
+
* Copies a file from a local absolute path into storage.
|
|
46
79
|
*/
|
|
47
|
-
copyFile(
|
|
80
|
+
copyFile(args: CopyFileArgs<TBucket>): Promise<boolean>;
|
|
48
81
|
/**
|
|
49
82
|
* Reads a file from storage as a readable stream.
|
|
50
|
-
* @param assetKey - The key of the file to read.
|
|
51
|
-
* @returns A readable file stream.
|
|
52
83
|
*/
|
|
53
|
-
readFile(
|
|
84
|
+
readFile(args: BucketKeyArgs<TBucket>): Promise<ReadableStream | NodeJS.ReadableStream>;
|
|
54
85
|
/**
|
|
55
86
|
* Reads an entire file from storage into a Buffer.
|
|
56
|
-
* @param assetKey - The key of the file to read.
|
|
57
|
-
* @returns The file contents as a Buffer.
|
|
58
87
|
*/
|
|
59
|
-
readFileAsBuffer(
|
|
88
|
+
readFileAsBuffer(args: BucketKeyArgs<TBucket>): Promise<Buffer>;
|
|
60
89
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PikkuWorkflowService } from '../wirings/workflow/pikku-workflow-service.js';
|
|
2
2
|
import type { SerializedError } from '../types/core.types.js';
|
|
3
|
-
import type { WorkflowRun, WorkflowRunService, WorkflowRunWire, StepState, WorkflowStatus, WorkflowVersionStatus, WorkflowStepOptions } from '../wirings/workflow/workflow.types.js';
|
|
3
|
+
import type { WorkflowPlannedStep, WorkflowRun, WorkflowRunService, WorkflowRunWire, StepState, WorkflowStatus, WorkflowVersionStatus, WorkflowStepOptions } from '../wirings/workflow/workflow.types.js';
|
|
4
4
|
/**
|
|
5
5
|
* In-memory implementation of WorkflowService for inline-only execution
|
|
6
6
|
*
|
|
@@ -24,7 +24,10 @@ export declare class InMemoryWorkflowService extends PikkuWorkflowService implem
|
|
|
24
24
|
private runState;
|
|
25
25
|
private branchKeys;
|
|
26
26
|
private workflowVersions;
|
|
27
|
-
createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire
|
|
27
|
+
createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire, options?: {
|
|
28
|
+
deterministic?: boolean;
|
|
29
|
+
plannedSteps?: WorkflowPlannedStep[];
|
|
30
|
+
}): Promise<string>;
|
|
28
31
|
getRun(id: string): Promise<WorkflowRun | null>;
|
|
29
32
|
getRunHistory(runId: string): Promise<Array<StepState & {
|
|
30
33
|
stepName: string;
|
|
@@ -23,7 +23,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
23
23
|
runState = new Map(); // keyed by runId
|
|
24
24
|
branchKeys = new Map(); // keyed by stepId
|
|
25
25
|
workflowVersions = new Map(); // keyed by `${name}:${graphHash}`
|
|
26
|
-
async createRun(workflowName, input, inline, graphHash, wire) {
|
|
26
|
+
async createRun(workflowName, input, inline, graphHash, wire, options) {
|
|
27
27
|
const runId = randomUUID();
|
|
28
28
|
const now = new Date();
|
|
29
29
|
const run = {
|
|
@@ -33,6 +33,8 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
33
33
|
input,
|
|
34
34
|
inline,
|
|
35
35
|
graphHash,
|
|
36
|
+
deterministic: options?.deterministic,
|
|
37
|
+
plannedSteps: options?.plannedSteps,
|
|
36
38
|
wire,
|
|
37
39
|
createdAt: now,
|
|
38
40
|
updatedAt: now,
|
package/dist/services/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export { InMemoryQueueService } from './in-memory-queue-service.js';
|
|
|
16
16
|
export { InMemoryTriggerService } from './in-memory-trigger-service.js';
|
|
17
17
|
export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js';
|
|
18
18
|
export { LocalGatewayService } from './local-gateway-service.js';
|
|
19
|
-
export type { ContentService } from './content-service.js';
|
|
19
|
+
export type { ContentService, SignContentKeyArgs, SignURLArgs, GetUploadURLArgs, UploadURLResult, BucketKeyArgs, WriteFileArgs, CopyFileArgs, } from './content-service.js';
|
|
20
20
|
export type { JWTService } from './jwt-service.js';
|
|
21
21
|
export type { Logger } from './logger.js';
|
|
22
22
|
export type { SecretService } from './secret-service.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContentService, JWTService, Logger } from '@pikku/core/services';
|
|
1
|
+
import type { BucketKeyArgs, ContentService, CopyFileArgs, GetUploadURLArgs, JWTService, Logger, SignContentKeyArgs, SignURLArgs, UploadURLResult, WriteFileArgs } from '@pikku/core/services';
|
|
2
2
|
export interface LocalContentConfig {
|
|
3
3
|
localFileUploadPath: string;
|
|
4
4
|
uploadUrlPrefix: string;
|
|
@@ -12,18 +12,16 @@ export declare class LocalContent implements ContentService {
|
|
|
12
12
|
private jwt?;
|
|
13
13
|
constructor(config: LocalContentConfig, logger: Logger, jwt?: JWTService | undefined);
|
|
14
14
|
private safePath;
|
|
15
|
+
private joinKey;
|
|
15
16
|
init(): Promise<void>;
|
|
16
17
|
private signParams;
|
|
17
|
-
signURL(
|
|
18
|
-
signContentKey(
|
|
19
|
-
getUploadURL(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
readFile(assetKey: string): Promise<ReadableStream | NodeJS.ReadableStream>;
|
|
26
|
-
readFileAsBuffer(assetKey: string): Promise<Buffer>;
|
|
27
|
-
deleteFile(assetKey: string): Promise<boolean>;
|
|
18
|
+
signURL(args: SignURLArgs): Promise<string>;
|
|
19
|
+
signContentKey(args: SignContentKeyArgs): Promise<string>;
|
|
20
|
+
getUploadURL(args: GetUploadURLArgs): Promise<UploadURLResult>;
|
|
21
|
+
writeFile(args: WriteFileArgs): Promise<boolean>;
|
|
22
|
+
copyFile(args: CopyFileArgs): Promise<boolean>;
|
|
23
|
+
readFile(args: BucketKeyArgs): Promise<ReadableStream | NodeJS.ReadableStream>;
|
|
24
|
+
readFileAsBuffer(args: BucketKeyArgs): Promise<Buffer>;
|
|
25
|
+
deleteFile(args: BucketKeyArgs): Promise<boolean>;
|
|
28
26
|
private createDirectoryForFile;
|
|
29
27
|
}
|
|
@@ -11,14 +11,18 @@ export class LocalContent {
|
|
|
11
11
|
this.logger = logger;
|
|
12
12
|
this.jwt = jwt;
|
|
13
13
|
}
|
|
14
|
-
safePath(
|
|
14
|
+
safePath(bucket, key) {
|
|
15
15
|
const base = resolve(this.config.localFileUploadPath);
|
|
16
|
-
const
|
|
16
|
+
const scoped = resolve(base, normalize(bucket));
|
|
17
|
+
const target = resolve(scoped, normalize(key));
|
|
17
18
|
if (!target.startsWith(base + '/') && target !== base) {
|
|
18
19
|
throw new Error('Invalid asset key');
|
|
19
20
|
}
|
|
20
21
|
return target;
|
|
21
22
|
}
|
|
23
|
+
joinKey(bucket, key) {
|
|
24
|
+
return `${bucket}/${key}`;
|
|
25
|
+
}
|
|
22
26
|
async init() { }
|
|
23
27
|
async signParams(dateLessThan, dateGreaterThan) {
|
|
24
28
|
const signedAt = Date.now();
|
|
@@ -37,81 +41,86 @@ export class LocalContent {
|
|
|
37
41
|
}
|
|
38
42
|
return params.toString();
|
|
39
43
|
}
|
|
40
|
-
async signURL(
|
|
41
|
-
const params = await this.signParams(dateLessThan, dateGreaterThan);
|
|
42
|
-
return `${url}?${params}`;
|
|
44
|
+
async signURL(args) {
|
|
45
|
+
const params = await this.signParams(args.dateLessThan, args.dateGreaterThan);
|
|
46
|
+
return `${args.url}?${params}`;
|
|
43
47
|
}
|
|
44
|
-
async signContentKey(
|
|
48
|
+
async signContentKey(args) {
|
|
49
|
+
const fullKey = this.joinKey(args.bucket, args.contentKey);
|
|
45
50
|
const base = this.config.server
|
|
46
|
-
? `${this.config.server}${this.config.assetUrlPrefix}/${
|
|
47
|
-
: `${this.config.assetUrlPrefix}/${
|
|
48
|
-
return this.signURL(
|
|
51
|
+
? `${this.config.server}${this.config.assetUrlPrefix}/${fullKey}`
|
|
52
|
+
: `${this.config.assetUrlPrefix}/${fullKey}`;
|
|
53
|
+
return this.signURL({
|
|
54
|
+
url: base,
|
|
55
|
+
dateLessThan: args.dateLessThan,
|
|
56
|
+
dateGreaterThan: args.dateGreaterThan,
|
|
57
|
+
});
|
|
49
58
|
}
|
|
50
|
-
async getUploadURL(
|
|
51
|
-
this.
|
|
59
|
+
async getUploadURL(args) {
|
|
60
|
+
const fullKey = this.joinKey(args.bucket, args.fileKey);
|
|
61
|
+
this.logger.debug(`Going to upload with key: ${fullKey}`);
|
|
52
62
|
return {
|
|
53
|
-
uploadUrl: `${this.config.uploadUrlPrefix}/${
|
|
54
|
-
assetKey,
|
|
63
|
+
uploadUrl: `${this.config.uploadUrlPrefix}/${fullKey}`,
|
|
64
|
+
assetKey: fullKey,
|
|
55
65
|
};
|
|
56
66
|
}
|
|
57
|
-
async writeFile(
|
|
58
|
-
this.logger.debug(`Writing file: ${
|
|
59
|
-
const path = this.safePath(
|
|
67
|
+
async writeFile(args) {
|
|
68
|
+
this.logger.debug(`Writing file: ${args.bucket}/${args.key}`);
|
|
69
|
+
const path = this.safePath(args.bucket, args.key);
|
|
60
70
|
try {
|
|
61
71
|
await this.createDirectoryForFile(path);
|
|
62
72
|
const fileStream = createWriteStream(path);
|
|
63
|
-
|
|
64
|
-
await pipeline(stream, fileStream);
|
|
73
|
+
await pipeline(args.stream, fileStream);
|
|
65
74
|
return true;
|
|
66
75
|
}
|
|
67
76
|
catch (e) {
|
|
68
77
|
console.error(e);
|
|
69
|
-
this.logger.error(`Error writing content ${
|
|
78
|
+
this.logger.error(`Error writing content ${args.bucket}/${args.key}`, e);
|
|
70
79
|
return false;
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
|
-
async copyFile(
|
|
74
|
-
this.logger.debug(`Writing file: ${
|
|
82
|
+
async copyFile(args) {
|
|
83
|
+
this.logger.debug(`Writing file: ${args.bucket}/${args.key}`);
|
|
75
84
|
try {
|
|
76
|
-
const path = this.safePath(
|
|
85
|
+
const path = this.safePath(args.bucket, args.key);
|
|
77
86
|
await this.createDirectoryForFile(path);
|
|
78
|
-
await promises.copyFile(fromAbsolutePath, path);
|
|
87
|
+
await promises.copyFile(args.fromAbsolutePath, path);
|
|
88
|
+
return true;
|
|
79
89
|
}
|
|
80
90
|
catch (e) {
|
|
81
91
|
console.error(e);
|
|
82
|
-
this.logger.error(`Error inserting content ${
|
|
92
|
+
this.logger.error(`Error inserting content ${args.bucket}/${args.key}`, e);
|
|
83
93
|
}
|
|
84
94
|
return false;
|
|
85
95
|
}
|
|
86
|
-
async readFile(
|
|
87
|
-
this.logger.debug(`Getting key: ${
|
|
88
|
-
const filePath = this.safePath(
|
|
96
|
+
async readFile(args) {
|
|
97
|
+
this.logger.debug(`Getting key: ${args.bucket}/${args.key}`);
|
|
98
|
+
const filePath = this.safePath(args.bucket, args.key);
|
|
89
99
|
try {
|
|
90
100
|
const stream = createReadStream(filePath);
|
|
91
|
-
// Handle early stream errors (like file not found, permission denied, etc.)
|
|
92
101
|
stream.on('error', (err) => {
|
|
93
|
-
this.logger.error(`Error getting content ${
|
|
102
|
+
this.logger.error(`Error getting content ${args.bucket}/${args.key}`, err);
|
|
94
103
|
});
|
|
95
104
|
return stream;
|
|
96
105
|
}
|
|
97
106
|
catch (e) {
|
|
98
|
-
this.logger.error(`Error setting up stream for ${
|
|
107
|
+
this.logger.error(`Error setting up stream for ${args.bucket}/${args.key}`, e);
|
|
99
108
|
throw e;
|
|
100
109
|
}
|
|
101
110
|
}
|
|
102
|
-
async readFileAsBuffer(
|
|
103
|
-
const filePath = this.safePath(
|
|
104
|
-
this.logger.debug(`Reading file as buffer: ${
|
|
111
|
+
async readFileAsBuffer(args) {
|
|
112
|
+
const filePath = this.safePath(args.bucket, args.key);
|
|
113
|
+
this.logger.debug(`Reading file as buffer: ${args.bucket}/${args.key}`);
|
|
105
114
|
return readFile(filePath);
|
|
106
115
|
}
|
|
107
|
-
async deleteFile(
|
|
108
|
-
this.logger.debug(`deleting key: ${
|
|
116
|
+
async deleteFile(args) {
|
|
117
|
+
this.logger.debug(`deleting key: ${args.bucket}/${args.key}`);
|
|
109
118
|
try {
|
|
110
|
-
await promises.unlink(this.safePath(
|
|
119
|
+
await promises.unlink(this.safePath(args.bucket, args.key));
|
|
111
120
|
return true;
|
|
112
121
|
}
|
|
113
122
|
catch (e) {
|
|
114
|
-
this.logger.error(`Error deleting content ${
|
|
123
|
+
this.logger.error(`Error deleting content ${args.bucket}/${args.key}`, e);
|
|
115
124
|
}
|
|
116
125
|
return false;
|
|
117
126
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { SerializedError } from '../types/core.types.js';
|
|
2
|
-
import type { WorkflowRun, WorkflowRunWire, WorkflowRunStatus, StepState, WorkflowStatus, WorkflowVersionStatus } from '../wirings/workflow/workflow.types.js';
|
|
2
|
+
import type { WorkflowRun, WorkflowPlannedStep, WorkflowRunWire, WorkflowRunStatus, StepState, WorkflowStatus, WorkflowVersionStatus } from '../wirings/workflow/workflow.types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Interface for workflow orchestration
|
|
5
5
|
* Handles workflow execution, replay, orchestration logic, and run-level state
|
|
6
6
|
*/
|
|
7
7
|
export interface WorkflowService {
|
|
8
|
-
createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire
|
|
8
|
+
createRun(workflowName: string, input: any, inline: boolean, graphHash: string, wire: WorkflowRunWire, options?: {
|
|
9
|
+
deterministic?: boolean;
|
|
10
|
+
plannedSteps?: WorkflowPlannedStep[];
|
|
11
|
+
}): Promise<string>;
|
|
9
12
|
getRun(id: string): Promise<WorkflowRun | null>;
|
|
10
13
|
getRunStatus(id: string): Promise<WorkflowRunStatus | null>;
|
|
11
14
|
getRunHistory(runId: string): Promise<Array<StepState & {
|