@pikku/core 0.12.146 → 0.12.147
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/dist/function/function-runner.js +7 -0
- package/dist/function/functions.types.d.ts +8 -0
- package/dist/types/state.types.d.ts +5 -4
- package/dist/wirings/addon/addon-reach.d.ts +10 -0
- package/dist/wirings/addon/addon-reach.js +29 -0
- package/dist/wirings/addon/index.d.ts +2 -2
- package/dist/wirings/addon/index.js +1 -1
- package/dist/wirings/addon/pikku-studio.d.ts +60 -0
- package/dist/wirings/addon/pikku-studio.js +7 -0
- package/dist/wirings/addon/wire-addon.d.ts +11 -7
- package/dist/wirings/agent/agent-rpc.js +6 -3
- package/dist/wirings/rpc/rpc-runner.d.ts +2 -3
- package/dist/wirings/rpc/rpc-runner.js +3 -22
- package/dist/wirings/workflow/graph/graph-runner.js +2 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +3 -4
- package/dist/wirings/workflow/workflow-compensation.js +2 -2
- package/dist/wirings/workflow/workflow-meta-resolver.d.ts +5 -0
- package/dist/wirings/workflow/workflow-meta-resolver.js +5 -0
- package/dist/wirings/workflow/workflow-queue-routing.js +2 -2
- package/package.json +2 -1
- package/dist/wirings/addon/define-screens.d.ts +0 -34
- package/dist/wirings/addon/define-screens.js +0 -5
|
@@ -62,6 +62,13 @@ async function resolveSession(wire, singletonServices, sessionService) {
|
|
|
62
62
|
* @param packageName - The addon package it belongs to; `null` for the app.
|
|
63
63
|
*/
|
|
64
64
|
export const addFunction = (funcName, funcConfig, packageName = null) => {
|
|
65
|
+
if (funcConfig.disabled) {
|
|
66
|
+
throw new Error(`Function '${funcName}' is disabled` +
|
|
67
|
+
(typeof funcConfig.disabled === 'string'
|
|
68
|
+
? ` (${funcConfig.disabled})`
|
|
69
|
+
: '') +
|
|
70
|
+
' and cannot be registered.');
|
|
71
|
+
}
|
|
65
72
|
pikkuState(packageName, 'function', 'functions').set(funcName, funcConfig);
|
|
66
73
|
};
|
|
67
74
|
export const getFunctionNames = (packageName = null) => {
|
|
@@ -128,6 +128,14 @@ export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any,
|
|
|
128
128
|
version?: number;
|
|
129
129
|
/** Filters this function in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
130
130
|
tags?: string[];
|
|
131
|
+
/**
|
|
132
|
+
* Switches the function off: `true`, or a string saying why. The build
|
|
133
|
+
* leaves it out of every map, client and wiring, and refuses (PKU562)
|
|
134
|
+
* anything that still references it, so marking a function believed dead
|
|
135
|
+
* proves it. The source keeps type-checking; deleting the flag turns it
|
|
136
|
+
* back on.
|
|
137
|
+
*/
|
|
138
|
+
disabled?: true | string;
|
|
131
139
|
/** Makes the function callable from outside as `POST /rpc/<name>`. Without a session requirement, a permission or an addon gate, that means callable by anyone. */
|
|
132
140
|
expose?: boolean;
|
|
133
141
|
/**
|
|
@@ -15,7 +15,7 @@ import type { CoreScheduledTask, ScheduledTasksMeta } from '../wirings/scheduler
|
|
|
15
15
|
import type { CoreWorkflow, WorkflowsRuntimeMeta } from '../wirings/workflow/workflow.types.js';
|
|
16
16
|
import type { CoreFeature } from '../wirings/workflow/scenario.types.js';
|
|
17
17
|
import type { CoreTrigger, CoreTriggerSource, TriggerMeta, TriggerSourceMeta } from '../wirings/trigger/trigger.types.js';
|
|
18
|
-
import type { CreateConfig, CreateSingletonServices, CreateWireServices,
|
|
18
|
+
import type { CreateConfig, CreateSingletonServices, CreateWireServices, CoreSingletonServices } from './core.types.js';
|
|
19
19
|
import type { FunctionsMeta } from '../function/function-meta.types.js';
|
|
20
20
|
import type { CorePikkuMiddleware, CorePikkuMiddlewareGroup } from '../middleware/middleware.types.js';
|
|
21
21
|
import type { CorePikkuChannelMiddleware } from '../wirings/channel/channel.types.js';
|
|
@@ -168,10 +168,11 @@ export interface PikkuPackageState {
|
|
|
168
168
|
permissions: Record<string, CorePermissionGroup | CorePikkuPermission[]>;
|
|
169
169
|
};
|
|
170
170
|
package: {
|
|
171
|
+
/** Every package's own factories, each typed where it is defined. */
|
|
171
172
|
factories: {
|
|
172
|
-
createConfig?: CreateConfig<
|
|
173
|
-
createSingletonServices?: CreateSingletonServices<
|
|
174
|
-
createWireServices?: CreateWireServices<
|
|
173
|
+
createConfig?: CreateConfig<any>;
|
|
174
|
+
createSingletonServices?: CreateSingletonServices<any, any>;
|
|
175
|
+
createWireServices?: CreateWireServices<any, any, any>;
|
|
175
176
|
} | null;
|
|
176
177
|
singletonServices: CoreSingletonServices | null;
|
|
177
178
|
/** The pikkuBetterAuth factory, self-registered when auth.ts is evaluated.
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { PikkuError } from '../../errors/error-handler.js';
|
|
1
2
|
import type { PikkuRawWire } from '../../types/core.types.js';
|
|
2
3
|
export declare const addonMayReach: (caller: string, funcName: string) => boolean;
|
|
3
4
|
export declare const addonCallerOf: (wire: PikkuRawWire) => string | undefined;
|
|
5
|
+
export declare class AddonNotUsedError extends PikkuError {
|
|
6
|
+
constructor(caller: string, target: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The name an addon's call resolves to on this host, through its `uses`.
|
|
10
|
+
*
|
|
11
|
+
* knowledge: decisions/security/an-addon-may-reach-only-itself-and-the-addons-it-uses.md
|
|
12
|
+
*/
|
|
13
|
+
export declare const mapAddonCall: (caller: string | undefined, funcName: string) => string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ForbiddenError } from '../../errors/errors.js';
|
|
2
|
+
import { PikkuError, addError } from '../../errors/error-handler.js';
|
|
2
3
|
import { ADDON_HEADER } from '../../middleware/addon-scope-cap.js';
|
|
3
4
|
import { pikkuState } from '../../pikku-state.js';
|
|
4
5
|
export const addonMayReach = (caller, funcName) => {
|
|
@@ -19,3 +20,31 @@ export const addonCallerOf = (wire) => {
|
|
|
19
20
|
}
|
|
20
21
|
return name;
|
|
21
22
|
};
|
|
23
|
+
export class AddonNotUsedError extends PikkuError {
|
|
24
|
+
constructor(caller, target) {
|
|
25
|
+
super(`Addon '${caller}' called '${target}' but does not list it in its wireAddon 'uses'.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
addError(AddonNotUsedError, {
|
|
29
|
+
status: 403,
|
|
30
|
+
message: 'Addon called another addon it does not use.',
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* The name an addon's call resolves to on this host, through its `uses`.
|
|
34
|
+
*
|
|
35
|
+
* knowledge: decisions/security/an-addon-may-reach-only-itself-and-the-addons-it-uses.md
|
|
36
|
+
*/
|
|
37
|
+
export const mapAddonCall = (caller, funcName) => {
|
|
38
|
+
if (!caller)
|
|
39
|
+
return funcName;
|
|
40
|
+
const colon = funcName.indexOf(':');
|
|
41
|
+
if (colon === -1)
|
|
42
|
+
return funcName;
|
|
43
|
+
const prefix = funcName.slice(0, colon);
|
|
44
|
+
if (prefix === caller)
|
|
45
|
+
return funcName;
|
|
46
|
+
const mapped = pikkuState(null, 'addons', 'packages').get(caller)?.uses?.[prefix];
|
|
47
|
+
if (mapped)
|
|
48
|
+
return `${mapped}${funcName.slice(colon)}`;
|
|
49
|
+
throw new AddonNotUsedError(caller, funcName);
|
|
50
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { wireAddon } from './wire-addon.js';
|
|
2
2
|
export type { WireAddonConfig } from './wire-addon.js';
|
|
3
|
-
export {
|
|
4
|
-
export type {
|
|
3
|
+
export { pikkuStudio } from './pikku-studio.js';
|
|
4
|
+
export type { StudioManifest, StudioScreen, StudioComponentScreen, StudioAppScreen, StudioNav, } from './pikku-studio.js';
|
|
5
5
|
export { wireRemoteAddon } from './wire-remote-addon.js';
|
|
6
6
|
export type { WireRemoteAddonConfig, RemoteAddonAuth, } from './wire-remote-addon.js';
|
|
7
7
|
export { resolveRemoteAddonToken, RemoteAddonAuthError, } from './remote-addon-auth.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { wireAddon } from './wire-addon.js';
|
|
2
|
-
export {
|
|
2
|
+
export { pikkuStudio } from './pikku-studio.js';
|
|
3
3
|
export { wireRemoteAddon } from './wire-remote-addon.js';
|
|
4
4
|
export { resolveRemoteAddonToken, RemoteAddonAuthError, } from './remote-addon-auth.js';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an addon screen shows in Studio's navigation. Leave it out and the
|
|
3
|
+
* screen is routed but not listed.
|
|
4
|
+
*/
|
|
5
|
+
export type StudioNav = {
|
|
6
|
+
area: 'rail' | 'team' | 'project' | 'account';
|
|
7
|
+
/** The icon's name. */
|
|
8
|
+
icon: string;
|
|
9
|
+
order: number;
|
|
10
|
+
/** Names a function of the addon that returns a count shown on the entry. */
|
|
11
|
+
badge?: string;
|
|
12
|
+
};
|
|
13
|
+
type StudioScreenBase = {
|
|
14
|
+
/** The route path, under the prefix its `scope` gives it. */
|
|
15
|
+
path: string;
|
|
16
|
+
/** A message key in the addon's catalog, `messages/{locale}.json`. It is also the nav label. */
|
|
17
|
+
title: string;
|
|
18
|
+
nav?: StudioNav;
|
|
19
|
+
/** Takes over the core screen at the same path. Only first-party addons may use it. */
|
|
20
|
+
replaces?: boolean;
|
|
21
|
+
/** `'bare'` renders the screen without the shell. */
|
|
22
|
+
layout?: 'bare';
|
|
23
|
+
/** Hides the screen and its nav for viewers without access. The addon's functions must enforce the same scopes: hiding a screen is not access control. */
|
|
24
|
+
scopes?: string[];
|
|
25
|
+
} & ({
|
|
26
|
+
/** `'studio'` gives `/<path>`, `'team'` gives `/<team>/<path>`. */
|
|
27
|
+
scope: 'studio' | 'team';
|
|
28
|
+
needsApp?: never;
|
|
29
|
+
} | {
|
|
30
|
+
/** `/<team>/<project>[/<target>]/<path>`. */
|
|
31
|
+
scope: 'project';
|
|
32
|
+
/** The screen needs the project's app running. Defaults to `false`. */
|
|
33
|
+
needsApp?: boolean;
|
|
34
|
+
});
|
|
35
|
+
/** A screen rendered by the host from the addon's source. */
|
|
36
|
+
export type StudioComponentScreen = StudioScreenBase & {
|
|
37
|
+
component: () => Promise<{
|
|
38
|
+
default: unknown;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
/** A static program the host serves in a sandboxed iframe. */
|
|
42
|
+
export type StudioAppScreen = StudioScreenBase & {
|
|
43
|
+
/** Directory of the built program, relative to the addon package. */
|
|
44
|
+
app: string;
|
|
45
|
+
};
|
|
46
|
+
/** One screen an addon ships: either a component the host renders or a static app it serves in a sandboxed iframe. */
|
|
47
|
+
export type StudioScreen = StudioComponentScreen | StudioAppScreen;
|
|
48
|
+
/** The manifest passed to `pikkuStudio`: the addon's title, a message key, and its screens. */
|
|
49
|
+
export type StudioManifest = {
|
|
50
|
+
title: string;
|
|
51
|
+
screens: StudioScreen[];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Declares the screens and nav an addon brings to Pikku Studio. Read
|
|
55
|
+
* statically by `pikku all`, so every field must be a literal. Titles are
|
|
56
|
+
* message keys in the addon's own catalog, `messages/{locale}.json`: a key
|
|
57
|
+
* missing from `messages/en.json` fails the build.
|
|
58
|
+
*/
|
|
59
|
+
export declare const pikkuStudio: (manifest: StudioManifest) => StudioManifest;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declares the screens and nav an addon brings to Pikku Studio. Read
|
|
3
|
+
* statically by `pikku all`, so every field must be a literal. Titles are
|
|
4
|
+
* message keys in the addon's own catalog, `messages/{locale}.json`: a key
|
|
5
|
+
* missing from `messages/en.json` fails the build.
|
|
6
|
+
*/
|
|
7
|
+
export const pikkuStudio = (manifest) => manifest;
|
|
@@ -46,18 +46,22 @@ export type WireAddonConfig = {
|
|
|
46
46
|
/** Filters this addon in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
47
47
|
tags?: string[];
|
|
48
48
|
/**
|
|
49
|
-
* Mounts the screens the addon ships (see `
|
|
50
|
-
*
|
|
51
|
-
* must declare at least one, or `pikku all` rejects the wiring.
|
|
49
|
+
* Mounts the screens the addon ships (see `pikkuStudio`) in the host, each at
|
|
50
|
+
* the path its `scope` gives it. Without it the addon's screens are ignored.
|
|
51
|
+
* The package must declare at least one, or `pikku all` rejects the wiring.
|
|
52
52
|
*/
|
|
53
53
|
ui?: boolean;
|
|
54
54
|
/** Required of every function in the addon, on top of the function's own. */
|
|
55
55
|
scopes?: string[];
|
|
56
56
|
/**
|
|
57
|
-
* The other addons this one may call, as `{ '<
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
57
|
+
* The other addons this one may call, as `{ '<alias>': '<name>' }`: the name
|
|
58
|
+
* the addon calls another by, and the name that addon is wired under in this
|
|
59
|
+
* app — by `wireAddon` or `wireRemoteAddon`, either serves. The alias is the
|
|
60
|
+
* one the addon's package.json declares under `pikku.uses`
|
|
61
|
+
* (`{ "stripe": "@acme/addon-stripe@^1.0.0" }`), or, for an addon that lists
|
|
62
|
+
* plain names, the package name itself. The addon calls
|
|
63
|
+
* `'<alias>:<function>'`; anything it did not declare, or this app did not
|
|
64
|
+
* map, is refused, whatever else the app has wired.
|
|
61
65
|
*/
|
|
62
66
|
uses?: Record<string, string>;
|
|
63
67
|
/** Points a secret the addon reads at a different key in this deployment, so two instances can hold different credentials. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pikkuState } from '../../pikku-state.js';
|
|
2
|
+
import { mapAddonCall } from '../addon/addon-reach.js';
|
|
2
3
|
import { runAgent, resumeAgentSync } from './agent-runner.js';
|
|
3
4
|
import { streamAgent, resumeAgent, interruptAgent } from './agent-stream.js';
|
|
4
5
|
import { wrapChannelWithAGUI } from './agent-agui.js';
|
|
@@ -31,10 +32,10 @@ export const createAgentRPC = (wire, options) => {
|
|
|
31
32
|
}),
|
|
32
33
|
});
|
|
33
34
|
return {
|
|
34
|
-
run: async (agentName, input) => asRunResult(await runAgent(agentName, input, params())),
|
|
35
|
+
run: async (agentName, input) => asRunResult(await runAgent(mapAddonCall(wire.addonNamespace, agentName), input, params())),
|
|
35
36
|
stream: async (agentName, input, streamOptions) => {
|
|
36
37
|
let currentRunId;
|
|
37
|
-
await streamAgent(agentName, input, wrapChannelWithAGUI(streamingChannel(), {
|
|
38
|
+
await streamAgent(mapAddonCall(wire.addonNamespace, agentName), input, wrapChannelWithAGUI(streamingChannel(), {
|
|
38
39
|
threadId: input.threadId,
|
|
39
40
|
getRunId: () => currentRunId,
|
|
40
41
|
}), params(), undefined, {
|
|
@@ -49,7 +50,9 @@ export const createAgentRPC = (wire, options) => {
|
|
|
49
50
|
await resumeAgent({ runId, ...input }, wrapChannelWithAGUI(streamingChannel(), { runId }), params(), streamOptions);
|
|
50
51
|
},
|
|
51
52
|
interrupt: async (runId, reason) => interruptAgent({ runId, ...(reason ? { reason } : {}) }, { sessionService: options.sessionService }),
|
|
52
|
-
approve: async (runId, approvals, expectedAgentName) => asRunResult(await resumeAgentSync(runId, approvals, { sessionService: options.sessionService }, expectedAgentName
|
|
53
|
+
approve: async (runId, approvals, expectedAgentName) => asRunResult(await resumeAgentSync(runId, approvals, { sessionService: options.sessionService }, expectedAgentName === undefined
|
|
54
|
+
? undefined
|
|
55
|
+
: mapAddonCall(wire.addonNamespace, expectedAgentName))),
|
|
53
56
|
};
|
|
54
57
|
};
|
|
55
58
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AddonNotUsedError } from '../addon/addon-reach.js';
|
|
1
2
|
import type { CoreServices, PikkuRawWire } from '../../types/core.types.js';
|
|
2
3
|
import type { SessionService } from '../../services/user-session-service.js';
|
|
3
4
|
import type { CoreUserSession } from '../../types/core.types.js';
|
|
@@ -29,9 +30,7 @@ export declare class RemoteAddonFailedError extends PikkuError {
|
|
|
29
30
|
export declare class RemoteAddonTimeoutError extends PikkuError {
|
|
30
31
|
constructor(namespace: string, fnName: string, timeoutMs: number);
|
|
31
32
|
}
|
|
32
|
-
export
|
|
33
|
-
constructor(caller: string, target: string);
|
|
34
|
-
}
|
|
33
|
+
export { AddonNotUsedError };
|
|
35
34
|
export declare const resolveNamespace: (namespacedFunction: string) => ResolvedFunction | null;
|
|
36
35
|
export declare class ContextAwareRPCService {
|
|
37
36
|
private services;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addonCallerOf, addonMayReach } from '../addon/addon-reach.js';
|
|
1
|
+
import { AddonNotUsedError, addonCallerOf, addonMayReach, mapAddonCall, } from '../addon/addon-reach.js';
|
|
2
2
|
import { forwardStepName, isCompensationStepName, } from '../../compensation-name.js';
|
|
3
3
|
import { addonInstanceForNamespace } from '../addon/addon-runner.js';
|
|
4
4
|
import { isAddonFunctionExposed } from '../addon/wire-addon.js';
|
|
@@ -166,15 +166,7 @@ const remoteAddonResponseError = async (res, namespace, fnName, logger) => {
|
|
|
166
166
|
logger.error(`Remote addon '${namespace}:${fnName}' answered ${res.status}: ${body.slice(0, 1000)}`);
|
|
167
167
|
return new RemoteAddonFailedError(namespace, fnName);
|
|
168
168
|
};
|
|
169
|
-
export
|
|
170
|
-
constructor(caller, target) {
|
|
171
|
-
super(`Addon '${caller}' called '${target}' but does not list it in its wireAddon 'uses'.`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
addError(AddonNotUsedError, {
|
|
175
|
-
status: 403,
|
|
176
|
-
message: 'Addon called another addon it does not use.',
|
|
177
|
-
});
|
|
169
|
+
export { AddonNotUsedError };
|
|
178
170
|
export const resolveNamespace = (namespacedFunction) => {
|
|
179
171
|
const colonIndex = namespacedFunction.indexOf(':');
|
|
180
172
|
if (colonIndex === -1) {
|
|
@@ -367,18 +359,7 @@ export class ContextAwareRPCService {
|
|
|
367
359
|
});
|
|
368
360
|
}
|
|
369
361
|
mapUsedAddon(funcName) {
|
|
370
|
-
|
|
371
|
-
if (!caller)
|
|
372
|
-
return funcName;
|
|
373
|
-
const colon = funcName.indexOf(':');
|
|
374
|
-
const prefix = funcName.slice(0, colon);
|
|
375
|
-
if (prefix === caller)
|
|
376
|
-
return funcName;
|
|
377
|
-
const uses = pikkuState(null, 'addons', 'packages').get(caller)?.uses;
|
|
378
|
-
const mapped = uses?.[prefix];
|
|
379
|
-
if (mapped)
|
|
380
|
-
return `${mapped}${funcName.slice(colon)}`;
|
|
381
|
-
throw new AddonNotUsedError(caller, funcName);
|
|
362
|
+
return mapAddonCall(this.wire.addonNamespace, funcName);
|
|
382
363
|
}
|
|
383
364
|
/**
|
|
384
365
|
* Resolution half of an addon RPC call: never executes anything, so an
|
|
@@ -737,7 +737,7 @@ async function invokeGraphNodeRpc(workflowService, rpcService, runId, stepId, no
|
|
|
737
737
|
export async function executeGraphStep(workflowService, rpcService, runId, stepId, nodeId, rpcName, data, graphName) {
|
|
738
738
|
try {
|
|
739
739
|
let result;
|
|
740
|
-
const subWorkflowMeta =
|
|
740
|
+
const subWorkflowMeta = getWorkflowMeta(rpcName);
|
|
741
741
|
const agentMeta = subWorkflowMeta
|
|
742
742
|
? undefined
|
|
743
743
|
: pikkuState(null, 'agent', 'agentsMeta')[rpcName];
|
|
@@ -801,7 +801,7 @@ async function executeGraphNodeInline(workflowService, rpcService, runId, graphN
|
|
|
801
801
|
await workflowService.setStepRunning(stepState.stepId);
|
|
802
802
|
try {
|
|
803
803
|
let result;
|
|
804
|
-
const subWorkflowMeta =
|
|
804
|
+
const subWorkflowMeta = getWorkflowMeta(rpcName);
|
|
805
805
|
const agentMeta = subWorkflowMeta
|
|
806
806
|
? undefined
|
|
807
807
|
: pikkuState(null, 'agent', 'agentsMeta')[rpcName];
|
|
@@ -691,8 +691,7 @@ export class PikkuWorkflowService {
|
|
|
691
691
|
if (!run) {
|
|
692
692
|
throw new Error(`Workflow run not found: ${runId}`);
|
|
693
693
|
}
|
|
694
|
-
const
|
|
695
|
-
const workflowMeta = meta[run.workflow];
|
|
694
|
+
const workflowMeta = resolveWorkflowMeta(run.workflow)?.meta;
|
|
696
695
|
const isGraphWorkflow = workflowMeta?.source === 'graph';
|
|
697
696
|
let graphNodeId;
|
|
698
697
|
if (isGraphWorkflow && workflowMeta?.nodes) {
|
|
@@ -709,7 +708,7 @@ export class PikkuWorkflowService {
|
|
|
709
708
|
result = await executeGraphStep(this, rpcService, runId, stepState.stepId, graphNodeId, workflowMeta.nodes[graphNodeId].rpcName, data, run.workflow);
|
|
710
709
|
}
|
|
711
710
|
else {
|
|
712
|
-
if (
|
|
711
|
+
if (resolveWorkflowMeta(rpcName)) {
|
|
713
712
|
result = await startQueuedChildStep({
|
|
714
713
|
startWorkflow: (...args) => this.startWorkflow(...args),
|
|
715
714
|
setStepChildRunId: (...args) => this.setStepChildRunId(...args),
|
|
@@ -860,7 +859,7 @@ export class PikkuWorkflowService {
|
|
|
860
859
|
if (resolvedStepOptions.virtualUser) {
|
|
861
860
|
return resolvedStepOptions.virtualUser.invoke(rpcName, data);
|
|
862
861
|
}
|
|
863
|
-
if (
|
|
862
|
+
if (resolveWorkflowMeta(rpcName)) {
|
|
864
863
|
return runInlineChildStep({
|
|
865
864
|
startWorkflow: (...args) => this.startWorkflow(...args),
|
|
866
865
|
setStepChildRunId: (...args) => this.setStepChildRunId(...args),
|
|
@@ -2,6 +2,7 @@ import { DEFAULT_STEP_RETRIES, NO_COMPENSATE_STATE_PREFIX, WORKFLOW_TERMINAL_STA
|
|
|
2
2
|
import { pikkuState } from '../../pikku-state.js';
|
|
3
3
|
import { resolveNamespace } from '../rpc/rpc-runner.js';
|
|
4
4
|
import { runningStepLease } from './workflow-step-lease.js';
|
|
5
|
+
import { isWorkflowName } from './workflow-meta-resolver.js';
|
|
5
6
|
import { planUnwind } from './workflow-unwind-plan.js';
|
|
6
7
|
const ENTERABLE = new Set([
|
|
7
8
|
'running',
|
|
@@ -177,8 +178,7 @@ export const functionHasCompensation = (rpcName) => {
|
|
|
177
178
|
?.compensate === true
|
|
178
179
|
: false;
|
|
179
180
|
};
|
|
180
|
-
export const isChildWorkflowRecord = (record) => !!record.rpcName &&
|
|
181
|
-
pikkuState(null, 'workflows', 'meta')[record.rpcName] !== undefined;
|
|
181
|
+
export const isChildWorkflowRecord = (record) => !!record.rpcName && isWorkflowName(record.rpcName);
|
|
182
182
|
export const recordHasCompensation = (record) => isChildWorkflowRecord(record)
|
|
183
183
|
? !!record.childRunId
|
|
184
184
|
: !!record.rpcName && functionHasCompensation(record.rpcName);
|
|
@@ -9,3 +9,8 @@ export type ResolvedWorkflowMeta = {
|
|
|
9
9
|
* `namespace:name` form — against the addon package that claims the namespace.
|
|
10
10
|
*/
|
|
11
11
|
export declare const resolveWorkflowMeta: (name: string) => ResolvedWorkflowMeta | null;
|
|
12
|
+
/**
|
|
13
|
+
* Whether a step's rpcName names a workflow — the app's, or an addon's by
|
|
14
|
+
* `namespace:name` — and so starts a child run rather than an RPC.
|
|
15
|
+
*/
|
|
16
|
+
export declare const isWorkflowName: (name: string) => boolean;
|
|
@@ -29,3 +29,8 @@ export const resolveWorkflowMeta = (name) => {
|
|
|
29
29
|
resolvedName: localName,
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* Whether a step's rpcName names a workflow — the app's, or an addon's by
|
|
34
|
+
* `namespace:name` — and so starts a child run rather than an RPC.
|
|
35
|
+
*/
|
|
36
|
+
export const isWorkflowName = (name) => resolveWorkflowMeta(name) !== null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getSingletonServices, pikkuState } from '../../pikku-state.js';
|
|
2
2
|
import { getDurationInMilliseconds } from '../../time-utils.js';
|
|
3
3
|
import { DEFAULT_STEP_RETRIES, forwardStepName } from './workflow-constants.js';
|
|
4
|
+
import { isWorkflowName } from './workflow-meta-resolver.js';
|
|
4
5
|
const toKebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
5
6
|
export const resolveWorkflowConfig = () => {
|
|
6
7
|
const workflow = getSingletonServices().config?.workflow;
|
|
@@ -54,8 +55,7 @@ export const stepDispatchTarget = async (rpcName, stepName, parentIsInline) => {
|
|
|
54
55
|
}
|
|
55
56
|
return 'queue';
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
-
if (!isWorkflow || !hasQueue) {
|
|
58
|
+
if (!isWorkflowName(rpcName) || !hasQueue) {
|
|
59
59
|
return 'inline';
|
|
60
60
|
}
|
|
61
61
|
return (await parentIsInline()) ? 'inline' : 'queue';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/core",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.147",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"sideEffects": [
|
|
26
26
|
"./dist/errors/errors.js",
|
|
27
|
+
"./dist/wirings/addon/addon-reach.js",
|
|
27
28
|
"./dist/wirings/addon/remote-addon-auth.js",
|
|
28
29
|
"./dist/wirings/agent/agent-rpc.js",
|
|
29
30
|
"./dist/wirings/rpc/rpc-runner.js",
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
type AddonScreenBase = {
|
|
2
|
-
/** Where the screen mounts under the addon, e.g. `/` or `/:id`. */
|
|
3
|
-
path: string;
|
|
4
|
-
title: string;
|
|
5
|
-
/** Lists the screen in the host's navigation. */
|
|
6
|
-
nav?: boolean;
|
|
7
|
-
/** Required of the viewer to see the screen. The addon's functions must enforce the same scopes: hiding a screen is not access control. */
|
|
8
|
-
scopes?: string[];
|
|
9
|
-
};
|
|
10
|
-
/** A screen rendered by the host from the addon's source. */
|
|
11
|
-
export type AddonComponentScreen = AddonScreenBase & {
|
|
12
|
-
component: () => Promise<{
|
|
13
|
-
default: unknown;
|
|
14
|
-
}>;
|
|
15
|
-
};
|
|
16
|
-
/** A static program the host serves in a sandboxed iframe. */
|
|
17
|
-
export type AddonAppScreen = AddonScreenBase & {
|
|
18
|
-
/** Directory of the built program, relative to the addon package. */
|
|
19
|
-
app: string;
|
|
20
|
-
};
|
|
21
|
-
/** One screen an addon ships: either a component the host renders or a static app it serves in a sandboxed iframe. */
|
|
22
|
-
export type AddonScreen = AddonComponentScreen | AddonAppScreen;
|
|
23
|
-
/** The manifest passed to `defineScreens`: the addon's title, optional icon and its screens. */
|
|
24
|
-
export type ScreensManifest = {
|
|
25
|
-
title: string;
|
|
26
|
-
icon?: string;
|
|
27
|
-
screens: AddonScreen[];
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* Declares the screens an addon ships, which the host mounts when it is wired
|
|
31
|
-
* with `ui: true`. Read statically by `pikku all`, so `path`, `title`, `nav`, `scopes` and `app` must be literals.
|
|
32
|
-
*/
|
|
33
|
-
export declare const defineScreens: (manifest: ScreensManifest) => ScreensManifest;
|
|
34
|
-
export {};
|