@plasius/graph-runtime-azure-functions 0.1.1 → 0.1.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/README.md CHANGED
@@ -1,22 +1,123 @@
1
1
  # @plasius/graph-runtime-azure-functions
2
2
 
3
- Azure Functions runtime adapter for graph gateway read/write endpoints.
3
+ [![npm version](https://img.shields.io/npm/v/@plasius/graph-runtime-azure-functions.svg)](https://www.npmjs.com/package/@plasius/graph-runtime-azure-functions)
4
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/Plasius-LTD/graph-runtime-azure-functions/ci.yml?branch=main&label=build&style=flat)](https://github.com/Plasius-LTD/graph-runtime-azure-functions/actions/workflows/ci.yml)
5
+ [![coverage](https://img.shields.io/codecov/c/github/Plasius-LTD/graph-runtime-azure-functions)](https://codecov.io/gh/Plasius-LTD/graph-runtime-azure-functions)
6
+ [![License](https://img.shields.io/github/license/Plasius-LTD/graph-runtime-azure-functions)](./LICENSE)
7
+ [![Code of Conduct](https://img.shields.io/badge/code%20of%20conduct-yes-blue.svg)](./CODE_OF_CONDUCT.md)
8
+ [![Security Policy](https://img.shields.io/badge/security%20policy-yes-orange.svg)](./SECURITY.md)
9
+ [![Changelog](https://img.shields.io/badge/changelog-md-blue.svg)](./CHANGELOG.md)
10
+
11
+ [![CI](https://github.com/Plasius-LTD/graph-runtime-azure-functions/actions/workflows/ci.yml/badge.svg)](https://github.com/Plasius-LTD/graph-runtime-azure-functions/actions/workflows/ci.yml)
12
+ [![CD](https://github.com/Plasius-LTD/graph-runtime-azure-functions/actions/workflows/cd.yml/badge.svg)](https://github.com/Plasius-LTD/graph-runtime-azure-functions/actions/workflows/cd.yml)
13
+
14
+ Azure Functions runtime adapter for graph read and write HTTP handlers.
15
+
16
+ Apache-2.0. ESM + CJS builds. TypeScript types included.
17
+
18
+ ---
19
+
20
+ ## Requirements
21
+
22
+ - Node.js 24+ (matches `.nvmrc` and CI/CD)
23
+ - `@azure/functions` 4.x (`peerDependencies`)
24
+ - `@plasius/graph-gateway-core`
25
+ - `@plasius/graph-write-coordinator`
26
+
27
+ ---
4
28
 
5
29
  ## Installation
6
30
 
7
- npm install @plasius/graph-runtime-azure-functions
31
+ ```bash
32
+ npm install @plasius/graph-runtime-azure-functions @azure/functions
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Exports
38
+
39
+ ```ts
40
+ import {
41
+ createGraphReadHandler,
42
+ createGraphWriteHandler,
43
+ type GraphReadHandlerOptions,
44
+ type GraphWriteHandlerOptions,
45
+ } from "@plasius/graph-runtime-azure-functions";
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Quick Start
51
+
52
+ ```ts
53
+ import { app } from "@azure/functions";
54
+ import {
55
+ createGraphReadHandler,
56
+ createGraphWriteHandler,
57
+ } from "@plasius/graph-runtime-azure-functions";
58
+
59
+ app.http("graph-read", {
60
+ methods: ["POST"],
61
+ authLevel: "function",
62
+ handler: createGraphReadHandler({ gateway, telemetry }),
63
+ });
64
+
65
+ app.http("graph-write", {
66
+ methods: ["POST"],
67
+ authLevel: "function",
68
+ handler: createGraphWriteHandler({ coordinator, telemetry }),
69
+ });
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Security and Input Validation
75
+
76
+ Handler factories enforce boundary validation for external input:
77
+
78
+ - Read payloads must satisfy `isGraphQuery`.
79
+ - Write payloads must satisfy strict write-command shape rules (idempotency/partition/aggregate keys + payload + timestamp).
80
+ - Optional payload size guard via `maxBodyBytes` (default `64KB`, returns `413` when exceeded).
81
+ - Optional authorization guard via `authorize(context)` (returns `403` when denied).
82
+
83
+ Failure responses are bounded and sanitized:
84
+
85
+ - Read validation/auth/body-limit: `400/403/413`
86
+ - Read upstream execution failure: `502`
87
+ - Write validation/auth/body-limit: `400/403/413`
88
+ - Write upstream execution failure: `503`
89
+
90
+ ---
8
91
 
9
92
  ## Development
10
93
 
94
+ ```bash
95
+ npm run clean
11
96
  npm install
97
+ npm run lint
98
+ npm run typecheck
99
+ npm run test:coverage
12
100
  npm run build
13
- npm test
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Telemetry
106
+
107
+ Both handlers accept optional `telemetry` (`TelemetrySink`) and emit:
108
+
109
+ - Read: `graph.runtime.read.request`, `graph.runtime.read.latency`, `graph.runtime.read.error`
110
+ - Write: `graph.runtime.write.request`, `graph.runtime.write.latency`, `graph.runtime.write.error`
111
+
112
+ ---
14
113
 
15
114
  ## Architecture
16
115
 
17
- - ADR documents are in docs/adrs/.
18
- - Cross-package architecture is tracked in plasius-ltd-site/docs/adrs/adr-0020 through adr-0024.
116
+ - Package ADRs: [`docs/adrs`](./docs/adrs)
117
+ - Cross-package ADRs: `plasius-ltd-site/docs/adrs/adr-0020` to `adr-0024`
118
+
119
+ ---
19
120
 
20
121
  ## License
21
122
 
22
- Apache-2.0
123
+ Licensed under the [Apache-2.0 License](./LICENSE).
package/dist/index.cjs CHANGED
@@ -26,6 +26,40 @@ __export(index_exports, {
26
26
  module.exports = __toCommonJS(index_exports);
27
27
 
28
28
  // src/handlers.ts
29
+ var import_graph_contracts = require("@plasius/graph-contracts");
30
+ var DEFAULT_MAX_BODY_BYTES = 64 * 1024;
31
+ var isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
32
+ var isSafeKey = (value) => /^[A-Za-z0-9:_-]+$/.test(value);
33
+ var isWriteCommandPayloadValid = (value) => {
34
+ if (!isObject(value)) {
35
+ return false;
36
+ }
37
+ if (typeof value.idempotencyKey !== "string" || value.idempotencyKey.length === 0 || value.idempotencyKey.length > 128 || !isSafeKey(value.idempotencyKey)) {
38
+ return false;
39
+ }
40
+ if (typeof value.partitionKey !== "string" || value.partitionKey.length === 0 || value.partitionKey.length > 128 || !isSafeKey(value.partitionKey)) {
41
+ return false;
42
+ }
43
+ if (typeof value.aggregateKey !== "string" || value.aggregateKey.length === 0 || value.aggregateKey.length > 256 || !isSafeKey(value.aggregateKey)) {
44
+ return false;
45
+ }
46
+ if (!isObject(value.payload)) {
47
+ return false;
48
+ }
49
+ if (typeof value.submittedAtEpochMs !== "number" || !Number.isFinite(value.submittedAtEpochMs) || value.submittedAtEpochMs < 0) {
50
+ return false;
51
+ }
52
+ return value.actorId === void 0 || typeof value.actorId === "string" && value.actorId.length > 0 && value.actorId.length <= 128;
53
+ };
54
+ var HandlerValidationError = class extends Error {
55
+ status;
56
+ code;
57
+ constructor(status, code, message) {
58
+ super(message);
59
+ this.status = status;
60
+ this.code = code;
61
+ }
62
+ };
29
63
  var jsonResponse = (status, body) => ({
30
64
  status,
31
65
  jsonBody: body,
@@ -34,34 +68,167 @@ var jsonResponse = (status, body) => ({
34
68
  "cache-control": "no-store"
35
69
  }
36
70
  });
71
+ var getContentLength = (request) => {
72
+ const raw = request.headers?.get?.("content-length");
73
+ if (!raw) {
74
+ return null;
75
+ }
76
+ const parsed = Number.parseInt(raw, 10);
77
+ if (!Number.isFinite(parsed) || parsed < 0) {
78
+ return null;
79
+ }
80
+ return parsed;
81
+ };
82
+ var enforceBodyLimit = (request, maxBodyBytes) => {
83
+ const contentLength = getContentLength(request);
84
+ if (contentLength !== null && contentLength > maxBodyBytes) {
85
+ throw new HandlerValidationError(413, "GRAPH_REQUEST_BODY_TOO_LARGE", "Request payload exceeds allowed limit.");
86
+ }
87
+ };
88
+ var enforceAuthorization = async (authorize, operation, request, context) => {
89
+ if (!authorize) {
90
+ return;
91
+ }
92
+ const allowed = await authorize({ operation, request, context });
93
+ if (!allowed) {
94
+ throw new HandlerValidationError(403, "GRAPH_FORBIDDEN", "Forbidden");
95
+ }
96
+ };
37
97
  var createGraphReadHandler = (options) => {
38
98
  return async (request, context) => {
99
+ const startedAt = Date.now();
100
+ options.telemetry?.metric({
101
+ name: "graph.runtime.read.request",
102
+ value: 1,
103
+ unit: "count"
104
+ });
39
105
  try {
40
- const query = await request.json();
106
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
107
+ enforceBodyLimit(request, maxBodyBytes);
108
+ await enforceAuthorization(options.authorize, "read", request, context);
109
+ let rawBody;
110
+ try {
111
+ rawBody = await request.json();
112
+ } catch {
113
+ throw new HandlerValidationError(400, "GRAPH_READ_REQUEST_INVALID", "Invalid graph read request.");
114
+ }
115
+ if (!(0, import_graph_contracts.isGraphQuery)(rawBody)) {
116
+ throw new HandlerValidationError(400, "GRAPH_READ_REQUEST_INVALID", "Invalid graph read request.");
117
+ }
118
+ const query = rawBody;
41
119
  const result = await options.gateway.execute({
42
120
  ...query,
43
121
  traceId: query.traceId ?? context.traceContext?.traceParent
44
122
  });
123
+ options.telemetry?.metric({
124
+ name: "graph.runtime.read.latency",
125
+ value: Date.now() - startedAt,
126
+ unit: "ms",
127
+ tags: { status: "200" }
128
+ });
45
129
  return jsonResponse(200, result);
46
130
  } catch (error) {
47
- return jsonResponse(400, {
48
- code: "GRAPH_READ_REQUEST_INVALID",
49
- message: error instanceof Error ? error.message : "Invalid read request"
131
+ if (error instanceof HandlerValidationError) {
132
+ options.telemetry?.metric({
133
+ name: "graph.runtime.read.error",
134
+ value: 1,
135
+ unit: "count",
136
+ tags: { code: error.code }
137
+ });
138
+ options.telemetry?.error({
139
+ message: error.message,
140
+ source: "graph-runtime-azure-functions.read",
141
+ code: error.code
142
+ });
143
+ return jsonResponse(error.status, {
144
+ code: error.code,
145
+ message: error.message
146
+ });
147
+ }
148
+ options.telemetry?.metric({
149
+ name: "graph.runtime.read.error",
150
+ value: 1,
151
+ unit: "count",
152
+ tags: { code: "GRAPH_READ_UPSTREAM_FAILED" }
153
+ });
154
+ options.telemetry?.error({
155
+ message: "Graph read failed",
156
+ source: "graph-runtime-azure-functions.read",
157
+ code: "GRAPH_READ_UPSTREAM_FAILED"
158
+ });
159
+ return jsonResponse(502, {
160
+ code: "GRAPH_READ_UPSTREAM_FAILED",
161
+ message: "Graph read failed."
50
162
  });
51
163
  }
52
164
  };
53
165
  };
54
166
  var createGraphWriteHandler = (options) => {
55
- return async (request) => {
167
+ return async (request, context) => {
168
+ const startedAt = Date.now();
169
+ options.telemetry?.metric({
170
+ name: "graph.runtime.write.request",
171
+ value: 1,
172
+ unit: "count"
173
+ });
56
174
  try {
57
- const command = await request.json();
175
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
176
+ enforceBodyLimit(request, maxBodyBytes);
177
+ await enforceAuthorization(options.authorize, "write", request, context);
178
+ let rawBody;
179
+ try {
180
+ rawBody = await request.json();
181
+ } catch {
182
+ throw new HandlerValidationError(400, "GRAPH_WRITE_REQUEST_INVALID", "Invalid graph write request.");
183
+ }
184
+ if (!isWriteCommandPayloadValid(rawBody)) {
185
+ throw new HandlerValidationError(400, "GRAPH_WRITE_REQUEST_INVALID", "Invalid graph write request.");
186
+ }
187
+ const command = rawBody;
58
188
  const operation = await options.coordinator.submit(command);
59
189
  const status = operation.state === "succeeded" ? 200 : 202;
190
+ options.telemetry?.metric({
191
+ name: "graph.runtime.write.latency",
192
+ value: Date.now() - startedAt,
193
+ unit: "ms",
194
+ tags: {
195
+ status: String(status),
196
+ state: operation.state
197
+ }
198
+ });
60
199
  return jsonResponse(status, operation);
61
200
  } catch (error) {
62
- return jsonResponse(400, {
63
- code: "GRAPH_WRITE_REQUEST_INVALID",
64
- message: error instanceof Error ? error.message : "Invalid write request"
201
+ if (error instanceof HandlerValidationError) {
202
+ options.telemetry?.metric({
203
+ name: "graph.runtime.write.error",
204
+ value: 1,
205
+ unit: "count",
206
+ tags: { code: error.code }
207
+ });
208
+ options.telemetry?.error({
209
+ message: error.message,
210
+ source: "graph-runtime-azure-functions.write",
211
+ code: error.code
212
+ });
213
+ return jsonResponse(error.status, {
214
+ code: error.code,
215
+ message: error.message
216
+ });
217
+ }
218
+ options.telemetry?.metric({
219
+ name: "graph.runtime.write.error",
220
+ value: 1,
221
+ unit: "count",
222
+ tags: { code: "GRAPH_WRITE_UPSTREAM_FAILED" }
223
+ });
224
+ options.telemetry?.error({
225
+ message: "Graph write failed",
226
+ source: "graph-runtime-azure-functions.write",
227
+ code: "GRAPH_WRITE_UPSTREAM_FAILED"
228
+ });
229
+ return jsonResponse(503, {
230
+ code: "GRAPH_WRITE_UPSTREAM_FAILED",
231
+ message: "Graph write failed."
65
232
  });
66
233
  }
67
234
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/handlers.ts"],"sourcesContent":["export * from \"./handlers.js\";\n","import type { HttpHandler, HttpRequest, HttpResponseInit, InvocationContext } from \"@azure/functions\";\nimport type { GraphQuery, WriteCommand } from \"@plasius/graph-contracts\";\nimport type { GraphGateway } from \"@plasius/graph-gateway-core\";\nimport type { WriteCoordinator } from \"@plasius/graph-write-coordinator\";\n\nexport interface GraphReadHandlerOptions {\n gateway: Pick<GraphGateway, \"execute\">;\n}\n\nexport interface GraphWriteHandlerOptions {\n coordinator: Pick<WriteCoordinator, \"submit\">;\n}\n\nconst jsonResponse = (status: number, body: unknown): HttpResponseInit => ({\n status,\n jsonBody: body,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n});\n\nexport const createGraphReadHandler = (options: GraphReadHandlerOptions): HttpHandler => {\n return async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {\n try {\n const query = (await request.json()) as GraphQuery;\n const result = await options.gateway.execute({\n ...query,\n traceId: query.traceId ?? context.traceContext?.traceParent,\n });\n return jsonResponse(200, result);\n } catch (error) {\n return jsonResponse(400, {\n code: \"GRAPH_READ_REQUEST_INVALID\",\n message: error instanceof Error ? error.message : \"Invalid read request\",\n });\n }\n };\n};\n\nexport const createGraphWriteHandler = (options: GraphWriteHandlerOptions): HttpHandler => {\n return async (request: HttpRequest): Promise<HttpResponseInit> => {\n try {\n const command = (await request.json()) as WriteCommand;\n const operation = await options.coordinator.submit(command);\n const status = operation.state === \"succeeded\" ? 200 : 202;\n return jsonResponse(status, operation);\n } catch (error) {\n return jsonResponse(400, {\n code: \"GRAPH_WRITE_REQUEST_INVALID\",\n message: error instanceof Error ? error.message : \"Invalid write request\",\n });\n }\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACaA,IAAM,eAAe,CAAC,QAAgB,UAAqC;AAAA,EACzE;AAAA,EACA,UAAU;AAAA,EACV,SAAS;AAAA,IACP,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,EACnB;AACF;AAEO,IAAM,yBAAyB,CAAC,YAAkD;AACvF,SAAO,OAAO,SAAsB,YAA0D;AAC5F,QAAI;AACF,YAAM,QAAS,MAAM,QAAQ,KAAK;AAClC,YAAM,SAAS,MAAM,QAAQ,QAAQ,QAAQ;AAAA,QAC3C,GAAG;AAAA,QACH,SAAS,MAAM,WAAW,QAAQ,cAAc;AAAA,MAClD,CAAC;AACD,aAAO,aAAa,KAAK,MAAM;AAAA,IACjC,SAAS,OAAO;AACd,aAAO,aAAa,KAAK;AAAA,QACvB,MAAM;AAAA,QACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACpD,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEO,IAAM,0BAA0B,CAAC,YAAmD;AACzF,SAAO,OAAO,YAAoD;AAChE,QAAI;AACF,YAAM,UAAW,MAAM,QAAQ,KAAK;AACpC,YAAM,YAAY,MAAM,QAAQ,YAAY,OAAO,OAAO;AAC1D,YAAM,SAAS,UAAU,UAAU,cAAc,MAAM;AACvD,aAAO,aAAa,QAAQ,SAAS;AAAA,IACvC,SAAS,OAAO;AACd,aAAO,aAAa,KAAK;AAAA,QACvB,MAAM;AAAA,QACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACpD,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/handlers.ts"],"sourcesContent":["export * from \"./handlers.js\";\n","import type { HttpHandler, HttpRequest, HttpResponseInit, InvocationContext } from \"@azure/functions\";\nimport { isGraphQuery, type GraphQuery, type TelemetrySink, type WriteCommand } from \"@plasius/graph-contracts\";\nimport type { GraphGateway } from \"@plasius/graph-gateway-core\";\nimport type { WriteCoordinator } from \"@plasius/graph-write-coordinator\";\n\nexport interface HandlerAuthContext {\n operation: \"read\" | \"write\";\n request: HttpRequest;\n context: InvocationContext;\n}\n\nexport type AuthorizeHandler = (context: HandlerAuthContext) => Promise<boolean> | boolean;\n\nexport interface GraphReadHandlerOptions {\n gateway: Pick<GraphGateway, \"execute\">;\n telemetry?: TelemetrySink;\n authorize?: AuthorizeHandler;\n maxBodyBytes?: number;\n}\n\nexport interface GraphWriteHandlerOptions {\n coordinator: Pick<WriteCoordinator, \"submit\">;\n telemetry?: TelemetrySink;\n authorize?: AuthorizeHandler;\n maxBodyBytes?: number;\n}\n\nconst DEFAULT_MAX_BODY_BYTES = 64 * 1024;\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\nconst isSafeKey = (value: string): boolean => /^[A-Za-z0-9:_-]+$/.test(value);\n\nconst isWriteCommandPayloadValid = (value: unknown): value is WriteCommand => {\n if (!isObject(value)) {\n return false;\n }\n\n if (\n typeof value.idempotencyKey !== \"string\"\n || value.idempotencyKey.length === 0\n || value.idempotencyKey.length > 128\n || !isSafeKey(value.idempotencyKey)\n ) {\n return false;\n }\n\n if (\n typeof value.partitionKey !== \"string\"\n || value.partitionKey.length === 0\n || value.partitionKey.length > 128\n || !isSafeKey(value.partitionKey)\n ) {\n return false;\n }\n\n if (\n typeof value.aggregateKey !== \"string\"\n || value.aggregateKey.length === 0\n || value.aggregateKey.length > 256\n || !isSafeKey(value.aggregateKey)\n ) {\n return false;\n }\n\n if (!isObject(value.payload)) {\n return false;\n }\n\n if (typeof value.submittedAtEpochMs !== \"number\" || !Number.isFinite(value.submittedAtEpochMs) || value.submittedAtEpochMs < 0) {\n return false;\n }\n\n return value.actorId === undefined || (typeof value.actorId === \"string\" && value.actorId.length > 0 && value.actorId.length <= 128);\n};\n\nclass HandlerValidationError extends Error {\n public readonly status: number;\n public readonly code: string;\n\n public constructor(status: number, code: string, message: string) {\n super(message);\n this.status = status;\n this.code = code;\n }\n}\n\nconst jsonResponse = (status: number, body: unknown): HttpResponseInit => ({\n status,\n jsonBody: body,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n});\n\nconst getContentLength = (request: HttpRequest): number | null => {\n const raw = request.headers?.get?.(\"content-length\");\n if (!raw) {\n return null;\n }\n\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return null;\n }\n\n return parsed;\n};\n\nconst enforceBodyLimit = (request: HttpRequest, maxBodyBytes: number): void => {\n const contentLength = getContentLength(request);\n if (contentLength !== null && contentLength > maxBodyBytes) {\n throw new HandlerValidationError(413, \"GRAPH_REQUEST_BODY_TOO_LARGE\", \"Request payload exceeds allowed limit.\");\n }\n};\n\nconst enforceAuthorization = async (\n authorize: AuthorizeHandler | undefined,\n operation: \"read\" | \"write\",\n request: HttpRequest,\n context: InvocationContext,\n): Promise<void> => {\n if (!authorize) {\n return;\n }\n\n const allowed = await authorize({ operation, request, context });\n if (!allowed) {\n throw new HandlerValidationError(403, \"GRAPH_FORBIDDEN\", \"Forbidden\");\n }\n};\n\nexport const createGraphReadHandler = (options: GraphReadHandlerOptions): HttpHandler => {\n return async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {\n const startedAt = Date.now();\n options.telemetry?.metric({\n name: \"graph.runtime.read.request\",\n value: 1,\n unit: \"count\",\n });\n try {\n const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;\n enforceBodyLimit(request, maxBodyBytes);\n await enforceAuthorization(options.authorize, \"read\", request, context);\n\n let rawBody: unknown;\n try {\n rawBody = await request.json();\n } catch {\n throw new HandlerValidationError(400, \"GRAPH_READ_REQUEST_INVALID\", \"Invalid graph read request.\");\n }\n if (!isGraphQuery(rawBody)) {\n throw new HandlerValidationError(400, \"GRAPH_READ_REQUEST_INVALID\", \"Invalid graph read request.\");\n }\n\n const query = rawBody as GraphQuery;\n const result = await options.gateway.execute({\n ...query,\n traceId: query.traceId ?? context.traceContext?.traceParent,\n });\n options.telemetry?.metric({\n name: \"graph.runtime.read.latency\",\n value: Date.now() - startedAt,\n unit: \"ms\",\n tags: { status: \"200\" },\n });\n return jsonResponse(200, result);\n } catch (error) {\n if (error instanceof HandlerValidationError) {\n options.telemetry?.metric({\n name: \"graph.runtime.read.error\",\n value: 1,\n unit: \"count\",\n tags: { code: error.code },\n });\n options.telemetry?.error({\n message: error.message,\n source: \"graph-runtime-azure-functions.read\",\n code: error.code,\n });\n return jsonResponse(error.status, {\n code: error.code,\n message: error.message,\n });\n }\n\n options.telemetry?.metric({\n name: \"graph.runtime.read.error\",\n value: 1,\n unit: \"count\",\n tags: { code: \"GRAPH_READ_UPSTREAM_FAILED\" },\n });\n options.telemetry?.error({\n message: \"Graph read failed\",\n source: \"graph-runtime-azure-functions.read\",\n code: \"GRAPH_READ_UPSTREAM_FAILED\",\n });\n return jsonResponse(502, {\n code: \"GRAPH_READ_UPSTREAM_FAILED\",\n message: \"Graph read failed.\",\n });\n }\n };\n};\n\nexport const createGraphWriteHandler = (options: GraphWriteHandlerOptions): HttpHandler => {\n return async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {\n const startedAt = Date.now();\n options.telemetry?.metric({\n name: \"graph.runtime.write.request\",\n value: 1,\n unit: \"count\",\n });\n try {\n const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;\n enforceBodyLimit(request, maxBodyBytes);\n await enforceAuthorization(options.authorize, \"write\", request, context);\n\n let rawBody: unknown;\n try {\n rawBody = await request.json();\n } catch {\n throw new HandlerValidationError(400, \"GRAPH_WRITE_REQUEST_INVALID\", \"Invalid graph write request.\");\n }\n if (!isWriteCommandPayloadValid(rawBody)) {\n throw new HandlerValidationError(400, \"GRAPH_WRITE_REQUEST_INVALID\", \"Invalid graph write request.\");\n }\n\n const command = rawBody as WriteCommand;\n const operation = await options.coordinator.submit(command);\n const status = operation.state === \"succeeded\" ? 200 : 202;\n options.telemetry?.metric({\n name: \"graph.runtime.write.latency\",\n value: Date.now() - startedAt,\n unit: \"ms\",\n tags: {\n status: String(status),\n state: operation.state,\n },\n });\n return jsonResponse(status, operation);\n } catch (error) {\n if (error instanceof HandlerValidationError) {\n options.telemetry?.metric({\n name: \"graph.runtime.write.error\",\n value: 1,\n unit: \"count\",\n tags: { code: error.code },\n });\n options.telemetry?.error({\n message: error.message,\n source: \"graph-runtime-azure-functions.write\",\n code: error.code,\n });\n return jsonResponse(error.status, {\n code: error.code,\n message: error.message,\n });\n }\n\n options.telemetry?.metric({\n name: \"graph.runtime.write.error\",\n value: 1,\n unit: \"count\",\n tags: { code: \"GRAPH_WRITE_UPSTREAM_FAILED\" },\n });\n options.telemetry?.error({\n message: \"Graph write failed\",\n source: \"graph-runtime-azure-functions.write\",\n code: \"GRAPH_WRITE_UPSTREAM_FAILED\",\n });\n return jsonResponse(503, {\n code: \"GRAPH_WRITE_UPSTREAM_FAILED\",\n message: \"Graph write failed.\",\n });\n }\n };\n};\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,14 +1,27 @@
1
- import { HttpHandler } from '@azure/functions';
1
+ import { HttpRequest, InvocationContext, HttpHandler } from '@azure/functions';
2
+ import { TelemetrySink } from '@plasius/graph-contracts';
2
3
  import { GraphGateway } from '@plasius/graph-gateway-core';
3
4
  import { WriteCoordinator } from '@plasius/graph-write-coordinator';
4
5
 
6
+ interface HandlerAuthContext {
7
+ operation: "read" | "write";
8
+ request: HttpRequest;
9
+ context: InvocationContext;
10
+ }
11
+ type AuthorizeHandler = (context: HandlerAuthContext) => Promise<boolean> | boolean;
5
12
  interface GraphReadHandlerOptions {
6
13
  gateway: Pick<GraphGateway, "execute">;
14
+ telemetry?: TelemetrySink;
15
+ authorize?: AuthorizeHandler;
16
+ maxBodyBytes?: number;
7
17
  }
8
18
  interface GraphWriteHandlerOptions {
9
19
  coordinator: Pick<WriteCoordinator, "submit">;
20
+ telemetry?: TelemetrySink;
21
+ authorize?: AuthorizeHandler;
22
+ maxBodyBytes?: number;
10
23
  }
11
24
  declare const createGraphReadHandler: (options: GraphReadHandlerOptions) => HttpHandler;
12
25
  declare const createGraphWriteHandler: (options: GraphWriteHandlerOptions) => HttpHandler;
13
26
 
14
- export { type GraphReadHandlerOptions, type GraphWriteHandlerOptions, createGraphReadHandler, createGraphWriteHandler };
27
+ export { type AuthorizeHandler, type GraphReadHandlerOptions, type GraphWriteHandlerOptions, type HandlerAuthContext, createGraphReadHandler, createGraphWriteHandler };
package/dist/index.d.ts CHANGED
@@ -1,14 +1,27 @@
1
- import { HttpHandler } from '@azure/functions';
1
+ import { HttpRequest, InvocationContext, HttpHandler } from '@azure/functions';
2
+ import { TelemetrySink } from '@plasius/graph-contracts';
2
3
  import { GraphGateway } from '@plasius/graph-gateway-core';
3
4
  import { WriteCoordinator } from '@plasius/graph-write-coordinator';
4
5
 
6
+ interface HandlerAuthContext {
7
+ operation: "read" | "write";
8
+ request: HttpRequest;
9
+ context: InvocationContext;
10
+ }
11
+ type AuthorizeHandler = (context: HandlerAuthContext) => Promise<boolean> | boolean;
5
12
  interface GraphReadHandlerOptions {
6
13
  gateway: Pick<GraphGateway, "execute">;
14
+ telemetry?: TelemetrySink;
15
+ authorize?: AuthorizeHandler;
16
+ maxBodyBytes?: number;
7
17
  }
8
18
  interface GraphWriteHandlerOptions {
9
19
  coordinator: Pick<WriteCoordinator, "submit">;
20
+ telemetry?: TelemetrySink;
21
+ authorize?: AuthorizeHandler;
22
+ maxBodyBytes?: number;
10
23
  }
11
24
  declare const createGraphReadHandler: (options: GraphReadHandlerOptions) => HttpHandler;
12
25
  declare const createGraphWriteHandler: (options: GraphWriteHandlerOptions) => HttpHandler;
13
26
 
14
- export { type GraphReadHandlerOptions, type GraphWriteHandlerOptions, createGraphReadHandler, createGraphWriteHandler };
27
+ export { type AuthorizeHandler, type GraphReadHandlerOptions, type GraphWriteHandlerOptions, type HandlerAuthContext, createGraphReadHandler, createGraphWriteHandler };
package/dist/index.js CHANGED
@@ -1,4 +1,38 @@
1
1
  // src/handlers.ts
2
+ import { isGraphQuery } from "@plasius/graph-contracts";
3
+ var DEFAULT_MAX_BODY_BYTES = 64 * 1024;
4
+ var isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
5
+ var isSafeKey = (value) => /^[A-Za-z0-9:_-]+$/.test(value);
6
+ var isWriteCommandPayloadValid = (value) => {
7
+ if (!isObject(value)) {
8
+ return false;
9
+ }
10
+ if (typeof value.idempotencyKey !== "string" || value.idempotencyKey.length === 0 || value.idempotencyKey.length > 128 || !isSafeKey(value.idempotencyKey)) {
11
+ return false;
12
+ }
13
+ if (typeof value.partitionKey !== "string" || value.partitionKey.length === 0 || value.partitionKey.length > 128 || !isSafeKey(value.partitionKey)) {
14
+ return false;
15
+ }
16
+ if (typeof value.aggregateKey !== "string" || value.aggregateKey.length === 0 || value.aggregateKey.length > 256 || !isSafeKey(value.aggregateKey)) {
17
+ return false;
18
+ }
19
+ if (!isObject(value.payload)) {
20
+ return false;
21
+ }
22
+ if (typeof value.submittedAtEpochMs !== "number" || !Number.isFinite(value.submittedAtEpochMs) || value.submittedAtEpochMs < 0) {
23
+ return false;
24
+ }
25
+ return value.actorId === void 0 || typeof value.actorId === "string" && value.actorId.length > 0 && value.actorId.length <= 128;
26
+ };
27
+ var HandlerValidationError = class extends Error {
28
+ status;
29
+ code;
30
+ constructor(status, code, message) {
31
+ super(message);
32
+ this.status = status;
33
+ this.code = code;
34
+ }
35
+ };
2
36
  var jsonResponse = (status, body) => ({
3
37
  status,
4
38
  jsonBody: body,
@@ -7,34 +41,167 @@ var jsonResponse = (status, body) => ({
7
41
  "cache-control": "no-store"
8
42
  }
9
43
  });
44
+ var getContentLength = (request) => {
45
+ const raw = request.headers?.get?.("content-length");
46
+ if (!raw) {
47
+ return null;
48
+ }
49
+ const parsed = Number.parseInt(raw, 10);
50
+ if (!Number.isFinite(parsed) || parsed < 0) {
51
+ return null;
52
+ }
53
+ return parsed;
54
+ };
55
+ var enforceBodyLimit = (request, maxBodyBytes) => {
56
+ const contentLength = getContentLength(request);
57
+ if (contentLength !== null && contentLength > maxBodyBytes) {
58
+ throw new HandlerValidationError(413, "GRAPH_REQUEST_BODY_TOO_LARGE", "Request payload exceeds allowed limit.");
59
+ }
60
+ };
61
+ var enforceAuthorization = async (authorize, operation, request, context) => {
62
+ if (!authorize) {
63
+ return;
64
+ }
65
+ const allowed = await authorize({ operation, request, context });
66
+ if (!allowed) {
67
+ throw new HandlerValidationError(403, "GRAPH_FORBIDDEN", "Forbidden");
68
+ }
69
+ };
10
70
  var createGraphReadHandler = (options) => {
11
71
  return async (request, context) => {
72
+ const startedAt = Date.now();
73
+ options.telemetry?.metric({
74
+ name: "graph.runtime.read.request",
75
+ value: 1,
76
+ unit: "count"
77
+ });
12
78
  try {
13
- const query = await request.json();
79
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
80
+ enforceBodyLimit(request, maxBodyBytes);
81
+ await enforceAuthorization(options.authorize, "read", request, context);
82
+ let rawBody;
83
+ try {
84
+ rawBody = await request.json();
85
+ } catch {
86
+ throw new HandlerValidationError(400, "GRAPH_READ_REQUEST_INVALID", "Invalid graph read request.");
87
+ }
88
+ if (!isGraphQuery(rawBody)) {
89
+ throw new HandlerValidationError(400, "GRAPH_READ_REQUEST_INVALID", "Invalid graph read request.");
90
+ }
91
+ const query = rawBody;
14
92
  const result = await options.gateway.execute({
15
93
  ...query,
16
94
  traceId: query.traceId ?? context.traceContext?.traceParent
17
95
  });
96
+ options.telemetry?.metric({
97
+ name: "graph.runtime.read.latency",
98
+ value: Date.now() - startedAt,
99
+ unit: "ms",
100
+ tags: { status: "200" }
101
+ });
18
102
  return jsonResponse(200, result);
19
103
  } catch (error) {
20
- return jsonResponse(400, {
21
- code: "GRAPH_READ_REQUEST_INVALID",
22
- message: error instanceof Error ? error.message : "Invalid read request"
104
+ if (error instanceof HandlerValidationError) {
105
+ options.telemetry?.metric({
106
+ name: "graph.runtime.read.error",
107
+ value: 1,
108
+ unit: "count",
109
+ tags: { code: error.code }
110
+ });
111
+ options.telemetry?.error({
112
+ message: error.message,
113
+ source: "graph-runtime-azure-functions.read",
114
+ code: error.code
115
+ });
116
+ return jsonResponse(error.status, {
117
+ code: error.code,
118
+ message: error.message
119
+ });
120
+ }
121
+ options.telemetry?.metric({
122
+ name: "graph.runtime.read.error",
123
+ value: 1,
124
+ unit: "count",
125
+ tags: { code: "GRAPH_READ_UPSTREAM_FAILED" }
126
+ });
127
+ options.telemetry?.error({
128
+ message: "Graph read failed",
129
+ source: "graph-runtime-azure-functions.read",
130
+ code: "GRAPH_READ_UPSTREAM_FAILED"
131
+ });
132
+ return jsonResponse(502, {
133
+ code: "GRAPH_READ_UPSTREAM_FAILED",
134
+ message: "Graph read failed."
23
135
  });
24
136
  }
25
137
  };
26
138
  };
27
139
  var createGraphWriteHandler = (options) => {
28
- return async (request) => {
140
+ return async (request, context) => {
141
+ const startedAt = Date.now();
142
+ options.telemetry?.metric({
143
+ name: "graph.runtime.write.request",
144
+ value: 1,
145
+ unit: "count"
146
+ });
29
147
  try {
30
- const command = await request.json();
148
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
149
+ enforceBodyLimit(request, maxBodyBytes);
150
+ await enforceAuthorization(options.authorize, "write", request, context);
151
+ let rawBody;
152
+ try {
153
+ rawBody = await request.json();
154
+ } catch {
155
+ throw new HandlerValidationError(400, "GRAPH_WRITE_REQUEST_INVALID", "Invalid graph write request.");
156
+ }
157
+ if (!isWriteCommandPayloadValid(rawBody)) {
158
+ throw new HandlerValidationError(400, "GRAPH_WRITE_REQUEST_INVALID", "Invalid graph write request.");
159
+ }
160
+ const command = rawBody;
31
161
  const operation = await options.coordinator.submit(command);
32
162
  const status = operation.state === "succeeded" ? 200 : 202;
163
+ options.telemetry?.metric({
164
+ name: "graph.runtime.write.latency",
165
+ value: Date.now() - startedAt,
166
+ unit: "ms",
167
+ tags: {
168
+ status: String(status),
169
+ state: operation.state
170
+ }
171
+ });
33
172
  return jsonResponse(status, operation);
34
173
  } catch (error) {
35
- return jsonResponse(400, {
36
- code: "GRAPH_WRITE_REQUEST_INVALID",
37
- message: error instanceof Error ? error.message : "Invalid write request"
174
+ if (error instanceof HandlerValidationError) {
175
+ options.telemetry?.metric({
176
+ name: "graph.runtime.write.error",
177
+ value: 1,
178
+ unit: "count",
179
+ tags: { code: error.code }
180
+ });
181
+ options.telemetry?.error({
182
+ message: error.message,
183
+ source: "graph-runtime-azure-functions.write",
184
+ code: error.code
185
+ });
186
+ return jsonResponse(error.status, {
187
+ code: error.code,
188
+ message: error.message
189
+ });
190
+ }
191
+ options.telemetry?.metric({
192
+ name: "graph.runtime.write.error",
193
+ value: 1,
194
+ unit: "count",
195
+ tags: { code: "GRAPH_WRITE_UPSTREAM_FAILED" }
196
+ });
197
+ options.telemetry?.error({
198
+ message: "Graph write failed",
199
+ source: "graph-runtime-azure-functions.write",
200
+ code: "GRAPH_WRITE_UPSTREAM_FAILED"
201
+ });
202
+ return jsonResponse(503, {
203
+ code: "GRAPH_WRITE_UPSTREAM_FAILED",
204
+ message: "Graph write failed."
38
205
  });
39
206
  }
40
207
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/handlers.ts"],"sourcesContent":["import type { HttpHandler, HttpRequest, HttpResponseInit, InvocationContext } from \"@azure/functions\";\nimport type { GraphQuery, WriteCommand } from \"@plasius/graph-contracts\";\nimport type { GraphGateway } from \"@plasius/graph-gateway-core\";\nimport type { WriteCoordinator } from \"@plasius/graph-write-coordinator\";\n\nexport interface GraphReadHandlerOptions {\n gateway: Pick<GraphGateway, \"execute\">;\n}\n\nexport interface GraphWriteHandlerOptions {\n coordinator: Pick<WriteCoordinator, \"submit\">;\n}\n\nconst jsonResponse = (status: number, body: unknown): HttpResponseInit => ({\n status,\n jsonBody: body,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n});\n\nexport const createGraphReadHandler = (options: GraphReadHandlerOptions): HttpHandler => {\n return async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {\n try {\n const query = (await request.json()) as GraphQuery;\n const result = await options.gateway.execute({\n ...query,\n traceId: query.traceId ?? context.traceContext?.traceParent,\n });\n return jsonResponse(200, result);\n } catch (error) {\n return jsonResponse(400, {\n code: \"GRAPH_READ_REQUEST_INVALID\",\n message: error instanceof Error ? error.message : \"Invalid read request\",\n });\n }\n };\n};\n\nexport const createGraphWriteHandler = (options: GraphWriteHandlerOptions): HttpHandler => {\n return async (request: HttpRequest): Promise<HttpResponseInit> => {\n try {\n const command = (await request.json()) as WriteCommand;\n const operation = await options.coordinator.submit(command);\n const status = operation.state === \"succeeded\" ? 200 : 202;\n return jsonResponse(status, operation);\n } catch (error) {\n return jsonResponse(400, {\n code: \"GRAPH_WRITE_REQUEST_INVALID\",\n message: error instanceof Error ? error.message : \"Invalid write request\",\n });\n }\n };\n};\n"],"mappings":";AAaA,IAAM,eAAe,CAAC,QAAgB,UAAqC;AAAA,EACzE;AAAA,EACA,UAAU;AAAA,EACV,SAAS;AAAA,IACP,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,EACnB;AACF;AAEO,IAAM,yBAAyB,CAAC,YAAkD;AACvF,SAAO,OAAO,SAAsB,YAA0D;AAC5F,QAAI;AACF,YAAM,QAAS,MAAM,QAAQ,KAAK;AAClC,YAAM,SAAS,MAAM,QAAQ,QAAQ,QAAQ;AAAA,QAC3C,GAAG;AAAA,QACH,SAAS,MAAM,WAAW,QAAQ,cAAc;AAAA,MAClD,CAAC;AACD,aAAO,aAAa,KAAK,MAAM;AAAA,IACjC,SAAS,OAAO;AACd,aAAO,aAAa,KAAK;AAAA,QACvB,MAAM;AAAA,QACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACpD,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEO,IAAM,0BAA0B,CAAC,YAAmD;AACzF,SAAO,OAAO,YAAoD;AAChE,QAAI;AACF,YAAM,UAAW,MAAM,QAAQ,KAAK;AACpC,YAAM,YAAY,MAAM,QAAQ,YAAY,OAAO,OAAO;AAC1D,YAAM,SAAS,UAAU,UAAU,cAAc,MAAM;AACvD,aAAO,aAAa,QAAQ,SAAS;AAAA,IACvC,SAAS,OAAO;AACd,aAAO,aAAa,KAAK;AAAA,QACvB,MAAM;AAAA,QACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACpD,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/handlers.ts"],"sourcesContent":["import type { HttpHandler, HttpRequest, HttpResponseInit, InvocationContext } from \"@azure/functions\";\nimport { isGraphQuery, type GraphQuery, type TelemetrySink, type WriteCommand } from \"@plasius/graph-contracts\";\nimport type { GraphGateway } from \"@plasius/graph-gateway-core\";\nimport type { WriteCoordinator } from \"@plasius/graph-write-coordinator\";\n\nexport interface HandlerAuthContext {\n operation: \"read\" | \"write\";\n request: HttpRequest;\n context: InvocationContext;\n}\n\nexport type AuthorizeHandler = (context: HandlerAuthContext) => Promise<boolean> | boolean;\n\nexport interface GraphReadHandlerOptions {\n gateway: Pick<GraphGateway, \"execute\">;\n telemetry?: TelemetrySink;\n authorize?: AuthorizeHandler;\n maxBodyBytes?: number;\n}\n\nexport interface GraphWriteHandlerOptions {\n coordinator: Pick<WriteCoordinator, \"submit\">;\n telemetry?: TelemetrySink;\n authorize?: AuthorizeHandler;\n maxBodyBytes?: number;\n}\n\nconst DEFAULT_MAX_BODY_BYTES = 64 * 1024;\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\nconst isSafeKey = (value: string): boolean => /^[A-Za-z0-9:_-]+$/.test(value);\n\nconst isWriteCommandPayloadValid = (value: unknown): value is WriteCommand => {\n if (!isObject(value)) {\n return false;\n }\n\n if (\n typeof value.idempotencyKey !== \"string\"\n || value.idempotencyKey.length === 0\n || value.idempotencyKey.length > 128\n || !isSafeKey(value.idempotencyKey)\n ) {\n return false;\n }\n\n if (\n typeof value.partitionKey !== \"string\"\n || value.partitionKey.length === 0\n || value.partitionKey.length > 128\n || !isSafeKey(value.partitionKey)\n ) {\n return false;\n }\n\n if (\n typeof value.aggregateKey !== \"string\"\n || value.aggregateKey.length === 0\n || value.aggregateKey.length > 256\n || !isSafeKey(value.aggregateKey)\n ) {\n return false;\n }\n\n if (!isObject(value.payload)) {\n return false;\n }\n\n if (typeof value.submittedAtEpochMs !== \"number\" || !Number.isFinite(value.submittedAtEpochMs) || value.submittedAtEpochMs < 0) {\n return false;\n }\n\n return value.actorId === undefined || (typeof value.actorId === \"string\" && value.actorId.length > 0 && value.actorId.length <= 128);\n};\n\nclass HandlerValidationError extends Error {\n public readonly status: number;\n public readonly code: string;\n\n public constructor(status: number, code: string, message: string) {\n super(message);\n this.status = status;\n this.code = code;\n }\n}\n\nconst jsonResponse = (status: number, body: unknown): HttpResponseInit => ({\n status,\n jsonBody: body,\n headers: {\n \"content-type\": \"application/json\",\n \"cache-control\": \"no-store\",\n },\n});\n\nconst getContentLength = (request: HttpRequest): number | null => {\n const raw = request.headers?.get?.(\"content-length\");\n if (!raw) {\n return null;\n }\n\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return null;\n }\n\n return parsed;\n};\n\nconst enforceBodyLimit = (request: HttpRequest, maxBodyBytes: number): void => {\n const contentLength = getContentLength(request);\n if (contentLength !== null && contentLength > maxBodyBytes) {\n throw new HandlerValidationError(413, \"GRAPH_REQUEST_BODY_TOO_LARGE\", \"Request payload exceeds allowed limit.\");\n }\n};\n\nconst enforceAuthorization = async (\n authorize: AuthorizeHandler | undefined,\n operation: \"read\" | \"write\",\n request: HttpRequest,\n context: InvocationContext,\n): Promise<void> => {\n if (!authorize) {\n return;\n }\n\n const allowed = await authorize({ operation, request, context });\n if (!allowed) {\n throw new HandlerValidationError(403, \"GRAPH_FORBIDDEN\", \"Forbidden\");\n }\n};\n\nexport const createGraphReadHandler = (options: GraphReadHandlerOptions): HttpHandler => {\n return async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {\n const startedAt = Date.now();\n options.telemetry?.metric({\n name: \"graph.runtime.read.request\",\n value: 1,\n unit: \"count\",\n });\n try {\n const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;\n enforceBodyLimit(request, maxBodyBytes);\n await enforceAuthorization(options.authorize, \"read\", request, context);\n\n let rawBody: unknown;\n try {\n rawBody = await request.json();\n } catch {\n throw new HandlerValidationError(400, \"GRAPH_READ_REQUEST_INVALID\", \"Invalid graph read request.\");\n }\n if (!isGraphQuery(rawBody)) {\n throw new HandlerValidationError(400, \"GRAPH_READ_REQUEST_INVALID\", \"Invalid graph read request.\");\n }\n\n const query = rawBody as GraphQuery;\n const result = await options.gateway.execute({\n ...query,\n traceId: query.traceId ?? context.traceContext?.traceParent,\n });\n options.telemetry?.metric({\n name: \"graph.runtime.read.latency\",\n value: Date.now() - startedAt,\n unit: \"ms\",\n tags: { status: \"200\" },\n });\n return jsonResponse(200, result);\n } catch (error) {\n if (error instanceof HandlerValidationError) {\n options.telemetry?.metric({\n name: \"graph.runtime.read.error\",\n value: 1,\n unit: \"count\",\n tags: { code: error.code },\n });\n options.telemetry?.error({\n message: error.message,\n source: \"graph-runtime-azure-functions.read\",\n code: error.code,\n });\n return jsonResponse(error.status, {\n code: error.code,\n message: error.message,\n });\n }\n\n options.telemetry?.metric({\n name: \"graph.runtime.read.error\",\n value: 1,\n unit: \"count\",\n tags: { code: \"GRAPH_READ_UPSTREAM_FAILED\" },\n });\n options.telemetry?.error({\n message: \"Graph read failed\",\n source: \"graph-runtime-azure-functions.read\",\n code: \"GRAPH_READ_UPSTREAM_FAILED\",\n });\n return jsonResponse(502, {\n code: \"GRAPH_READ_UPSTREAM_FAILED\",\n message: \"Graph read failed.\",\n });\n }\n };\n};\n\nexport const createGraphWriteHandler = (options: GraphWriteHandlerOptions): HttpHandler => {\n return async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {\n const startedAt = Date.now();\n options.telemetry?.metric({\n name: \"graph.runtime.write.request\",\n value: 1,\n unit: \"count\",\n });\n try {\n const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;\n enforceBodyLimit(request, maxBodyBytes);\n await enforceAuthorization(options.authorize, \"write\", request, context);\n\n let rawBody: unknown;\n try {\n rawBody = await request.json();\n } catch {\n throw new HandlerValidationError(400, \"GRAPH_WRITE_REQUEST_INVALID\", \"Invalid graph write request.\");\n }\n if (!isWriteCommandPayloadValid(rawBody)) {\n throw new HandlerValidationError(400, \"GRAPH_WRITE_REQUEST_INVALID\", \"Invalid graph write request.\");\n }\n\n const command = rawBody as WriteCommand;\n const operation = await options.coordinator.submit(command);\n const status = operation.state === \"succeeded\" ? 200 : 202;\n options.telemetry?.metric({\n name: \"graph.runtime.write.latency\",\n value: Date.now() - startedAt,\n unit: \"ms\",\n tags: {\n status: String(status),\n state: operation.state,\n },\n });\n return jsonResponse(status, operation);\n } catch (error) {\n if (error instanceof HandlerValidationError) {\n options.telemetry?.metric({\n name: \"graph.runtime.write.error\",\n value: 1,\n unit: \"count\",\n tags: { code: error.code },\n });\n options.telemetry?.error({\n message: error.message,\n source: \"graph-runtime-azure-functions.write\",\n code: error.code,\n });\n return jsonResponse(error.status, {\n code: error.code,\n message: error.message,\n });\n }\n\n options.telemetry?.metric({\n name: \"graph.runtime.write.error\",\n value: 1,\n unit: \"count\",\n tags: { code: \"GRAPH_WRITE_UPSTREAM_FAILED\" },\n });\n options.telemetry?.error({\n message: \"Graph write failed\",\n source: \"graph-runtime-azure-functions.write\",\n code: \"GRAPH_WRITE_UPSTREAM_FAILED\",\n });\n return jsonResponse(503, {\n code: \"GRAPH_WRITE_UPSTREAM_FAILED\",\n message: \"Graph write failed.\",\n });\n }\n };\n};\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/graph-runtime-azure-functions",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Azure Functions runtime adapter for graph gateway read/write endpoints",
5
5
  "type": "module",
6
6
  "sideEffects": false,