@pikku/azure-functions 0.12.7 → 0.12.9

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 CHANGED
@@ -1,3 +1,49 @@
1
+ ## 0.12.9
2
+
3
+ ### Patch Changes
4
+
5
+ - 4c7a1b5: Run the monorepo's own scripts through bun instead of yarn. What moves is the
6
+ package manager each package's `prepublishOnly` and build scripts invoke, plus
7
+ the two manifest fixes bun needs to resolve the tree: `uWebSockets.js` is
8
+ declared with an explicit `github:` specifier, and `@pikku/uws-handler` marks
9
+ its `uWebSockets.js` peer optional so a bun install of a consumer that brings
10
+ its own uWS app does not try to fetch it from the registry.
11
+
12
+ Three published behaviours change, all of them cases where an isolated
13
+ `node_modules` or bun as the runtime had been papered over by yarn's hoisting:
14
+
15
+ - `@pikku/migrator-sql` turns foreign keys on when it opens a sqlite database
16
+ through bun. `node:sqlite` enforces them by default and `bun:sqlite` does not,
17
+ which silently turned every `ON DELETE CASCADE` into a no-op under
18
+ `bunx --bun pikku`.
19
+ - `@pikku/cli` resolves a deploy provider against the project being deployed
20
+ rather than against wherever the CLI itself is installed, which is what its
21
+ own "is not installed" error asks the user to arrange.
22
+ - `@pikku/cli` treats a specifier a runtime hands straight back — bun does this
23
+ for the modules it implements itself — as not resolved from the project, so
24
+ it falls back rather than loading the runtime's own copy.
25
+
26
+ - Updated dependencies [4c7a1b5]
27
+ - @pikku/core@0.12.108
28
+
29
+ ## 0.12.8
30
+
31
+ ### Patch Changes
32
+
33
+ - 3a83f85: Stop re-exporting package internals through entry points
34
+
35
+ 66 names reached consumers only because an `export *` in an entry point swept
36
+ them up. Each one is referenced solely inside its own package, so the star is
37
+ now an explicit named re-export listing what is genuinely public. The
38
+ declarations themselves are untouched — this narrows the entry point, not the
39
+ module.
40
+
41
+ - Updated dependencies [9687ad1]
42
+ - Updated dependencies [2d21628]
43
+ - Updated dependencies [985b87b]
44
+ - Updated dependencies [3a83f85]
45
+ - @pikku/core@0.12.87
46
+
1
47
  ## 0.12.7
2
48
 
3
49
  ### Patch Changes
@@ -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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './pikku-az-functions-logger.js';
2
2
  export * from './pikku-az-timer-request.js';
3
- export * from './handler-factories.js';
3
+ export { createAzureHandler, createAzureWorkerHandler, createAzureWebSocketHandler, } from './handler-factories.js';
4
4
  export { AzureQueueService } from './azure-queue-service.js';
5
5
  export { AzureDeploymentService } from './azure-deployment-service.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './pikku-az-functions-logger.js';
2
2
  export * from './pikku-az-timer-request.js';
3
- export * from './handler-factories.js';
3
+ export { createAzureHandler, createAzureWorkerHandler, createAzureWebSocketHandler, } from './handler-factories.js';
4
4
  export { AzureQueueService } from './azure-queue-service.js';
5
5
  export { AzureDeploymentService } from './azure-deployment-service.js';
6
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAA;AAC9C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,wBAAwB,CAAA;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAA;AAC9C,cAAc,6BAA6B,CAAA;AAC3C,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,2BAA2B,GAC5B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA"}
@@ -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.7",
3
+ "version": "0.12.9",
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",
@@ -15,13 +20,10 @@
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"
19
24
  },
20
25
  "peerDependencies": {
21
- "@pikku/core": "^0.12.85"
22
- },
23
- "devDependencies": {
24
- "typescript": "^6.0.3"
26
+ "@pikku/core": "^0.12.108"
25
27
  },
26
28
  "engines": {
27
29
  "node": ">=24"
@@ -33,4 +35,4 @@
33
35
  "exports": {
34
36
  ".": "./dist/index.js"
35
37
  }
36
- }
38
+ }
package/run-tests.sh CHANGED
@@ -34,20 +34,35 @@ files=($(find src -type f -name "*.test.ts"))
34
34
  # Check if any files matched the pattern
35
35
  if [ ${#files[@]} -eq 0 ]; then
36
36
  echo "No test files found matching pattern: $pattern"
37
+ if [ "${STRICT_TEST_DISCOVERY}" = "1" ]; then
38
+ exit 1
39
+ fi
37
40
  exit 0
38
41
  fi
39
42
 
40
- # Construct the node command
41
- node_cmd=(node --import tsx --test)
43
+ # Construct the bun command
44
+ #
45
+ # --parallel gives every test file its own worker process, which is what node's
46
+ # `--test` did. Several suites keep module-level caches (a TypeScript program, a
47
+ # temp fixture directory) that a shared process would leak between files.
48
+ #
49
+ # --timeout because bun caps a test at 5s by default where node had no cap, and
50
+ # the suites that build real TypeScript programs run well past that.
51
+ bun_cmd=(bun test --parallel --timeout 120000)
42
52
 
43
53
  # Append options based on flags
44
54
  if [ "$watch_mode" = true ]; then
45
- node_cmd+=(--watch)
55
+ # --watch reruns in one process; parallel workers have nothing to watch.
56
+ bun_cmd=(bun test --watch --timeout 120000)
46
57
  fi
47
58
 
48
59
  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)
60
+ # --coverage-dir=. keeps lcov.info at the package root: CI merges every
61
+ # package's report by prefixing its SF paths with the file's own directory,
62
+ # so a nested coverage/ directory would re-root them one level too deep.
63
+ bun_cmd+=(--coverage --coverage-reporter=lcov --coverage-reporter=text --coverage-dir=.)
64
+ export PIKKU_TEST_COVERAGE=1
50
65
  fi
51
66
 
52
- # Execute the node command with the expanded list of files
53
- "${node_cmd[@]}" "${files[@]}"
67
+ # Execute the bun command with the expanded list of files
68
+ "${bun_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,5 +0,0 @@
1
- export * from './pikku-az-functions-logger.js'
2
- export * from './pikku-az-timer-request.js'
3
- export * from './handler-factories.js'
4
- export { AzureQueueService } from './azure-queue-service.js'
5
- 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
- }