@crossplane-org/function-sdk-typescript 0.6.0 → 0.7.0-20260829081314-5f2a0f216756

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
@@ -20,7 +20,46 @@ npm install @crossplane-org/function-sdk-typescript
20
20
 
21
21
  ### Creating Your Function
22
22
 
23
- Implement the `FunctionHandler` interface:
23
+ A function is a plain function given the request and a response to fill in:
24
+
25
+ ```typescript
26
+ import {
27
+ Resource,
28
+ normal,
29
+ type ComposeFunction,
30
+ } from "@crossplane-org/function-sdk-typescript";
31
+
32
+ export const compose: ComposeFunction = (req, rsp, logger) => {
33
+ logger?.info("Processing function request");
34
+
35
+ rsp.desired.resources["my-config"] = Resource.fromJSON({
36
+ resource: {
37
+ apiVersion: "v1",
38
+ kind: "ConfigMap",
39
+ metadata: { name: "my-config" },
40
+ data: { key: "value" },
41
+ },
42
+ });
43
+
44
+ normal(rsp, "Function completed successfully");
45
+ return rsp;
46
+ };
47
+ ```
48
+
49
+ Your entry point hands it to `serve()`, which parses the standard function flags,
50
+ builds a logger, starts the gRPC server and handles shutdown:
51
+
52
+ ```typescript
53
+ #!/usr/bin/env node
54
+
55
+ import { serve } from "@crossplane-org/function-sdk-typescript";
56
+ import { compose } from "./my-function.js";
57
+
58
+ serve(compose);
59
+ ```
60
+
61
+ If your function needs the full interface, implement `FunctionHandler` — `serve()`
62
+ accepts either:
24
63
 
25
64
  ```typescript
26
65
  import type {
@@ -625,11 +664,23 @@ import {
625
664
 
626
665
  // Runtime types
627
666
  ServerOptions,
667
+ ComposeFunction,
668
+ ComposeResponse,
669
+ ServeOptions,
628
670
  } from "@crossplane-org/function-sdk-typescript";
629
671
  ```
630
672
 
631
673
  ### Core Functions
632
674
 
675
+ #### Server Functions
676
+
677
+ - **`serve(fn, opts?)`** - Run a `ComposeFunction` or `FunctionHandler` as a gRPC server
678
+ - **`fromCompose(compose)`** - Adapt a `ComposeFunction` to the `FunctionHandler` interface
679
+ - **`parseArgs(argv)`** - Parse the standard function flags
680
+ - **`helpText(name)`** - The `--help` text for the standard flags
681
+ - **`newGrpcServer(runner, logger)`** - Create a gRPC server instance
682
+ - **`startServer(server, opts, logger)`** - Bind and start the server
683
+
633
684
  #### Response Functions
634
685
 
635
686
  - **`to(req, ttl?)`** - Initialize a response from a request
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { advertiseCapabilities, getContextKey, getCredentials, getDesiredCompose
4
4
  export { DEFAULT_TTL, fatal, normal, requireResource, requireSchema, setContextKey, setDesiredComposedResources, setDesiredCompositeResource, setDesiredCompositeStatus, setDesiredResources, setOutput, to, update, updateDesiredComposedResources, warning, } from './response/response.js';
5
5
  export { asObject, asStruct, type Composite, type Condition as ResourceCondition, type ConnectionDetails, type DesiredComposed, fromModel, fromObject, getCondition, type MergeOptions, mustStructJSON, mustStructObject, newDesiredComposed, type ObservedComposed, toObject, update as updateResource, } from './resource/resource.js';
6
6
  export { getServerCredentials, newGrpcServer, type ServerOptions, startServer, } from './runtime/runtime.js';
7
+ export { type ComposeFunction, type ComposeResponse, DEFAULT_ADDRESS, DEFAULT_TLS_SERVER_CERTS_DIR, fromCompose, helpText, parseArgs, serve, type ServeOptions, } from './serve/serve.js';
7
8
  export { Capability, Condition, CredentialData, Credentials, FunctionRunnerServiceService, Ready, Requirements, Resource, Resources, ResourceSelector, Result, RunFunctionRequest, RunFunctionResponse, Schema, SchemaSelector, Severity, State, Status, Target, } from './proto/run_function.js';
8
9
  export type { Logger } from 'pino';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnE,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,cAAc,EACd,2BAA2B,EAC3B,2BAA2B,EAC3B,QAAQ,EACR,4BAA4B,EAC5B,4BAA4B,EAC5B,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,WAAW,EACX,KAAK,EACL,MAAM,EACN,eAAe,EACf,aAAa,EACb,aAAa,EACb,2BAA2B,EAC3B,2BAA2B,EAC3B,yBAAyB,EACzB,mBAAmB,EACnB,SAAS,EACT,EAAE,EACF,MAAM,EACN,8BAA8B,EAC9B,OAAO,GACR,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,KAAK,SAAS,EACd,KAAK,SAAS,IAAI,iBAAiB,EACnC,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,SAAS,EACT,UAAU,EACV,YAAY,EACZ,KAAK,YAAY,EACjB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,QAAQ,EACR,MAAM,IAAI,cAAc,GACzB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,KAAK,aAAa,EAClB,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,UAAU,EACV,SAAS,EACT,cAAc,EACd,WAAW,EACX,4BAA4B,EAC5B,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,MAAM,EACN,kBAAkB,EAClB,mBAAmB,EACnB,MAAM,EACN,cAAc,EACd,QAAQ,EACR,KAAK,EACL,MAAM,EACN,MAAM,GACP,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnE,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,cAAc,EACd,2BAA2B,EAC3B,2BAA2B,EAC3B,QAAQ,EACR,4BAA4B,EAC5B,4BAA4B,EAC5B,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,WAAW,EACX,KAAK,EACL,MAAM,EACN,eAAe,EACf,aAAa,EACb,aAAa,EACb,2BAA2B,EAC3B,2BAA2B,EAC3B,yBAAyB,EACzB,mBAAmB,EACnB,SAAS,EACT,EAAE,EACF,MAAM,EACN,8BAA8B,EAC9B,OAAO,GACR,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,KAAK,SAAS,EACd,KAAK,SAAS,IAAI,iBAAiB,EACnC,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,SAAS,EACT,UAAU,EACV,YAAY,EACZ,KAAK,YAAY,EACjB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,QAAQ,EACR,MAAM,IAAI,cAAc,GACzB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,KAAK,aAAa,EAClB,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,eAAe,EACf,4BAA4B,EAC5B,WAAW,EACX,QAAQ,EACR,SAAS,EACT,KAAK,EACL,KAAK,YAAY,GAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,UAAU,EACV,SAAS,EACT,cAAc,EACd,WAAW,EACX,4BAA4B,EAC5B,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,MAAM,EACN,kBAAkB,EAClB,mBAAmB,EACnB,MAAM,EACN,cAAc,EACd,QAAQ,EACR,KAAK,EACL,MAAM,EACN,MAAM,GACP,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -8,6 +8,8 @@ export { DEFAULT_TTL, fatal, normal, requireResource, requireSchema, setContextK
8
8
  export { asObject, asStruct, fromModel, fromObject, getCondition, mustStructJSON, mustStructObject, newDesiredComposed, toObject, update as updateResource, } from './resource/resource.js';
9
9
  // Runtime utilities
10
10
  export { getServerCredentials, newGrpcServer, startServer, } from './runtime/runtime.js';
11
+ // Entrypoint helpers
12
+ export { DEFAULT_ADDRESS, DEFAULT_TLS_SERVER_CERTS_DIR, fromCompose, helpText, parseArgs, serve, } from './serve/serve.js';
11
13
  // Protocol buffer types
12
14
  export { Capability, Condition, CredentialData, Credentials, FunctionRunnerServiceService, Ready, Requirements, Resource, Resources, ResourceSelector, Result, RunFunctionRequest, RunFunctionResponse, Schema, SchemaSelector, Severity, State, Status, Target, } from './proto/run_function.js';
13
15
  //# 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,mDAAmD;AAInD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnE,kBAAkB;AAClB,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,cAAc,EACd,2BAA2B,EAC3B,2BAA2B,EAC3B,QAAQ,EACR,4BAA4B,EAC5B,4BAA4B,EAC5B,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,mBAAmB;AACnB,OAAO,EACL,WAAW,EACX,KAAK,EACL,MAAM,EACN,eAAe,EACf,aAAa,EACb,aAAa,EACb,2BAA2B,EAC3B,2BAA2B,EAC3B,yBAAyB,EACzB,mBAAmB,EACnB,SAAS,EACT,EAAE,EACF,MAAM,EACN,8BAA8B,EAC9B,OAAO,GACR,MAAM,wBAAwB,CAAC;AAEhC,qBAAqB;AACrB,OAAO,EACL,QAAQ,EACR,QAAQ,EAKR,SAAS,EACT,UAAU,EACV,YAAY,EAEZ,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAElB,QAAQ,EACR,MAAM,IAAI,cAAc,GACzB,MAAM,wBAAwB,CAAC;AAEhC,oBAAoB;AACpB,OAAO,EACL,oBAAoB,EACpB,aAAa,EAEb,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAE9B,wBAAwB;AACxB,OAAO,EACL,UAAU,EACV,SAAS,EACT,cAAc,EACd,WAAW,EACX,4BAA4B,EAC5B,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,MAAM,EACN,kBAAkB,EAClB,mBAAmB,EACnB,MAAM,EACN,cAAc,EACd,QAAQ,EACR,KAAK,EACL,MAAM,EACN,MAAM,GACP,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mDAAmD;AAInD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnE,kBAAkB;AAClB,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,cAAc,EACd,2BAA2B,EAC3B,2BAA2B,EAC3B,QAAQ,EACR,4BAA4B,EAC5B,4BAA4B,EAC5B,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,mBAAmB;AACnB,OAAO,EACL,WAAW,EACX,KAAK,EACL,MAAM,EACN,eAAe,EACf,aAAa,EACb,aAAa,EACb,2BAA2B,EAC3B,2BAA2B,EAC3B,yBAAyB,EACzB,mBAAmB,EACnB,SAAS,EACT,EAAE,EACF,MAAM,EACN,8BAA8B,EAC9B,OAAO,GACR,MAAM,wBAAwB,CAAC;AAEhC,qBAAqB;AACrB,OAAO,EACL,QAAQ,EACR,QAAQ,EAKR,SAAS,EACT,UAAU,EACV,YAAY,EAEZ,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAElB,QAAQ,EACR,MAAM,IAAI,cAAc,GACzB,MAAM,wBAAwB,CAAC;AAEhC,oBAAoB;AACpB,OAAO,EACL,oBAAoB,EACpB,aAAa,EAEb,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAE9B,qBAAqB;AACrB,OAAO,EAGL,eAAe,EACf,4BAA4B,EAC5B,WAAW,EACX,QAAQ,EACR,SAAS,EACT,KAAK,GAEN,MAAM,kBAAkB,CAAC;AAE1B,wBAAwB;AACxB,OAAO,EACL,UAAU,EACV,SAAS,EACT,cAAc,EACd,WAAW,EACX,4BAA4B,EAC5B,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,MAAM,EACN,kBAAkB,EAClB,mBAAmB,EACnB,MAAM,EACN,cAAc,EACd,QAAQ,EACR,KAAK,EACL,MAAM,EACN,MAAM,GACP,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * A batteries-included entrypoint for composition functions.
3
+ *
4
+ * Writing a function should not require assembling a gRPC server. This module
5
+ * provides {@link serve}, which parses the standard function flags, builds a
6
+ * logger, starts the server and handles shutdown — so a function's entrypoint
7
+ * is a single call, and its author only writes composition logic.
8
+ *
9
+ * It also provides the {@link ComposeFunction} shape: a plain function handed
10
+ * a request and a response to populate, rather than a class implementing
11
+ * {@link FunctionHandler}. Both are accepted, so existing handlers keep
12
+ * working.
13
+ */
14
+ import * as grpc from '@grpc/grpc-js';
15
+ import { type Logger } from 'pino';
16
+ import { type FunctionHandler } from '../function/function.js';
17
+ import { type ServerOptions } from '../runtime/runtime.js';
18
+ import type { RunFunctionRequest, RunFunctionResponse, State } from '../proto/run_function.js';
19
+ /** The address a function listens on unless told otherwise. */
20
+ export declare const DEFAULT_ADDRESS = "0.0.0.0:9443";
21
+ /**
22
+ * Where the package reconciler mounts the TLS certificates it generates.
23
+ */
24
+ export declare const DEFAULT_TLS_SERVER_CERTS_DIR = "/tls/server";
25
+ /**
26
+ * A RunFunctionResponse whose desired state is guaranteed to be present.
27
+ *
28
+ * `RunFunctionResponse.desired` is optional in the protobuf schema, but a
29
+ * response built by {@link to} always has it, so a compose function can write
30
+ * `rsp.desired.resources[name]` without a non-null assertion.
31
+ */
32
+ export type ComposeResponse = RunFunctionResponse & {
33
+ desired: State;
34
+ };
35
+ /**
36
+ * A composition function.
37
+ *
38
+ * Receives the request along with a response already initialised from it, so
39
+ * there is no need to call {@link to}, and returns the response to send.
40
+ *
41
+ * The response is passed in as a convenience, not as an out parameter: it is
42
+ * yours to fill in and return, or to ignore in favour of one you build
43
+ * yourself. Returning it is required, so forgetting is a compile error rather
44
+ * than an empty response at runtime.
45
+ *
46
+ * Note that when the request already carries desired state — as it does for
47
+ * every function after the first in a pipeline — `rsp.desired` is the same
48
+ * object as `req.desired`, not a copy. Writing to `rsp.desired.resources`
49
+ * therefore also changes `req.desired.resources`. That is inherited from
50
+ * {@link to} and is usually harmless, since a function reads observed state
51
+ * and writes desired state, but do not rely on `req.desired` still holding
52
+ * what the previous function left once you have started writing.
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * import { Resource, type ComposeFunction } from '@crossplane-org/function-sdk-typescript';
57
+ * import { VPC } from 'crossplane-models/ec2.aws.m.upbound.io/v1beta1';
58
+ *
59
+ * export const compose: ComposeFunction = (req, rsp) => {
60
+ * const vpc = new VPC({ spec: { forProvider: { region: 'us-west-2' } } });
61
+ * vpc.validate();
62
+ * rsp.desired.resources['vpc'] = Resource.fromJSON({ resource: vpc.toJSON() });
63
+ * return rsp;
64
+ * };
65
+ * ```
66
+ */
67
+ export type ComposeFunction = (req: RunFunctionRequest, rsp: ComposeResponse, logger?: Logger) => RunFunctionResponse | Promise<RunFunctionResponse>;
68
+ /**
69
+ * Adapt a {@link ComposeFunction} to the {@link FunctionHandler} interface.
70
+ *
71
+ * Builds the response from the request, hands it to the compose function, and
72
+ * returns whatever that function returns.
73
+ */
74
+ export declare function fromCompose(compose: ComposeFunction): FunctionHandler;
75
+ /** Options for {@link serve}. */
76
+ export interface ServeOptions {
77
+ /**
78
+ * Program name shown in --help. Defaults to the basename of the running
79
+ * script.
80
+ */
81
+ name?: string;
82
+ /**
83
+ * Arguments to parse, without the node executable or script path.
84
+ * Defaults to process.argv.slice(2).
85
+ */
86
+ argv?: string[];
87
+ /**
88
+ * Logger to use. One is created from the parsed --debug flag if omitted.
89
+ */
90
+ logger?: Logger;
91
+ /** Overrides applied on top of the parsed flags. */
92
+ serverOptions?: Partial<ServerOptions>;
93
+ }
94
+ /** The result of {@link parseArgs}: server options, plus whether help was asked for. */
95
+ export interface ParsedArgs extends ServerOptions {
96
+ /** Whether --help or -h was given. */
97
+ help: boolean;
98
+ }
99
+ /**
100
+ * Parse the standard function command line flags.
101
+ *
102
+ * Exported so that it can be tested, and so that a function needing extra
103
+ * flags of its own can reuse the standard ones.
104
+ *
105
+ * @param argv - Arguments without the node executable or script path.
106
+ * @returns The parsed options.
107
+ * @throws If a flag is unrecognised, missing its value, or given a value it
108
+ * does not take.
109
+ */
110
+ export declare function parseArgs(argv: string[]): ParsedArgs;
111
+ /** The --help text, derived from {@link flags} and {@link descriptions}. */
112
+ export declare function helpText(name: string): string;
113
+ /**
114
+ * The message shown when the command line cannot be parsed.
115
+ *
116
+ * Kept separate from {@link serve} so that it can be tested without spawning a
117
+ * process, since serve's own handling ends in process.exit.
118
+ */
119
+ export declare function usageErrorText(name: string, error: unknown): string;
120
+ /**
121
+ * Run a composition function as a gRPC server.
122
+ *
123
+ * Parses the standard flags, creates a logger, starts the server, and shuts it
124
+ * down cleanly on SIGINT and SIGTERM. This is everything a function's
125
+ * entrypoint needs to do.
126
+ *
127
+ * @param fn - A {@link ComposeFunction}, or a {@link FunctionHandler} for
128
+ * functions that need the full interface.
129
+ * @param opts - Overrides, mostly useful in tests.
130
+ * @returns The running gRPC server.
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * #!/usr/bin/env node
135
+ * import { serve } from '@crossplane-org/function-sdk-typescript';
136
+ * import { compose } from './function.js';
137
+ *
138
+ * serve(compose);
139
+ * ```
140
+ */
141
+ export declare function serve(fn: ComposeFunction | FunctionHandler, opts?: ServeOptions): grpc.Server;
142
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/serve/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAGtC,OAAO,EAAQ,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAA8B,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEvF,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAE/F,+DAA+D;AAC/D,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,4BAA4B,gBAAgB,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,mBAAmB,GAAG;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,CAAC;AAcvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,MAAM,eAAe,GAAG,CAC5B,GAAG,EAAE,kBAAkB,EACvB,GAAG,EAAE,eAAe,EACpB,MAAM,CAAC,EAAE,MAAM,KACZ,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,eAAe,CAQrE;AAED,iCAAiC;AACjC,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,oDAAoD;IACpD,aAAa,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACxC;AA6BD,wFAAwF;AACxF,MAAM,WAAW,UAAW,SAAQ,aAAa;IAC/C,sCAAsC;IACtC,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CASpD;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc7C;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAGnE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,eAAe,GAAG,eAAe,EAAE,IAAI,GAAE,YAAiB,GAAG,IAAI,CAAC,MAAM,CAqDjG"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * A batteries-included entrypoint for composition functions.
3
+ *
4
+ * Writing a function should not require assembling a gRPC server. This module
5
+ * provides {@link serve}, which parses the standard function flags, builds a
6
+ * logger, starts the server and handles shutdown — so a function's entrypoint
7
+ * is a single call, and its author only writes composition logic.
8
+ *
9
+ * It also provides the {@link ComposeFunction} shape: a plain function handed
10
+ * a request and a response to populate, rather than a class implementing
11
+ * {@link FunctionHandler}. Both are accepted, so existing handlers keep
12
+ * working.
13
+ */
14
+ import * as grpc from '@grpc/grpc-js';
15
+ import { basename } from 'node:path';
16
+ import { parseArgs as parseNodeArgs } from 'node:util';
17
+ import { pino } from 'pino';
18
+ import { FunctionRunner } from '../function/function.js';
19
+ import { newGrpcServer, startServer } from '../runtime/runtime.js';
20
+ import { to } from '../response/response.js';
21
+ /** The address a function listens on unless told otherwise. */
22
+ export const DEFAULT_ADDRESS = '0.0.0.0:9443';
23
+ /**
24
+ * Where the package reconciler mounts the TLS certificates it generates.
25
+ */
26
+ export const DEFAULT_TLS_SERVER_CERTS_DIR = '/tls/server';
27
+ /**
28
+ * The program name to show in --help when the caller does not supply one.
29
+ *
30
+ * Taken from the script being run, the way most command line tools do it, so
31
+ * the help of a function started as `node dist/main.js` says `main.js` rather
32
+ * than something generic.
33
+ */
34
+ function defaultName() {
35
+ const script = process.argv[1];
36
+ return script ? basename(script) : 'function';
37
+ }
38
+ /**
39
+ * Adapt a {@link ComposeFunction} to the {@link FunctionHandler} interface.
40
+ *
41
+ * Builds the response from the request, hands it to the compose function, and
42
+ * returns whatever that function returns.
43
+ */
44
+ export function fromCompose(compose) {
45
+ return {
46
+ async RunFunction(req, logger) {
47
+ // to() always populates desired, so the cast holds.
48
+ const rsp = to(req);
49
+ return await compose(req, rsp, logger);
50
+ },
51
+ };
52
+ }
53
+ /**
54
+ * The flags every composition function accepts.
55
+ *
56
+ * This is the single source of truth: both the parser and the help text are
57
+ * derived from it, so a flag cannot be added to one and forgotten in the
58
+ * other.
59
+ */
60
+ const flags = {
61
+ address: { type: 'string', default: DEFAULT_ADDRESS },
62
+ debug: { type: 'boolean', short: 'd', default: false },
63
+ insecure: { type: 'boolean', default: false },
64
+ 'tls-server-certs-dir': { type: 'string', default: DEFAULT_TLS_SERVER_CERTS_DIR },
65
+ help: { type: 'boolean', short: 'h', default: false },
66
+ };
67
+ /**
68
+ * What each flag does. Keyed by {@link flags}, so adding a flag without
69
+ * describing it is a compile error.
70
+ */
71
+ const descriptions = {
72
+ address: `Address to listen for gRPC connections. Default ${DEFAULT_ADDRESS}.`,
73
+ debug: 'Emit debug logs.',
74
+ insecure: 'Run without mTLS credentials.',
75
+ 'tls-server-certs-dir': `Directory holding tls.key, tls.crt and ca.crt. Default ${DEFAULT_TLS_SERVER_CERTS_DIR}.`,
76
+ help: 'Show this help.',
77
+ };
78
+ /**
79
+ * Parse the standard function command line flags.
80
+ *
81
+ * Exported so that it can be tested, and so that a function needing extra
82
+ * flags of its own can reuse the standard ones.
83
+ *
84
+ * @param argv - Arguments without the node executable or script path.
85
+ * @returns The parsed options.
86
+ * @throws If a flag is unrecognised, missing its value, or given a value it
87
+ * does not take.
88
+ */
89
+ export function parseArgs(argv) {
90
+ const { values } = parseNodeArgs({ args: argv, options: flags, allowPositionals: false });
91
+ return {
92
+ address: values.address,
93
+ debug: values.debug,
94
+ insecure: values.insecure,
95
+ tlsServerCertsDir: values['tls-server-certs-dir'],
96
+ help: values.help,
97
+ };
98
+ }
99
+ /** The --help text, derived from {@link flags} and {@link descriptions}. */
100
+ export function helpText(name) {
101
+ const usage = Object.entries(flags).map(([flag, spec]) => {
102
+ const short = 'short' in spec ? `-${spec.short}, ` : ' ';
103
+ const value = spec.type === 'string' ? ' <value>' : '';
104
+ return ` ${short}--${flag}${value}`.padEnd(38) + descriptions[flag];
105
+ });
106
+ return [
107
+ `Usage: ${name} [flags]`,
108
+ '',
109
+ 'A Crossplane composition function.',
110
+ '',
111
+ 'Flags:',
112
+ ...usage,
113
+ ].join('\n');
114
+ }
115
+ /**
116
+ * The message shown when the command line cannot be parsed.
117
+ *
118
+ * Kept separate from {@link serve} so that it can be tested without spawning a
119
+ * process, since serve's own handling ends in process.exit.
120
+ */
121
+ export function usageErrorText(name, error) {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ return `${name}: ${message}\nTry '${name} --help' for the available flags.`;
124
+ }
125
+ /**
126
+ * Run a composition function as a gRPC server.
127
+ *
128
+ * Parses the standard flags, creates a logger, starts the server, and shuts it
129
+ * down cleanly on SIGINT and SIGTERM. This is everything a function's
130
+ * entrypoint needs to do.
131
+ *
132
+ * @param fn - A {@link ComposeFunction}, or a {@link FunctionHandler} for
133
+ * functions that need the full interface.
134
+ * @param opts - Overrides, mostly useful in tests.
135
+ * @returns The running gRPC server.
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * #!/usr/bin/env node
140
+ * import { serve } from '@crossplane-org/function-sdk-typescript';
141
+ * import { compose } from './function.js';
142
+ *
143
+ * serve(compose);
144
+ * ```
145
+ */
146
+ export function serve(fn, opts = {}) {
147
+ const name = opts.name ?? defaultName();
148
+ const argv = opts.argv ?? process.argv.slice(2);
149
+ let args;
150
+ try {
151
+ args = parseArgs(argv);
152
+ }
153
+ catch (error) {
154
+ // node:util throws a TypeError whose message is exactly what the user
155
+ // needs to see. Without this it reaches the top level and Node prints it
156
+ // under a stack trace through its own internals, which buries it.
157
+ process.stderr.write(`${usageErrorText(name, error)}\n`);
158
+ process.exit(2);
159
+ }
160
+ const { help, ...parsed } = args;
161
+ if (help) {
162
+ process.stdout.write(`${helpText(name)}\n`);
163
+ process.exit(0);
164
+ }
165
+ const serverOptions = { ...parsed, ...opts.serverOptions };
166
+ const logger = opts.logger ??
167
+ pino({
168
+ level: serverOptions.debug ? 'debug' : 'info',
169
+ formatters: {
170
+ level: (label) => ({ severity: label.toUpperCase() }),
171
+ },
172
+ });
173
+ logger.debug({ options: serverOptions }, 'starting function');
174
+ const handler = typeof fn === 'function' ? fromCompose(fn) : fn;
175
+ const server = newGrpcServer(new FunctionRunner(handler, logger), logger);
176
+ startServer(server, serverOptions, logger);
177
+ const shutdown = (signal) => {
178
+ logger.info(`received ${signal}, shutting down`);
179
+ server.tryShutdown((err) => {
180
+ if (err) {
181
+ logger.error(err, 'error during shutdown');
182
+ process.exit(1);
183
+ }
184
+ logger.info('server shut down');
185
+ process.exit(0);
186
+ });
187
+ };
188
+ process.on('SIGINT', () => shutdown('SIGINT'));
189
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
190
+ return server;
191
+ }
192
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/serve/serve.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossplane-org/function-sdk-typescript",
3
- "version": "0.6.0",
3
+ "version": "0.7.0-20260829081314-5f2a0f216756",
4
4
  "description": "A Crossplane Function SDK for Typescript",
5
5
  "keywords": [
6
6
  "crossplane",