@crossplane-org/function-sdk-typescript 0.1.0

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.
Files changed (51) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +683 -0
  3. package/dist/example-function.d.ts +11 -0
  4. package/dist/example-function.d.ts.map +1 -0
  5. package/dist/example-function.js +93 -0
  6. package/dist/example-function.js.map +1 -0
  7. package/dist/function/function.d.ts +115 -0
  8. package/dist/function/function.d.ts.map +1 -0
  9. package/dist/function/function.js +111 -0
  10. package/dist/function/function.js.map +1 -0
  11. package/dist/index.d.ts +9 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +13 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/main.d.ts +3 -0
  16. package/dist/main.d.ts.map +1 -0
  17. package/dist/main.js +69 -0
  18. package/dist/main.js.map +1 -0
  19. package/dist/proto/google/protobuf/duration.d.ts +107 -0
  20. package/dist/proto/google/protobuf/duration.d.ts.map +1 -0
  21. package/dist/proto/google/protobuf/duration.js +90 -0
  22. package/dist/proto/google/protobuf/duration.js.map +1 -0
  23. package/dist/proto/google/protobuf/struct.d.ts +115 -0
  24. package/dist/proto/google/protobuf/struct.d.ts.map +1 -0
  25. package/dist/proto/google/protobuf/struct.js +452 -0
  26. package/dist/proto/google/protobuf/struct.js.map +1 -0
  27. package/dist/proto/run_function.d.ts +494 -0
  28. package/dist/proto/run_function.d.ts.map +1 -0
  29. package/dist/proto/run_function.js +2230 -0
  30. package/dist/proto/run_function.js.map +1 -0
  31. package/dist/request/request.d.ts +198 -0
  32. package/dist/request/request.d.ts.map +1 -0
  33. package/dist/request/request.js +219 -0
  34. package/dist/request/request.js.map +1 -0
  35. package/dist/resource/resource.d.ts +101 -0
  36. package/dist/resource/resource.d.ts.map +1 -0
  37. package/dist/resource/resource.js +98 -0
  38. package/dist/resource/resource.js.map +1 -0
  39. package/dist/response/response.d.ts +273 -0
  40. package/dist/response/response.d.ts.map +1 -0
  41. package/dist/response/response.js +339 -0
  42. package/dist/response/response.js.map +1 -0
  43. package/dist/runtime/runtime.d.ts +121 -0
  44. package/dist/runtime/runtime.d.ts.map +1 -0
  45. package/dist/runtime/runtime.js +124 -0
  46. package/dist/runtime/runtime.js.map +1 -0
  47. package/dist/vitest.config.d.ts +3 -0
  48. package/dist/vitest.config.d.ts.map +1 -0
  49. package/dist/vitest.config.js +17 -0
  50. package/dist/vitest.config.js.map +1 -0
  51. package/package.json +55 -0
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Response utilities for working with RunFunctionResponse
3
+ *
4
+ * This module provides helper functions to build and manipulate RunFunctionResponse objects,
5
+ * including setting desired state, managing results, handling context, and working with
6
+ * composite resources and their status.
7
+ */
8
+ import { Resource, RunFunctionRequest, RunFunctionResponse, Severity, Ready, } from "../proto/run_function.js";
9
+ import { Duration } from "../proto/google/protobuf/duration.js";
10
+ import { merge } from "ts-deepmerge";
11
+ /**
12
+ * Default time-to-live for function responses (60 seconds).
13
+ * Crossplane will call the function again when the TTL expires.
14
+ */
15
+ const DEFAULT_TTL = { seconds: 60, nanos: 0 };
16
+ export { DEFAULT_TTL };
17
+ /**
18
+ * Bootstrap a response from a request.
19
+ *
20
+ * This function creates a new RunFunctionResponse with the request's tag automatically
21
+ * copied, and initializes the desired state from the request. Using this function is
22
+ * the recommended pattern to ensure proper response initialization.
23
+ *
24
+ * The response will:
25
+ * - Copy the request's tag to the response metadata
26
+ * - Initialize the desired state from the request (or create empty if not present)
27
+ * - Set the TTL (time-to-live) for caching
28
+ * - Initialize empty results and conditions arrays
29
+ *
30
+ * @param req - The RunFunctionRequest to bootstrap from
31
+ * @param ttl - Optional time-to-live duration (defaults to 60 seconds)
32
+ * @returns A new RunFunctionResponse initialized from the request
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * async RunFunction(req: RunFunctionRequest): Promise<RunFunctionResponse> {
37
+ * let rsp = to(req);
38
+ * // Add your logic here
39
+ * normal(rsp, "Processing complete");
40
+ * return rsp;
41
+ * }
42
+ * ```
43
+ */
44
+ export function to(req, ttl) {
45
+ // Initialize desired state if needed
46
+ let desired = req.desired;
47
+ // If desired is not set, initialize it
48
+ if (!desired) {
49
+ desired = { composite: undefined, resources: {} };
50
+ }
51
+ // If composite is explicitly null, initialize it as an empty resource
52
+ if (desired.composite === null) {
53
+ desired.composite = Resource.fromJSON({});
54
+ }
55
+ return {
56
+ conditions: [],
57
+ context: req.context,
58
+ desired: desired,
59
+ meta: { tag: req.meta?.tag || "", ttl: ttl || DEFAULT_TTL },
60
+ requirements: undefined,
61
+ results: [],
62
+ };
63
+ }
64
+ /**
65
+ * Update a map of desired composed resources by adding or updating a named resource.
66
+ *
67
+ * This is a helper function to add a single resource to a map of composed resources.
68
+ * It's useful when building up desired resources before calling setDesiredComposedResources.
69
+ *
70
+ * @param cds - The current map of composed resources
71
+ * @param res - The named resource to add or update
72
+ * @returns The updated map of composed resources
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * let dcds = getDesiredComposedResources(req);
77
+ * dcds = updateDesiredComposedResources(dcds, {
78
+ * name: "my-bucket",
79
+ * resource: Resource.fromJSON({ resource: bucketConfig })
80
+ * });
81
+ * rsp = setDesiredComposedResources(rsp, dcds);
82
+ * ```
83
+ */
84
+ export function updateDesiredComposedResources(cds, res) {
85
+ cds[res.name] = res.resource;
86
+ return cds;
87
+ }
88
+ /**
89
+ * Add a fatal result to the response.
90
+ *
91
+ * Fatal results cause the function pipeline run to be considered a failure.
92
+ * Subsequent functions may still run, but the first fatal result will be
93
+ * returned as an error. Fatal results should be used for errors that prevent
94
+ * the function from producing valid output.
95
+ *
96
+ * @param rsp - The RunFunctionResponse to add the result to
97
+ * @param message - The error message describing the fatal condition
98
+ * @returns The updated response
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * if (!requiredInput) {
103
+ * fatal(rsp, "Required input 'databaseSize' not provided");
104
+ * return rsp;
105
+ * }
106
+ * ```
107
+ */
108
+ export function fatal(rsp, message) {
109
+ if (rsp && rsp.results) {
110
+ rsp.results.push({
111
+ severity: Severity.SEVERITY_FATAL,
112
+ message: message,
113
+ });
114
+ }
115
+ return rsp;
116
+ }
117
+ /**
118
+ * Add a normal result to the response.
119
+ *
120
+ * Normal results are informational and emitted as normal events and debug logs
121
+ * associated with the composite resource (XR) or operation. They indicate
122
+ * successful processing or expected conditions.
123
+ *
124
+ * @param rsp - The RunFunctionResponse to add the result to
125
+ * @param message - The informational message
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * normal(rsp, "Successfully configured 3 database replicas");
130
+ * ```
131
+ */
132
+ export function normal(rsp, message) {
133
+ if (rsp && rsp.results) {
134
+ rsp.results.push({
135
+ severity: Severity.SEVERITY_NORMAL,
136
+ message: message,
137
+ });
138
+ }
139
+ }
140
+ /**
141
+ * Add a warning result to the response.
142
+ *
143
+ * Warning results are non-fatal issues that should be brought to attention.
144
+ * The entire pipeline will run to completion, but warning events and debug logs
145
+ * will be emitted. Use warnings for recoverable issues or deprecated usage.
146
+ *
147
+ * @param rsp - The RunFunctionResponse to add the result to
148
+ * @param message - The warning message
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * if (input.legacyFormat) {
153
+ * warning(rsp, "Using deprecated input format, please migrate to new format");
154
+ * }
155
+ * ```
156
+ */
157
+ export function warning(rsp, message) {
158
+ if (rsp && rsp.results) {
159
+ rsp.results.push({
160
+ severity: Severity.SEVERITY_WARNING,
161
+ message: message,
162
+ });
163
+ }
164
+ }
165
+ /**
166
+ * Set the desired composed resources in the response.
167
+ *
168
+ * This function sets or merges the desired composed resources in the response.
169
+ * It uses deep merge to combine new resources with any existing resources,
170
+ * allowing functions to add or modify resources while preserving those set
171
+ * by previous functions in the pipeline.
172
+ *
173
+ * If the desired state or resources map don't exist, they will be initialized.
174
+ *
175
+ * @param rsp - The RunFunctionResponse to update
176
+ * @param dcds - A map of resource names to Resource objects to set as desired
177
+ * @returns The updated response
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * const dcds = getDesiredComposedResources(req);
182
+ * dcds["my-deployment"] = Resource.fromJSON({
183
+ * resource: { apiVersion: "apps/v1", kind: "Deployment", ... }
184
+ * });
185
+ * rsp = setDesiredComposedResources(rsp, dcds);
186
+ * ```
187
+ */
188
+ export function setDesiredComposedResources(rsp, dcds) {
189
+ // Ensure desired state exists
190
+ if (!rsp.desired) {
191
+ rsp.desired = { composite: undefined, resources: {} };
192
+ }
193
+ // Merge the new resources with existing ones
194
+ rsp.desired.resources = merge(rsp.desired.resources || {}, dcds);
195
+ return rsp;
196
+ }
197
+ /**
198
+ * Update a resource by merging source into target.
199
+ *
200
+ * This function performs a deep merge of the source resource into the target resource,
201
+ * allowing you to update specific fields while preserving others. The merge is performed
202
+ * using the ts-deepmerge library.
203
+ *
204
+ * @param src - The source Resource containing updates
205
+ * @param tgt - The target Resource to be updated
206
+ * @returns A new Resource with merged values
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * const existing = getDesiredComposedResources(req)["my-resource"];
211
+ * const updated = update(
212
+ * Resource.fromJSON({ resource: { spec: { replicas: 5 } } }),
213
+ * existing
214
+ * );
215
+ * ```
216
+ */
217
+ export function update(src, tgt) {
218
+ return merge(tgt, src);
219
+ }
220
+ /**
221
+ * Set the desired composite resource status.
222
+ *
223
+ * This function updates only the status field of the desired composite resource.
224
+ * It merges the provided status with any existing status, allowing partial updates.
225
+ * Note that functions should only set the status of composite resources (XRs),
226
+ * not their metadata or spec.
227
+ *
228
+ * @param params - Object containing the response and status to set
229
+ * @param params.rsp - The RunFunctionResponse to update
230
+ * @param params.status - The status object to merge into the composite resource
231
+ * @returns The updated response
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * rsp = setDesiredCompositeStatus({
236
+ * rsp,
237
+ * status: {
238
+ * phase: "Ready",
239
+ * conditions: [{ type: "Synced", status: "True" }]
240
+ * }
241
+ * });
242
+ * ```
243
+ */
244
+ export function setDesiredCompositeStatus({ rsp, status }) {
245
+ if (rsp.desired?.composite?.resource) {
246
+ rsp.desired.composite.resource = merge(rsp.desired.composite.resource, {
247
+ "status": status,
248
+ });
249
+ }
250
+ return rsp;
251
+ }
252
+ /**
253
+ * Set a context key in the response.
254
+ *
255
+ * Context allows functions to pass arbitrary data to subsequent functions in the
256
+ * pipeline. The context is initialized if it doesn't exist. Crossplane discards
257
+ * all context returned by the last function in the pipeline.
258
+ *
259
+ * @param rsp - The RunFunctionResponse to update
260
+ * @param key - The context key to set
261
+ * @param value - The value to associate with the key (can be any JSON-serializable value)
262
+ * @returns The updated response
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * // Set context for next function in pipeline
267
+ * rsp = setContextKey(rsp, "database-endpoint", "db.example.com:5432");
268
+ * rsp = setContextKey(rsp, "connection-config", { host: "db.example.com", port: 5432 });
269
+ * ```
270
+ */
271
+ export function setContextKey(rsp, key, value) {
272
+ if (!rsp.context) {
273
+ rsp.context = {};
274
+ }
275
+ rsp.context[key] = value;
276
+ return rsp;
277
+ }
278
+ /**
279
+ * Set the desired composite resource in the response.
280
+ *
281
+ * This function sets the entire desired composite resource and optionally its ready status.
282
+ * The ready status can be used to override Crossplane's standard readiness detection,
283
+ * which normally determines the composite's ready state based on composed resources.
284
+ *
285
+ * Note: Ready status is only used for composition functions, not operations.
286
+ *
287
+ * @param rsp - The RunFunctionResponse to update
288
+ * @param resource - The desired composite resource to set
289
+ * @param ready - Optional ready status (READY_TRUE, READY_FALSE, or READY_UNSPECIFIED)
290
+ * @returns The updated response
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * const composite = getObservedCompositeResource(req);
295
+ * if (composite) {
296
+ * // Modify and set as desired with ready status
297
+ * rsp = setDesiredCompositeResource(rsp, composite, Ready.READY_TRUE);
298
+ * }
299
+ * ```
300
+ */
301
+ export function setDesiredCompositeResource(rsp, resource, ready) {
302
+ if (!rsp.desired) {
303
+ rsp.desired = { composite: undefined, resources: {} };
304
+ }
305
+ // Create a new resource with the specified ready status using fromPartial
306
+ // to ensure proper type handling for the protobuf-generated Resource type
307
+ rsp.desired.composite = Resource.fromPartial({
308
+ resource: resource.resource,
309
+ connectionDetails: resource.connectionDetails,
310
+ ready: ready !== undefined ? ready : Ready.READY_UNSPECIFIED,
311
+ });
312
+ return rsp;
313
+ }
314
+ /**
315
+ * Set the function output in the response.
316
+ *
317
+ * Function output is specific to operation functions. The output must be a
318
+ * JSON-serializable object. Composite resources (XRs) will discard any function
319
+ * output, as it's only used by operations.
320
+ *
321
+ * @param rsp - The RunFunctionResponse to update
322
+ * @param output - The output object to set (must be JSON-serializable)
323
+ * @returns The updated response
324
+ *
325
+ * @example
326
+ * ```typescript
327
+ * // For operation functions
328
+ * rsp = setOutput(rsp, {
329
+ * resourcesCreated: 5,
330
+ * status: "success",
331
+ * details: { timestamp: new Date().toISOString() }
332
+ * });
333
+ * ```
334
+ */
335
+ export function setOutput(rsp, output) {
336
+ rsp.output = output;
337
+ return rsp;
338
+ }
339
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/response/response.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Runtime utilities for creating and managing gRPC servers
3
+ *
4
+ * This module provides functions to create gRPC servers for Crossplane functions,
5
+ * handle TLS credentials, and manage server lifecycle. It simplifies the process
6
+ * of setting up a production-ready function server with proper security.
7
+ */
8
+ import * as grpc from "@grpc/grpc-js";
9
+ import type { Logger } from "pino";
10
+ import { FunctionRunner } from "../function/function.js";
11
+ /**
12
+ * Configuration options for the gRPC server.
13
+ */
14
+ export interface ServerOptions {
15
+ /**
16
+ * The address to listen on (e.g., "0.0.0.0:9443" or ":9443")
17
+ * Default is ":9443" when using the CLI
18
+ */
19
+ address: string;
20
+ /**
21
+ * Whether to run without TLS encryption.
22
+ * Set to true for local development only. Production should use TLS.
23
+ * Default: false
24
+ */
25
+ insecure?: boolean;
26
+ /**
27
+ * Enable debug-level logging.
28
+ * Default: false
29
+ */
30
+ debug?: boolean;
31
+ /**
32
+ * Filesystem directory containing TLS certificates.
33
+ * Should contain: tls.key, tls.crt, and ca.crt
34
+ * Ignored if insecure is set to true.
35
+ */
36
+ tlsServerCertsDir?: string;
37
+ }
38
+ /**
39
+ * Create gRPC ServerCredentials from TLS certificate files.
40
+ *
41
+ * This function loads TLS certificates from the filesystem and creates
42
+ * appropriate ServerCredentials for the gRPC server. In insecure mode,
43
+ * it returns insecure credentials (suitable for local development only).
44
+ *
45
+ * For secure mode, it expects three files in tlsServerCertsDir:
46
+ * - tls.key: The server's private key
47
+ * - tls.crt: The server's certificate
48
+ * - ca.crt: The certificate authority certificate
49
+ *
50
+ * @param opts - Server options containing TLS configuration
51
+ * @returns gRPC ServerCredentials (secure or insecure based on options)
52
+ * @throws Error if certificate files cannot be read in secure mode
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // Secure mode (production)
57
+ * const creds = getServerCredentials({
58
+ * address: ":9443",
59
+ * tlsServerCertsDir: "/tls"
60
+ * });
61
+ *
62
+ * // Insecure mode (development only)
63
+ * const creds = getServerCredentials({
64
+ * address: ":9443",
65
+ * insecure: true
66
+ * });
67
+ * ```
68
+ */
69
+ export declare function getServerCredentials(opts?: ServerOptions): grpc.ServerCredentials;
70
+ /**
71
+ * Create a new gRPC server with the function runner registered.
72
+ *
73
+ * This function creates a gRPC server instance and registers the provided
74
+ * FunctionRunner to handle incoming RunFunction requests. The server is
75
+ * created but not yet bound to an address - use startServer to bind and start it.
76
+ *
77
+ * @param functionRunner - The FunctionRunner instance that will handle requests
78
+ * @param logger - Logger instance for debug and error logging
79
+ * @returns A configured gRPC Server instance ready to be started
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * const runner = new FunctionRunner(new MyFunction(), logger);
84
+ * const server = newGrpcServer(runner, logger);
85
+ * startServer(server, { address: ":9443", insecure: false }, logger);
86
+ * ```
87
+ */
88
+ export declare function newGrpcServer(functionRunner: FunctionRunner, logger: Logger): grpc.Server;
89
+ /**
90
+ * Bind and start a gRPC server.
91
+ *
92
+ * This function binds the server to the specified address and starts listening
93
+ * for incoming connections. It handles both secure (TLS) and insecure modes
94
+ * based on the provided options. The binding happens asynchronously.
95
+ *
96
+ * The server will log when it successfully starts listening or if an error occurs
97
+ * during binding.
98
+ *
99
+ * @param server - The gRPC Server instance to start
100
+ * @param opts - Server options including address and TLS configuration
101
+ * @param logger - Logger instance for info and error logging
102
+ * @returns The same server instance (now bound and listening)
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * const server = newGrpcServer(runner, logger);
107
+ * startServer(server, {
108
+ * address: "0.0.0.0:9443",
109
+ * tlsServerCertsDir: "/tls",
110
+ * debug: true
111
+ * }, logger);
112
+ *
113
+ * // For local development
114
+ * startServer(server, {
115
+ * address: "localhost:9443",
116
+ * insecure: true
117
+ * }, logger);
118
+ * ```
119
+ */
120
+ export declare function startServer(server: grpc.Server, opts: ServerOptions, logger: Logger): grpc.Server;
121
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC,OAAO,EAAE,cAAc,EAAa,MAAM,yBAAyB,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,oBAAoB,CAChC,IAAI,CAAC,EAAE,aAAa,GACrB,IAAI,CAAC,iBAAiB,CAkBxB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAMzF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAgBjG"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Runtime utilities for creating and managing gRPC servers
3
+ *
4
+ * This module provides functions to create gRPC servers for Crossplane functions,
5
+ * handle TLS credentials, and manage server lifecycle. It simplifies the process
6
+ * of setting up a production-ready function server with proper security.
7
+ */
8
+ import * as grpc from "@grpc/grpc-js";
9
+ import { readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { FunctionRunner, getServer } from "../function/function.js";
12
+ /**
13
+ * Create gRPC ServerCredentials from TLS certificate files.
14
+ *
15
+ * This function loads TLS certificates from the filesystem and creates
16
+ * appropriate ServerCredentials for the gRPC server. In insecure mode,
17
+ * it returns insecure credentials (suitable for local development only).
18
+ *
19
+ * For secure mode, it expects three files in tlsServerCertsDir:
20
+ * - tls.key: The server's private key
21
+ * - tls.crt: The server's certificate
22
+ * - ca.crt: The certificate authority certificate
23
+ *
24
+ * @param opts - Server options containing TLS configuration
25
+ * @returns gRPC ServerCredentials (secure or insecure based on options)
26
+ * @throws Error if certificate files cannot be read in secure mode
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * // Secure mode (production)
31
+ * const creds = getServerCredentials({
32
+ * address: ":9443",
33
+ * tlsServerCertsDir: "/tls"
34
+ * });
35
+ *
36
+ * // Insecure mode (development only)
37
+ * const creds = getServerCredentials({
38
+ * address: ":9443",
39
+ * insecure: true
40
+ * });
41
+ * ```
42
+ */
43
+ export function getServerCredentials(opts) {
44
+ if (opts?.insecure || opts?.tlsServerCertsDir === "" || opts?.tlsServerCertsDir === undefined) {
45
+ return grpc.ServerCredentials.createInsecure();
46
+ }
47
+ const tlsCertsDir = opts.tlsServerCertsDir;
48
+ if (typeof tlsCertsDir !== "string" || tlsCertsDir.trim() === "") {
49
+ throw new Error("tlsServerCertsDir must be a non-empty string when TLS is enabled");
50
+ }
51
+ const privateKey = readFileSync(join(tlsCertsDir, "tls.key"));
52
+ const certChain = readFileSync(join(tlsCertsDir, "tls.crt"));
53
+ const rootCerts = readFileSync(join(tlsCertsDir, "ca.crt"));
54
+ return grpc.ServerCredentials.createSsl(rootCerts, [{ private_key: privateKey, cert_chain: certChain }], false);
55
+ }
56
+ /**
57
+ * Create a new gRPC server with the function runner registered.
58
+ *
59
+ * This function creates a gRPC server instance and registers the provided
60
+ * FunctionRunner to handle incoming RunFunction requests. The server is
61
+ * created but not yet bound to an address - use startServer to bind and start it.
62
+ *
63
+ * @param functionRunner - The FunctionRunner instance that will handle requests
64
+ * @param logger - Logger instance for debug and error logging
65
+ * @returns A configured gRPC Server instance ready to be started
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const runner = new FunctionRunner(new MyFunction(), logger);
70
+ * const server = newGrpcServer(runner, logger);
71
+ * startServer(server, { address: ":9443", insecure: false }, logger);
72
+ * ```
73
+ */
74
+ export function newGrpcServer(functionRunner, logger) {
75
+ const server = getServer(functionRunner, logger);
76
+ if (logger) {
77
+ logger.debug("grpc server created");
78
+ }
79
+ return server;
80
+ }
81
+ /**
82
+ * Bind and start a gRPC server.
83
+ *
84
+ * This function binds the server to the specified address and starts listening
85
+ * for incoming connections. It handles both secure (TLS) and insecure modes
86
+ * based on the provided options. The binding happens asynchronously.
87
+ *
88
+ * The server will log when it successfully starts listening or if an error occurs
89
+ * during binding.
90
+ *
91
+ * @param server - The gRPC Server instance to start
92
+ * @param opts - Server options including address and TLS configuration
93
+ * @param logger - Logger instance for info and error logging
94
+ * @returns The same server instance (now bound and listening)
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * const server = newGrpcServer(runner, logger);
99
+ * startServer(server, {
100
+ * address: "0.0.0.0:9443",
101
+ * tlsServerCertsDir: "/tls",
102
+ * debug: true
103
+ * }, logger);
104
+ *
105
+ * // For local development
106
+ * startServer(server, {
107
+ * address: "localhost:9443",
108
+ * insecure: true
109
+ * }, logger);
110
+ * ```
111
+ */
112
+ export function startServer(server, opts, logger) {
113
+ const creds = getServerCredentials(opts);
114
+ logger.debug(`serverCredentials type: ${creds.constructor.name}`);
115
+ server.bindAsync(opts.address, creds, (err) => {
116
+ if (err) {
117
+ logger.error(`server bind error: ${err.message}`);
118
+ return;
119
+ }
120
+ logger.info(`server started and listening on ${opts.address}`);
121
+ });
122
+ return server;
123
+ }
124
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAiCpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,oBAAoB,CAChC,IAAoB;IAEpB,IAAI,IAAI,EAAE,QAAQ,IAAI,IAAI,EAAE,iBAAiB,KAAK,EAAE,IAAI,IAAI,EAAE,iBAAiB,KAAK,SAAS,EAAE,CAAC;QAC5F,OAAO,IAAI,CAAC,iBAAiB,CAAC,cAAc,EAAE,CAAC;IACnD,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,CAAC;IAC3C,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC,iBAAiB,CAAC,SAAS,CACnC,SAAS,EACT,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,EACpD,KAAK,CACR,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,aAAa,CAAC,cAA8B,EAAE,MAAc;IACxE,MAAM,MAAM,GAAG,SAAS,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IACjD,IAAI,MAAM,EAAE,CAAC;QACT,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,WAAW,CAAC,MAAmB,EAAE,IAAmB,EAAE,MAAc;IAChF,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,2BAA2B,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;IAElE,MAAM,CAAC,SAAS,CACZ,IAAI,CAAC,OAAO,EACZ,KAAK,EACL,CAAC,GAAG,EAAE,EAAE;QACJ,IAAI,GAAG,EAAE,CAAC;YACN,MAAM,CAAC,KAAK,CAAC,sBAAsB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAClD,OAAO;QACX,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,mCAAmC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IACnE,CAAC,CACJ,CAAC;IACF,OAAO,MAAM,CAAC;AAClB,CAAC"}
@@ -0,0 +1,3 @@
1
+ declare const _default: import("vite").UserConfig;
2
+ export default _default;
3
+ //# sourceMappingURL=vitest.config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../src/vitest.config.ts"],"names":[],"mappings":";AAEA,wBAcG"}
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ export default defineConfig({
3
+ test: {
4
+ globals: true,
5
+ environment: 'node',
6
+ coverage: {
7
+ provider: 'v8',
8
+ reporter: ['text', 'json', 'html'],
9
+ exclude: [
10
+ 'node_modules/',
11
+ 'dist/',
12
+ 'src/proto/',
13
+ ],
14
+ },
15
+ },
16
+ });
17
+ //# sourceMappingURL=vitest.config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.config.js","sourceRoot":"","sources":["../src/vitest.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,eAAe,YAAY,CAAC;IAC1B,IAAI,EAAE;QACJ,OAAO,EAAE,IAAI;QACb,WAAW,EAAE,MAAM;QACnB,QAAQ,EAAE;YACR,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YAClC,OAAO,EAAE;gBACP,eAAe;gBACf,OAAO;gBACP,YAAY;aACb;SACF;KACF;CACF,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@crossplane-org/function-sdk-typescript",
3
+ "version": "0.1.0",
4
+ "description": "A Crossplane Function SDK for Typescript",
5
+ "keywords": [
6
+ "crossplane",
7
+ "kubernetes",
8
+ "typescript"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "github.com/upbound/function-sdk-typescript"
13
+ },
14
+ "license": "Apache-2.0",
15
+ "author": "Steven Borrelli",
16
+ "type": "module",
17
+ "main": "dist/index.js",
18
+ "types": "dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.js",
22
+ "types": "./dist/index.d.ts"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ],
29
+ "scripts": {
30
+ "clean": "scripts/clean.sh",
31
+ "build": "npx tsc",
32
+ "pack": "npm pack",
33
+ "prepublishOnly": "npm run build",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
36
+ "test:coverage": "vitest run --coverage"
37
+ },
38
+ "dependencies": {
39
+ "@grpc/grpc-js": "^1.12.4",
40
+ "@grpc/proto-loader": "^0.8.0",
41
+ "google-protobuf": "^4.0.0",
42
+ "kubernetes-models": "^4.5.1",
43
+ "pino": "^10.1.0",
44
+ "ts-deepmerge": "^7.0.3",
45
+ "ts-proto": "^2.8.3"
46
+ },
47
+ "devDependencies": {
48
+ "@types/google-protobuf": "^3.15.12",
49
+ "@types/node": "^22.10.5",
50
+ "@vitest/coverage-v8": "^2.1.8",
51
+ "ts-node": "^10.9.2",
52
+ "typescript": "^5.7.2",
53
+ "vitest": "^2.1.8"
54
+ }
55
+ }