@pikku/azure-functions 0.12.8 → 0.12.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"azure-deployment-service.js","sourceRoot":"","sources":["../src/azure-deployment-service.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAEvD,MAAM,OAAO,sBAAsB;IAIvB;IACA;IAJF,QAAQ,CAAqB;IAErC,YACU,GAA2B,EAC3B,OAAsB,EAC9B,gBAAwC;QAFhC,QAAG,GAAH,GAAG,CAAwB;QAC3B,YAAO,GAAP,OAAO,CAAe;QAG9B,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAC3D,CAAC;IAED,KAAK,CAAC,IAAI,KAAmB,CAAC;IAC9B,KAAK,CAAC,KAAK,CAAC,OAAyB,IAAkB,CAAC;IACxD,KAAK,CAAC,IAAI,KAAmB,CAAC;IAE9B,KAAK,CAAC,MAAM,CACV,QAAgB,EAChB,IAAa,EACb,OAAiB,EACjB,OAAgB;QAEhB,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC/C,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,2CAA2C,QAAQ,KAAK;gBACtD,uBAAuB,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChE,CAAA;QACH,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,kBAAkB,CACtC,IAAI,CAAC,GAAG,EACR,IAAI,CAAC,OAAO,EACZ,QAAQ,EACR,OAAO,EACP,OAAO,CACR,CAAA;QAED,MAAM,GAAG,GAAG,GAAG,WAAW,eAAe,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAA;QACvE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;SAC/B,CAAC,CAAA;QAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,SAAiB,CAAA;YACrB,IAAI,CAAC;gBACH,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;YACnC,CAAC;YACD,MAAM,IAAI,KAAK,CACb,uBAAuB,QAAQ,aAAa,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CACnG,CAAA;QACH,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAA;IACxB,CAAC;CACF"}
1
+ {"version":3,"file":"azure-deployment-service.js","sourceRoot":"","sources":["../src/azure-deployment-service.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAEvD,MAAM,OAAO,sBAAsB;IAIvB,GAAG;IACH,OAAO;IAJT,QAAQ,CAAqB;IAErC,YACU,GAA2B,EAC3B,OAAsB,EAC9B,gBAAwC;mBAFhC,GAAG;uBACH,OAAO;QAGf,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAC3D,CAAC;IAED,KAAK,CAAC,IAAI,KAAmB,CAAC;IAC9B,KAAK,CAAC,KAAK,CAAC,OAAyB,IAAkB,CAAC;IACxD,KAAK,CAAC,IAAI,KAAmB,CAAC;IAE9B,KAAK,CAAC,MAAM,CACV,QAAgB,EAChB,IAAa,EACb,OAAiB,EACjB,OAAgB;QAEhB,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC/C,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,2CAA2C,QAAQ,KAAK;gBACtD,uBAAuB,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChE,CAAA;QACH,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,kBAAkB,CACtC,IAAI,CAAC,GAAG,EACR,IAAI,CAAC,OAAO,EACZ,QAAQ,EACR,OAAO,EACP,OAAO,CACR,CAAA;QAED,MAAM,GAAG,GAAG,GAAG,WAAW,eAAe,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAA;QACvE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;SAC/B,CAAC,CAAA;QAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,SAAiB,CAAA;YACrB,IAAI,CAAC;gBACH,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;YACnC,CAAC;YACD,MAAM,IAAI,KAAK,CACb,uBAAuB,QAAQ,aAAa,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE,CACnG,CAAA;QACH,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAA;IACxB,CAAC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"pikku-az-functions-logger.js","sourceRoot":"","sources":["../src/pikku-az-functions-logger.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,kBAAkB;IAGT;IAApB,YAAoB,OAA0B;QAA1B,YAAO,GAAP,OAAO,CAAmB;IAAG,CAAC;IAE3C,IAAI,CACT,YAAqB,EACrB,GAAG,IAAoC;QAEvC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAmB,EAAE,GAAG,IAAI,CAAC,CAAA;IACjD,CAAC;IAEM,IAAI,CACT,YAAqB,EACrB,GAAG,IAAoC;QAEvC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAmB,EAAE,GAAG,IAAI,CAAC,CAAA;IACjD,CAAC;IAEM,KAAK,CAGV,YAAqB,EAAE,GAAG,IAAoC;QAC9D,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAmB,EAAE,GAAG,IAAI,CAAC,CAAA;IAClD,CAAC;IAEM,KAAK,CACV,OAAe,EACf,GAAG,IAAoC;QAEvC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAA;IACtC,CAAC;IAEM,KAAK,CACV,OAAe,EACf,GAAG,IAAoC;QAEvC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAA;IACtC,CAAC;IAEM,QAAQ,CAAC,KAAe;IAE/B,CAAC;CACF"}
1
+ {"version":3,"file":"pikku-az-functions-logger.js","sourceRoot":"","sources":["../src/pikku-az-functions-logger.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,kBAAkB;IAGT,OAAO;IAA3B,YAAoB,OAA0B;uBAA1B,OAAO;IAAsB,CAAC;IAE3C,IAAI,CACT,YAAqB,EACrB,GAAG,IAAoC;QAEvC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAmB,EAAE,GAAG,IAAI,CAAC,CAAA;IACjD,CAAC;IAEM,IAAI,CACT,YAAqB,EACrB,GAAG,IAAoC;QAEvC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAmB,EAAE,GAAG,IAAI,CAAC,CAAA;IACjD,CAAC;IAEM,KAAK,CAGV,YAAqB,EAAE,GAAG,IAAoC;QAC9D,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAmB,EAAE,GAAG,IAAI,CAAC,CAAA;IAClD,CAAC;IAEM,KAAK,CACV,OAAe,EACf,GAAG,IAAoC;QAEvC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAA;IACtC,CAAC;IAEM,KAAK,CACV,OAAe,EACf,GAAG,IAAoC;QAEvC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAA;IACtC,CAAC;IAEM,QAAQ,CAAC,KAAe;IAE/B,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@pikku/azure-functions",
3
- "version": "0.12.8",
3
+ "version": "0.12.10",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/pikkujs/pikku.git",
7
+ "directory": "packages/runtimes/azure-functions"
8
+ },
4
9
  "description": "Azure Functions runtime for Pikku — HTTP, timer and storage-queue handlers",
5
10
  "license": "MIT",
6
11
  "author": "yasser.fadl@gmail.com",
@@ -10,27 +15,30 @@
10
15
  "scripts": {
11
16
  "tsc": "tsc && tsc -p tsconfig.type-tests.json",
12
17
  "ncu": "npx npm-check-updates",
13
- "build": "tsc -b",
18
+ "build": "bun run clean && tsc -b",
14
19
  "release": "npm run build && npm test",
15
20
  "test": "bash run-tests.sh",
16
21
  "test:watch": "bash run-tests.sh --watch",
17
22
  "test:coverage": "bash run-tests.sh --coverage",
18
- "prepublishOnly": "yarn build"
23
+ "prepublishOnly": "bun run build",
24
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
19
25
  },
20
26
  "peerDependencies": {
21
- "@pikku/core": "^0.12.87"
22
- },
23
- "devDependencies": {
24
- "typescript": "^6.0.3"
27
+ "@pikku/core": "^0.12.144"
25
28
  },
26
29
  "engines": {
27
30
  "node": ">=24"
28
31
  },
29
32
  "dependencies": {
30
- "@azure/functions": "4.16.2",
31
- "@azure/storage-queue": "^12.31.0"
33
+ "@azure/functions": "4.16.5",
34
+ "@azure/storage-queue": "^12.32.0"
32
35
  },
33
36
  "exports": {
34
- ".": "./dist/index.js"
35
- }
36
- }
37
+ ".": "./dist/index.js",
38
+ "./package.json": "./package.json"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "!dist/**/*.tsbuildinfo"
43
+ ]
44
+ }
package/CHANGELOG.md DELETED
@@ -1,625 +0,0 @@
1
- ## 0.12.8
2
-
3
- ### Patch Changes
4
-
5
- - 3a83f85: Stop re-exporting package internals through entry points
6
-
7
- 66 names reached consumers only because an `export *` in an entry point swept
8
- them up. Each one is referenced solely inside its own package, so the star is
9
- now an explicit named re-export listing what is genuinely public. The
10
- declarations themselves are untouched — this narrows the entry point, not the
11
- module.
12
-
13
- - Updated dependencies [9687ad1]
14
- - Updated dependencies [2d21628]
15
- - Updated dependencies [985b87b]
16
- - Updated dependencies [3a83f85]
17
- - @pikku/core@0.12.87
18
-
19
- ## 0.12.7
20
-
21
- ### Patch Changes
22
-
23
- - 266e3bc: One door per name: `@pikku/core/ecosystem/*` and the package root are gone
24
-
25
- `@pikku/core` published every module twice. `ecosystem/http` re-exported
26
- `./http`, `ecosystem/services` re-exported `./services`, and a name was
27
- reachable through either — so every addition had to be made in two places, and a
28
- consumer's import said nothing about what it actually used. The package root was
29
- the same problem at a larger scale: a single barrel of 206 names that no bundler
30
- could take apart, and the one specifier that revealed nothing at all.
31
-
32
- Both are deleted. Every name now lives on the subpath that owns it, and every
33
- import carries that subpath — `@pikku/core/http`, `@pikku/core/services`,
34
- `@pikku/core/errors`, `@pikku/core/types`.
35
-
36
- Deleting the facades meant the raw subpaths had to become a superset of them,
37
- which they were not: the facade tree had accumulated 25 names with no raw home
38
- and about 26 more filed under a different area than the module they came from.
39
- Those names moved to the area that owns them, and three areas were published as
40
- new entry points rather than left on a root that is going away — `./types` (the
41
- shared type surface, the largest single destination), `./state` and
42
- `./classification`.
43
-
44
- `./classification` is one door onto one subject: what a value is and how it must
45
- be handled. Its three halves would each have been an entry point — the brands
46
- and manifest types, the stored-form helpers (`hashToken`, `unsafeAsSealed` and
47
- friends), and `SecretValue` — split by whether a name happens to be a type or a
48
- value, which is the same defect as the facades. The duration and versioned-id
49
- helpers went to `./utils`, which already published, and `PikkuRequest` went to
50
- `./function`: it is the transport-agnostic request base, not an HTTP one — HTTP
51
- has `PikkuHTTPAbstractRequest`, and the only thing outside core that extends
52
- `PikkuRequest` is Azure's timer request.
53
-
54
- `./types` inherited the root barrel's habit before it inherited its contents, so
55
- the names with an owner elsewhere were moved off it. The middleware types and the
56
- five middleware factories — `pikkuMiddleware`, `pikkuMiddlewareFactory`,
57
- `pikkuChannelMiddleware`, `pikkuChannelMiddlewareFactory` and
58
- `pikkuAgentMiddleware`, runtime values on a types entry point — are now
59
- `@pikku/core/middleware`; the function meta types are `@pikku/core/function`;
60
- `SerializedError` is `@pikku/core/errors`; and the generic TypeScript helpers
61
- (`MakeRequired`, `PickRequired`, `PickOptional`, `RequireAtLeastOne`,
62
- `JSONPrimitive`, `JSONValue`) are `@pikku/core/utils`. What is left on `./types`
63
- is the vocabulary the wirings share, which no single module owns.
64
-
65
- `pikku` was itself a root barrel — `export * from '@pikku/core'` — and
66
- now exports only the services it bundles.
67
-
68
- One module survives at the old specifier, and only for the bootstrap:
69
- `packages/cli` is generated by the _published_ CLI pinned in its `build.sh`, and
70
- that CLI still writes a bare `@pikku/core` into the files it generates for the
71
- CLI itself. `bootstrap-compat/root.ts` carries the eight types it names, a test
72
- in core fails if that list grows, and it goes when the pin moves to a CLI
73
- released from this branch. The adapter names the pinned CLI reaches for —
74
- `pikkuState` and `CreateWireServices` — are rewritten to `@pikku/core/state` and
75
- `@pikku/core/types` by the same `build.sh` patch pass, so no second shim is
76
- needed for them.
77
-
78
- A guard test keeps the root shut: it parses imports and rejects a bare
79
- `@pikku/core` rather than grepping for it, because several tests hold a user's
80
- file as a template literal, where `import … from '@pikku/core'` is fixture text
81
- rather than an import this repo makes.
82
-
83
- An agent scaffold a project generated under an older CLI is refreshed rather
84
- than left to fail: `pikku all` already deleted one importing an entry point
85
- `@pikku/core` no longer publishes, and the `#pikku` hub joins that list.
86
- Without it a project that scaffolds the agent endpoint but
87
- declares no agents keeps the old file forever — the generator that would rewrite
88
- it only runs when agents exist, and the file being present is what stops it
89
- being regenerated as missing.
90
-
91
- `pikku new addon` also wrote a tsconfig `paths` map naming only the deleted hub.
92
- An addon's `imports` map points into `dist`, so `paths` is what resolves
93
- `#pikku/<leaf>` for the addon's own source build — it now names the two leaf
94
- patterns, in both the addon and its test harness.
95
-
96
- - 6eef0a0: Bump every dependency to its latest compatible minor/patch across the monorepo.
97
- - Updated dependencies [7722ceb]
98
- - Updated dependencies [375c1ff]
99
- - Updated dependencies [02a70cd]
100
- - Updated dependencies [aeef159]
101
- - Updated dependencies [a281de6]
102
- - Updated dependencies [266e3bc]
103
- - Updated dependencies [02a70cd]
104
- - Updated dependencies [786dae5]
105
- - Updated dependencies [6eef0a0]
106
- - Updated dependencies [3561d67]
107
- - Updated dependencies [a91c433]
108
- - Updated dependencies [02a70cd]
109
- - Updated dependencies [9537f74]
110
- - Updated dependencies [2b57ca8]
111
- - Updated dependencies [266e3bc]
112
- - Updated dependencies [9fce0f1]
113
- - Updated dependencies [83683a0]
114
- - Updated dependencies [456c88b]
115
- - Updated dependencies [456c88b]
116
- - Updated dependencies [c127273]
117
- - @pikku/core@0.12.85
118
-
119
- ## 0.12.6
120
-
121
- ### Patch Changes
122
-
123
- - 6794681: Publish the ecosystem surface as per-area sub-barrels under `@pikku/core/ecosystem/*`, and point generated code, the CLI, the inspector and the runtime adapters at them.
124
-
125
- 348 names that only generated code, the toolchain or a runtime adapter ever imports now have a second home on `@pikku/core/ecosystem/<area>` — one sub-barrel per area, matching how core already publishes its entrypoints, so no single barrel grows without bound and a consumer only pulls in the area it uses.
126
-
127
- This step is additive: every name is still exported from the entrypoint it was published from before, so nothing downstream breaks. Removing them from the app-facing barrels is a later change, and needs a release carrying `./ecosystem/*` first.
128
-
129
- - Updated dependencies [7406bfe]
130
- - Updated dependencies [6794681]
131
- - @pikku/core@0.12.84
132
-
133
- ## 0.12.5
134
-
135
- ### Patch Changes
136
-
137
- - 78b29f0: `SecretService` now returns a `SecretValue<T>` rather than the bare value, so a
138
- vault secret cannot reach a sink by accident.
139
-
140
- `SecretValue` is nominally typed, which means it is not assignable to `string`
141
- (or to any other concretely-typed field). Every sink with a real type — a
142
- database column, an email body, a session payload — rejects it with no lint
143
- rule involved. The sinks typed `any`, `unknown`, or a free generic — the logger,
144
- queue payloads, webhook and email inputs, and a function's own output — are
145
- guarded with `Safe<T>`, which collapses a `SecretValue` found anywhere inside
146
- `T`, however deeply nested, to `never`.
147
-
148
- Unwrap deliberately at the point the secret reaches the wire:
149
-
150
- ```ts
151
- const secret = await secrets.getSecret('BETTER_AUTH_SECRET')
152
- betterAuth({ secret: secret.reveal() })
153
- ```
154
-
155
- Two behaviours cover what types cannot see. Structured serialization redacts —
156
- `JSON.stringify` and node's inspect both yield `[secret]`, so an audit or log
157
- write stays honest without crashing the request. String coercion throws
158
- `SecretCoercionError`, because a template literal is always a leak.
159
-
160
- `AuditLog.write` is guarded the same way as the logger, since an audit event
161
- carries `input` and `metadata` as `unknown` and nominality alone cannot stop a
162
- secret landing in one.
163
-
164
- `.reveal()` is the deliberate escape hatch, and what it hands back is an
165
- ordinary string as far as every sink signature is concerned. **PKU953** closes
166
- that gap: under `pikku all --security` the inspector reports a revealed secret
167
- that flows into a logger, an audit, a queue, an email or a webhook — `console` included.
168
-
169
- This also fixed a real one: `remote-addon-auth.ts` called `String(token)` on an
170
- `unknown` and wrote the result straight into an `Authorization` header.
171
-
172
- - Updated dependencies [62ea4cc]
173
- - Updated dependencies [9dddff8]
174
- - Updated dependencies [78b29f0]
175
- - @pikku/core@0.12.76
176
-
177
- ## 0.12.4
178
-
179
- ### Patch Changes
180
-
181
- - afef587: Close eleven security weaknesses found in a review of `@pikku/core`. Most are
182
- breaking, and two invalidate data or credentials already in the wild — read the
183
- migration notes before upgrading.
184
-
185
- **Breaking: AI agent thread ownership now fails closed.** Reading, listing,
186
- resuming or approving an existing thread or run requires a resolved session
187
- principal (`userId`, or `orgId` for `sessionScope: 'org'`), regardless of the
188
- agent's `auth` setting. Previously a request without a session had no ownership
189
- model at all: the caller-supplied `resourceId` was accepted as the ownership
190
- key, so any caller could read or resume another party's thread by naming its
191
- `resourceId`. Worse, `threadOwnerConstraint` returned `undefined` for a
192
- sessionless caller, and `undefined` means _no filter_ rather than _no rows_ —
193
- so `getAgentThreads` returned every thread in the deployment. It now returns
194
- `string[]`, empty for a sessionless caller, which every storage backend already
195
- treats as matching nothing. Sessionless agents still run one-shot conversations,
196
- each with a fresh unguessable owner; what they lose is cross-request continuity.
197
- Wire a session to restore it.
198
-
199
- **Breaking: stored secrets and credentials must be re-entered.** `deriveKey` ran
200
- a single unsalted round of SHA-256 over the passphrase and used the digest
201
- directly as the AES-GCM key — roughly one hash per brute-force guess, with one
202
- rainbow table working against every deployment. It is now PBKDF2-HMAC-SHA256 at
203
- 600,000 iterations over a random salt. There is no compatibility path: every
204
- value held by the kysely, mongodb and redis secret services and the kysely
205
- credential service becomes undecryptable. They fail loud, naming the key and
206
- `key_version`, so the app hard-fails on first secret read until each is re-set.
207
-
208
- The KEK salt is scoped to the key version and stored alongside it, rather than
209
- per secret, so a bulk read costs one derivation instead of N — `getSecrets` over
210
- 50 secrets went from ~2.3s to ~48ms, and rotation from ~4.6s to ~94ms. This adds
211
- a salt table (kysely), hash field (redis) or collection (mongodb), created
212
- automatically on first use.
213
-
214
- **Breaking: `PIKKU_REMOTE_SECRET` must be at least 32 characters.** The
215
- remote-RPC session envelope moved from PBKDF2 to HKDF, which expands
216
- high-entropy key material rather than stretching a low-entropy passphrase. That
217
- took a remote hop from ~269ms to ~0.4ms — PBKDF2 was running twice per request —
218
- but HKDF supplies no brute-force resistance, so the secret must carry the
219
- entropy itself. A shorter secret now throws `WeakKeyMaterialError` at both ends.
220
- Generate one with `openssl rand -base64 32` and roll it out to every service in
221
- the mesh together: existing bearer tokens are format-incompatible, so a partial
222
- rollout produces 401s until every instance is updated. The Cloudflare, Lambda
223
- and Azure deployment services each hand-rolled a copy of `buildRemoteHeaders`
224
- and now call the shared one, which is what keeps the two sides in step.
225
-
226
- **Breaking: previously signed content URLs stop verifying.** `LocalContent`
227
- signed only `{signedAt, expiresAt, notBefore}`, so a signature proved when a URL
228
- was issued but never what it was issued for — any valid token was a skeleton
229
- key, and swapping the pathname from a public thumbnail to a private document
230
- still verified. The signature now binds the request path. Separately, the
231
- verifier returned "valid" when no JWT service was wired, which is how
232
- `pikku serve` ran: a forged `?signedAt=0&expiresAt=99999999999999` was accepted.
233
- It now rejects with 403, `LocalContent` requires a `JWTService`, and
234
- `pikku serve`/`pikku dev` mint an ephemeral per-process signing key so local
235
- development works without shipping a fail-open path. In-flight signed URLs must
236
- be re-issued.
237
-
238
- **Request body size limits now apply to every adapter.** The `maxBodySize` cap
239
- existed only in `PikkuFetchHTTPRequest`. The real hole was uWebSockets, which
240
- drove `res.onData` itself and concatenated every chunk with no bound and nothing
241
- downstream able to intervene; it now drops chunks past the limit and replies 413
242
- before routing. Fastify delegates to its native `bodyLimit` (set only when
243
- `maxBodySize` is configured, so fastify's stricter 1 MB default is never
244
- loosened), and `PikkuExpressServer` feeds the limit into its body parsers. Two
245
- paths can only reject rather than prevent, and are documented as such:
246
- `express-middleware` mounted on your own app receives an already-parsed body, so
247
- that deployment must bound its own parser; Next server actions bottom out at
248
- `experimental.serverActions.bodySizeLimit`.
249
-
250
- **Breaking: the console addon's privileged functions are gated by default.**
251
- `wireAddon` gains a `scopes` option that applies to every function in the
252
- addon's namespace, and the console scaffold now generates
253
- `wireAddon({ name: 'console', package: '@pikku/addon-console', scopes: ['admin'] })`.
254
- Previously the console's entire privileged surface — around 54 functions
255
- including `credentialGet`, which returns a resolved OAuth token for an arbitrary
256
- `userId`, `updateFunctionBody`, and `installAddon`, which shells out to a
257
- package install — was protected only by an optional host-registered
258
- `addGlobalPermission`. `resolveGlobalPermissions` returns `[]` when none is
259
- registered and permission checking then no-ops, so an app that never registered
260
- one served those functions to anyone, and with the template's default
261
- `scaffold.rpc: "no-auth"` that meant unauthenticated. All of them now return 403
262
- `MissingScopeError` without an `admin`, `admin:*` or `*` scope. **Regenerating
263
- is required** — an app holding an old `console.gen.ts` stays open.
264
- `installAddon` and `installOpenapiAddon` additionally declare their own
265
- `auth: true, scopes: ['admin']`, and `getAgentThreads` now scopes its listing to
266
- the session's own threads unless the caller holds admin.
267
-
268
- Addon scopes are enforced in `runPikkuFunc` rather than at the RPC boundary,
269
- because a wiring can reference an addon function directly — the inspector
270
- records the addon's `packageName` on HTTP, channel, schedule, queue, CLI,
271
- trigger, gateway and MCP wirings — and those paths never call `resolveNamespace`.
272
- Enforcing at the RPC seam would have covered only the `namespace:function` form
273
- while reading as complete.
274
-
275
- **Breaking: `wireAddon`'s `auth` and `tags` now apply on direct wirings too.**
276
- Both were read only by `resolveAddonFunction`, so they had exactly the hole
277
- scopes had: `wireAddon({ name: 'console', package: '@pikku/addon-console', auth:
278
- true, tags: ['admin'] })` gated `rpc('console:credentialGet')` and gated nothing
279
- at all on an HTTP route wired straight to `credentialGet`. A consumer who
280
- reached for the documented way to lock an addon down got a control that was
281
- silently inert on every wiring except one. Both now resolve in `runPikkuFunc`.
282
- `auth` merges as an OR — `auth: false` from an addon is ignored on a direct
283
- wiring, because an addon may require a session the wiring did not but must never
284
- waive one it did. Addon tags resolve to concrete middleware against the **root**
285
- tag groups before the call rather than being folded into the function's
286
- inherited middleware: `addTagMiddleware('admin', …)` is written by the consuming
287
- app and registers under the root package, whereas `combineMiddleware` would look
288
- the tag up under the addon's own `packageName`, where it does not exist.
289
-
290
- One consequence worth naming: an addon that wires `auth: true` and also runs its
291
- own sessionless internal work — a scheduled task or queue worker inside the
292
- addon calling a sibling function — is now gated where it previously was not,
293
- because a bare `rpc('fn')` from inside the addon reaches `runPikkuFunc` with the
294
- addon's `packageName` like any other call. This reverses a decision that
295
- deliberately scoped the gate to the namespaced boundary; that reasoning held
296
- only while the boundary was real, and a direct wiring can enter an addon without
297
- crossing it, so "already inside" is not something the runtime can infer. Such an
298
- addon should carry authorization on the function via
299
- `pikkuFunc({ permissions })`, which has always been enforced on every path. A
300
- follow-up will add execution provenance so an intra-addon call can be
301
- distinguished from an external one and skip the addon-level check; that needs a
302
- marker no caller outside the process can set, which is its own design problem
303
- and does not belong in a security fix.
304
-
305
- **Codegen now warns when an exposed function has no gate (PKU574).** The
306
- generated `POST /rpc/:rpcName` dispatcher forwards to `rpc.exposed`, which
307
- refuses anything without `expose: true` — but nothing checked whether the
308
- target was gated, because a dispatcher cannot know what it dispatches to. The
309
- console shipped ~54 privileged functions through that gap and the toolchain was
310
- silent. The inspector now reports every function that is exposed, sessionless,
311
- and carries no `auth`, `scopes` or `permissions` of its own and none from a
312
- governing `wireAddon`. It is a `warn`, not a critical: `expose: true` on an
313
- ungated sessionless function is correct for a genuinely public endpoint, so it
314
- blocks a build only under `--fail-on-warn`.
315
-
316
- Two pieces of metadata were missing for this to be answerable statically, and
317
- both are now recorded. A `pikkuSessionlessFunc`'s own `auth: true` was read at
318
- runtime but never written to function meta, so a self-gated function was
319
- indistinguishable from an ungated one — `sessionless` carries the baseline, and
320
- `auth` now carries the tightening. And `wireAddon`'s `scopes`, `auth` and `tags`
321
- were not parsed at all: the inspector recorded the addon's `rpcEndpoint` and its
322
- secret, variable and credential overrides, and dropped every one of its gates.
323
- An addon whose gates are not statically knowable is treated as gated, because a
324
- false positive on a correctly-secured addon costs more than the one case it
325
- would catch.
326
-
327
- **Breaking: an application's global permissions now apply inside addons.**
328
- `resolveGlobalPermissions` read only the bucket matching the function's own
329
- package, but the generated `addGlobalPermission` wrapper takes no package
330
- argument and always registers under the root. An app-wide rule like "every
331
- request needs a signed-in user" therefore stopped at the addon boundary, and the
332
- bucket an addon's functions did read was one no host could write to — which is
333
- why the console addon's recommended `addGlobalPermission([isAdmin],
334
- '@pikku/addon-console')` was never a gate anybody could actually install. A
335
- function now resolves the root bucket and its own package's, root first.
336
- Unioning is safe in a way nothing else here would be: globals AND, so adding the
337
- root ones can only tighten. Package buckets stay one-way — a package's globals
338
- never reach root functions, or an installed addon could gate the whole
339
- application. Apps with both a root global and addon-provided functions will see
340
- those functions gated where they previously were not.
341
-
342
- **Codegen now records whether each HTTP route requires a session, and warns
343
- about inert addon tags (PKU575).** Four separate things can demand a session —
344
- the function's `sessionless`, its own `auth`, the route's (or its group's)
345
- `auth`, and the addon it belongs to — and answering "which routes are open?"
346
- meant joining all four by hand and knowing which wins. Each route's meta now
347
- carries the resolved `requiresSession` alongside the route's own `auth`. Scopes
348
- count as requiring one, since they are matched against the session's and fail
349
- closed. Anything not statically knowable resolves to `true`, matching PKU574:
350
- a route that looks stricter than it is costs less than one that looks open and
351
- isn't. Separately, `wireAddon({ tags: ['admin'] })` reads like a gate and is
352
- applied like one right up until no `addTagMiddleware('admin', …)` exists, at
353
- which point it resolves to an empty list and gates nothing; that now warns.
354
- Only addon tags are reported — a tag on a function is as likely to be
355
- organizational, and warning about those would bury the case that matters.
356
-
357
- **Object-shorthand permissions were missing from meta.** The inspector visited
358
- `ts.PropertyAssignment` but not `ts.ShorthandPropertyAssignment`, so
359
- `permissions: { canAdminOrg }` — enforced identically to the longhand form at
360
- runtime, since `verifyPermissions` has a non-array branch — was recorded as _no
361
- permissions at all_. That is the most dangerous direction for meta to be wrong
362
- in: an audit reading it sees an open door where one is shut. It cost this review
363
- a false IDOR report across ~35 billing and org functions before the source
364
- contradicted the metadata.
365
-
366
- **Functions that authorize in their own body can say so.** A webhook receiver
367
- verifying a signature, or a handler redeeming a signed invite, is genuinely
368
- closed while carrying no session, scope or permission — indistinguishable in
369
- meta from one nobody remembered to gate, and so warned about forever by PKU574.
370
- `selfAuthenticated: true` on the function config records the claim and silences
371
- the warning for that function. It is declarative only: nothing at runtime reads
372
- it and it grants nothing. Detection was rejected deliberately — inferring it
373
- from the body means a function that _looks_ like it checks something silences
374
- the warning while checking nothing, and a warning that is usually wrong stops
375
- being read.
376
-
377
- **Breaking: `scaffold.<feature>` is now `boolean | { auth?, path? }`, and `true`
378
- means authenticated.** The old `'auth' | 'no-auth' | false` read like a
379
- starter-file preference while being a live authorization decision, set three
380
- directories from the functions it governed — the shape the console incident
381
- took. A surface now becomes public only by writing `{ auth: false }`, so
382
- omitting a field can never open anything: the failure mode of a forgotten flag
383
- is a locked door. `{ path }` additionally overrides where the file is
384
- generated, which previously could only be set for all features at once via
385
- `pikkuDir`.
386
-
387
- The legacy strings are **refused, not coerced**. `resolveScaffoldFeature` throws
388
- naming the key and its replacement (`"rpc": "no-auth"` → `"rpc": { "auth":
389
- false }`), and it does so at config load, not downstream. An earlier design used
390
- a bare `string` for the output path, under which `"no-auth"` would have parsed
391
- as a file named `no-auth` and every unmigrated config would have silently
392
- produced nonsense; the object form makes any string invalid, so the failure is
393
- loud.
394
-
395
- The collapse is deliberately not uniform in effect. For `rpc` the flag was a
396
- blanket "no anonymous RPC in this app" set on a dispatcher that cannot know what
397
- it dispatches to; for `userAdmin` it was redundant, since the generated
398
- functions are already `pikkuFunc` with `scopes: ['admin:users:list']`. But for
399
- `agent`, `workflow`, `events` and `scenarios` it is the only gate — those
400
- generate real endpoints the app never authors — so `true` keeps them
401
- authenticated rather than opening them as a side effect of a config cleanup.
402
- `webhook` and `remoteRpc` have no auth dimension at all (`serialize-remote-rpc.ts`
403
- hardcodes `auth: false`), so an `{ auth }` on them is ignored. The three
404
- configs in this repo are migrated preserving their current behaviour exactly.
405
-
406
- **Queue job identities are signed.** A job carried the producer's `pikkuUserId`
407
- as a plain string and the worker resolved a session from it with no
408
- verification, so write access to the queue backend was act-as-any-user. The
409
- identity is now `pq1.<claim>.<hmac>`, HMAC-SHA256 over the claim and the
410
- canonicalized job payload, keyed by HKDF expansion of a new
411
- `PIKKU_QUEUE_IDENTITY_SECRET`. Producers opt in by wrapping their queue service
412
- with `SignedQueueService`. This fails safe rather than closed: with no secret
413
- configured the identity is dropped and jobs still process, warning once per
414
- process, so no existing deployment breaks on upgrade — it simply loses queue
415
- identity until the secret is set. The payload rather than the job id is bound
416
- because SQS, Cloudflare Queues, Azure and the in-memory service all mint ids
417
- after `add` returns.
418
-
419
- **Workflow inline state is read from the run record.** `isInline` consulted a
420
- process-local `Map`, while `WorkflowRun.inline` is durable. Any instance that
421
- did not start a run disagreed with the record, so one instance could dispatch a
422
- queued job for a workflow another was already executing in-process. It is now
423
- async and resolves through the durable identity, cached only when a context
424
- already exists so a passive reader allocates nothing. The same `Map` also leaked:
425
- `nextStepKey` fabricated replay state on every step, and `releaseContext`
426
- refused to free anything carrying it, so runs whose steps executed outside a
427
- `beginReplay` bracket — the step-worker queue path — stranded their context and
428
- step state for the process lifetime. Contexts are now released by an explicit
429
- execution counter. Step ordinals reset per execution rather than accumulating
430
- across step-worker invocations in one process, which makes step naming
431
- independent of how work was distributed.
432
-
433
- **Secret reads fail loud in every store.** `MongoDBSecretService.getSecrets`
434
- skipped rows that failed to decrypt, and the redis equivalent dropped every
435
- rejection via `Promise.allSettled`, including the "No KEK available for
436
- key_version N" configuration error. Both now throw, naming the key and its key
437
- version, matching the kysely behaviour. This matters most alongside the KEK
438
- change above: without it, an upgrade surfaces as a partial secrets map and an
439
- opaque downstream failure instead of an error naming the secret to re-enter.
440
-
441
- **A second middleware registration for a pattern no longer erases the first.**
442
- `addHTTPMiddleware`, `addTagMiddleware` and `addChannelMiddleware` groups are
443
- keyed by pattern or tag and held one source file each, so a second file's call
444
- overwrote the first's. Codegen emits its imports from what is stored, so the
445
- losing file was never imported and its middleware never registered — the
446
- runtime composes repeated registrations for a pattern happily, and only codegen
447
- dropped one. Adding an unrelated `addHTTPMiddleware('*', …)` to an app was
448
- enough to silently unregister the generated better-auth session bridge, which
449
- fails open and gives no sign until a request arrives without a session. A group
450
- now carries every registration made for it, and all of them are imported.
451
-
452
- - Updated dependencies [6a307f0]
453
- - Updated dependencies [afef587]
454
- - Updated dependencies [8075f6a]
455
- - @pikku/core@0.12.74
456
-
457
- ## 0.12.3
458
-
459
- ### Patch Changes
460
-
461
- - daec082: Drop Node 22 support — the minimum supported runtime is now Node 24 (LTS).
462
-
463
- Node 22 deadlocks `pikku dev` at `loadUserBootstrap` (tsx `register()` + `require(esm)` cycle handling on node 22.12+), and Node 20 is already below our floor. The `engines.node` requirement is raised to `>=24` across all packages, matching `.nvmrc` and the CI test matrix. Closes #751.
464
-
465
- - Updated dependencies [7b17b14]
466
- - Updated dependencies [daec082]
467
- - Updated dependencies [e0fd352]
468
- - @pikku/core@0.12.58
469
-
470
- ## 0.12.2
471
-
472
- ### Patch Changes
473
-
474
- - 41ce2cb: Upgrade to TypeScript 6 and raise the minimum Node.js version to 22.
475
-
476
- All packages now build against `typescript@^6.0.3` and declare `engines.node >= 22`. Internal tooling (`ts-json-schema-generator`, `zod-to-ts`) was bumped to TypeScript 6-compatible releases.
477
-
478
- - Updated dependencies [41ce2cb]
479
- - @pikku/core@0.12.44
480
-
481
- ## 0.12.0
482
-
483
- ## 0.12.1
484
-
485
- ### Patch Changes
486
-
487
- - e04531f: Code quality improvements: resolve oxlint warnings and apply autofixes across the codebase (unused bindings, unnecessary constructors, prefer `const` over `let`, etc.). No behaviour changes.
488
- - Updated dependencies [62a8725]
489
- - Updated dependencies [a3bdb0d]
490
- - Updated dependencies [e0349ff]
491
- - Updated dependencies [62a8725]
492
- - Updated dependencies [e04531f]
493
- - Updated dependencies [62a8725]
494
- - Updated dependencies [a83efb8]
495
- - Updated dependencies [8eed717]
496
- - Updated dependencies [62a8725]
497
- - Updated dependencies [62a8725]
498
- - Updated dependencies [62a8725]
499
- - @pikku/core@0.12.1
500
-
501
- - Updated dependencies
502
-
503
- ## 0.11.0
504
-
505
- ## 0.11.1
506
-
507
- ### Patch Changes
508
-
509
- - 4b811db: chore: updating all dependencies
510
- - Updated dependencies [4b811db]
511
- - Updated dependencies [e12a00c]
512
- - Updated dependencies [4579434]
513
- - Updated dependencies [28aeb7f]
514
- - Updated dependencies [ce902b1]
515
- - Updated dependencies [06e1a31]
516
- - @pikku/core@0.11.1
517
-
518
- ### Minor Changes
519
-
520
- - Workflow support
521
-
522
- # @pikku/azure-functions
523
-
524
- ## 0.10.0
525
-
526
- This release includes significant improvements across the framework including tree-shaking support, middleware/permission factories, enhanced CLI functionality, improved TypeScript type safety, and comprehensive test strategies.
527
-
528
- For complete details, see https://pikku.dev/changelogs/0_10_0.md
529
-
530
- ## 0.9.3-next.0
531
-
532
- ### Patch Changes
533
-
534
- - Updated dependencies
535
- - @pikku/core@0.9.12-next.0
536
-
537
- ## 0.9.2
538
-
539
- ### Patch Changes
540
-
541
- - a5905a9: chore: updating all dependencies
542
- - Updated dependencies [1256238]
543
- - Updated dependencies [6cf8efd]
544
- - Updated dependencies [d3a9a09]
545
- - Updated dependencies [840e078]
546
- - Updated dependencies [667d23c]
547
- - Updated dependencies [a5905a9]
548
- - @pikku/core@0.9.2
549
-
550
- ## 0.9.1
551
-
552
- ### Patch Changes
553
-
554
- - fdb1593: core: bumping everything with a patch to sync up the major release inconsistencies in dependencies
555
- - Updated dependencies [fdb1593]
556
- - @pikku/core@0.9.1
557
-
558
- ## 0.9.0
559
-
560
- ### Breaking Changes
561
-
562
- - Normalized all transports to use "wirings" instead of events/routes/transports for consistency across the framework
563
-
564
- ## 0.8.0
565
-
566
- - Updating to match remaining packages
567
-
568
- ## 0.7.0
569
-
570
- - Updating to match remaining packages
571
-
572
- ## 0.6.1
573
-
574
- ### Patch Changes
575
-
576
- - 0a92fa7: refactor: pulling schema into seperate package since ajv doesnt work on cloudflare (also keeps bundle size small!)
577
- - Updated dependencies [0a92fa7]
578
- - @pikku/core@0.6.7
579
-
580
- ## 0.6
581
-
582
- Marking a major release to include channels and scheduled tasks
583
-
584
- ## 0.5.4
585
-
586
- ### Patch Changes
587
-
588
- - 886a2fb: refactor: moving singletons (like routes and channels) to global to avoid nodemodule overrides
589
- - Updated dependencies [a768bad]
590
- - Updated dependencies [886a2fb]
591
- - Updated dependencies [886a2fb]
592
- - @pikku/core@0.5.28
593
-
594
- ## 0.5.3
595
-
596
- ### Patch Changes
597
-
598
- - 0f96787: refactor: dropping cjs support
599
- - 64e4a1e: refactor: seperating core into cleaner sub-packages
600
- - c23524a: refactor: bump to versions to ensure correct package usage
601
- - Updated dependencies [0f96787]
602
- - Updated dependencies [64e4a1e]
603
- - Updated dependencies [c23524a]
604
- - @pikku/core@0.5.25
605
-
606
- ## 0.5.2
607
-
608
- ### Patch Changes
609
-
610
- - bba25cc: chore: updating all packages to reflect major changes
611
- - Updated dependencies [bba25cc]
612
- - Updated dependencies [9deb482]
613
- - Updated dependencies [ee0c6ea]
614
- - @pikku/core@0.5.24
615
-
616
- ## 0.5.1
617
-
618
- ### Patch Changes
619
-
620
- - d58c440: refactor: making http requests explicit to support other types
621
- - b01ecbf: fix: adding index file
622
- - Updated dependencies [cbcc75b]
623
- - Updated dependencies [d58c440]
624
- - Updated dependencies [11c50d4]
625
- - @pikku/core@0.5.19
package/run-tests.sh DELETED
@@ -1,53 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Enable nullglob to handle cases where no files match the pattern
4
- shopt -s nullglob
5
-
6
- # Initialize variables for options
7
- watch_mode=false
8
- coverage_mode=false
9
-
10
- # Parse command-line options
11
- while [[ $# -gt 0 ]]; do
12
- case $1 in
13
- --watch)
14
- watch_mode=true
15
- shift
16
- ;;
17
- --coverage)
18
- coverage_mode=true
19
- shift
20
- ;;
21
- *)
22
- echo "Unknown option: $1"
23
- exit 1
24
- ;;
25
- esac
26
- done
27
-
28
- # Define the pattern to match your test files
29
- pattern="src/*.test.ts"
30
-
31
- # Expand the pattern into an array of files
32
- files=($(find src -type f -name "*.test.ts"))
33
-
34
- # Check if any files matched the pattern
35
- if [ ${#files[@]} -eq 0 ]; then
36
- echo "No test files found matching pattern: $pattern"
37
- exit 0
38
- fi
39
-
40
- # Construct the node command
41
- node_cmd=(node --import tsx --test)
42
-
43
- # Append options based on flags
44
- if [ "$watch_mode" = true ]; then
45
- node_cmd+=(--watch)
46
- fi
47
-
48
- if [ "$coverage_mode" = true ]; then
49
- node_cmd+=(--test-coverage-include="src/**/*.{ts,js}" --test-coverage-exclude="**/dist/**" --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info)
50
- fi
51
-
52
- # Execute the node command with the expanded list of files
53
- "${node_cmd[@]}" "${files[@]}"
@@ -1,75 +0,0 @@
1
- /**
2
- * Azure Functions deployment service — routes remote() calls via HTTP.
3
- *
4
- * When a gateway (agent, MCP, channel, workflow orchestrator) calls
5
- * remote('funcName'), this service dispatches to the function's
6
- * Azure Function via HTTP. Uses the function's URL endpoint.
7
- *
8
- * Session propagation uses the existing pikkuRemoteAuthMiddleware:
9
- * JWT signed with PIKKU_REMOTE_SECRET, session encrypted in payload.
10
- */
11
-
12
- import type { DeploymentService, DeploymentConfig } from '@pikku/core/services'
13
- import type { JWTService } from '@pikku/core/services'
14
- import type { SecretService } from '@pikku/core/services'
15
- import { buildRemoteHeaders } from '@pikku/core/remote'
16
-
17
- export class AzureDeploymentService implements DeploymentService {
18
- private bindings: Map<string, string>
19
-
20
- constructor(
21
- private jwt: JWTService | undefined,
22
- private secrets: SecretService,
23
- functionBindings: Record<string, string>
24
- ) {
25
- this.bindings = new Map(Object.entries(functionBindings))
26
- }
27
-
28
- async init(): Promise<void> {}
29
- async start(_config: DeploymentConfig): Promise<void> {}
30
- async stop(): Promise<void> {}
31
-
32
- async invoke(
33
- funcName: string,
34
- data: unknown,
35
- session?: unknown,
36
- traceId?: string
37
- ): Promise<unknown> {
38
- const functionUrl = this.bindings.get(funcName)
39
- if (!functionUrl) {
40
- throw new Error(
41
- `No Azure Function binding for function '${funcName}'. ` +
42
- `Available bindings: ${[...this.bindings.keys()].join(', ')}`
43
- )
44
- }
45
-
46
- const headers = await buildRemoteHeaders(
47
- this.jwt,
48
- this.secrets,
49
- funcName,
50
- session,
51
- traceId
52
- )
53
-
54
- const url = `${functionUrl}/remote/rpc/${encodeURIComponent(funcName)}`
55
- const response = await fetch(url, {
56
- method: 'POST',
57
- headers,
58
- body: JSON.stringify({ data }),
59
- })
60
-
61
- if (!response.ok) {
62
- let errorBody: string
63
- try {
64
- errorBody = JSON.stringify(await response.json())
65
- } catch {
66
- errorBody = await response.text()
67
- }
68
- throw new Error(
69
- `Remote RPC call to '${funcName}' failed: ${response.status} ${response.statusText}. ${errorBody}`
70
- )
71
- }
72
-
73
- return response.json()
74
- }
75
- }
@@ -1,91 +0,0 @@
1
- /**
2
- * Azure Storage Queue implementation of QueueService (producer side).
3
- *
4
- * Sends messages to Azure Storage Queues. Queue URLs are resolved from
5
- * environment variables following the convention:
6
- * AZURE_QUEUE_NAME_<SCREAMING_SNAKE> (queue name) with the connection
7
- * string from AzureWebJobsStorage.
8
- */
9
-
10
- import { QueueServiceClient } from '@azure/storage-queue'
11
- import type { QueueClient } from '@azure/storage-queue'
12
- import type { QueueService, QueueJob } from '@pikku/core/queue'
13
- import type { JobOptions } from '@pikku/core/queue'
14
-
15
- export class AzureQueueService implements QueueService {
16
- readonly supportsResults = false
17
- private queueServiceClient: QueueServiceClient
18
- private queueClients = new Map<string, QueueClient>()
19
-
20
- constructor(connectionString?: string) {
21
- const connStr = connectionString ?? process.env.AzureWebJobsStorage
22
- if (!connStr) {
23
- throw new Error(
24
- 'Azure Queue connection string not provided; supply connectionString or set AzureWebJobsStorage'
25
- )
26
- }
27
- this.queueServiceClient = QueueServiceClient.fromConnectionString(connStr)
28
- }
29
-
30
- async add<T>(
31
- queueName: string,
32
- data: T,
33
- options?: JobOptions
34
- ): Promise<string> {
35
- const client = this.getQueueClient(queueName)
36
- const messageId = crypto.randomUUID()
37
-
38
- const message = {
39
- id: messageId,
40
- queueName,
41
- data,
42
- pikkuUserId: options?.pikkuUserId,
43
- }
44
-
45
- // Azure Storage Queue messages must be base64-encoded strings
46
- const encoded = Buffer.from(JSON.stringify(message)).toString('base64')
47
-
48
- const visibilityTimeout = options?.delay
49
- ? Math.min(Math.ceil(options.delay / 1000), 604800) // max 7 days
50
- : undefined
51
-
52
- await client.sendMessage(encoded, { visibilityTimeout })
53
- return messageId
54
- }
55
-
56
- async getJob<T, R>(
57
- _queueName: string,
58
- _jobId: string
59
- ): Promise<QueueJob<T, R> | null> {
60
- throw new Error(
61
- 'AzureQueueService does not support getJob(). ' +
62
- 'Azure Storage Queues are fire-and-forget. ' +
63
- 'Use BullMQ or PgBoss if you need job result tracking.'
64
- )
65
- }
66
-
67
- private getQueueClient(queueName: string): QueueClient {
68
- if (!this.queueClients.has(queueName)) {
69
- const resolvedName = this.resolveQueueName(queueName)
70
- const client = this.queueServiceClient.getQueueClient(resolvedName)
71
- this.queueClients.set(queueName, client)
72
- }
73
- return this.queueClients.get(queueName)!
74
- }
75
-
76
- /**
77
- * Resolves the Azure queue name. Checks env var
78
- * AZURE_QUEUE_NAME_<SCREAMING_SNAKE> first, falls back to the name as-is.
79
- */
80
- private resolveQueueName(queueName: string): string {
81
- const envKey = `AZURE_QUEUE_NAME_${toScreamingSnake(queueName)}`
82
- return process.env[envKey] ?? queueName
83
- }
84
- }
85
-
86
- function toScreamingSnake(name: string): string {
87
- return name
88
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
89
- .replace(/-/g, '_')
90
- .toUpperCase()
91
- }
@@ -1,205 +0,0 @@
1
- /**
2
- * Azure Functions handler factories — mirrors Lambda/CF handler factories.
3
- *
4
- * Creates handlers for Azure Functions v4 programming model:
5
- * - HTTP triggers (HttpRequest → HttpResponseInit)
6
- * - Storage Queue triggers (QueueItem → void)
7
- * - Timer triggers (Timer → void)
8
- *
9
- * Services are cached across invocations within the same function app instance.
10
- */
11
-
12
- import { LocalVariablesService, LocalSecretService } from '@pikku/core/services'
13
- import type { CoreSingletonServices } from '@pikku/core/types'
14
- import { PikkuFetchHTTPResponse, fetchData } from '@pikku/core/http'
15
- import { runQueueJob } from '@pikku/core/queue'
16
- import type { QueueJob, QueueJobStatus } from '@pikku/core/queue'
17
- import { runScheduledTask } from '@pikku/core/scheduler'
18
- import { getScheduledTasks } from '@pikku/core/scheduler'
19
- import type {
20
- HttpRequest,
21
- HttpResponseInit,
22
- InvocationContext,
23
- Timer,
24
- } from '@azure/functions'
25
-
26
- export interface AzureServiceFactories {
27
- createConfig: (
28
- variables: LocalVariablesService,
29
- ...args: unknown[]
30
- ) => Promise<unknown>
31
- createSingletonServices: (
32
- config: unknown,
33
- existingServices?: Partial<Record<string, unknown>>
34
- ) => Promise<CoreSingletonServices>
35
- createPlatformServices?: () =>
36
- Record<string, unknown> | Promise<Record<string, unknown>>
37
- }
38
-
39
- let cachedServices: CoreSingletonServices | null = null
40
-
41
- async function setupServices(
42
- factories: AzureServiceFactories
43
- ): Promise<CoreSingletonServices> {
44
- if (cachedServices) return cachedServices
45
- const variables = new LocalVariablesService(
46
- process.env as Record<string, string | undefined>
47
- )
48
- const config = await factories.createConfig(variables)
49
- const secrets = new LocalSecretService(variables)
50
-
51
- const platformServices = factories.createPlatformServices
52
- ? await factories.createPlatformServices()
53
- : {}
54
-
55
- cachedServices = await factories.createSingletonServices(config, {
56
- variables,
57
- secrets,
58
- ...platformServices,
59
- })
60
- return cachedServices
61
- }
62
-
63
- /**
64
- * Creates combined Azure Function handlers based on which handler types
65
- * the unit needs. Returns an object with named handler functions that
66
- * the entry file registers via app.http(), app.storageQueue(), app.timer().
67
- */
68
- export function createAzureHandler(
69
- factories: AzureServiceFactories,
70
- handlerTypes: string[]
71
- ) {
72
- const result: Record<string, unknown> = {}
73
-
74
- if (handlerTypes.includes('fetch')) {
75
- result.http = async (
76
- request: HttpRequest,
77
- _context: InvocationContext
78
- ): Promise<HttpResponseInit> => {
79
- await setupServices(factories)
80
-
81
- // Convert Azure HttpRequest to standard Request
82
- const body =
83
- request.method !== 'GET' && request.method !== 'HEAD'
84
- ? await request.text()
85
- : undefined
86
- const webRequest = new Request(request.url, {
87
- method: request.method,
88
- headers: Object.fromEntries(request.headers.entries()),
89
- body,
90
- })
91
-
92
- const response = new PikkuFetchHTTPResponse()
93
- try {
94
- await fetchData(webRequest, response)
95
- } catch (err) {
96
- console.error('Azure HTTP handler: fetchData error', err)
97
- }
98
-
99
- const fetchResponse = response.toResponse()
100
- const headers: Record<string, string> = {}
101
- fetchResponse.headers.forEach((value, key) => {
102
- headers[key] = value
103
- })
104
-
105
- return {
106
- status: fetchResponse.status,
107
- headers,
108
- body: await fetchResponse.text(),
109
- }
110
- }
111
- }
112
-
113
- if (handlerTypes.includes('queue')) {
114
- result.queue = async (
115
- queueItem: unknown,
116
- context: InvocationContext
117
- ): Promise<void> => {
118
- await setupServices(factories)
119
-
120
- let data: Record<string, unknown>
121
- if (typeof queueItem === 'string') {
122
- try {
123
- data = JSON.parse(queueItem)
124
- } catch {
125
- data = { body: queueItem }
126
- }
127
- } else {
128
- data = queueItem as Record<string, unknown>
129
- }
130
-
131
- const queueName =
132
- (data.queueName as string) ??
133
- context.triggerMetadata?.queueTrigger ??
134
- 'unknown'
135
-
136
- const job: QueueJob = {
137
- queueName,
138
- data: data.data ?? data,
139
- id: context.invocationId,
140
- status: async () => 'active' as QueueJobStatus,
141
- metadata: () => ({
142
- processedAt: new Date(),
143
- attemptsMade: (context.triggerMetadata?.dequeueCount as number) ?? 0,
144
- maxAttempts: undefined,
145
- result: undefined,
146
- progress: 0,
147
- createdAt: new Date(),
148
- completedAt: undefined,
149
- failedAt: undefined,
150
- error: undefined,
151
- }),
152
- waitForCompletion: async () => {
153
- throw new Error(
154
- 'Azure Storage Queues do not support waitForCompletion'
155
- )
156
- },
157
- }
158
-
159
- await runQueueJob({ job })
160
- }
161
- }
162
-
163
- if (handlerTypes.includes('scheduled')) {
164
- result.timer = async (
165
- _timer: Timer,
166
- _context: InvocationContext
167
- ): Promise<void> => {
168
- await setupServices(factories)
169
- const traceId = `cron-${crypto.randomUUID()}`
170
- const scheduledTasks = getScheduledTasks()
171
- for (const [name] of scheduledTasks) {
172
- await runScheduledTask({ name, traceId })
173
- }
174
- }
175
- }
176
-
177
- return result
178
- }
179
-
180
- /**
181
- * Creates an Azure Function handler with an HTTP trigger for gateway units.
182
- */
183
- export function createAzureWorkerHandler(factories: AzureServiceFactories) {
184
- const handlers = createAzureHandler(factories, ['fetch'])
185
- return handlers
186
- }
187
-
188
- /**
189
- * Creates Azure Function handlers for WebSocket channel units.
190
- * Uses HTTP negotiation endpoint for Azure Web PubSub.
191
- */
192
- export function createAzureWebSocketHandler(_factories: AzureServiceFactories) {
193
- return {
194
- async negotiate(
195
- _request: HttpRequest,
196
- _context: InvocationContext
197
- ): Promise<HttpResponseInit> {
198
- // Web PubSub negotiation — to be implemented with @azure/web-pubsub
199
- return {
200
- status: 501,
201
- body: 'WebSocket via Azure Web PubSub not yet implemented',
202
- }
203
- },
204
- }
205
- }
package/src/index.test.ts DELETED
@@ -1,13 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { describe, test } from 'node:test'
3
-
4
- import * as azureFunctions from './index.js'
5
-
6
- describe('@pikku/azure-functions', () => {
7
- test('exports the azure runtime API', () => {
8
- assert.equal(typeof azureFunctions.createAzureHandler, 'function')
9
- assert.equal(typeof azureFunctions.AzureDeploymentService, 'function')
10
- assert.equal(typeof azureFunctions.AzureQueueService, 'function')
11
- assert.equal(typeof azureFunctions.AzInvocationLogger, 'function')
12
- })
13
- })
package/src/index.ts DELETED
@@ -1,9 +0,0 @@
1
- export * from './pikku-az-functions-logger.js'
2
- export * from './pikku-az-timer-request.js'
3
- export {
4
- createAzureHandler,
5
- createAzureWorkerHandler,
6
- createAzureWebSocketHandler,
7
- } from './handler-factories.js'
8
- export { AzureQueueService } from './azure-queue-service.js'
9
- export { AzureDeploymentService } from './azure-deployment-service.js'
@@ -1,48 +0,0 @@
1
- import type { InvocationContext } from '@azure/functions'
2
- import type { Logger, LogLevel } from '@pikku/core/services'
3
- import type { Safe } from '@pikku/core/classification'
4
-
5
- export class AzInvocationLogger implements Logger {
6
- // private logLevel: LogLevel = LogLevel.info
7
-
8
- constructor(private context: InvocationContext) {}
9
-
10
- public info<M extends string | Record<string, any>, A extends unknown[]>(
11
- messageOrObj: Safe<M>,
12
- ...meta: { [K in keyof A]: Safe<A[K]> }
13
- ): void {
14
- this.context.info(messageOrObj as any, ...meta)
15
- }
16
-
17
- public warn<M extends string | Record<string, any>, A extends unknown[]>(
18
- messageOrObj: Safe<M>,
19
- ...meta: { [K in keyof A]: Safe<A[K]> }
20
- ): void {
21
- this.context.warn(messageOrObj as any, ...meta)
22
- }
23
-
24
- public error<
25
- M extends string | Record<string, any> | Error,
26
- A extends unknown[],
27
- >(messageOrObj: Safe<M>, ...meta: { [K in keyof A]: Safe<A[K]> }): void {
28
- this.context.error(messageOrObj as any, ...meta)
29
- }
30
-
31
- public debug<A extends unknown[]>(
32
- message: string,
33
- ...meta: { [K in keyof A]: Safe<A[K]> }
34
- ): void {
35
- this.context.debug(message, ...meta)
36
- }
37
-
38
- public trace<A extends unknown[]>(
39
- message: string,
40
- ...meta: { [K in keyof A]: Safe<A[K]> }
41
- ): void {
42
- this.context.trace(message, ...meta)
43
- }
44
-
45
- public setLevel(level: LogLevel): void {
46
- // this.logLevel = level
47
- }
48
- }
@@ -1,18 +0,0 @@
1
- import { createSecretValue } from '@pikku/core/classification'
2
- import type { AzInvocationLogger } from './pikku-az-functions-logger.js'
3
-
4
- // A concrete logger must not be a way around the `Logger` guard.
5
- const _assertions = (logger: AzInvocationLogger) => {
6
- const secret = createSecretValue('sk-live-DEADBEEF')
7
-
8
- // @ts-expect-error a secret cannot be logged
9
- logger.info(secret)
10
- // @ts-expect-error nor nested in the message
11
- logger.info({ token: secret })
12
- // @ts-expect-error nor in the metadata
13
- logger.warn('using', { token: secret })
14
-
15
- logger.info('fine')
16
- logger.info({ count: 1 })
17
- }
18
- void _assertions
@@ -1,8 +0,0 @@
1
- import type { InvocationContext } from '@azure/functions'
2
- import { PikkuRequest } from '@pikku/core/function'
3
-
4
- export class PikkuAZTimerRequest<In = any> extends PikkuRequest<In> {
5
- constructor(_context: InvocationContext, data: In) {
6
- super(data)
7
- }
8
- }
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "module": "Node18",
6
- "outDir": "dist",
7
- "target": "esnext",
8
- "declaration": true
9
- },
10
- "include": ["bin/**/*.ts", "src/**/*.ts"],
11
- "exclude": ["**/*.test.ts", "node_modules", "bin/dist"],
12
- "references": [
13
- {
14
- "path": "../../core/tsconfig.json"
15
- }
16
- ]
17
- }
@@ -1 +0,0 @@
1
- {"root":["./src/azure-deployment-service.ts","./src/azure-queue-service.ts","./src/handler-factories.ts","./src/index.ts","./src/pikku-az-functions-logger.ts","./src/pikku-az-timer-request.ts"],"version":"6.0.3"}
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "noEmit": true,
5
- "composite": false,
6
- "declaration": false,
7
- "declarationMap": false
8
- },
9
- "files": ["src/pikku-az-functions-logger.type.test.ts"]
10
- }