@pikku/core 0.12.26 → 0.12.28
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 +30 -0
- package/dist/data-classification.d.ts +7 -3
- package/dist/errors/error-handler.js +1 -0
- package/dist/function/function-runner.d.ts +1 -2
- package/dist/function/function-runner.js +2 -22
- package/dist/function/functions.types.d.ts +2 -0
- package/dist/handle-error.js +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/pikku-state.d.ts +1 -0
- package/dist/pikku-state.js +3 -0
- package/dist/services/gopass-secrets.d.ts +1 -0
- package/dist/services/gopass-secrets.js +9 -0
- package/dist/services/local-secrets.d.ts +1 -0
- package/dist/services/local-secrets.js +9 -0
- package/dist/services/meta-service.js +18 -7
- package/dist/services/scoped-secret-service.d.ts +1 -0
- package/dist/services/scoped-secret-service.js +4 -0
- package/dist/services/secret-service.d.ts +7 -0
- package/dist/services/typed-secret-service.d.ts +1 -0
- package/dist/services/typed-secret-service.js +3 -0
- package/dist/test-utils.d.ts +18 -0
- package/dist/test-utils.js +33 -0
- package/dist/types/core.types.d.ts +2 -0
- package/dist/wirings/channel/channel-common.js +9 -2
- package/dist/wirings/channel/local/local-channel-handler.d.ts +7 -4
- package/dist/wirings/channel/local/local-channel-handler.js +13 -5
- package/dist/wirings/channel/local/local-channel-runner.js +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.js +10 -1
- package/package.json +1 -1
- package/src/data-classification.ts +11 -4
- package/src/dev/hot-reload.test.ts +2 -0
- package/src/errors/error-handler.ts +1 -0
- package/src/function/function-runner.test.ts +2 -28
- package/src/function/function-runner.ts +2 -35
- package/src/function/functions.types.ts +2 -0
- package/src/handle-error.test.ts +1 -0
- package/src/handle-error.ts +1 -0
- package/src/index.ts +7 -1
- package/src/pikku-state.ts +4 -0
- package/src/services/gopass-secrets.ts +10 -0
- package/src/services/local-secrets.ts +10 -0
- package/src/services/meta-service.ts +17 -7
- package/src/services/scoped-secret-service.ts +5 -0
- package/src/services/secret-service.ts +7 -0
- package/src/services/typed-secret-service.ts +4 -0
- package/src/test-utils.ts +38 -0
- package/src/types/core.types.ts +2 -0
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +2 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +28 -4
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +3 -0
- package/src/wirings/channel/channel-common.ts +9 -2
- package/src/wirings/channel/local/local-channel-handler.ts +24 -9
- package/src/wirings/channel/local/local-channel-runner.ts +2 -1
- package/src/wirings/cli/cli-runner.test.ts +4 -0
- package/src/wirings/http/http-runner.test.ts +1 -0
- package/src/wirings/queue/queue-runner.test.ts +1 -0
- package/src/wirings/scheduler/scheduler-runner.test.ts +10 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +10 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
## 0.12.28
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 2cf67be: Add inline option to pikkuFunc/pikkuSessionlessFunc for workflow step dispatch
|
|
6
|
+
|
|
7
|
+
By default, workflow steps now run inline (no queue hop). Set inline: false on a function to force dispatch through the queue for that step.
|
|
8
|
+
|
|
9
|
+
## 0.12.27
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 4b5c75b: feat(auth-js): wire OIDC config (issuer/tenantId) as variables, expand provider registry
|
|
14
|
+
- Move `issuer` and `tenantId` out of the secret blob for OIDC providers (auth0, okta, azure-ad, keycloak, cognito, microsoft-entra-id) — they are public config URLs, not secrets. Now registered via `wireVariable` and loaded at runtime via `services.variables.get()`.
|
|
15
|
+
- Expand provider registry from 13 to 31 providers: reddit, notion, instagram, zoom, figma, tiktok, threads, patreon, dropbox, bitbucket, hubspot, salesforce, atlassian, strava, keycloak, cognito, microsoft-entra-id added.
|
|
16
|
+
- `serialize-auth-gen` emits `wireVariable({...})` declarations and `services.variables.get()` calls in the generated factory for OIDC providers.
|
|
17
|
+
- Integration verifier exercises real `/auth/providers` endpoint with `LocalSecretService` + `LocalVariablesService`, including a spy test proving `services.variables.get('AUTH0_ISSUER')` is called at request time.
|
|
18
|
+
|
|
19
|
+
- 4b5c75b: Add end-to-end data classification for SQLite and Postgres projects.
|
|
20
|
+
|
|
21
|
+
**Core (`@pikku/core`):** New `Private<T>` and `Secret<T>` intersection brands, `ClassificationManifest`, `ColumnClassification`, and `AnonymizeStrategy` types exported from `data-classification.ts`.
|
|
22
|
+
|
|
23
|
+
**CLI (`@pikku/cli`):**
|
|
24
|
+
- SQL comment annotations: `-- @public`, `-- @private[:strategy]`, `-- @secret[:strategy]` on `CREATE TABLE` columns and `ALTER TABLE ... ADD COLUMN` statements. Unannotated columns default to `private`.
|
|
25
|
+
- `pikku db migrate` now emits a `classification.gen.ts` manifest alongside `schema.d.ts`.
|
|
26
|
+
- New `pikku db audit` command — prints a per-column classification summary and warns on `private`/`secret` columns with no anonymize strategy.
|
|
27
|
+
- Postgres dialect support in `resolveDb`, `PostgresMigrationExecutor`, and `PostgresIntrospector`.
|
|
28
|
+
|
|
29
|
+
**Inspector (`@pikku/inspector`):** New PKU910 check — `findPiiPaths()` walks inferred function return types looking for `__pii__` brands (including inside `Array<T>`, `Record<K,V>`, and index signatures) and fails the build if a function exposes branded fields in its output.
|
|
30
|
+
|
|
1
31
|
## 0.12.26
|
|
2
32
|
|
|
3
33
|
### Patch Changes
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
export type Private<T> = T & {
|
|
2
|
-
readonly
|
|
2
|
+
readonly __classification__: 'private';
|
|
3
|
+
};
|
|
4
|
+
export type Pii<T> = T & {
|
|
5
|
+
readonly __classification__: 'pii';
|
|
3
6
|
};
|
|
4
7
|
export type Secret<T> = T & {
|
|
5
|
-
readonly
|
|
8
|
+
readonly __classification__: 'secret';
|
|
6
9
|
};
|
|
7
|
-
export type Classification = 'public' | 'private' | 'secret';
|
|
10
|
+
export type Classification = 'public' | 'private' | 'pii' | 'secret';
|
|
8
11
|
export type AnonymizeStrategy = 'fake:email' | 'fake:name' | 'hash' | 'keep' | null;
|
|
9
12
|
export interface ColumnClassification {
|
|
10
13
|
classification: Classification;
|
|
11
14
|
anonymize_strategy: AnonymizeStrategy;
|
|
15
|
+
description?: string;
|
|
12
16
|
}
|
|
13
17
|
export type ClassificationManifest = {
|
|
14
18
|
version: 1;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CoreUserSession, CorePikkuMiddleware, PikkuWiringTypes, PikkuWire, MiddlewareMetadata, PermissionMetadata, CoreSingletonServices, CreateWireServices } from '../types/core.types.js';
|
|
2
2
|
import type { CorePikkuChannelMiddleware } from '../wirings/channel/channel.types.js';
|
|
3
3
|
import type { CorePermissionGroup, CorePikkuFunctionConfig, CorePikkuPermission } from './functions.types.js';
|
|
4
4
|
import type { SessionService } from '../services/user-session-service.js';
|
|
@@ -6,7 +6,6 @@ import { PikkuCredentialWireService } from '../services/credential-wire-service.
|
|
|
6
6
|
export declare const addFunction: (funcName: string, funcConfig: CorePikkuFunctionConfig<any, any>, packageName?: string | null) => void;
|
|
7
7
|
export declare const getFunctionNames: (packageName?: string | null) => string[];
|
|
8
8
|
export declare const getAllFunctionNames: () => string[];
|
|
9
|
-
export declare const runPikkuFuncDirectly: <In, Out>(funcName: string, allServices: CoreServices, wire: PikkuWire, data: In, userSession?: SessionService<CoreUserSession>, packageName?: string | null) => Promise<Out>;
|
|
10
9
|
export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTypes, wireId: string, funcName: string, { singletonServices, createWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, inheritedPermissions, wirePermissions, coerceDataFromSchema, tags, wire, sessionService, credentialWireService, packageName, }: {
|
|
11
10
|
singletonServices: CoreSingletonServices;
|
|
12
11
|
createWireServices?: CreateWireServices;
|
|
@@ -4,7 +4,7 @@ import { runPermissions } from '../permissions.js';
|
|
|
4
4
|
import { pikkuState } from '../pikku-state.js';
|
|
5
5
|
import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
|
|
6
6
|
import { parseVersionedId } from '../version.js';
|
|
7
|
-
import { PikkuSessionService
|
|
7
|
+
import { PikkuSessionService } from '../services/user-session-service.js';
|
|
8
8
|
import { ForbiddenError, ReadonlySessionError } from '../errors/errors.js';
|
|
9
9
|
import { PikkuCredentialWireService, createWireServicesCredentialWireProps, } from '../services/credential-wire-service.js';
|
|
10
10
|
import { defaultPikkuUserIdResolver } from '../services/pikku-user-id.js';
|
|
@@ -132,17 +132,6 @@ export const getAllFunctionNames = () => {
|
|
|
132
132
|
}
|
|
133
133
|
return functions;
|
|
134
134
|
};
|
|
135
|
-
export const runPikkuFuncDirectly = async (funcName, allServices, wire, data, userSession, packageName = null) => {
|
|
136
|
-
const funcConfig = pikkuState(packageName, 'function', 'functions').get(funcName);
|
|
137
|
-
if (!funcConfig) {
|
|
138
|
-
throw new Error(`Function not found: ${funcName}`);
|
|
139
|
-
}
|
|
140
|
-
const wireWithSession = {
|
|
141
|
-
...wire,
|
|
142
|
-
...(userSession && createFunctionSessionWireProps(userSession)),
|
|
143
|
-
};
|
|
144
|
-
return (await funcConfig.func(allServices, data, wireWithSession));
|
|
145
|
-
};
|
|
146
135
|
export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServices, createWireServices, data, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedChannelMiddleware, wireChannelMiddleware, inheritedPermissions, wirePermissions, coerceDataFromSchema, tags = [], wire, sessionService, credentialWireService, packageName = null, }) => {
|
|
147
136
|
wire.wireType ??= wireType;
|
|
148
137
|
wire.wireId ??= wireId;
|
|
@@ -230,7 +219,7 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
|
|
|
230
219
|
}
|
|
231
220
|
}
|
|
232
221
|
}
|
|
233
|
-
else
|
|
222
|
+
else {
|
|
234
223
|
if (wiringAuth === false || funcConfig.auth === false) {
|
|
235
224
|
resolvedSingletonServices.logger.warn(`Function '${funcName}' requires a session but auth was explicitly disabled — use pikkuSessionlessFunc instead.`);
|
|
236
225
|
}
|
|
@@ -238,15 +227,6 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
|
|
|
238
227
|
throw new ForbiddenError('Authentication required');
|
|
239
228
|
}
|
|
240
229
|
}
|
|
241
|
-
else {
|
|
242
|
-
// TODO: Remove after a couple of releases — backward compat for
|
|
243
|
-
// generated metadata that doesn't include the `sessionless` field yet.
|
|
244
|
-
if (wiringAuth === true || funcConfig.auth === true) {
|
|
245
|
-
if (!session) {
|
|
246
|
-
throw new ForbiddenError('Authentication required');
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
230
|
if (session?.readonly && !funcMeta.readonly) {
|
|
251
231
|
throw new ReadonlySessionError();
|
|
252
232
|
}
|
|
@@ -149,6 +149,8 @@ export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any,
|
|
|
149
149
|
readonly?: boolean;
|
|
150
150
|
deploy?: 'serverless' | 'server' | 'auto';
|
|
151
151
|
approvalRequired?: boolean;
|
|
152
|
+
/** When false, workflow steps calling this function are dispatched via the queue instead of running inline. Defaults to true (inline). */
|
|
153
|
+
inline?: boolean;
|
|
152
154
|
audit?: boolean | {
|
|
153
155
|
durability?: 'best-effort' | 'transactional';
|
|
154
156
|
};
|
package/dist/handle-error.js
CHANGED
|
@@ -23,6 +23,7 @@ export const handleHTTPError = (e, http, traceId, logger, logWarningsForStatusCo
|
|
|
23
23
|
// Set status and response body
|
|
24
24
|
http?.response?.status(errorResponse.status);
|
|
25
25
|
http?.response?.json({
|
|
26
|
+
name: e instanceof Error ? e.name : undefined,
|
|
26
27
|
message: e instanceof Error && e.message && e.message !== 'An error occurred'
|
|
27
28
|
? e.message
|
|
28
29
|
: errorResponse.message,
|
package/dist/index.d.ts
CHANGED
|
@@ -47,7 +47,8 @@ export type { PikkuPackageState } from './types/state.types.js';
|
|
|
47
47
|
export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
|
|
48
48
|
export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, checkAuthPermissions, } from './permissions.js';
|
|
49
49
|
export { isSerializable, stopSingletonServices } from './utils.js';
|
|
50
|
-
export { getSingletonServices, getCreateWireServices } from './pikku-state.js';
|
|
50
|
+
export { getSingletonServices, getCreateWireServices, setSingletonServices, } from './pikku-state.js';
|
|
51
|
+
export { clearPikkuRuntimeState } from './test-utils.js';
|
|
51
52
|
export { type ScheduledTaskInfo, type ScheduledTaskSummary, } from './services/scheduler-service.js';
|
|
52
53
|
export { SchedulerService } from './services/scheduler-service.js';
|
|
53
|
-
export type { Private, Secret, Classification, AnonymizeStrategy, ColumnClassification, ClassificationManifest, } from './data-classification.js';
|
|
54
|
+
export type { Private, Pii, Secret, Classification, AnonymizeStrategy, ColumnClassification, ClassificationManifest, } from './data-classification.js';
|
package/dist/index.js
CHANGED
|
@@ -17,5 +17,6 @@ export { wireAddon } from './wirings/rpc/wire-addon.js';
|
|
|
17
17
|
export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
|
|
18
18
|
export { addTagPermission, addTagPermission as addPermission, addGlobalPermission, checkAuthPermissions, } from './permissions.js';
|
|
19
19
|
export { isSerializable, stopSingletonServices } from './utils.js';
|
|
20
|
-
export { getSingletonServices, getCreateWireServices } from './pikku-state.js';
|
|
20
|
+
export { getSingletonServices, getCreateWireServices, setSingletonServices, } from './pikku-state.js';
|
|
21
|
+
export { clearPikkuRuntimeState } from './test-utils.js';
|
|
21
22
|
export { SchedulerService } from './services/scheduler-service.js';
|
package/dist/pikku-state.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export declare const pikkuState: <Type extends keyof PikkuPackageState, Content
|
|
|
24
24
|
export declare const initializePikkuState: (packageName: string) => void;
|
|
25
25
|
export declare const resetPikkuState: () => void;
|
|
26
26
|
export declare const getSingletonServices: () => CoreSingletonServices;
|
|
27
|
+
export declare const setSingletonServices: (services: CoreSingletonServices) => void;
|
|
27
28
|
export declare const getCreateWireServices: () => CreateWireServices | undefined;
|
|
28
29
|
/**
|
|
29
30
|
* Register service factories for an addon package.
|
package/dist/pikku-state.js
CHANGED
|
@@ -157,6 +157,9 @@ export const getSingletonServices = () => {
|
|
|
157
157
|
throw new Error('Singleton services not initialized');
|
|
158
158
|
return services;
|
|
159
159
|
};
|
|
160
|
+
export const setSingletonServices = (services) => {
|
|
161
|
+
pikkuState(null, 'package', 'singletonServices', services);
|
|
162
|
+
};
|
|
160
163
|
export const getCreateWireServices = () => {
|
|
161
164
|
return pikkuState(null, 'package', 'factories')?.createWireServices;
|
|
162
165
|
};
|
|
@@ -11,4 +11,5 @@ export declare class GopassSecretService implements SecretService {
|
|
|
11
11
|
hasSecret(key: string): Promise<boolean>;
|
|
12
12
|
setSecret(key: string, value: unknown): Promise<void>;
|
|
13
13
|
deleteSecret(key: string): Promise<void>;
|
|
14
|
+
getSecrets(keys: string[]): Promise<Record<string, unknown>>;
|
|
14
15
|
}
|
|
@@ -64,4 +64,13 @@ export class GopassSecretService {
|
|
|
64
64
|
// Ignore errors if secret doesn't exist
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
async getSecrets(keys) {
|
|
68
|
+
const results = await Promise.allSettled(keys.map((k) => this.getSecret(k)));
|
|
69
|
+
const out = {};
|
|
70
|
+
keys.forEach((key, i) => {
|
|
71
|
+
if (results[i].status === 'fulfilled')
|
|
72
|
+
out[key] = results[i].value;
|
|
73
|
+
});
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
67
76
|
}
|
|
@@ -13,4 +13,5 @@ export declare class LocalSecretService implements SecretService {
|
|
|
13
13
|
setSecret(key: string, value: unknown): Promise<void>;
|
|
14
14
|
hasSecret(key: string): Promise<boolean>;
|
|
15
15
|
deleteSecret(key: string): Promise<void>;
|
|
16
|
+
getSecrets(keys: string[]): Promise<Record<string, unknown>>;
|
|
16
17
|
}
|
|
@@ -41,4 +41,13 @@ export class LocalSecretService {
|
|
|
41
41
|
async deleteSecret(key) {
|
|
42
42
|
this.localSecrets.delete(key);
|
|
43
43
|
}
|
|
44
|
+
async getSecrets(keys) {
|
|
45
|
+
const results = await Promise.allSettled(keys.map((k) => this.getSecret(k)));
|
|
46
|
+
const out = {};
|
|
47
|
+
keys.forEach((key, i) => {
|
|
48
|
+
if (results[i].status === 'fulfilled')
|
|
49
|
+
out[key] = results[i].value;
|
|
50
|
+
});
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
44
53
|
}
|
|
@@ -285,15 +285,26 @@ export class LocalMetaService {
|
|
|
285
285
|
if (!emailsMeta.src) {
|
|
286
286
|
throw new Error('No generated email metadata found. Run `pikku emails generate`.');
|
|
287
287
|
}
|
|
288
|
+
// emailsMeta.src is an absolute path resolved by the CLI at generation time.
|
|
289
|
+
// Read directly rather than through readProjectFile (which prepends the project
|
|
290
|
+
// root and produces a wrong compound path when baseDir is absolute).
|
|
288
291
|
const baseDir = emailsMeta.src;
|
|
292
|
+
const readEmailFile = async (rel) => {
|
|
293
|
+
try {
|
|
294
|
+
return await readFile(join(baseDir, rel), 'utf-8');
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
289
300
|
const [themeRaw, localeRaw, layoutRaw, footerRaw, templateHtml, templateSubject, templateText,] = await Promise.all([
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
301
|
+
readEmailFile('theme.json'),
|
|
302
|
+
readEmailFile(`locales/${locale}.json`),
|
|
303
|
+
readEmailFile('partials/layout.html'),
|
|
304
|
+
readEmailFile('partials/footer.html'),
|
|
305
|
+
readEmailFile(`templates/${templateName}.html`),
|
|
306
|
+
readEmailFile(`templates/${templateName}.subject.txt`),
|
|
307
|
+
readEmailFile(`templates/${templateName}.text.txt`),
|
|
297
308
|
]);
|
|
298
309
|
return {
|
|
299
310
|
theme: themeRaw ? JSON.parse(themeRaw) : {},
|
|
@@ -12,4 +12,5 @@ export declare class ScopedSecretService implements SecretService {
|
|
|
12
12
|
hasSecret(key: string): Promise<boolean>;
|
|
13
13
|
setSecret(_key: string, _value: unknown): Promise<void>;
|
|
14
14
|
deleteSecret(_key: string): Promise<void>;
|
|
15
|
+
getSecrets(keys: string[]): Promise<Record<string, unknown>>;
|
|
15
16
|
}
|
|
@@ -28,4 +28,8 @@ export class ScopedSecretService {
|
|
|
28
28
|
async deleteSecret(_key) {
|
|
29
29
|
throw new Error('deleteSecret is not allowed in scoped secret service');
|
|
30
30
|
}
|
|
31
|
+
async getSecrets(keys) {
|
|
32
|
+
const allowed = keys.filter((k) => this.allowedKeys.has(k));
|
|
33
|
+
return this.secrets.getSecrets(allowed);
|
|
34
|
+
}
|
|
31
35
|
}
|
|
@@ -28,4 +28,11 @@ export interface SecretService {
|
|
|
28
28
|
* @returns A promise that resolves when the secret is deleted.
|
|
29
29
|
*/
|
|
30
30
|
deleteSecret(key: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Retrieves multiple secrets in a single batch operation.
|
|
33
|
+
* Returns a map of key → value for successfully fetched secrets; missing
|
|
34
|
+
* keys are omitted rather than throwing.
|
|
35
|
+
* @param keys - The keys of the secrets to retrieve.
|
|
36
|
+
*/
|
|
37
|
+
getSecrets(keys: string[]): Promise<Record<string, unknown>>;
|
|
31
38
|
}
|
|
@@ -24,6 +24,7 @@ export declare class TypedSecretService<TMap = Record<string, unknown>> implemen
|
|
|
24
24
|
hasSecret(key: string): Promise<boolean>;
|
|
25
25
|
setSecret<K extends string>(key: K, value: K extends keyof TMap ? TMap[K] : unknown): Promise<void>;
|
|
26
26
|
deleteSecret(key: string): Promise<void>;
|
|
27
|
+
getSecrets(keys: string[]): Promise<Record<string, unknown>>;
|
|
27
28
|
getAllStatus(): Promise<CredentialStatus[]>;
|
|
28
29
|
getMissing(): Promise<CredentialStatus[]>;
|
|
29
30
|
}
|
|
@@ -17,6 +17,9 @@ export class TypedSecretService {
|
|
|
17
17
|
async deleteSecret(key) {
|
|
18
18
|
return this.secrets.deleteSecret(key);
|
|
19
19
|
}
|
|
20
|
+
async getSecrets(keys) {
|
|
21
|
+
return this.secrets.getSecrets(keys);
|
|
22
|
+
}
|
|
20
23
|
async getAllStatus() {
|
|
21
24
|
const results = [];
|
|
22
25
|
for (const [secretId, meta] of Object.entries(this.credentialsMeta)) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clears all runtime caches between test scenarios without touching registered
|
|
3
|
+
* routes, functions, or middleware groups (which are set at module load time
|
|
4
|
+
* and must persist for the whole test run).
|
|
5
|
+
*
|
|
6
|
+
* Call this alongside your DB snapshot restore in your Before hook:
|
|
7
|
+
*
|
|
8
|
+
* ```typescript
|
|
9
|
+
* Before(async function() {
|
|
10
|
+
* await restoreSnapshot()
|
|
11
|
+
* clearPikkuRuntimeState()
|
|
12
|
+
* setSingletonServices(bundle.services)
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Only works when NODE_ENV === 'test'. Logs an error and returns otherwise.
|
|
17
|
+
*/
|
|
18
|
+
export declare const clearPikkuRuntimeState: () => void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getAllPackageStates } from './pikku-state.js';
|
|
2
|
+
import { clearMiddlewareCache } from './middleware-runner.js';
|
|
3
|
+
import { clearPermissionsCache } from './permissions.js';
|
|
4
|
+
import { clearChannelMiddlewareCache } from './wirings/channel/channel-middleware-runner.js';
|
|
5
|
+
/**
|
|
6
|
+
* Clears all runtime caches between test scenarios without touching registered
|
|
7
|
+
* routes, functions, or middleware groups (which are set at module load time
|
|
8
|
+
* and must persist for the whole test run).
|
|
9
|
+
*
|
|
10
|
+
* Call this alongside your DB snapshot restore in your Before hook:
|
|
11
|
+
*
|
|
12
|
+
* ```typescript
|
|
13
|
+
* Before(async function() {
|
|
14
|
+
* await restoreSnapshot()
|
|
15
|
+
* clearPikkuRuntimeState()
|
|
16
|
+
* setSingletonServices(bundle.services)
|
|
17
|
+
* })
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Only works when NODE_ENV === 'test'. Logs an error and returns otherwise.
|
|
21
|
+
*/
|
|
22
|
+
export const clearPikkuRuntimeState = () => {
|
|
23
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
24
|
+
console.error('[pikku] clearPikkuRuntimeState() must only be called in test environments. Ignoring.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
clearMiddlewareCache();
|
|
28
|
+
clearPermissionsCache();
|
|
29
|
+
clearChannelMiddlewareCache();
|
|
30
|
+
for (const packageState of getAllPackageStates().values()) {
|
|
31
|
+
packageState.package.singletonServices = null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -82,6 +82,8 @@ export type FunctionRuntimeMeta = {
|
|
|
82
82
|
readonly?: boolean;
|
|
83
83
|
deploy?: 'serverless' | 'server' | 'auto';
|
|
84
84
|
sessionless?: boolean;
|
|
85
|
+
/** When false, workflow steps calling this function are dispatched via the queue. Defaults to true (inline). */
|
|
86
|
+
inline?: boolean;
|
|
85
87
|
version?: number;
|
|
86
88
|
approvalRequired?: boolean;
|
|
87
89
|
approvalDescription?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
2
|
-
import {
|
|
2
|
+
import { runPikkuFunc } from '../../function/function-runner.js';
|
|
3
3
|
import { combineChannelMiddleware, wrapChannelWithMiddleware, } from './channel-middleware-runner.js';
|
|
4
4
|
/**
|
|
5
5
|
* Runs a channel lifecycle function (onConnect or onDisconnect) with proper middleware handling.
|
|
@@ -37,7 +37,14 @@ export const runChannelLifecycleWithMiddleware = async ({ channelConfig, meta, l
|
|
|
37
37
|
wire = wrapChannelWithMiddleware(wire, services, allChannelMiddleware);
|
|
38
38
|
}
|
|
39
39
|
const runLifecycle = async () => {
|
|
40
|
-
return await
|
|
40
|
+
return await runPikkuFunc('channel', channelConfig.name, meta.pikkuFuncId, {
|
|
41
|
+
singletonServices: services,
|
|
42
|
+
data: () => data,
|
|
43
|
+
wire,
|
|
44
|
+
tags: meta.tags ?? [],
|
|
45
|
+
inheritedPermissions: meta.permissions,
|
|
46
|
+
packageName: meta.packageName ?? null,
|
|
47
|
+
});
|
|
41
48
|
};
|
|
42
49
|
if (allMiddleware.length > 0) {
|
|
43
50
|
return await runMiddleware(services, wire, allMiddleware, runLifecycle);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Logger } from '../../../services/logger.js';
|
|
1
2
|
import type { BinaryData } from '../channel.types.js';
|
|
2
3
|
import { PikkuAbstractChannelHandler } from '../pikku-abstract-channel-handler.js';
|
|
3
4
|
export declare class PikkuLocalChannelHandler<OpeningData = unknown, Out = unknown> extends PikkuAbstractChannelHandler<OpeningData, Out> {
|
|
@@ -7,14 +8,16 @@ export declare class PikkuLocalChannelHandler<OpeningData = unknown, Out = unkno
|
|
|
7
8
|
private closeCallbacks;
|
|
8
9
|
private sendCallback?;
|
|
9
10
|
private sendBinaryCallback?;
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
private logger?;
|
|
12
|
+
constructor(channelId: string, channelName: string, openingData: OpeningData, logger?: Logger);
|
|
13
|
+
registerOnOpen(callback: () => Promise<void> | void): void;
|
|
14
|
+
open(): Promise<void>;
|
|
12
15
|
registerOnMessage(callback: (data: any) => Promise<unknown>): void;
|
|
13
16
|
message(data: unknown): Promise<unknown>;
|
|
14
17
|
registerOnBinaryMessage(callback: (data: BinaryData) => Promise<BinaryData | void> | BinaryData | void): void;
|
|
15
18
|
binaryMessage(data: BinaryData): Promise<BinaryData | void>;
|
|
16
|
-
registerOnClose(callback: () => void): void;
|
|
17
|
-
close(): void
|
|
19
|
+
registerOnClose(callback: () => Promise<void> | void): void;
|
|
20
|
+
close(): Promise<void>;
|
|
18
21
|
registerOnSend(send: (message: Out) => void): void;
|
|
19
22
|
send(message: Out, isBinary?: boolean): void;
|
|
20
23
|
registerOnSendBinary(send: (data: BinaryData) => void): void;
|
|
@@ -6,13 +6,18 @@ export class PikkuLocalChannelHandler extends PikkuAbstractChannelHandler {
|
|
|
6
6
|
closeCallbacks = [];
|
|
7
7
|
sendCallback;
|
|
8
8
|
sendBinaryCallback;
|
|
9
|
+
logger;
|
|
10
|
+
constructor(channelId, channelName, openingData, logger) {
|
|
11
|
+
super(channelId, channelName, openingData);
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
9
14
|
registerOnOpen(callback) {
|
|
10
15
|
this.openCallBack = callback;
|
|
11
16
|
}
|
|
12
|
-
open() {
|
|
17
|
+
async open() {
|
|
13
18
|
this.getChannel().state = 'open';
|
|
14
19
|
if (this.openCallBack) {
|
|
15
|
-
this.openCallBack();
|
|
20
|
+
await this.openCallBack();
|
|
16
21
|
}
|
|
17
22
|
}
|
|
18
23
|
registerOnMessage(callback) {
|
|
@@ -30,13 +35,16 @@ export class PikkuLocalChannelHandler extends PikkuAbstractChannelHandler {
|
|
|
30
35
|
registerOnClose(callback) {
|
|
31
36
|
this.closeCallbacks.push(callback);
|
|
32
37
|
}
|
|
33
|
-
close() {
|
|
38
|
+
async close() {
|
|
34
39
|
if (this.getChannel().state === 'closed') {
|
|
35
40
|
return;
|
|
36
41
|
}
|
|
37
42
|
super.close();
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
const results = await Promise.allSettled(this.closeCallbacks.map((cb) => cb()));
|
|
44
|
+
for (const result of results) {
|
|
45
|
+
if (result.status === 'rejected') {
|
|
46
|
+
this.logger?.error('Error in channel close callback:', result.reason);
|
|
47
|
+
}
|
|
40
48
|
}
|
|
41
49
|
}
|
|
42
50
|
registerOnSend(send) {
|
|
@@ -59,7 +59,7 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
59
59
|
}
|
|
60
60
|
const main = async () => {
|
|
61
61
|
try {
|
|
62
|
-
channelHandler = new PikkuLocalChannelHandler(channelId, channelConfig.name, openingData);
|
|
62
|
+
channelHandler = new PikkuLocalChannelHandler(channelId, channelConfig.name, openingData, singletonServices.logger);
|
|
63
63
|
const channel = channelHandler.getChannel();
|
|
64
64
|
const wire = {
|
|
65
65
|
channel,
|
|
@@ -484,7 +484,16 @@ export class PikkuWorkflowService {
|
|
|
484
484
|
* through to the inline execution path.
|
|
485
485
|
*/
|
|
486
486
|
async dispatchStep(runId, stepName, rpcName, data, stepOptions) {
|
|
487
|
-
if (
|
|
487
|
+
if (!getSingletonServices()?.queueService) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
// Functions default to inline execution. Only dispatch via queue when the
|
|
491
|
+
// function explicitly sets inline: false.
|
|
492
|
+
const functionsMeta = pikkuState(null, 'function', 'meta');
|
|
493
|
+
const rpcFuncId = pikkuState(null, 'rpc', 'meta')[rpcName];
|
|
494
|
+
const rpcMeta = typeof rpcFuncId === 'string' ? functionsMeta[rpcFuncId] : undefined;
|
|
495
|
+
const forceQueue = rpcMeta?.inline === false;
|
|
496
|
+
if (!forceQueue && this.isInline(runId)) {
|
|
488
497
|
return false;
|
|
489
498
|
}
|
|
490
499
|
const retries = stepOptions?.retries ?? 0;
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
export type Private<T> = T & { readonly
|
|
2
|
-
export type
|
|
1
|
+
export type Private<T> = T & { readonly __classification__: 'private' }
|
|
2
|
+
export type Pii<T> = T & { readonly __classification__: 'pii' }
|
|
3
|
+
export type Secret<T> = T & { readonly __classification__: 'secret' }
|
|
3
4
|
|
|
4
|
-
export type Classification = 'public' | 'private' | 'secret'
|
|
5
|
-
export type AnonymizeStrategy =
|
|
5
|
+
export type Classification = 'public' | 'private' | 'pii' | 'secret'
|
|
6
|
+
export type AnonymizeStrategy =
|
|
7
|
+
| 'fake:email'
|
|
8
|
+
| 'fake:name'
|
|
9
|
+
| 'hash'
|
|
10
|
+
| 'keep'
|
|
11
|
+
| null
|
|
6
12
|
|
|
7
13
|
export interface ColumnClassification {
|
|
8
14
|
classification: Classification
|
|
9
15
|
anonymize_strategy: AnonymizeStrategy
|
|
16
|
+
description?: string
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
export type ClassificationManifest = {
|
|
@@ -308,6 +308,7 @@ describe('pikkuDevReloader', { concurrency: false }, () => {
|
|
|
308
308
|
pikkuFuncId: 'hotTask',
|
|
309
309
|
inputSchemaName: null,
|
|
310
310
|
outputSchemaName: null,
|
|
311
|
+
sessionless: true,
|
|
311
312
|
}
|
|
312
313
|
|
|
313
314
|
addFunction('hotTask', {
|
|
@@ -375,6 +376,7 @@ describe('pikkuDevReloader', { concurrency: false }, () => {
|
|
|
375
376
|
pikkuFuncId: 'queue_hot-queue',
|
|
376
377
|
inputSchemaName: null,
|
|
377
378
|
outputSchemaName: null,
|
|
379
|
+
sessionless: true,
|
|
378
380
|
}
|
|
379
381
|
|
|
380
382
|
addFunction('queue_hot-queue', {
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
getAllFunctionNames,
|
|
6
6
|
getFunctionNames,
|
|
7
7
|
runPikkuFunc,
|
|
8
|
-
runPikkuFuncDirectly,
|
|
9
8
|
} from './function-runner.js'
|
|
10
9
|
import { addTagMiddleware, addTagPermission } from '../index.js'
|
|
11
10
|
import { resetPikkuState, pikkuState } from '../pikku-state.js'
|
|
@@ -33,6 +32,7 @@ const addTestFunction = (funcName: string, funcConfig: any) => {
|
|
|
33
32
|
pikkuFuncId: funcName,
|
|
34
33
|
inputSchemaName: null,
|
|
35
34
|
outputSchemaName: null,
|
|
35
|
+
sessionless: true,
|
|
36
36
|
middleware,
|
|
37
37
|
permissions,
|
|
38
38
|
}
|
|
@@ -676,7 +676,7 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
676
676
|
)
|
|
677
677
|
})
|
|
678
678
|
|
|
679
|
-
test('should
|
|
679
|
+
test('should require auth when sessionless metadata is absent (undefined defaults to session-required)', async () => {
|
|
680
680
|
addTestFunction('legacyAuth', {
|
|
681
681
|
func: async () => 'ok',
|
|
682
682
|
auth: true,
|
|
@@ -1227,32 +1227,6 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
1227
1227
|
})
|
|
1228
1228
|
|
|
1229
1229
|
describe('function-runner helpers', () => {
|
|
1230
|
-
test('runPikkuFuncDirectly should pass through the provided wire and session helpers', async () => {
|
|
1231
|
-
let receivedWire: any
|
|
1232
|
-
const sessionService = new PikkuSessionService({
|
|
1233
|
-
get: async () => undefined,
|
|
1234
|
-
} as any)
|
|
1235
|
-
|
|
1236
|
-
addTestFunction('directFunc', {
|
|
1237
|
-
func: async (_services: any, _data: any, wire: any) => {
|
|
1238
|
-
receivedWire = wire
|
|
1239
|
-
return 'ok'
|
|
1240
|
-
},
|
|
1241
|
-
})
|
|
1242
|
-
|
|
1243
|
-
const result = await runPikkuFuncDirectly(
|
|
1244
|
-
'directFunc',
|
|
1245
|
-
mockServices,
|
|
1246
|
-
{ traceId: 'trace-1' },
|
|
1247
|
-
{ hello: 'world' },
|
|
1248
|
-
sessionService
|
|
1249
|
-
)
|
|
1250
|
-
|
|
1251
|
-
assert.equal(result, 'ok')
|
|
1252
|
-
assert.equal(receivedWire.traceId, 'trace-1')
|
|
1253
|
-
assert.equal(typeof receivedWire.getSession, 'function')
|
|
1254
|
-
})
|
|
1255
|
-
|
|
1256
1230
|
test('getFunctionNames and getAllFunctionNames should include addon namespaces', () => {
|
|
1257
1231
|
addTestFunction('rootFunc', { func: async () => 'ok' })
|
|
1258
1232
|
pikkuState(null, 'addons', 'packages').set('stripe', {
|