@pikku/azure-functions 0.12.0 → 0.12.2

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,5 +1,34 @@
1
+ ## 0.12.2
2
+
3
+ ### Patch Changes
4
+
5
+ - 41ce2cb: Upgrade to TypeScript 6 and raise the minimum Node.js version to 22.
6
+
7
+ 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.
8
+
9
+ - Updated dependencies [41ce2cb]
10
+ - @pikku/core@0.12.44
11
+
1
12
  ## 0.12.0
2
13
 
14
+ ## 0.12.1
15
+
16
+ ### Patch Changes
17
+
18
+ - 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.
19
+ - Updated dependencies [62a8725]
20
+ - Updated dependencies [a3bdb0d]
21
+ - Updated dependencies [e0349ff]
22
+ - Updated dependencies [62a8725]
23
+ - Updated dependencies [e04531f]
24
+ - Updated dependencies [62a8725]
25
+ - Updated dependencies [a83efb8]
26
+ - Updated dependencies [8eed717]
27
+ - Updated dependencies [62a8725]
28
+ - Updated dependencies [62a8725]
29
+ - Updated dependencies [62a8725]
30
+ - @pikku/core@0.12.1
31
+
3
32
  - Updated dependencies
4
33
 
5
34
  ## 0.11.0
@@ -0,0 +1,13 @@
1
+ import type { DeploymentService, DeploymentConfig } from '@pikku/core/services';
2
+ import type { JWTService } from '@pikku/core/services';
3
+ import type { SecretService } from '@pikku/core/services';
4
+ export declare class AzureDeploymentService implements DeploymentService {
5
+ private jwt;
6
+ private secrets;
7
+ private bindings;
8
+ constructor(jwt: JWTService | undefined, secrets: SecretService, functionBindings: Record<string, string>);
9
+ init(): Promise<void>;
10
+ start(_config: DeploymentConfig): Promise<void>;
11
+ stop(): Promise<void>;
12
+ invoke(funcName: string, data: unknown, session?: unknown, traceId?: string): Promise<unknown>;
13
+ }
@@ -0,0 +1,60 @@
1
+ import { encryptJSON } from '@pikku/core/crypto-utils';
2
+ export class AzureDeploymentService {
3
+ jwt;
4
+ secrets;
5
+ bindings;
6
+ constructor(jwt, secrets, functionBindings) {
7
+ this.jwt = jwt;
8
+ this.secrets = secrets;
9
+ this.bindings = new Map(Object.entries(functionBindings));
10
+ }
11
+ async init() { }
12
+ async start(_config) { }
13
+ async stop() { }
14
+ async invoke(funcName, data, session, traceId) {
15
+ const functionUrl = this.bindings.get(funcName);
16
+ if (!functionUrl) {
17
+ throw new Error(`No Azure Function binding for function '${funcName}'. ` +
18
+ `Available bindings: ${[...this.bindings.keys()].join(', ')}`);
19
+ }
20
+ const headers = {
21
+ 'content-type': 'application/json',
22
+ ...(traceId && { 'x-request-id': traceId }),
23
+ };
24
+ let secret;
25
+ try {
26
+ secret = await this.secrets.getSecret('PIKKU_REMOTE_SECRET');
27
+ }
28
+ catch { }
29
+ if (secret && this.jwt) {
30
+ const sessionEnc = session
31
+ ? await encryptJSON(secret, { session })
32
+ : undefined;
33
+ const token = await this.jwt.encode({ value: 5, unit: 'minute' }, {
34
+ aud: 'pikku-remote',
35
+ fn: funcName,
36
+ iat: Math.floor(Date.now() / 1000),
37
+ session: sessionEnc,
38
+ });
39
+ headers.authorization = `Bearer ${token}`;
40
+ }
41
+ const url = `${functionUrl}/remote/rpc/${encodeURIComponent(funcName)}`;
42
+ const response = await fetch(url, {
43
+ method: 'POST',
44
+ headers,
45
+ body: JSON.stringify({ data }),
46
+ });
47
+ if (!response.ok) {
48
+ let errorBody;
49
+ try {
50
+ errorBody = JSON.stringify(await response.json());
51
+ }
52
+ catch {
53
+ errorBody = await response.text();
54
+ }
55
+ throw new Error(`Remote RPC call to '${funcName}' failed: ${response.status} ${response.statusText}. ${errorBody}`);
56
+ }
57
+ return response.json();
58
+ }
59
+ }
60
+ //# sourceMappingURL=azure-deployment-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"azure-deployment-service.js","sourceRoot":"","sources":["../src/azure-deployment-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { QueueService, QueueJob, JobOptions } from '@pikku/core/queue';
2
+ export declare class AzureQueueService implements QueueService {
3
+ readonly supportsResults = false;
4
+ private queueServiceClient;
5
+ private queueClients;
6
+ constructor(connectionString?: string);
7
+ add<T>(queueName: string, data: T, options?: JobOptions): Promise<string>;
8
+ getJob<T, R>(_queueName: string, _jobId: string): Promise<QueueJob<T, R> | null>;
9
+ private getQueueClient;
10
+ private resolveQueueName;
11
+ }
@@ -0,0 +1,53 @@
1
+ import { QueueServiceClient } from '@azure/storage-queue';
2
+ export class AzureQueueService {
3
+ supportsResults = false;
4
+ queueServiceClient;
5
+ queueClients = new Map();
6
+ constructor(connectionString) {
7
+ const connStr = connectionString ?? process.env.AzureWebJobsStorage;
8
+ if (!connStr) {
9
+ throw new Error('Azure Queue connection string not provided; supply connectionString or set AzureWebJobsStorage');
10
+ }
11
+ this.queueServiceClient = QueueServiceClient.fromConnectionString(connStr);
12
+ }
13
+ async add(queueName, data, options) {
14
+ const client = this.getQueueClient(queueName);
15
+ const messageId = crypto.randomUUID();
16
+ const message = {
17
+ id: messageId,
18
+ queueName,
19
+ data,
20
+ pikkuUserId: options?.pikkuUserId,
21
+ };
22
+ const encoded = Buffer.from(JSON.stringify(message)).toString('base64');
23
+ const visibilityTimeout = options?.delay
24
+ ? Math.min(Math.ceil(options.delay / 1000), 604800)
25
+ : undefined;
26
+ await client.sendMessage(encoded, { visibilityTimeout });
27
+ return messageId;
28
+ }
29
+ async getJob(_queueName, _jobId) {
30
+ throw new Error('AzureQueueService does not support getJob(). ' +
31
+ 'Azure Storage Queues are fire-and-forget. ' +
32
+ 'Use BullMQ or PgBoss if you need job result tracking.');
33
+ }
34
+ getQueueClient(queueName) {
35
+ if (!this.queueClients.has(queueName)) {
36
+ const resolvedName = this.resolveQueueName(queueName);
37
+ const client = this.queueServiceClient.getQueueClient(resolvedName);
38
+ this.queueClients.set(queueName, client);
39
+ }
40
+ return this.queueClients.get(queueName);
41
+ }
42
+ resolveQueueName(queueName) {
43
+ const envKey = `AZURE_QUEUE_NAME_${toScreamingSnake(queueName)}`;
44
+ return process.env[envKey] ?? queueName;
45
+ }
46
+ }
47
+ function toScreamingSnake(name) {
48
+ return name
49
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
50
+ .replace(/-/g, '_')
51
+ .toUpperCase();
52
+ }
53
+ //# sourceMappingURL=azure-queue-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"azure-queue-service.js","sourceRoot":"","sources":["../src/azure-queue-service.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAIzD,MAAM,OAAO,iBAAiB;IACnB,eAAe,GAAG,KAAK,CAAA;IACxB,kBAAkB,CAAoB;IACtC,YAAY,GAAG,IAAI,GAAG,EAAuB,CAAA;IAErD,YAAY,gBAAyB;QACnC,MAAM,OAAO,GAAG,gBAAgB,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAA;QACnE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,gGAAgG,CACjG,CAAA;QACH,CAAC;QACD,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAA;IAC5E,CAAC;IAED,KAAK,CAAC,GAAG,CACP,SAAiB,EACjB,IAAO,EACP,OAAoB;QAEpB,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAA;QAC7C,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,EAAE,CAAA;QAErC,MAAM,OAAO,GAAG;YACd,EAAE,EAAE,SAAS;YACb,SAAS;YACT,IAAI;YACJ,WAAW,EAAE,OAAO,EAAE,WAAW;SAClC,CAAA;QAGD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAEvE,MAAM,iBAAiB,GAAG,OAAO,EAAE,KAAK;YACtC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,MAAM,CAAC;YACnD,CAAC,CAAC,SAAS,CAAA;QAEb,MAAM,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,iBAAiB,EAAE,CAAC,CAAA;QACxD,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,KAAK,CAAC,MAAM,CACV,UAAkB,EAClB,MAAc;QAEd,MAAM,IAAI,KAAK,CACb,+CAA+C;YAC7C,4CAA4C;YAC5C,uDAAuD,CAC1D,CAAA;IACH,CAAC;IAEO,cAAc,CAAC,SAAiB;QACtC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACtC,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAA;YACrD,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAC,cAAc,CAAC,YAAY,CAAC,CAAA;YACnE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAC1C,CAAC;QACD,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAE,CAAA;IAC1C,CAAC;IAMO,gBAAgB,CAAC,SAAiB;QACxC,MAAM,MAAM,GAAG,oBAAoB,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAA;QAChE,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS,CAAA;IACzC,CAAC;CACF;AAED,SAAS,gBAAgB,CAAC,IAAY;IACpC,OAAO,IAAI;SACR,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;SAClB,WAAW,EAAE,CAAA;AAClB,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { LocalVariablesService } from '@pikku/core/services';
2
+ import type { CoreSingletonServices } from '@pikku/core';
3
+ import type { HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
4
+ export interface AzureServiceFactories {
5
+ createConfig: (variables: LocalVariablesService, ...args: unknown[]) => Promise<unknown>;
6
+ createSingletonServices: (config: unknown, existingServices?: Partial<Record<string, unknown>>) => Promise<CoreSingletonServices>;
7
+ createPlatformServices?: () => Record<string, unknown> | Promise<Record<string, unknown>>;
8
+ }
9
+ export declare function createAzureHandler(factories: AzureServiceFactories, handlerTypes: string[]): Record<string, unknown>;
10
+ export declare function createAzureWorkerHandler(factories: AzureServiceFactories): Record<string, unknown>;
11
+ export declare function createAzureWebSocketHandler(_factories: AzureServiceFactories): {
12
+ negotiate(_request: HttpRequest, _context: InvocationContext): Promise<HttpResponseInit>;
13
+ };
@@ -0,0 +1,121 @@
1
+ import { LocalVariablesService, LocalSecretService } from '@pikku/core/services';
2
+ import { PikkuFetchHTTPResponse, fetchData } from '@pikku/core/http';
3
+ import { runQueueJob } from '@pikku/core/queue';
4
+ import { runScheduledTask, getScheduledTasks } from '@pikku/core/scheduler';
5
+ let cachedServices = null;
6
+ async function setupServices(factories) {
7
+ if (cachedServices)
8
+ return cachedServices;
9
+ const variables = new LocalVariablesService(process.env);
10
+ const config = await factories.createConfig(variables);
11
+ const secrets = new LocalSecretService(variables);
12
+ const platformServices = factories.createPlatformServices
13
+ ? await factories.createPlatformServices()
14
+ : {};
15
+ cachedServices = await factories.createSingletonServices(config, {
16
+ variables,
17
+ secrets,
18
+ ...platformServices,
19
+ });
20
+ return cachedServices;
21
+ }
22
+ export function createAzureHandler(factories, handlerTypes) {
23
+ const result = {};
24
+ if (handlerTypes.includes('fetch')) {
25
+ result.http = async (request, _context) => {
26
+ await setupServices(factories);
27
+ const body = request.method !== 'GET' && request.method !== 'HEAD'
28
+ ? await request.text()
29
+ : undefined;
30
+ const webRequest = new Request(request.url, {
31
+ method: request.method,
32
+ headers: Object.fromEntries(request.headers.entries()),
33
+ body,
34
+ });
35
+ const response = new PikkuFetchHTTPResponse();
36
+ try {
37
+ await fetchData(webRequest, response);
38
+ }
39
+ catch (err) {
40
+ console.error('Azure HTTP handler: fetchData error', err);
41
+ }
42
+ const fetchResponse = response.toResponse();
43
+ const headers = {};
44
+ fetchResponse.headers.forEach((value, key) => {
45
+ headers[key] = value;
46
+ });
47
+ return {
48
+ status: fetchResponse.status,
49
+ headers,
50
+ body: await fetchResponse.text(),
51
+ };
52
+ };
53
+ }
54
+ if (handlerTypes.includes('queue')) {
55
+ result.queue = async (queueItem, context) => {
56
+ await setupServices(factories);
57
+ let data;
58
+ if (typeof queueItem === 'string') {
59
+ try {
60
+ data = JSON.parse(queueItem);
61
+ }
62
+ catch {
63
+ data = { body: queueItem };
64
+ }
65
+ }
66
+ else {
67
+ data = queueItem;
68
+ }
69
+ const queueName = data.queueName ??
70
+ context.triggerMetadata?.queueTrigger ??
71
+ 'unknown';
72
+ const job = {
73
+ queueName,
74
+ data: data.data ?? data,
75
+ id: context.invocationId,
76
+ status: async () => 'active',
77
+ metadata: () => ({
78
+ processedAt: new Date(),
79
+ attemptsMade: context.triggerMetadata?.dequeueCount ?? 0,
80
+ maxAttempts: undefined,
81
+ result: undefined,
82
+ progress: 0,
83
+ createdAt: new Date(),
84
+ completedAt: undefined,
85
+ failedAt: undefined,
86
+ error: undefined,
87
+ }),
88
+ waitForCompletion: async () => {
89
+ throw new Error('Azure Storage Queues do not support waitForCompletion');
90
+ },
91
+ };
92
+ await runQueueJob({ job });
93
+ };
94
+ }
95
+ if (handlerTypes.includes('scheduled')) {
96
+ result.timer = async (_timer, _context) => {
97
+ await setupServices(factories);
98
+ const traceId = `cron-${crypto.randomUUID()}`;
99
+ const scheduledTasks = getScheduledTasks();
100
+ for (const [name] of scheduledTasks) {
101
+ await runScheduledTask({ name, traceId });
102
+ }
103
+ };
104
+ }
105
+ return result;
106
+ }
107
+ export function createAzureWorkerHandler(factories) {
108
+ const handlers = createAzureHandler(factories, ['fetch']);
109
+ return handlers;
110
+ }
111
+ export function createAzureWebSocketHandler(_factories) {
112
+ return {
113
+ async negotiate(_request, _context) {
114
+ return {
115
+ status: 501,
116
+ body: 'WebSocket via Azure Web PubSub not yet implemented',
117
+ };
118
+ },
119
+ };
120
+ }
121
+ //# sourceMappingURL=handler-factories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-factories.js","sourceRoot":"","sources":["../src/handler-factories.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAEhF,OAAO,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAsB3E,IAAI,cAAc,GAAiC,IAAI,CAAA;AAEvD,KAAK,UAAU,aAAa,CAC1B,SAAgC;IAEhC,IAAI,cAAc;QAAE,OAAO,cAAc,CAAA;IACzC,MAAM,SAAS,GAAG,IAAI,qBAAqB,CACzC,OAAO,CAAC,GAAyC,CAClD,CAAA;IACD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,SAAS,CAAC,CAAA;IACtD,MAAM,OAAO,GAAG,IAAI,kBAAkB,CAAC,SAAS,CAAC,CAAA;IAEjD,MAAM,gBAAgB,GAAG,SAAS,CAAC,sBAAsB;QACvD,CAAC,CAAC,MAAM,SAAS,CAAC,sBAAsB,EAAE;QAC1C,CAAC,CAAC,EAAE,CAAA;IAEN,cAAc,GAAG,MAAM,SAAS,CAAC,uBAAuB,CAAC,MAAM,EAAE;QAC/D,SAAS;QACT,OAAO;QACP,GAAG,gBAAgB;KACpB,CAAC,CAAA;IACF,OAAO,cAAc,CAAA;AACvB,CAAC;AAOD,MAAM,UAAU,kBAAkB,CAChC,SAAgC,EAChC,YAAsB;IAEtB,MAAM,MAAM,GAA4B,EAAE,CAAA;IAE1C,IAAI,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACnC,MAAM,CAAC,IAAI,GAAG,KAAK,EACjB,OAAoB,EACpB,QAA2B,EACA,EAAE;YAC7B,MAAM,aAAa,CAAC,SAAS,CAAC,CAAA;YAG9B,MAAM,IAAI,GACR,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;gBACnD,CAAC,CAAC,MAAM,OAAO,CAAC,IAAI,EAAE;gBACtB,CAAC,CAAC,SAAS,CAAA;YACf,MAAM,UAAU,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;gBACtD,IAAI;aACL,CAAC,CAAA;YAEF,MAAM,QAAQ,GAAG,IAAI,sBAAsB,EAAE,CAAA;YAC7C,IAAI,CAAC;gBACH,MAAM,SAAS,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;YACvC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,GAAG,CAAC,CAAA;YAC3D,CAAC;YAED,MAAM,aAAa,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAA;YAC3C,MAAM,OAAO,GAA2B,EAAE,CAAA;YAC1C,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBAC3C,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;YACtB,CAAC,CAAC,CAAA;YAEF,OAAO;gBACL,MAAM,EAAE,aAAa,CAAC,MAAM;gBAC5B,OAAO;gBACP,IAAI,EAAE,MAAM,aAAa,CAAC,IAAI,EAAE;aACjC,CAAA;QACH,CAAC,CAAA;IACH,CAAC;IAED,IAAI,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACnC,MAAM,CAAC,KAAK,GAAG,KAAK,EAClB,SAAkB,EAClB,OAA0B,EACX,EAAE;YACjB,MAAM,aAAa,CAAC,SAAS,CAAC,CAAA;YAE9B,IAAI,IAA6B,CAAA;YACjC,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;gBAClC,IAAI,CAAC;oBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;gBAC9B,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;gBAC5B,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,IAAI,GAAG,SAAoC,CAAA;YAC7C,CAAC;YAED,MAAM,SAAS,GACZ,IAAI,CAAC,SAAoB;gBAC1B,OAAO,CAAC,eAAe,EAAE,YAAY;gBACrC,SAAS,CAAA;YAEX,MAAM,GAAG,GAAa;gBACpB,SAAS;gBACT,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;gBACvB,EAAE,EAAE,OAAO,CAAC,YAAY;gBACxB,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,QAA0B;gBAC9C,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;oBACf,WAAW,EAAE,IAAI,IAAI,EAAE;oBACvB,YAAY,EAAG,OAAO,CAAC,eAAe,EAAE,YAAuB,IAAI,CAAC;oBACpE,WAAW,EAAE,SAAS;oBACtB,MAAM,EAAE,SAAS;oBACjB,QAAQ,EAAE,CAAC;oBACX,SAAS,EAAE,IAAI,IAAI,EAAE;oBACrB,WAAW,EAAE,SAAS;oBACtB,QAAQ,EAAE,SAAS;oBACnB,KAAK,EAAE,SAAS;iBACjB,CAAC;gBACF,iBAAiB,EAAE,KAAK,IAAI,EAAE;oBAC5B,MAAM,IAAI,KAAK,CACb,uDAAuD,CACxD,CAAA;gBACH,CAAC;aACF,CAAA;YAED,MAAM,WAAW,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;QAC5B,CAAC,CAAA;IACH,CAAC;IAED,IAAI,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,KAAK,GAAG,KAAK,EAClB,MAAa,EACb,QAA2B,EACZ,EAAE;YACjB,MAAM,aAAa,CAAC,SAAS,CAAC,CAAA;YAC9B,MAAM,OAAO,GAAG,QAAQ,MAAM,CAAC,UAAU,EAAE,EAAE,CAAA;YAC7C,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAA;YAC1C,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,cAAc,EAAE,CAAC;gBACpC,MAAM,gBAAgB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;YAC3C,CAAC;QACH,CAAC,CAAA;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAKD,MAAM,UAAU,wBAAwB,CAAC,SAAgC;IACvE,MAAM,QAAQ,GAAG,kBAAkB,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IACzD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAMD,MAAM,UAAU,2BAA2B,CAAC,UAAiC;IAC3E,OAAO;QACL,KAAK,CAAC,SAAS,CACb,QAAqB,EACrB,QAA2B;YAG3B,OAAO;gBACL,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,oDAAoD;aAC3D,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +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';
4
+ export { AzureQueueService } from './azure-queue-service.js';
5
+ export { AzureDeploymentService } from './azure-deployment-service.js';
package/dist/index.js CHANGED
@@ -1,3 +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';
4
+ export { AzureQueueService } from './azure-queue-service.js';
5
+ export { AzureDeploymentService } from './azure-deployment-service.js';
3
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"}
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,5 +1,5 @@
1
- import { InvocationContext } from '@azure/functions';
2
- import { Logger, LogLevel } from '@pikku/core/services';
1
+ import type { InvocationContext } from '@azure/functions';
2
+ import type { Logger, LogLevel } from '@pikku/core/services';
3
3
  export declare class AzInvocationLogger implements Logger {
4
4
  private context;
5
5
  constructor(context: InvocationContext);
@@ -1,4 +1,4 @@
1
- import { InvocationContext } from '@azure/functions';
1
+ import type { InvocationContext } from '@azure/functions';
2
2
  import { PikkuRequest } from '@pikku/core';
3
3
  export declare class PikkuAZTimerRequest<In = any> extends PikkuRequest<In> {
4
4
  constructor(_context: InvocationContext, data: In);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/azure-functions",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
4
4
  "license": "MIT",
5
5
  "author": "yasser.fadl@gmail.com",
6
6
  "module": "dist/index.js",
@@ -13,18 +13,23 @@
13
13
  "release": "npm run build && npm test",
14
14
  "test": "bash run-tests.sh",
15
15
  "test:watch": "bash run-tests.sh --watch",
16
- "test:coverage": "bash run-tests.sh --coverage"
16
+ "test:coverage": "bash run-tests.sh --coverage",
17
+ "prepublishOnly": "yarn build"
17
18
  },
18
19
  "peerDependencies": {
19
- "@pikku/core": "^0.12.0"
20
+ "@pikku/core": "^0.12.44"
20
21
  },
21
22
  "devDependencies": {
22
- "typescript": "^5.9.3"
23
+ "typescript": "^6.0.3"
23
24
  },
24
25
  "engines": {
25
- "node": ">=18"
26
+ "node": ">=22"
26
27
  },
27
28
  "dependencies": {
28
- "@azure/functions": "4.11.2"
29
+ "@azure/functions": "4.11.2",
30
+ "@azure/storage-queue": "^12.25.0"
31
+ },
32
+ "exports": {
33
+ ".": "./dist/index.js"
29
34
  }
30
35
  }
@@ -0,0 +1,94 @@
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 { encryptJSON } from '@pikku/core/crypto-utils'
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: Record<string, string> = {
47
+ 'content-type': 'application/json',
48
+ ...(traceId && { 'x-request-id': traceId }),
49
+ }
50
+
51
+ // Sign session as JWT for pikkuRemoteAuthMiddleware
52
+ let secret: string | undefined
53
+ try {
54
+ secret = await this.secrets.getSecret('PIKKU_REMOTE_SECRET')
55
+ } catch {}
56
+
57
+ if (secret && this.jwt) {
58
+ const sessionEnc = session
59
+ ? await encryptJSON(secret, { session })
60
+ : undefined
61
+ const token = await this.jwt.encode(
62
+ { value: 5, unit: 'minute' },
63
+ {
64
+ aud: 'pikku-remote',
65
+ fn: funcName,
66
+ iat: Math.floor(Date.now() / 1000),
67
+ session: sessionEnc,
68
+ }
69
+ )
70
+ headers.authorization = `Bearer ${token}`
71
+ }
72
+
73
+ const url = `${functionUrl}/remote/rpc/${encodeURIComponent(funcName)}`
74
+ const response = await fetch(url, {
75
+ method: 'POST',
76
+ headers,
77
+ body: JSON.stringify({ data }),
78
+ })
79
+
80
+ if (!response.ok) {
81
+ let errorBody: string
82
+ try {
83
+ errorBody = JSON.stringify(await response.json())
84
+ } catch {
85
+ errorBody = await response.text()
86
+ }
87
+ throw new Error(
88
+ `Remote RPC call to '${funcName}' failed: ${response.status} ${response.statusText}. ${errorBody}`
89
+ )
90
+ }
91
+
92
+ return response.json()
93
+ }
94
+ }
@@ -0,0 +1,90 @@
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, JobOptions } from '@pikku/core/queue'
13
+
14
+ export class AzureQueueService implements QueueService {
15
+ readonly supportsResults = false
16
+ private queueServiceClient: QueueServiceClient
17
+ private queueClients = new Map<string, QueueClient>()
18
+
19
+ constructor(connectionString?: string) {
20
+ const connStr = connectionString ?? process.env.AzureWebJobsStorage
21
+ if (!connStr) {
22
+ throw new Error(
23
+ 'Azure Queue connection string not provided; supply connectionString or set AzureWebJobsStorage'
24
+ )
25
+ }
26
+ this.queueServiceClient = QueueServiceClient.fromConnectionString(connStr)
27
+ }
28
+
29
+ async add<T>(
30
+ queueName: string,
31
+ data: T,
32
+ options?: JobOptions
33
+ ): Promise<string> {
34
+ const client = this.getQueueClient(queueName)
35
+ const messageId = crypto.randomUUID()
36
+
37
+ const message = {
38
+ id: messageId,
39
+ queueName,
40
+ data,
41
+ pikkuUserId: options?.pikkuUserId,
42
+ }
43
+
44
+ // Azure Storage Queue messages must be base64-encoded strings
45
+ const encoded = Buffer.from(JSON.stringify(message)).toString('base64')
46
+
47
+ const visibilityTimeout = options?.delay
48
+ ? Math.min(Math.ceil(options.delay / 1000), 604800) // max 7 days
49
+ : undefined
50
+
51
+ await client.sendMessage(encoded, { visibilityTimeout })
52
+ return messageId
53
+ }
54
+
55
+ async getJob<T, R>(
56
+ _queueName: string,
57
+ _jobId: string
58
+ ): Promise<QueueJob<T, R> | null> {
59
+ throw new Error(
60
+ 'AzureQueueService does not support getJob(). ' +
61
+ 'Azure Storage Queues are fire-and-forget. ' +
62
+ 'Use BullMQ or PgBoss if you need job result tracking.'
63
+ )
64
+ }
65
+
66
+ private getQueueClient(queueName: string): QueueClient {
67
+ if (!this.queueClients.has(queueName)) {
68
+ const resolvedName = this.resolveQueueName(queueName)
69
+ const client = this.queueServiceClient.getQueueClient(resolvedName)
70
+ this.queueClients.set(queueName, client)
71
+ }
72
+ return this.queueClients.get(queueName)!
73
+ }
74
+
75
+ /**
76
+ * Resolves the Azure queue name. Checks env var
77
+ * AZURE_QUEUE_NAME_<SCREAMING_SNAKE> first, falls back to the name as-is.
78
+ */
79
+ private resolveQueueName(queueName: string): string {
80
+ const envKey = `AZURE_QUEUE_NAME_${toScreamingSnake(queueName)}`
81
+ return process.env[envKey] ?? queueName
82
+ }
83
+ }
84
+
85
+ function toScreamingSnake(name: string): string {
86
+ return name
87
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
88
+ .replace(/-/g, '_')
89
+ .toUpperCase()
90
+ }
@@ -0,0 +1,205 @@
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'
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, getScheduledTasks } from '@pikku/core/scheduler'
18
+ import type {
19
+ HttpRequest,
20
+ HttpResponseInit,
21
+ InvocationContext,
22
+ Timer,
23
+ } from '@azure/functions'
24
+
25
+ export interface AzureServiceFactories {
26
+ createConfig: (
27
+ variables: LocalVariablesService,
28
+ ...args: unknown[]
29
+ ) => Promise<unknown>
30
+ createSingletonServices: (
31
+ config: unknown,
32
+ existingServices?: Partial<Record<string, unknown>>
33
+ ) => Promise<CoreSingletonServices>
34
+ createPlatformServices?: () =>
35
+ | Record<string, unknown>
36
+ | 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
+ }
@@ -0,0 +1,13 @@
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 CHANGED
@@ -1,2 +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'
4
+ export { AzureQueueService } from './azure-queue-service.js'
5
+ export { AzureDeploymentService } from './azure-deployment-service.js'
@@ -1,5 +1,5 @@
1
- import { InvocationContext } from '@azure/functions'
2
- import { Logger, LogLevel } from '@pikku/core/services'
1
+ import type { InvocationContext } from '@azure/functions'
2
+ import type { Logger, LogLevel } from '@pikku/core/services'
3
3
 
4
4
  export class AzInvocationLogger implements Logger {
5
5
  // private logLevel: LogLevel = LogLevel.info
@@ -1,4 +1,4 @@
1
- import { InvocationContext } from '@azure/functions'
1
+ import type { InvocationContext } from '@azure/functions'
2
2
  import { PikkuRequest } from '@pikku/core'
3
3
 
4
4
  export class PikkuAZTimerRequest<In = any> extends PikkuRequest<In> {
@@ -1 +1 @@
1
- {"root":["./src/index.ts","./src/pikku-az-functions-logger.ts","./src/pikku-az-timer-request.ts"],"version":"5.9.3"}
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"}