@pikku/core 0.12.23 → 0.12.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/data-classification.d.ts +16 -0
  3. package/dist/data-classification.js +1 -0
  4. package/dist/function/function-runner.js +62 -15
  5. package/dist/function/functions.types.d.ts +8 -5
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.js +1 -0
  8. package/dist/middleware-runner.d.ts +2 -2
  9. package/dist/permissions.d.ts +1 -1
  10. package/dist/services/ai-agent-runner-service.d.ts +3 -0
  11. package/dist/services/audit-service.d.ts +50 -0
  12. package/dist/services/audit-service.js +106 -0
  13. package/dist/services/content-service.d.ts +1 -0
  14. package/dist/services/email-service.d.ts +36 -0
  15. package/dist/services/email-service.js +1 -0
  16. package/dist/services/gopass-secrets.d.ts +2 -35
  17. package/dist/services/gopass-secrets.js +10 -40
  18. package/dist/services/index.d.ts +5 -1
  19. package/dist/services/index.js +2 -0
  20. package/dist/services/local-content.js +1 -0
  21. package/dist/services/local-email-service.d.ts +4 -0
  22. package/dist/services/local-email-service.js +26 -0
  23. package/dist/services/local-secrets.d.ts +3 -36
  24. package/dist/services/local-secrets.js +12 -52
  25. package/dist/services/local-variables.d.ts +3 -5
  26. package/dist/services/local-variables.js +10 -10
  27. package/dist/services/meta-service.d.ts +36 -0
  28. package/dist/services/meta-service.js +59 -0
  29. package/dist/services/scoped-secret-service.d.ts +2 -3
  30. package/dist/services/scoped-secret-service.js +2 -6
  31. package/dist/services/secret-service.d.ts +5 -12
  32. package/dist/services/typed-secret-service.d.ts +3 -4
  33. package/dist/services/typed-secret-service.js +2 -5
  34. package/dist/services/typed-variables-service.d.ts +3 -6
  35. package/dist/services/typed-variables-service.js +0 -6
  36. package/dist/services/variables-service.d.ts +2 -4
  37. package/dist/testing/service-tests.js +13 -13
  38. package/dist/types/core.types.d.ts +15 -1
  39. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +2 -0
  40. package/dist/wirings/ai-agent/ai-agent-prepare.js +12 -3
  41. package/dist/wirings/ai-agent/ai-agent-runner.js +1 -1
  42. package/dist/wirings/ai-agent/ai-agent-stream.js +1 -1
  43. package/dist/wirings/ai-agent/ai-agent-utils.d.ts +1 -0
  44. package/dist/wirings/ai-agent/ai-agent-utils.js +1 -0
  45. package/dist/wirings/ai-agent/ai-agent.types.d.ts +4 -0
  46. package/dist/wirings/oauth2/oauth2-client.js +3 -3
  47. package/dist/wirings/rpc/rpc-runner.js +9 -2
  48. package/package.json +1 -1
  49. package/src/data-classification.ts +15 -0
  50. package/src/function/function-runner.test.ts +255 -0
  51. package/src/function/function-runner.ts +66 -20
  52. package/src/function/functions.types.ts +26 -11
  53. package/src/index.ts +44 -0
  54. package/src/middleware-runner.ts +10 -10
  55. package/src/permissions.ts +4 -4
  56. package/src/services/ai-agent-runner-service.ts +3 -0
  57. package/src/services/audit-service.test.ts +44 -0
  58. package/src/services/audit-service.ts +198 -0
  59. package/src/services/content-service.ts +1 -0
  60. package/src/services/email-service.ts +46 -0
  61. package/src/services/gopass-secrets.ts +10 -42
  62. package/src/services/index.ts +33 -0
  63. package/src/services/local-content.ts +1 -0
  64. package/src/services/local-email-service.test.ts +108 -0
  65. package/src/services/local-email-service.ts +30 -0
  66. package/src/services/local-secrets.test.ts +10 -10
  67. package/src/services/local-secrets.ts +16 -55
  68. package/src/services/local-variables.test.ts +4 -4
  69. package/src/services/local-variables.ts +12 -17
  70. package/src/services/meta-service.ts +115 -0
  71. package/src/services/scoped-secret-service.test.ts +10 -11
  72. package/src/services/scoped-secret-service.ts +4 -9
  73. package/src/services/secret-service.ts +5 -12
  74. package/src/services/typed-secret-service.test.ts +16 -17
  75. package/src/services/typed-secret-service.ts +5 -9
  76. package/src/services/typed-variables-service.test.ts +5 -5
  77. package/src/services/typed-variables-service.ts +5 -17
  78. package/src/services/variables-service.ts +2 -4
  79. package/src/testing/service-tests.ts +13 -13
  80. package/src/types/core.types.ts +20 -2
  81. package/src/wirings/ai-agent/ai-agent-prepare.ts +17 -3
  82. package/src/wirings/ai-agent/ai-agent-runner.test.ts +105 -0
  83. package/src/wirings/ai-agent/ai-agent-runner.ts +1 -1
  84. package/src/wirings/ai-agent/ai-agent-stream.ts +1 -1
  85. package/src/wirings/ai-agent/ai-agent-utils.ts +1 -0
  86. package/src/wirings/ai-agent/ai-agent.types.ts +4 -0
  87. package/src/wirings/oauth2/oauth2-client.test.ts +2 -3
  88. package/src/wirings/oauth2/oauth2-client.ts +3 -3
  89. package/src/wirings/rpc/rpc-runner.ts +9 -2
  90. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,33 @@
1
+ ## 0.12.25
2
+
3
+ ### Patch Changes
4
+
5
+ - 665bdb0: Add end-to-end data classification for SQLite and Postgres projects.
6
+
7
+ **Core (`@pikku/core`):** New `Private<T>` and `Secret<T>` intersection brands, `ClassificationManifest`, `ColumnClassification`, and `AnonymizeStrategy` types exported from `data-classification.ts`.
8
+
9
+ **CLI (`@pikku/cli`):**
10
+ - SQL comment annotations: `-- @public`, `-- @private[:strategy]`, `-- @secret[:strategy]` on `CREATE TABLE` columns and `ALTER TABLE ... ADD COLUMN` statements. Unannotated columns default to `private`.
11
+ - `pikku db migrate` now emits a `classification.gen.ts` manifest alongside `schema.d.ts`.
12
+ - New `pikku db audit` command — prints a per-column classification summary and warns on `private`/`secret` columns with no anonymize strategy.
13
+ - Postgres dialect support in `resolveDb`, `PostgresMigrationExecutor`, and `PostgresIntrospector`.
14
+
15
+ **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.
16
+
17
+ ## 0.12.24
18
+
19
+ ### Patch Changes
20
+
21
+ - c02275f: Add per-request API key override to AI agent runner
22
+
23
+ `VercelAIAgentRunner` gains an optional `providerFactory` constructor param and a `withApiKey(apiKey)` method that forks a new runner scoped to a given key without touching the global singleton.
24
+
25
+ `RunAIAgentParams` gains an optional `getCredential` accessor so callers can thread per-request credentials (e.g. a user's `AI_API_KEY` from the credential wire service) into `prepareAgentRun`. If a credential is found and the runner supports `withApiKey`, the runner is forked before the agent executes.
26
+
27
+ `AIAgentRunnerService` interface gains the optional `withApiKey?` method.
28
+
29
+ - 0bd0433: Add `db.engine` and `db.pgVersion` to the CLI config types, and make local env-backed secrets fall back to raw strings when JSON parsing fails.
30
+
1
31
  ## 0.12.23
2
32
 
3
33
  ### Patch Changes
@@ -0,0 +1,16 @@
1
+ export type Private<T> = T & {
2
+ readonly __pii__: 'private';
3
+ };
4
+ export type Secret<T> = T & {
5
+ readonly __pii__: 'secret';
6
+ };
7
+ export type Classification = 'public' | 'private' | 'secret';
8
+ export type AnonymizeStrategy = 'fake:email' | 'fake:name' | 'hash' | 'keep' | null;
9
+ export interface ColumnClassification {
10
+ classification: Classification;
11
+ anonymize_strategy: AnonymizeStrategy;
12
+ }
13
+ export type ClassificationManifest = {
14
+ version: 1;
15
+ tables: Record<string, Record<string, ColumnClassification>>;
16
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -8,6 +8,7 @@ import { PikkuSessionService, createFunctionSessionWireProps, } from '../service
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';
11
+ import { resolveAuditConfig } from '../services/audit-service.js';
11
12
  import { rpcService } from '../wirings/rpc/rpc-runner.js';
12
13
  import { closeWireServices } from '../utils.js';
13
14
  async function resolveSession(wire, singletonServices, sessionService) {
@@ -165,6 +166,7 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
165
166
  if (!funcMeta) {
166
167
  throw new Error(`Function meta not found: ${funcName}`);
167
168
  }
169
+ const resolvedFunctionId = funcMeta.pikkuFuncId ?? funcName;
168
170
  // For addon packages, get or create their singleton services
169
171
  const resolvedSingletonServices = packageName
170
172
  ? await getOrCreatePackageSingletonServices(packageName, singletonServices)
@@ -192,6 +194,13 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
192
194
  new PikkuCredentialWireService(resolvedSingletonServices.credentialService, resolvedWire);
193
195
  Object.assign(resolvedWire, createWireServicesCredentialWireProps(resolvedCredentialWireService));
194
196
  }
197
+ const resolvedAuditConfig = resolveAuditConfig(funcConfig.audit);
198
+ const invocationWire = resolvedWire;
199
+ const previousFunctionId = invocationWire.functionId;
200
+ const previousAudit = invocationWire.audit;
201
+ const previousRpcDescriptor = Object.getOwnPropertyDescriptor(invocationWire, 'rpc');
202
+ invocationWire.functionId = resolvedFunctionId;
203
+ invocationWire.audit = resolvedAuditConfig;
195
204
  // Convert tags to PermissionMetadata and merge with inheritedPermissions
196
205
  let mergedInheritedPermissions;
197
206
  if (tags && tags.length > 0) {
@@ -205,15 +214,15 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
205
214
  }
206
215
  // Helper function to run permissions and execute the function
207
216
  const executeFunction = async () => {
208
- await resolveSession(resolvedWire, resolvedSingletonServices, sessionService);
217
+ await resolveSession(invocationWire, resolvedSingletonServices, sessionService);
209
218
  if (sessionService) {
210
- resolvedWire.session = sessionService.freezeInitial();
211
- resolvedWire.setSession = (s) => sessionService.set(s);
212
- resolvedWire.clearSession = () => sessionService.clear();
213
- resolvedWire.getSession = () => sessionService.get();
214
- resolvedWire.hasSessionChanged = () => sessionService.sessionChanged;
219
+ invocationWire.session = sessionService.freezeInitial();
220
+ invocationWire.setSession = (s) => sessionService.set(s);
221
+ invocationWire.clearSession = () => sessionService.clear();
222
+ invocationWire.getSession = () => sessionService.get();
223
+ invocationWire.hasSessionChanged = () => sessionService.sessionChanged;
215
224
  }
216
- const session = resolvedWire.session;
225
+ const session = invocationWire.session;
217
226
  if (funcMeta.sessionless) {
218
227
  if (wiringAuth === true || funcConfig.auth === true) {
219
228
  if (!session) {
@@ -263,22 +272,22 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
263
272
  funcInheritedPermissions: funcMeta.permissions,
264
273
  funcPermissions: funcConfig.permissions,
265
274
  services: resolvedSingletonServices,
266
- wire: resolvedWire,
275
+ wire: invocationWire,
267
276
  data: actualData,
268
277
  packageName,
269
278
  });
270
279
  }
271
280
  let wireServices;
272
281
  try {
273
- wireServices = (await resolvedCreateWireServices?.(resolvedSingletonServices, resolvedWire));
282
+ wireServices = (await resolvedCreateWireServices?.(resolvedSingletonServices, invocationWire));
274
283
  const services = wireServices && Object.keys(wireServices).length > 0
275
284
  ? { ...resolvedSingletonServices, ...wireServices }
276
285
  : resolvedSingletonServices;
277
286
  const callerPackageName = packageName;
278
- Object.defineProperty(resolvedWire, 'rpc', {
287
+ Object.defineProperty(invocationWire, 'rpc', {
279
288
  get() {
280
- const rpc = rpcService.getContextRPCService(services, resolvedWire, { sessionService }, 0, callerPackageName);
281
- Object.defineProperty(resolvedWire, 'rpc', {
289
+ const rpc = rpcService.getContextRPCService(services, invocationWire, { sessionService }, 0, callerPackageName);
290
+ Object.defineProperty(invocationWire, 'rpc', {
282
291
  value: rpc,
283
292
  writable: true,
284
293
  configurable: true,
@@ -288,7 +297,7 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
288
297
  configurable: true,
289
298
  enumerable: true,
290
299
  });
291
- return await funcConfig.func(services, actualData, resolvedWire);
300
+ return await funcConfig.func(services, actualData, invocationWire);
292
301
  }
293
302
  finally {
294
303
  if (wireServices && Object.keys(wireServices).length > 0) {
@@ -304,7 +313,45 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { singletonServic
304
313
  packageName,
305
314
  });
306
315
  if (allMiddleware.length > 0) {
307
- return (await runMiddleware(resolvedSingletonServices, resolvedWire, allMiddleware, executeFunction));
316
+ try {
317
+ return (await runMiddleware(resolvedSingletonServices, invocationWire, allMiddleware, executeFunction));
318
+ }
319
+ finally {
320
+ if (previousRpcDescriptor) {
321
+ Object.defineProperty(invocationWire, 'rpc', previousRpcDescriptor);
322
+ }
323
+ if (previousFunctionId === undefined) {
324
+ delete invocationWire.functionId;
325
+ }
326
+ else {
327
+ invocationWire.functionId = previousFunctionId;
328
+ }
329
+ if (previousAudit === undefined) {
330
+ delete invocationWire.audit;
331
+ }
332
+ else {
333
+ invocationWire.audit = previousAudit;
334
+ }
335
+ }
336
+ }
337
+ try {
338
+ return (await executeFunction());
339
+ }
340
+ finally {
341
+ if (previousRpcDescriptor) {
342
+ Object.defineProperty(invocationWire, 'rpc', previousRpcDescriptor);
343
+ }
344
+ if (previousFunctionId === undefined) {
345
+ delete invocationWire.functionId;
346
+ }
347
+ else {
348
+ invocationWire.functionId = previousFunctionId;
349
+ }
350
+ if (previousAudit === undefined) {
351
+ delete invocationWire.audit;
352
+ }
353
+ else {
354
+ invocationWire.audit = previousAudit;
355
+ }
308
356
  }
309
- return (await executeFunction());
310
357
  };
@@ -15,7 +15,7 @@ export type ZodLike<T = any> = StandardSchemaV1<T, T>;
15
15
  * @template Services - The services type, defaults to `CoreServices`.
16
16
  * @template Wire - The wire type, defaults to `PikkuWire<In, Out>`.
17
17
  */
18
- export type CorePikkuFunction<In, Out, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession, Wire extends PikkuWire<In, Out> = PikkuWire<In, Out, true, Session>> = (services: Services, data: In, wire: Wire) => Wire['channel'] extends null ? Promise<Out> : Promise<Out> | Promise<void>;
18
+ export type CorePikkuFunction<In, Out, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession, Wire extends PikkuWire<In, Out, true, Session> = PikkuWire<In, Out, true, Session>> = (services: Services, data: In, wire: Wire) => Wire['channel'] extends null ? Promise<Out> : Promise<Out> | Promise<void>;
19
19
  /**
20
20
  * Represents a core API function that can be used without a session.
21
21
  *
@@ -32,7 +32,7 @@ export type CorePikkuFunctionSessionless<In, Out, Services extends CoreSingleton
32
32
  * @template Services - The services type, defaults to `CoreServices`.
33
33
  * @template Session - The session type, defaults to `CoreUserSession`.
34
34
  */
35
- export type CorePikkuPermission<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, CoreUserSession, any, never, never> = PikkuWire<In, never, false, CoreUserSession, never, never, never>> = (services: Services, data: In, wire: Wire) => Promise<boolean>;
35
+ export type CorePikkuPermission<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, any, any, never, never> = PikkuWire<In, never, false, any, never, never, never>> = (services: Services, data: In, wire: Wire) => Promise<boolean>;
36
36
  /**
37
37
  * Configuration object for creating a permission with metadata
38
38
  *
@@ -40,7 +40,7 @@ export type CorePikkuPermission<In = any, Services extends CoreSingletonServices
40
40
  * @template Services - The services type, defaults to `CoreServices`.
41
41
  * @template Session - The session type, defaults to `CoreUserSession`.
42
42
  */
43
- export type CorePikkuPermissionConfig<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, CoreUserSession> = PikkuWire<In, never, false, CoreUserSession>> = {
43
+ export type CorePikkuPermissionConfig<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, any> = PikkuWire<In, never, false, any>> = {
44
44
  /** The permission function */
45
45
  func: CorePikkuPermission<In, Services, Wire>;
46
46
  /** Optional human-readable name for the permission */
@@ -73,7 +73,7 @@ export type CorePikkuPermissionConfig<In = any, Services extends CoreSingletonSe
73
73
  * })
74
74
  * ```
75
75
  */
76
- export declare const pikkuPermission: <In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PickRequired<PikkuWire<In, never, false, CoreUserSession>, "session"> = PickRequired<PikkuWire<In, never, false, CoreUserSession>, "session">>(permission: CorePikkuPermission<In, Services, Wire> | CorePikkuPermissionConfig<In, Services, Wire>) => CorePikkuPermission<In, Services, Wire>;
76
+ export declare const pikkuPermission: <In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PickRequired<PikkuWire<In, never, false, any>, "session"> = PickRequired<PikkuWire<In, never, false, any>, "session">>(permission: CorePikkuPermission<In, Services, Wire> | CorePikkuPermissionConfig<In, Services, Wire>) => CorePikkuPermission<In, Services, Wire>;
77
77
  /**
78
78
  * A factory function that takes input and returns a permission
79
79
  * Used when permissions need configuration/input parameters
@@ -82,7 +82,7 @@ export declare const pikkuPermission: <In = any, Services extends CoreSingletonS
82
82
  * @template Services - The services type, defaults to `CoreServices`.
83
83
  * @template Session - The session type, defaults to `CoreUserSession`.
84
84
  */
85
- export type CorePikkuPermissionFactory<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, CoreUserSession> = PikkuWire<In, never, false, CoreUserSession>> = (input: In) => CorePikkuPermission<any, Services, Wire>;
85
+ export type CorePikkuPermissionFactory<In = any, Services extends CoreSingletonServices = CoreServices, Wire extends PikkuWire<In, never, false, any> = PikkuWire<In, never, false, any>> = (input: In) => CorePikkuPermission<any, Services, Wire>;
86
86
  /**
87
87
  * Factory function for creating permission factories
88
88
  * Use this when your permission needs configuration/input parameters
@@ -149,6 +149,9 @@ export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any,
149
149
  readonly?: boolean;
150
150
  deploy?: 'serverless' | 'server' | 'auto';
151
151
  approvalRequired?: boolean;
152
+ audit?: boolean | {
153
+ durability?: 'best-effort' | 'transactional';
154
+ };
152
155
  approvalDescription?: any;
153
156
  func: PikkuFunction;
154
157
  auth?: boolean;
package/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export { NotFoundError } from './errors/errors.js';
22
22
  export type { EventHubService } from './wirings/channel/eventhub-service.js';
23
23
  export type { QueueService } from './wirings/queue/queue.types.js';
24
24
  export type { JWTService } from './services/jwt-service.js';
25
+ export type { EmailService, EmailTemplateReference, SendEmailInput, SendEmailResult, SendHTMLEmailInput, SendTemplateEmailInput, SendTextEmailInput, } from './services/email-service.js';
25
26
  export type { SecretService } from './services/secret-service.js';
26
27
  export type { VariablesService } from './services/variables-service.js';
27
28
  export type { ContentService, SignContentKeyArgs, SignURLArgs, GetUploadURLArgs, UploadURLResult, BucketKeyArgs, WriteFileArgs, CopyFileArgs, } from './services/content-service.js';
@@ -31,9 +32,12 @@ export type { GatewayService } from './services/gateway-service.js';
31
32
  export type { TriggerService } from './services/trigger-service.js';
32
33
  export type { SchemaService } from './services/schema-service.js';
33
34
  export type { SessionService } from './services/user-session-service.js';
35
+ export { NoopAuditService, createInvocationAudit, resolveAuditActorFromWire, resolveAuditConfig, } from './services/audit-service.js';
36
+ export type { AuditActor, AuditConfig, AuditDurability, AuditEvent, AuditEventBatch, AuditLog, AuditLogWriteInput, AuditOutcome, AuditService, AuditSource, ResolvedAuditConfig, } from './services/audit-service.js';
34
37
  export type { AIAgentRunnerService } from './services/ai-agent-runner-service.js';
35
38
  export type { AIRunStateService } from './services/ai-run-state-service.js';
36
39
  export type { AIStorageService } from './services/ai-storage-service.js';
40
+ export type { EmailsMeta, EmailTemplateMeta, EmailTemplateLocaleMeta, EmailTemplateAssets, MetaService, } from './services/meta-service.js';
37
41
  export type { HTTPMethod } from './wirings/http/http.types.js';
38
42
  export type { GraphNodeConfig } from './wirings/workflow/graph/workflow-graph.types.js';
39
43
  export { createGraph } from './wirings/workflow/graph/graph-node.js';
@@ -46,3 +50,4 @@ export { isSerializable, stopSingletonServices } from './utils.js';
46
50
  export { getSingletonServices, getCreateWireServices } from './pikku-state.js';
47
51
  export { type ScheduledTaskInfo, type ScheduledTaskSummary, } from './services/scheduler-service.js';
48
52
  export { SchedulerService } from './services/scheduler-service.js';
53
+ export type { Private, Secret, Classification, AnonymizeStrategy, ColumnClassification, ClassificationManifest, } from './data-classification.js';
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ export { runMCPTool, runMCPResource, runMCPPrompt, } from './wirings/mcp/mcp-run
11
11
  export { runQueueJob } from './wirings/queue/queue-runner.js';
12
12
  export { runScheduledTask } from './wirings/scheduler/scheduler-runner.js';
13
13
  export { NotFoundError } from './errors/errors.js';
14
+ export { NoopAuditService, createInvocationAudit, resolveAuditActorFromWire, resolveAuditConfig, } from './services/audit-service.js';
14
15
  export { createGraph } from './wirings/workflow/graph/graph-node.js';
15
16
  export { wireAddon } from './wirings/rpc/wire-addon.js';
16
17
  export { runMiddleware, addTagMiddleware, addTagMiddleware as addMiddleware, addGlobalMiddleware, } from './middleware-runner.js';
@@ -1,4 +1,4 @@
1
- import type { CoreSingletonServices, PikkuWire, CorePikkuMiddleware, CorePikkuMiddlewareGroup, PikkuWiringTypes, MiddlewareMetadata } from './types/core.types.js';
1
+ import type { CorePikkuMiddleware, CorePikkuMiddlewareGroup, PikkuWiringTypes, MiddlewareMetadata } from './types/core.types.js';
2
2
  /**
3
3
  * Runs a chain of middleware functions in sequence before executing the main function.
4
4
  *
@@ -16,7 +16,7 @@ import type { CoreSingletonServices, PikkuWire, CorePikkuMiddleware, CorePikkuMi
16
16
  * async () => { return await runMain(); }
17
17
  * );
18
18
  */
19
- export declare const runMiddleware: <Middleware extends CorePikkuMiddleware>(services: CoreSingletonServices, wire: PikkuWire, middlewares: readonly Middleware[], main?: () => Promise<unknown>) => Promise<unknown>;
19
+ export declare const runMiddleware: <Middleware extends CorePikkuMiddleware<any, any>>(services: Parameters<Middleware>[0], wire: Parameters<Middleware>[1], middlewares: readonly Middleware[], main?: () => Promise<unknown>) => Promise<unknown>;
20
20
  /**
21
21
  * Registers tag-scoped middleware. Applies to any wiring (HTTP, Channel,
22
22
  * Queue, Scheduler, MCP, CLI, Workflow, Agent) that carries the matching tag.
@@ -33,7 +33,7 @@ export declare const runPermissions: (wireType: PikkuWiringTypes, uid: string, {
33
33
  funcInheritedPermissions?: PermissionMetadata[];
34
34
  funcPermissions?: CorePermissionGroup | CorePikkuPermission[];
35
35
  services: CoreServices;
36
- wire: PikkuWire<any, never, any, never, never, never>;
36
+ wire: PikkuWire<any, never, any, CoreUserSession, never, never, never>;
37
37
  data: any;
38
38
  packageName?: string | null;
39
39
  }) => Promise<void>;
@@ -44,4 +44,7 @@ export type AIAgentStepResult = {
44
44
  export interface AIAgentRunnerService {
45
45
  stream(params: AIAgentRunnerParams, channel: AIStreamChannel): Promise<AIAgentStepResult>;
46
46
  run(params: AIAgentRunnerParams): Promise<AIAgentStepResult>;
47
+ /** Return a new runner that uses the given API key for every LLM call.
48
+ * Optional — runners that don't support per-key scoping leave this undefined. */
49
+ withApiKey?(apiKey: string): AIAgentRunnerService;
47
50
  }
@@ -0,0 +1,50 @@
1
+ import type { CoreUserSession, PikkuWire, PikkuWiringTypes } from '../types/core.types.js';
2
+ export type AuditDurability = 'best-effort' | 'transactional';
3
+ export type AuditOutcome = 'success' | 'failed' | 'denied';
4
+ export type AuditSource = 'auto' | 'explicit';
5
+ export type AuditConfig = boolean | {
6
+ durability?: AuditDurability;
7
+ };
8
+ export type ResolvedAuditConfig = {
9
+ durability: AuditDurability;
10
+ };
11
+ export type AuditActor = {
12
+ userId?: string;
13
+ orgId?: string;
14
+ pikkuUserId?: string;
15
+ };
16
+ export type AuditEvent = {
17
+ eventId?: string;
18
+ type: string;
19
+ source: AuditSource;
20
+ outcome?: AuditOutcome;
21
+ occurredAt: string;
22
+ functionId?: string;
23
+ wireType?: PikkuWiringTypes;
24
+ wireId?: string;
25
+ traceId?: string;
26
+ transactionId?: string | null;
27
+ queryId?: string | null;
28
+ actor?: AuditActor;
29
+ input?: unknown;
30
+ metadata?: Record<string, unknown>;
31
+ };
32
+ export type AuditEventBatch = AuditEvent[];
33
+ export interface AuditService {
34
+ audit(event: AuditEvent): Promise<void>;
35
+ write?(batch: AuditEventBatch): Promise<void>;
36
+ }
37
+ export declare class NoopAuditService implements AuditService {
38
+ audit(_event: AuditEvent): Promise<void>;
39
+ write(_batch: AuditEventBatch): Promise<void>;
40
+ }
41
+ export type AuditLogWriteInput = Omit<AuditEvent, 'occurredAt'>;
42
+ export interface AuditLog {
43
+ readonly config: ResolvedAuditConfig | undefined;
44
+ write(event: AuditLogWriteInput): Promise<void>;
45
+ flush(): Promise<void>;
46
+ close(): Promise<void>;
47
+ }
48
+ export declare const resolveAuditConfig: (config?: AuditConfig) => ResolvedAuditConfig | undefined;
49
+ export declare const createInvocationAudit: (service: AuditService, wire: PikkuWire<any, any, any, CoreUserSession>) => AuditLog;
50
+ export declare const resolveAuditActorFromWire: (wire: PikkuWire<any, any, any, CoreUserSession>) => AuditActor | undefined;
@@ -0,0 +1,106 @@
1
+ export class NoopAuditService {
2
+ async audit(_event) { }
3
+ async write(_batch) { }
4
+ }
5
+ class DisabledInvocationAudit {
6
+ wire;
7
+ config = undefined;
8
+ warned = false;
9
+ constructor(wire) {
10
+ this.wire = wire;
11
+ }
12
+ async write(_event) {
13
+ if (!this.warned) {
14
+ this.warned = true;
15
+ this.wire.logger?.warn?.('audit.write() called for an invocation without wire.audit enabled');
16
+ }
17
+ }
18
+ async flush() { }
19
+ async close() { }
20
+ }
21
+ class InvocationAuditLog {
22
+ config;
23
+ service;
24
+ wire;
25
+ buffer = [];
26
+ constructor(config, service, wire) {
27
+ this.config = config;
28
+ this.service = service;
29
+ this.wire = wire;
30
+ }
31
+ async write(event) {
32
+ const resolved = this.resolveEvent(event);
33
+ if (this.config.durability === 'transactional') {
34
+ await this.service.audit(resolved);
35
+ return;
36
+ }
37
+ this.buffer.push(resolved);
38
+ }
39
+ async flush() {
40
+ if (this.config.durability === 'transactional' ||
41
+ this.buffer.length === 0) {
42
+ return;
43
+ }
44
+ const batch = this.buffer.splice(0, this.buffer.length);
45
+ const queryEvents = batch.filter((event) => event.queryId != null);
46
+ if (queryEvents.length === 1) {
47
+ queryEvents[0].queryId = null;
48
+ }
49
+ try {
50
+ if (this.service.write) {
51
+ await this.service.write(batch);
52
+ return;
53
+ }
54
+ for (const event of batch) {
55
+ await this.service.audit(event);
56
+ }
57
+ }
58
+ catch (error) {
59
+ ;
60
+ this.wire.logger?.warn?.('best-effort audit flush failed', error);
61
+ }
62
+ }
63
+ async close() {
64
+ await this.flush();
65
+ }
66
+ resolveEvent(event) {
67
+ return {
68
+ functionId: this.wire.functionId,
69
+ wireType: this.wire.wireType,
70
+ wireId: this.wire.wireId,
71
+ traceId: this.wire.traceId,
72
+ actor: event.actor ?? resolveAuditActorFromWire(this.wire),
73
+ ...event,
74
+ occurredAt: new Date().toISOString(),
75
+ };
76
+ }
77
+ }
78
+ export const resolveAuditConfig = (config) => {
79
+ if (config === true) {
80
+ return { durability: 'best-effort' };
81
+ }
82
+ if (!config) {
83
+ return undefined;
84
+ }
85
+ return {
86
+ durability: config.durability ?? 'best-effort',
87
+ };
88
+ };
89
+ export const createInvocationAudit = (service, wire) => {
90
+ if (!wire.audit) {
91
+ return new DisabledInvocationAudit(wire);
92
+ }
93
+ return new InvocationAuditLog(wire.audit, service, wire);
94
+ };
95
+ export const resolveAuditActorFromWire = (wire) => {
96
+ const session = wire.session;
97
+ const actor = {
98
+ userId: session?.userId,
99
+ orgId: session?.orgId,
100
+ pikkuUserId: wire.pikkuUserId,
101
+ };
102
+ if (!actor.userId && !actor.orgId && !actor.pikkuUserId) {
103
+ return undefined;
104
+ }
105
+ return actor;
106
+ };
@@ -23,6 +23,7 @@ export interface GetUploadURLArgs<TBucket extends string = string> {
23
23
  fileKey: string;
24
24
  contentType: string;
25
25
  size?: number;
26
+ visibility?: 'private' | 'public';
26
27
  }
27
28
  /**
28
29
  * Result of minting a presigned upload URL.
@@ -0,0 +1,36 @@
1
+ export interface EmailTemplateReference {
2
+ name: string;
3
+ locale?: string;
4
+ data?: Record<string, unknown>;
5
+ }
6
+ export interface BaseSendEmailInput {
7
+ to: string | string[];
8
+ from?: string;
9
+ cc?: string | string[];
10
+ bcc?: string | string[];
11
+ replyTo?: string | string[];
12
+ headers?: Record<string, string>;
13
+ subject?: string;
14
+ }
15
+ export interface SendTextEmailInput extends BaseSendEmailInput {
16
+ text: string;
17
+ html?: never;
18
+ template?: never;
19
+ }
20
+ export interface SendHTMLEmailInput extends BaseSendEmailInput {
21
+ html: string;
22
+ text?: string;
23
+ template?: never;
24
+ }
25
+ export interface SendTemplateEmailInput extends BaseSendEmailInput {
26
+ template: EmailTemplateReference;
27
+ html?: never;
28
+ text?: never;
29
+ }
30
+ export type SendEmailInput = SendTextEmailInput | SendHTMLEmailInput | SendTemplateEmailInput;
31
+ export interface SendEmailResult {
32
+ messageId?: string;
33
+ }
34
+ export interface EmailService {
35
+ send(input: SendEmailInput): Promise<SendEmailResult>;
36
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -5,43 +5,10 @@ import type { SecretService } from './secret-service.js';
5
5
  */
6
6
  export declare class GopassSecretService implements SecretService {
7
7
  private prefix;
8
- /**
9
- * Creates an instance of GopassSecretService.
10
- * @param prefix - Optional prefix for all secret keys (e.g., 'pikku/' will look up 'pikku/mykey' for key 'mykey')
11
- */
12
8
  constructor(prefix?: string);
13
9
  private getFullKey;
14
- /**
15
- * Retrieves a secret by key and parses it as JSON.
16
- * @param key - The key of the secret to retrieve.
17
- * @returns A promise that resolves to the parsed secret value.
18
- * @throws {Error} If the secret is not found or gopass fails.
19
- */
20
- getSecretJSON<R>(key: string): Promise<R>;
21
- /**
22
- * Retrieves a secret by key as a string.
23
- * @param key - The key of the secret to retrieve.
24
- * @returns A promise that resolves to the secret value.
25
- * @throws {Error} If the secret is not found or gopass fails.
26
- */
27
- getSecret(key: string): Promise<string>;
28
- /**
29
- * Checks if a secret exists without throwing.
30
- * @param key - The key of the secret to check.
31
- * @returns A promise that resolves to true if the secret exists.
32
- */
10
+ getSecret<T = string>(key: string): Promise<T>;
33
11
  hasSecret(key: string): Promise<boolean>;
34
- /**
35
- * Stores a JSON value as a secret in gopass.
36
- * @param key - The key to store the secret under.
37
- * @param value - The JSON value to store.
38
- * @returns A promise that resolves when the secret is stored.
39
- */
40
- setSecretJSON(key: string, value: unknown): Promise<void>;
41
- /**
42
- * Deletes a secret from gopass.
43
- * @param key - The key of the secret to delete.
44
- * @returns A promise that resolves when the secret is deleted.
45
- */
12
+ setSecret(key: string, value: unknown): Promise<void>;
46
13
  deleteSecret(key: string): Promise<void>;
47
14
  }
@@ -5,10 +5,6 @@ import { execFileSync } from 'child_process';
5
5
  */
6
6
  export class GopassSecretService {
7
7
  prefix;
8
- /**
9
- * Creates an instance of GopassSecretService.
10
- * @param prefix - Optional prefix for all secret keys (e.g., 'pikku/' will look up 'pikku/mykey' for key 'mykey')
11
- */
12
8
  constructor(prefix = '') {
13
9
  this.prefix = prefix;
14
10
  }
@@ -18,38 +14,23 @@ export class GopassSecretService {
18
14
  }
19
15
  return this.prefix ? `${this.prefix}${key}` : key;
20
16
  }
21
- /**
22
- * Retrieves a secret by key and parses it as JSON.
23
- * @param key - The key of the secret to retrieve.
24
- * @returns A promise that resolves to the parsed secret value.
25
- * @throws {Error} If the secret is not found or gopass fails.
26
- */
27
- async getSecretJSON(key) {
28
- const value = await this.getSecret(key);
29
- return JSON.parse(value);
30
- }
31
- /**
32
- * Retrieves a secret by key as a string.
33
- * @param key - The key of the secret to retrieve.
34
- * @returns A promise that resolves to the secret value.
35
- * @throws {Error} If the secret is not found or gopass fails.
36
- */
37
17
  async getSecret(key) {
38
18
  const fullKey = this.getFullKey(key);
39
19
  try {
40
- return execFileSync('gopass', ['show', '-o', fullKey], {
20
+ const raw = execFileSync('gopass', ['show', '-o', fullKey], {
41
21
  encoding: 'utf8',
42
22
  }).trim();
23
+ try {
24
+ return JSON.parse(raw);
25
+ }
26
+ catch {
27
+ return raw;
28
+ }
43
29
  }
44
30
  catch (error) {
45
31
  throw new Error(`Secret Not Found: ${key}`, { cause: error });
46
32
  }
47
33
  }
48
- /**
49
- * Checks if a secret exists without throwing.
50
- * @param key - The key of the secret to check.
51
- * @returns A promise that resolves to true if the secret exists.
52
- */
53
34
  async hasSecret(key) {
54
35
  const fullKey = this.getFullKey(key);
55
36
  try {
@@ -60,19 +41,13 @@ export class GopassSecretService {
60
41
  return false;
61
42
  }
62
43
  }
63
- /**
64
- * Stores a JSON value as a secret in gopass.
65
- * @param key - The key to store the secret under.
66
- * @param value - The JSON value to store.
67
- * @returns A promise that resolves when the secret is stored.
68
- */
69
- async setSecretJSON(key, value) {
44
+ async setSecret(key, value) {
70
45
  const fullKey = this.getFullKey(key);
71
- const jsonValue = JSON.stringify(value);
46
+ const encoded = typeof value === 'string' ? value : JSON.stringify(value);
72
47
  try {
73
48
  execFileSync('gopass', ['insert', '-f', fullKey], {
74
49
  encoding: 'utf8',
75
- input: jsonValue,
50
+ input: encoded,
76
51
  stdio: ['pipe', 'pipe', 'pipe'],
77
52
  });
78
53
  }
@@ -80,11 +55,6 @@ export class GopassSecretService {
80
55
  throw new Error(`Failed to set secret: ${key}`, { cause: error });
81
56
  }
82
57
  }
83
- /**
84
- * Deletes a secret from gopass.
85
- * @param key - The key of the secret to delete.
86
- * @returns A promise that resolves when the secret is deleted.
87
- */
88
58
  async deleteSecret(key) {
89
59
  const fullKey = this.getFullKey(key);
90
60
  try {