@pikku/skills 0.12.22 → 0.12.26
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 +134 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-a11y/SKILL.md +59 -0
- package/skills/pikku-addon/SKILL.md +2 -2
- package/skills/pikku-agent/SKILL.md +67 -316
- package/skills/pikku-agent/references/agents.md +299 -0
- package/skills/{pikku-ai-vercel/SKILL.md → pikku-agent/references/runner-vercel.md} +2 -24
- package/skills/{pikku-ai-voice/SKILL.md → pikku-agent/references/voice.md} +1 -22
- package/skills/pikku-architect/SKILL.md +265 -0
- package/skills/pikku-auth/SKILL.md +89 -0
- package/skills/{pikku-better-auth/SKILL.md → pikku-auth/references/better-auth.md} +126 -34
- package/skills/{pikku-jose/SKILL.md → pikku-auth/references/jose.md} +1 -23
- package/skills/{pikku-machine-auth/SKILL.md → pikku-auth/references/machine-auth.md} +0 -23
- package/skills/pikku-auth/references/permissions.md +261 -0
- package/skills/{pikku-security/SKILL.md → pikku-auth/references/sessions.md} +0 -20
- package/skills/pikku-build/SKILL.md +88 -0
- package/skills/{pikku-build-app/SKILL.md → pikku-build/references/app.md} +76 -24
- package/skills/{pikku-feature/SKILL.md → pikku-build/references/feature.md} +1 -8
- package/skills/{pikku-build-app → pikku-build}/references/multi-app.md +56 -1
- package/skills/{pikku-build-platform/SKILL.md → pikku-build/references/platform.md} +22 -37
- package/skills/{pikku-template-clone/SKILL.md → pikku-build/references/post-clone.md} +0 -6
- package/skills/{pikku-build-quick/SKILL.md → pikku-build/references/quick.md} +4 -20
- package/skills/{pikku-build-app → pikku-build}/references/ship.md +9 -3
- package/skills/pikku-concepts/SKILL.md +72 -7
- package/skills/pikku-concepts/references/concept-mapping.md +8 -8
- package/skills/pikku-deploy/SKILL.md +158 -0
- package/skills/{pikku-deploy-azure/SKILL.md → pikku-deploy/references/azure.md} +18 -50
- package/skills/pikku-deploy/references/cloudflare.md +104 -0
- package/skills/pikku-deploy/references/express.md +92 -0
- package/skills/{pikku-deploy-fastify/SKILL.md → pikku-deploy/references/fastify.md} +8 -32
- package/skills/{pikku-deploy-lambda/SKILL.md → pikku-deploy/references/lambda.md} +6 -27
- package/skills/{pikku-deploy-nextjs/SKILL.md → pikku-deploy/references/nextjs.md} +9 -33
- package/skills/pikku-deploy/references/uws.md +72 -0
- package/skills/pikku-deploy/references/ws.md +75 -0
- package/skills/pikku-emails/SKILL.md +3 -2
- package/skills/pikku-fabric/SKILL.md +47 -20
- package/skills/{pikku-fabric-debug/SKILL.md → pikku-fabric/references/debugging.md} +0 -6
- package/skills/pikku-i18n/SKILL.md +62 -207
- package/skills/{pikku-paraglide/SKILL.md → pikku-i18n/references/enum-labels.md} +0 -6
- package/skills/pikku-i18n/references/messages.md +218 -0
- package/skills/{pikku-rtl/SKILL.md → pikku-i18n/references/rtl.md} +3 -9
- package/skills/pikku-knowledge/SKILL.md +15 -0
- package/skills/pikku-kysely/SKILL.md +13 -13
- package/skills/pikku-list-query/SKILL.md +163 -0
- package/skills/pikku-meta/SKILL.md +58 -130
- package/skills/{pikku-deps/SKILL.md → pikku-meta/references/audit.md} +1 -17
- package/skills/pikku-meta/references/meta.md +114 -0
- package/skills/{pikku-versioning/SKILL.md → pikku-meta/references/versioning.md} +0 -26
- package/skills/pikku-middleware/SKILL.md +5 -5
- package/skills/pikku-n8n-import/SKILL.md +0 -1
- package/skills/pikku-permissions/SKILL.md +75 -229
- package/skills/pikku-react/SKILL.md +50 -298
- package/skills/pikku-react/references/client.md +313 -0
- package/skills/{pikku-react-query/SKILL.md → pikku-react/references/react-query.md} +2 -22
- package/skills/{pikku-workflows-client/SKILL.md → pikku-react/references/workflows.md} +1 -22
- package/skills/pikku-realtime/SKILL.md +110 -251
- package/skills/pikku-scenario/SKILL.md +60 -45
- package/skills/pikku-scenario/references/persona-run.md +148 -0
- package/skills/pikku-seo/SKILL.md +133 -0
- package/skills/pikku-service-backends/SKILL.md +154 -0
- package/skills/pikku-service-backends/references/aws.md +106 -0
- package/skills/pikku-service-backends/references/backblaze.md +57 -0
- package/skills/pikku-service-backends/references/mongodb.md +90 -0
- package/skills/pikku-service-backends/references/redis.md +75 -0
- package/skills/pikku-service-backends/references/schema.md +63 -0
- package/skills/pikku-services/SKILL.md +68 -291
- package/skills/{pikku-audit/SKILL.md → pikku-services/references/audit.md} +0 -22
- package/skills/{pikku-config/SKILL.md → pikku-services/references/config.md} +1 -25
- package/skills/{pikku-pino/SKILL.md → pikku-services/references/pino.md} +0 -20
- package/skills/pikku-services/references/services.md +272 -0
- package/skills/pikku-software-archaeology/README.md +5 -1
- package/skills/pikku-software-archaeology/SKILL.md +16 -2
- package/skills/{pikku-product-second-opinion/example/sample-report.md → pikku-software-archaeology/example/second-opinion-sample-report.md} +1 -1
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +1 -1
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
- package/skills/{pikku-product-second-opinion/SKILL.md → pikku-software-archaeology/references/second-opinion.md} +4 -9
- package/skills/pikku-webhook/SKILL.md +224 -0
- package/skills/pikku-wiring/SKILL.md +180 -0
- package/skills/{pikku-websocket/SKILL.md → pikku-wiring/references/channel.md} +2 -35
- package/skills/{pikku-cli/SKILL.md → pikku-wiring/references/cli.md} +1 -33
- package/skills/{pikku-gateway-slack/SKILL.md → pikku-wiring/references/gateway-slack.md} +0 -23
- package/skills/{pikku-http/SKILL.md → pikku-wiring/references/http.md} +3 -39
- package/skills/{pikku-mcp/SKILL.md → pikku-wiring/references/mcp.md} +0 -33
- package/skills/{pikku-queue/SKILL.md → pikku-wiring/references/queue.md} +1 -33
- package/skills/pikku-wiring/references/realtime.md +265 -0
- package/skills/{pikku-rpc/SKILL.md → pikku-wiring/references/rpc.md} +0 -32
- package/skills/{pikku-schedule/SKILL.md → pikku-wiring/references/scheduler.md} +1 -35
- package/skills/{pikku-trigger/SKILL.md → pikku-wiring/references/trigger.md} +0 -43
- package/skills/pikku-workflow/SKILL.md +39 -2
- package/skills/pikku-aws/SKILL.md +0 -161
- package/skills/pikku-backblaze/SKILL.md +0 -104
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -123
- package/skills/pikku-deploy-express/SKILL.md +0 -122
- package/skills/pikku-deploy-uws/SKILL.md +0 -144
- package/skills/pikku-mongodb/SKILL.md +0 -113
- package/skills/pikku-product-second-opinion/README.md +0 -43
- package/skills/pikku-redis/SKILL.md +0 -99
- package/skills/pikku-schema-ajv/SKILL.md +0 -83
- package/skills/pikku-schema-cfworker/SKILL.md +0 -82
- package/skills/pikku-ws/SKILL.md +0 -87
- /package/skills/{pikku-build-app → pikku-build}/references/theming.md +0 -0
- /package/skills/{pikku-product-second-opinion/references/report-template.md → pikku-software-archaeology/references/second-opinion-report-template.md} +0 -0
- /package/skills/{pikku-cli/references/complete-example.md → pikku-wiring/references/cli-complete-example.md} +0 -0
- /package/skills/{pikku-http → pikku-wiring}/references/http-options.md +0 -0
- /package/skills/{pikku-realtime/references/other-routes.md → pikku-wiring/references/realtime-other-routes.md} +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# AWS (`@pikku/aws-services`)
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
yarn add @pikku/aws-services
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
AWS-backed implementations of the content, queue, and secret interfaces.
|
|
8
|
+
|
|
9
|
+
## `S3Content` — ContentService
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { S3Content } from '@pikku/aws-services'
|
|
13
|
+
|
|
14
|
+
const content = new S3Content(
|
|
15
|
+
config: { bucketName: string; region: string; endpoint?: string },
|
|
16
|
+
logger: Logger,
|
|
17
|
+
signConfig: { keyPairId: string; privateKey: string }
|
|
18
|
+
)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`endpoint` is what points the client at LocalStack or an S3-compatible store.
|
|
22
|
+
|
|
23
|
+
Every method takes a single **args object**, matching the shared `ContentService`
|
|
24
|
+
interface. None of them are positional:
|
|
25
|
+
|
|
26
|
+
- `signURL({ url, dateLessThan, dateGreaterThan? }): Promise<string>` — CloudFront-sign an absolute URL
|
|
27
|
+
- `signContentKey({ bucket, contentKey, dateLessThan, dateGreaterThan? }): Promise<string>`
|
|
28
|
+
- `getUploadURL({ bucket, fileKey, contentType, visibility? }): Promise<{ uploadUrl, assetKey }>` — `visibility` is ignored
|
|
29
|
+
- `readFile({ bucket, key }): Promise<ReadableStream | NodeJS.ReadableStream>`
|
|
30
|
+
- `readFileAsBuffer({ bucket, key }): Promise<Buffer>`
|
|
31
|
+
- `writeFile({ bucket, key, stream }): Promise<boolean>`
|
|
32
|
+
- `copyFile({ bucket, key, fromAbsolutePath }): Promise<boolean>`
|
|
33
|
+
- `deleteFile({ bucket, key }): Promise<boolean>`
|
|
34
|
+
|
|
35
|
+
`signContentKey` builds `https://<bucketName>/<bucket>/<contentKey>` — it uses
|
|
36
|
+
`bucketName` as the **host**. For signed content that value must therefore be
|
|
37
|
+
your CloudFront domain, not a plain bucket name, which means the same config
|
|
38
|
+
field is doing two jobs.
|
|
39
|
+
|
|
40
|
+
Presigned upload URLs expire after a fixed **3600s**, not configurable through
|
|
41
|
+
the service.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const createSingletonServices = pikkuServices(async (config) => {
|
|
45
|
+
const logger = new PinoLogger()
|
|
46
|
+
const content = new S3Content(
|
|
47
|
+
{ bucketName: config.s3Bucket, region: config.awsRegion },
|
|
48
|
+
logger,
|
|
49
|
+
{ keyPairId: config.cfKeyPairId, privateKey: config.cfPrivateKey }
|
|
50
|
+
)
|
|
51
|
+
return { config, logger, content }
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## `SQSQueueService` — QueueService
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { SQSQueueService } from '@pikku/aws-services'
|
|
59
|
+
|
|
60
|
+
const queue = new SQSQueueService({
|
|
61
|
+
region: string,
|
|
62
|
+
queueUrlPrefix: string, // e.g. 'https://sqs.us-east-1.amazonaws.com/123456789/'
|
|
63
|
+
endpoint?: string, // LocalStack or a custom SQS endpoint
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- `add<T>(queueName: string, data: T, options?: JobOptions): Promise<string>` — returns SQS's `MessageId`
|
|
68
|
+
- `getJob()` — always **throws**
|
|
69
|
+
|
|
70
|
+
The queue URL is `queueUrlPrefix + queueName`, so the name in `wireQueueWorker`
|
|
71
|
+
has to match the SQS queue exactly.
|
|
72
|
+
|
|
73
|
+
Constraints inherited from SQS, enforced in `add`:
|
|
74
|
+
|
|
75
|
+
- `options.delay` is in **milliseconds**, floored to whole seconds. Over
|
|
76
|
+
900_000ms (15 minutes) or negative throws before the message is sent.
|
|
77
|
+
- Standard queues only — no FIFO, so no `MessageGroupId` and no ordering
|
|
78
|
+
guarantee.
|
|
79
|
+
- `data` is `JSON.stringify`d, which is where a `Date` or a `Map` quietly
|
|
80
|
+
degrades.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const createSingletonServices = pikkuServices(async (config) => {
|
|
84
|
+
const queue = new SQSQueueService({
|
|
85
|
+
region: config.awsRegion,
|
|
86
|
+
queueUrlPrefix: config.sqsUrlPrefix,
|
|
87
|
+
})
|
|
88
|
+
return { config, queue }
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## `AWSSecrets` — SecretService
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { AWSSecrets } from '@pikku/aws-services'
|
|
96
|
+
|
|
97
|
+
const secrets = new AWSSecrets({ awsRegion: 'eu-west-2' })
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`AWSConfig` has one field, `awsRegion` — there is no credentials option; the
|
|
101
|
+
SDK's default provider chain (instance role, env, profile) supplies those.
|
|
102
|
+
|
|
103
|
+
- `getSecret<T = string>(SecretId: string): Promise<SecretValue<T>>` — a JSON secret is parsed automatically, so pass a shape as `T` (a non-JSON value comes back as the raw string). The result is a branded `SecretValue`, not a bare value — reveal it where it is used rather than passing it through logs
|
|
104
|
+
- `getSecrets<T>(SecretIds: (keyof T & string)[]): Promise<Partial<T>>` — missing keys are omitted rather than thrown
|
|
105
|
+
- `hasSecret(SecretId: string): Promise<boolean>` — performs a full fetch
|
|
106
|
+
- `setSecret` / `deleteSecret` — **not implemented**; they throw
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Backblaze B2 (`@pikku/backblaze`)
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
yarn add @pikku/backblaze
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
`B2Content` implements `ContentService` over Backblaze B2.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { B2Content } from '@pikku/backblaze'
|
|
11
|
+
|
|
12
|
+
const content = new B2Content(config: B2ContentConfig, logger: Logger)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`B2ContentConfig` has exactly three fields — `applicationKeyId`, `applicationKey`
|
|
16
|
+
and `bucketId`. There is no `cdnUrl`: downloads are served from the `downloadUrl`
|
|
17
|
+
B2 returns at authorization.
|
|
18
|
+
|
|
19
|
+
Every method takes a single **args object**, matching the shared `ContentService`
|
|
20
|
+
interface. None of them are positional:
|
|
21
|
+
|
|
22
|
+
- `signContentKey({ bucket, contentKey, dateLessThan }): Promise<string>` — a full download URL with an `Authorization` query param
|
|
23
|
+
- `signURL({ url, dateLessThan }): Promise<string>` — re-signs an existing `/file/` URL; a URL with no `/file/` segment is returned untouched
|
|
24
|
+
- `getUploadURL({ bucket, fileKey, contentType, visibility? }): Promise<UploadURLResult>` — `visibility` is ignored
|
|
25
|
+
- `writeFile({ bucket, key, stream }): Promise<boolean>`
|
|
26
|
+
- `copyFile({ bucket, key, fromAbsolutePath }): Promise<boolean>`
|
|
27
|
+
- `readFile({ bucket, key }): Promise<ReadableStream | NodeJS.ReadableStream>`
|
|
28
|
+
- `readFileAsBuffer({ bucket, key }): Promise<Buffer>`
|
|
29
|
+
- `deleteFile({ bucket, key }): Promise<boolean>`
|
|
30
|
+
|
|
31
|
+
Because `writeFile` drains the whole stream into a `Buffer` before uploading
|
|
32
|
+
(B2's upload endpoint needs a SHA-1 and a content length up front), large uploads
|
|
33
|
+
should go through `getUploadURL` and be sent by the client directly.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const createSingletonServices = pikkuServices(async (config) => {
|
|
37
|
+
const logger = new PinoLogger()
|
|
38
|
+
const content = new B2Content(
|
|
39
|
+
{
|
|
40
|
+
applicationKeyId: config.b2KeyId,
|
|
41
|
+
applicationKey: config.b2AppKey,
|
|
42
|
+
bucketId: config.b2BucketId,
|
|
43
|
+
},
|
|
44
|
+
logger
|
|
45
|
+
)
|
|
46
|
+
return { config, logger, content }
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
await content.writeFile({ bucket: 'avatars', key: `${userId}.png`, stream })
|
|
52
|
+
const url = await content.signContentKey({
|
|
53
|
+
bucket: 'avatars',
|
|
54
|
+
contentKey: `${userId}.png`,
|
|
55
|
+
dateLessThan: new Date(Date.now() + 60_000),
|
|
56
|
+
})
|
|
57
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# MongoDB (`@pikku/mongodb`)
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
yarn add @pikku/mongodb
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
## `PikkuMongoDB` — connection wrapper
|
|
8
|
+
|
|
9
|
+
Every service below takes a `Db`, so the wrapper is constructed and initialised
|
|
10
|
+
first.
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { PikkuMongoDB } from '@pikku/mongodb'
|
|
14
|
+
|
|
15
|
+
const mongo = new PikkuMongoDB(
|
|
16
|
+
logger: Logger,
|
|
17
|
+
clientOrUri: MongoClient | string,
|
|
18
|
+
dbName: string,
|
|
19
|
+
options?: MongoClientOptions
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
await mongo.init()
|
|
23
|
+
mongo.db // Db instance for queries
|
|
24
|
+
await mongo.close()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Available services
|
|
28
|
+
|
|
29
|
+
| Service | Interface | Purpose |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| `MongoDBChannelStore` | `ChannelStore` | WebSocket channel state persistence |
|
|
32
|
+
| `MongoDBEventHubStore` | `EventHubStore` | Event hub state persistence |
|
|
33
|
+
| `MongoDBWorkflowService` | `PikkuWorkflowService` | Workflow definition storage |
|
|
34
|
+
| `MongoDBWorkflowRunService` | `WorkflowRunService` | Workflow execution tracking |
|
|
35
|
+
| `MongoDBDeploymentService` | `DeploymentService` | Deployment state management |
|
|
36
|
+
| `MongoDBAgentStorageService` | `AgentStorageService`, `AgentRunStateService` | AI conversation/run storage |
|
|
37
|
+
| `MongoDBAgentRunService` | `AgentRunService` | Agent execution tracking |
|
|
38
|
+
| `MongoDBSecretService` | `SecretService` | Encrypted secret storage (envelope encryption) |
|
|
39
|
+
| `MongoDBSessionStore` | `SessionStore` | Persisted user sessions |
|
|
40
|
+
|
|
41
|
+
All of them take a `Db` in the constructor and have an `init()` method that
|
|
42
|
+
creates the collections and indexes. **Await it** — a service used without it
|
|
43
|
+
behaves like an unindexed collection.
|
|
44
|
+
|
|
45
|
+
## `MongoDBSecretService`
|
|
46
|
+
|
|
47
|
+
Envelope encryption: `key` derives the KEK that wraps each secret's own DEK.
|
|
48
|
+
Keeping `previousKey` set is what makes `rotateKEK()` possible — it re-wraps
|
|
49
|
+
every secret onto the current key and returns the new version.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { MongoDBSecretService } from '@pikku/mongodb'
|
|
53
|
+
|
|
54
|
+
const secrets = new MongoDBSecretService(mongo.db, {
|
|
55
|
+
key: 'your-key-encryption-passphrase',
|
|
56
|
+
keyVersion: 2, // defaults to 1
|
|
57
|
+
previousKey: 'the-passphrase-you-are-rotating-away-from',
|
|
58
|
+
audit: true, // log write/delete/rotate through the audit sink
|
|
59
|
+
auditReads: false, // reads too — noisy, off by default
|
|
60
|
+
})
|
|
61
|
+
await secrets.init()
|
|
62
|
+
|
|
63
|
+
await secrets.setSecret('api-key', { key: 'sk-...' })
|
|
64
|
+
const value = await secrets.getSecret<{ key: string }>('api-key')
|
|
65
|
+
await secrets.rotateKEK()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Full setup
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import {
|
|
72
|
+
PikkuMongoDB,
|
|
73
|
+
MongoDBChannelStore,
|
|
74
|
+
MongoDBWorkflowService,
|
|
75
|
+
} from '@pikku/mongodb'
|
|
76
|
+
|
|
77
|
+
const createSingletonServices = pikkuServices(async (config) => {
|
|
78
|
+
const logger = new PinoLogger()
|
|
79
|
+
const mongo = new PikkuMongoDB(logger, config.mongoUri, 'myapp')
|
|
80
|
+
await mongo.init()
|
|
81
|
+
|
|
82
|
+
const channelStore = new MongoDBChannelStore(mongo.db)
|
|
83
|
+
await channelStore.init()
|
|
84
|
+
|
|
85
|
+
const workflowService = new MongoDBWorkflowService(mongo.db)
|
|
86
|
+
await workflowService.init()
|
|
87
|
+
|
|
88
|
+
return { config, logger, database: mongo, channelStore, workflowService }
|
|
89
|
+
})
|
|
90
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Redis (`@pikku/redis`)
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
yarn add @pikku/redis
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Redis-backed implementations of Pikku's core service interfaces, using
|
|
8
|
+
[ioredis](https://github.com/redis/ioredis). Every service accepts a Redis
|
|
9
|
+
connection — an ioredis `Redis` instance, `RedisOptions`, or a connection
|
|
10
|
+
string — in its constructor. None of them need an `init()` call.
|
|
11
|
+
|
|
12
|
+
| Service | Interface | Purpose |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| `RedisChannelStore` | `ChannelStore` | WebSocket channel state persistence |
|
|
15
|
+
| `RedisEventHubStore` | `EventHubStore` | Event hub state persistence |
|
|
16
|
+
| `RedisWorkflowService` | `PikkuWorkflowService` | Workflow definition storage |
|
|
17
|
+
| `RedisWorkflowRunService` | `WorkflowRunService` | Workflow execution tracking |
|
|
18
|
+
| `RedisDeploymentService` | `DeploymentService` | Deployment state management |
|
|
19
|
+
| `RedisAgentRunService` | `AgentRunService` | Agent execution tracking |
|
|
20
|
+
| `RedisSecretService` | `SecretService` | Encrypted secret storage (envelope encryption) |
|
|
21
|
+
| `RedisSessionStore` | `SessionStore` | Persisted user sessions |
|
|
22
|
+
|
|
23
|
+
There is no Redis implementation of `AgentStorageService` — AI conversation
|
|
24
|
+
storage is MongoDB-only.
|
|
25
|
+
|
|
26
|
+
## `RedisSecretService`
|
|
27
|
+
|
|
28
|
+
Envelope encryption: `key` derives the KEK that wraps each secret's own DEK.
|
|
29
|
+
Keeping `previousKey` set is what makes `rotateKEK()` possible — it re-wraps
|
|
30
|
+
every secret onto the current key and returns the new version.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { RedisSecretService } from '@pikku/redis'
|
|
34
|
+
|
|
35
|
+
const secrets = new RedisSecretService(
|
|
36
|
+
connectionOrConfig: Redis | RedisOptions | string,
|
|
37
|
+
config: {
|
|
38
|
+
key: string // the KEK passphrase
|
|
39
|
+
keyVersion?: number // defaults to 1
|
|
40
|
+
previousKey?: string // required to rotate
|
|
41
|
+
keyPrefix?: string // namespaces the redis keys
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
await secrets.getSecret<T = string>(key: string): Promise<T>
|
|
46
|
+
await secrets.getSecrets<T>(keys: (keyof T & string)[]): Promise<Partial<T>>
|
|
47
|
+
await secrets.hasSecret(key: string): Promise<boolean>
|
|
48
|
+
await secrets.setSecret(key: string, value: unknown): Promise<void>
|
|
49
|
+
await secrets.deleteSecret(key: string): Promise<void>
|
|
50
|
+
await secrets.rotateKEK(): Promise<number>
|
|
51
|
+
await secrets.close(): Promise<void>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Full setup
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import {
|
|
58
|
+
RedisChannelStore,
|
|
59
|
+
RedisWorkflowService,
|
|
60
|
+
RedisSecretService,
|
|
61
|
+
} from '@pikku/redis'
|
|
62
|
+
|
|
63
|
+
const createSingletonServices = pikkuServices(async (config) => {
|
|
64
|
+
const logger = new PinoLogger()
|
|
65
|
+
|
|
66
|
+
const channelStore = new RedisChannelStore(config.redisUrl)
|
|
67
|
+
const workflowService = new RedisWorkflowService(config.redisUrl)
|
|
68
|
+
|
|
69
|
+
const secrets = new RedisSecretService(config.redisUrl, {
|
|
70
|
+
key: config.kekPassphrase,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
return { config, logger, channelStore, workflowService, secrets }
|
|
74
|
+
})
|
|
75
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Schema validation (`@pikku/schema-ajv`, `@pikku/schema-cfworker`)
|
|
2
|
+
|
|
3
|
+
Two implementations of `SchemaService` from `@pikku/core`. Pikku uses whichever
|
|
4
|
+
one is wired to validate function inputs and outputs against the schemas codegen
|
|
5
|
+
derives from your function definitions.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn add @pikku/schema-ajv # default for Node.js
|
|
9
|
+
yarn add @pikku/schema-cfworker # Cloudflare Workers
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Both expose the same four methods:
|
|
13
|
+
|
|
14
|
+
- `compileSchema(name: string, schema: any): void` — compile and register under `name`
|
|
15
|
+
- `validateSchema(schemaName: string, json: any): void` — throws on failure
|
|
16
|
+
- `getSchemaNames(): Set<string>`
|
|
17
|
+
- `getSchemaKeys(schemaName: string): string[]` — top-level property keys, or `[]` if the schema has no `properties`
|
|
18
|
+
|
|
19
|
+
On `compileSchema` the first argument is the **name** and the second the schema —
|
|
20
|
+
the parameter is called `schema` in the source, which reads backwards.
|
|
21
|
+
|
|
22
|
+
## `AjvSchemaService`
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { AjvSchemaService } from '@pikku/schema-ajv'
|
|
26
|
+
|
|
27
|
+
const schema = new AjvSchemaService(logger: Logger)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Backed by [AJV](https://ajv.js.org/).
|
|
31
|
+
|
|
32
|
+
- **AJV is a module-level singleton**, shared by every `AjvSchemaService` you
|
|
33
|
+
construct, so compiled schema names are global to the process.
|
|
34
|
+
- **`useDefaults: true` mutates the validated object**, filling in schema
|
|
35
|
+
defaults in place.
|
|
36
|
+
- `ajv-formats` is registered, so `format` keywords (`email`, `uuid`,
|
|
37
|
+
`date-time`) are enforced.
|
|
38
|
+
|
|
39
|
+
## `CFWorkerSchemaService`
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { CFWorkerSchemaService } from '@pikku/schema-cfworker'
|
|
43
|
+
|
|
44
|
+
const schema = new CFWorkerSchemaService(logger: Logger)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Backed by [@cfworker/json-schema](https://github.com/cfworker/cfworker), which
|
|
48
|
+
uses no `eval` or `new Function` and so runs where AJV cannot.
|
|
49
|
+
|
|
50
|
+
- Each validator gets a **deep clone** of the schema (`@cfworker/json-schema`
|
|
51
|
+
mutates what it is given, which throws on a frozen generated object).
|
|
52
|
+
- A compile failure throws `Error('Failed to compile schema: <name>')` with the
|
|
53
|
+
underlying cause swallowed — check the schema by hand when you see it.
|
|
54
|
+
|
|
55
|
+
## Wiring either one
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const createSingletonServices = pikkuServices(async (config) => {
|
|
59
|
+
const logger = new ConsoleLogger()
|
|
60
|
+
const schema = new AjvSchemaService(logger)
|
|
61
|
+
return { config, logger, schema }
|
|
62
|
+
})
|
|
63
|
+
```
|