@pikku/azure-functions 0.12.8 → 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 +28 -0
- package/dist/azure-deployment-service.js.map +1 -1
- package/dist/pikku-az-functions-logger.js.map +1 -1
- package/package.json +9 -7
- package/run-tests.sh +21 -6
- package/src/azure-deployment-service.ts +0 -75
- package/src/azure-queue-service.ts +0 -91
- package/src/handler-factories.ts +0 -205
- package/src/index.test.ts +0 -13
- package/src/index.ts +0 -9
- package/src/pikku-az-functions-logger.ts +0 -48
- package/src/pikku-az-functions-logger.type.test.ts +0 -18
- package/src/pikku-az-timer-request.ts +0 -8
- package/tsconfig.json +0 -17
- package/tsconfig.tsbuildinfo +0 -1
- package/tsconfig.type-tests.json +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
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
|
+
|
|
1
29
|
## 0.12.8
|
|
2
30
|
|
|
3
31
|
### 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;
|
|
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;
|
|
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.
|
|
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": "
|
|
23
|
+
"prepublishOnly": "bun run build"
|
|
19
24
|
},
|
|
20
25
|
"peerDependencies": {
|
|
21
|
-
"@pikku/core": "^0.12.
|
|
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
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
53
|
-
"${
|
|
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
|
-
}
|
package/src/handler-factories.ts
DELETED
|
@@ -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
|
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
|
-
}
|
package/tsconfig.tsbuildinfo
DELETED
|
@@ -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"}
|