@temporalio/common 1.9.3 → 1.10.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.
@@ -5,12 +5,22 @@ const payload_converter_1 = require("../converter/payload-converter");
5
5
  const data_converter_1 = require("../converter/data-converter");
6
6
  const type_helpers_1 = require("../type-helpers");
7
7
  const errors_1 = require("../errors");
8
- const isValidPayloadConverter = (converter) => typeof converter === 'object' &&
9
- converter !== null &&
10
- ['toPayload', 'fromPayload'].every((method) => typeof converter[method] === 'function');
11
- const isValidFailureConverter = (converter) => typeof converter === 'object' &&
12
- converter !== null &&
13
- ['errorToFailure', 'failureToError'].every((method) => typeof converter[method] === 'function');
8
+ const isValidPayloadConverter = (converter, path) => {
9
+ const isValid = typeof converter === 'object' &&
10
+ converter !== null &&
11
+ ['toPayload', 'fromPayload'].every((method) => typeof converter[method] === 'function');
12
+ if (!isValid) {
13
+ throw new errors_1.ValueError(`payloadConverter export at ${path} must be an object with toPayload and fromPayload methods`);
14
+ }
15
+ };
16
+ const isValidFailureConverter = (converter, path) => {
17
+ const isValid = typeof converter === 'object' &&
18
+ converter !== null &&
19
+ ['errorToFailure', 'failureToError'].every((method) => typeof converter[method] === 'function');
20
+ if (!isValid) {
21
+ throw new errors_1.ValueError(`failureConverter export at ${path} must be an object with errorToFailure and failureToError methods`);
22
+ }
23
+ };
14
24
  function requireConverter(path, type, validator) {
15
25
  let module;
16
26
  try {
@@ -24,15 +34,11 @@ function requireConverter(path, type, validator) {
24
34
  }
25
35
  if ((0, type_helpers_1.isRecord)(module) && (0, type_helpers_1.hasOwnProperty)(module, type)) {
26
36
  const converter = module[type];
27
- if (validator(converter)) {
28
- return converter;
29
- }
30
- else {
31
- throw new errors_1.ValueError(`payloadConverter export at ${path} must be an object with toPayload and fromPayload methods`);
32
- }
37
+ validator(converter, path);
38
+ return converter;
33
39
  }
34
40
  else {
35
- throw new errors_1.ValueError(`Module ${path} does not have a \`payloadConverter\` named export`);
41
+ throw new errors_1.ValueError(`Module ${path} does not have a \`${type}\` named export`);
36
42
  }
37
43
  }
38
44
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"data-converter-helpers.js","sourceRoot":"","sources":["../../src/internal-non-workflow/data-converter-helpers.ts"],"names":[],"mappings":";;;AAAA,sEAA2F;AAC3F,gEAA0G;AAE1G,kDAAsE;AACtE,sCAAuC;AAEvC,MAAM,uBAAuB,GAAG,CAAC,SAAkB,EAAiC,EAAE,CACpF,OAAO,SAAS,KAAK,QAAQ;IAC7B,SAAS,KAAK,IAAI;IAClB,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAQ,SAAqC,CAAC,MAAM,CAAC,KAAK,UAAU,CAAC,CAAC;AAEvH,MAAM,uBAAuB,GAAG,CAAC,SAAkB,EAAiC,EAAE,CACpF,OAAO,SAAS,KAAK,QAAQ;IAC7B,SAAS,KAAK,IAAI;IAClB,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,KAAK,CACxC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAQ,SAAqC,CAAC,MAAM,CAAC,KAAK,UAAU,CACjF,CAAC;AAEJ,SAAS,gBAAgB,CAAI,IAAY,EAAE,IAAY,EAAE,SAAiD;IACxG,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,yDAAyD;IACnF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,IAAA,wBAAS,EAAC,KAAK,CAAC,KAAK,kBAAkB,EAAE,CAAC;YAC5C,MAAM,IAAI,mBAAU,CAAC,0CAA0C,IAAI,UAAU,IAAI,IAAI,CAAC,CAAC;QACzF,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IAED,IAAI,IAAA,uBAAQ,EAAC,MAAM,CAAC,IAAI,IAAA,6BAAc,EAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;YACzB,OAAO,SAAS,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,mBAAU,CAClB,8BAA8B,IAAI,2DAA2D,CAC9F,CAAC;QACJ,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,mBAAU,CAAC,UAAU,IAAI,oDAAoD,CAAC,CAAC;IAC3F,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,aAA6B;IAC7D,IAAI,gBAAgB,GAAqB,2CAAuB,CAAC;IACjE,IAAI,aAAa,EAAE,oBAAoB,EAAE,CAAC;QACxC,gBAAgB,GAAG,gBAAgB,CACjC,aAAa,CAAC,oBAAoB,EAClC,kBAAkB,EAClB,uBAAuB,CACxB,CAAC;IACJ,CAAC;IACD,IAAI,gBAAgB,GAAqB,wCAAuB,CAAC;IACjE,IAAI,aAAa,EAAE,oBAAoB,EAAE,CAAC;QACxC,gBAAgB,GAAG,gBAAgB,CACjC,aAAa,CAAC,oBAAoB,EAClC,kBAAkB,EAClB,uBAAuB,CACxB,CAAC;IACJ,CAAC;IACD,OAAO;QACL,gBAAgB;QAChB,gBAAgB;QAChB,aAAa,EAAE,aAAa,EAAE,aAAa,IAAI,EAAE;KAClD,CAAC;AACJ,CAAC;AAtBD,8CAsBC;AAED;;GAEG;AACH,SAAgB,qBAAqB,CACnC,aAAmD;IAEnD,OAAO,IAAA,uBAAQ,EAAC,aAAa,CAAC,IAAI,IAAA,6BAAc,EAAC,aAAa,EAAE,kBAAkB,CAAC,CAAC;AACtF,CAAC;AAJD,sDAIC"}
1
+ {"version":3,"file":"data-converter-helpers.js","sourceRoot":"","sources":["../../src/internal-non-workflow/data-converter-helpers.ts"],"names":[],"mappings":"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"}
@@ -6,5 +6,7 @@
6
6
  export * from './codec-helpers';
7
7
  export * from './codec-types';
8
8
  export * from './data-converter-helpers';
9
+ export * from './parse-host-uri';
10
+ export * from './proxy-config';
9
11
  export * from './tls-config';
10
12
  export * from './utils';
@@ -22,6 +22,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
22
22
  __exportStar(require("./codec-helpers"), exports);
23
23
  __exportStar(require("./codec-types"), exports);
24
24
  __exportStar(require("./data-converter-helpers"), exports);
25
+ __exportStar(require("./parse-host-uri"), exports);
26
+ __exportStar(require("./proxy-config"), exports);
25
27
  __exportStar(require("./tls-config"), exports);
26
28
  __exportStar(require("./utils"), exports);
27
29
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/internal-non-workflow/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;;;GAIG;AACH,kDAAgC;AAChC,gDAA8B;AAC9B,2DAAyC;AACzC,+CAA6B;AAC7B,0CAAwB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/internal-non-workflow/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;;;GAIG;AACH,kDAAgC;AAChC,gDAA8B;AAC9B,2DAAyC;AACzC,mDAAiC;AACjC,iDAA+B;AAC/B,+CAA6B;AAC7B,0CAAwB"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * This file contain helper functions to parse specific subsets of URIs.
3
+ *
4
+ * The ECMAScript-compliant URL class don't properly handle some syntaxes that
5
+ * we care about, such as not providing a protocol (e.g. '127.0.0.1:7233'), and
6
+ * performs some normalizations that are not desirable for our use cases
7
+ * (e.g. parsing 'http://127.0.0.1:7233' adds a '/' path). On the other side,
8
+ * simply using `split(':')` breaks on IPv6 addresses. Hence these helpers.
9
+ */
10
+ export interface ProtoHostPort {
11
+ scheme?: string;
12
+ hostname: string;
13
+ port?: number;
14
+ }
15
+ /**
16
+ * Split a URI composed only of a scheme, a hostname, and port.
17
+ * The scheme and port are optional.
18
+ *
19
+ * Examples of valid URIs for HTTP CONNECT proxies:
20
+ *
21
+ * ```
22
+ * http://test.com:8080 => { scheme: 'http', host: 'test.com', port: 8080 }
23
+ * http://192.168.0.1:8080 => { scheme: 'http', host: '192.168.0.1', port: 8080 }
24
+ * [::1]:8080 => { scheme: 'http', host: '::1', port: 8080 }
25
+ * [::ffff:192.0.2.128]:8080 => { scheme: 'http', host: '::ffff:192.0.2.128', port: 8080 }
26
+ * 192.168.0.1:8080 => { scheme: 'http', host: '192.168.0.1', port: 8080 }
27
+ * ```
28
+ */
29
+ export declare function splitProtoHostPort(uri: string): ProtoHostPort | undefined;
30
+ export declare function joinProtoHostPort(components: ProtoHostPort): string;
31
+ /**
32
+ * Parse the address for the gRPC endpoint of a Temporal server.
33
+ *
34
+ * - The URI may only contain a hostname and a port.
35
+ * - Port is optional; it defaults to 7233.
36
+ *
37
+ * Examples of valid URIs:
38
+ *
39
+ * ```
40
+ * 127.0.0.1:7233 => { host: '192.168.0.1', port: 7233 }
41
+ * my.temporal.service.com:7233 => { host: 'my.temporal.service.com', port: 7233 }
42
+ * [::ffff:192.0.2.128]:8080 => { host: '[::ffff:192.0.2.128]', port: 8080 }
43
+ * ```
44
+ */
45
+ export declare function normalizeTemporalGrpcEndpointAddress(uri: string): string;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ /**
3
+ * This file contain helper functions to parse specific subsets of URIs.
4
+ *
5
+ * The ECMAScript-compliant URL class don't properly handle some syntaxes that
6
+ * we care about, such as not providing a protocol (e.g. '127.0.0.1:7233'), and
7
+ * performs some normalizations that are not desirable for our use cases
8
+ * (e.g. parsing 'http://127.0.0.1:7233' adds a '/' path). On the other side,
9
+ * simply using `split(':')` breaks on IPv6 addresses. Hence these helpers.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.normalizeTemporalGrpcEndpointAddress = exports.joinProtoHostPort = exports.splitProtoHostPort = void 0;
13
+ /**
14
+ * Scheme. Requires but doesn't capture the ':' or '://' separator that follows.
15
+ * e.g. `http:` will be captured as 'http'.
16
+ */
17
+ const scheme = '(?:(?<scheme>[a-z][a-z0-9]+):(?:\\/\\/)?)';
18
+ /**
19
+ * IPv4-style hostname. Not captured.
20
+ * e.g.: `192.168.1.100`.
21
+ */
22
+ const ipv4Hostname = '(?:\\d{1,3}(?:\\.\\d{1,3}){3})';
23
+ /**
24
+ * IPv6-style hostname; must be enclosed in square brackets. Not captured.
25
+ * e.g.: `[::1]`, `[2001:db8::1]`, `[::FFFF:129.144.52.38]`, etc.
26
+ */
27
+ const ipv6Hostname = '(?:\\[(?<ipv6>[0-9a-fA-F.:]+)\\])';
28
+ // DNS-style hostname. Not captured.
29
+ // e.g.: `test.com` or `localhost`.
30
+ const dnsHostname = '(?:[^:/]+)';
31
+ const hostname = `(?:${ipv4Hostname}|${ipv6Hostname}|${dnsHostname})`;
32
+ // Port number. Requires but don't capture a preceeding ':' separator.
33
+ // For example, `:7233` will be captured as `7233`.
34
+ const port = '(?::(?<port>\\d+))';
35
+ const protoHostPortRegex = new RegExp(`^${scheme}??(?<hostname>${hostname})${port}?$`);
36
+ /**
37
+ * Split a URI composed only of a scheme, a hostname, and port.
38
+ * The scheme and port are optional.
39
+ *
40
+ * Examples of valid URIs for HTTP CONNECT proxies:
41
+ *
42
+ * ```
43
+ * http://test.com:8080 => { scheme: 'http', host: 'test.com', port: 8080 }
44
+ * http://192.168.0.1:8080 => { scheme: 'http', host: '192.168.0.1', port: 8080 }
45
+ * [::1]:8080 => { scheme: 'http', host: '::1', port: 8080 }
46
+ * [::ffff:192.0.2.128]:8080 => { scheme: 'http', host: '::ffff:192.0.2.128', port: 8080 }
47
+ * 192.168.0.1:8080 => { scheme: 'http', host: '192.168.0.1', port: 8080 }
48
+ * ```
49
+ */
50
+ function splitProtoHostPort(uri) {
51
+ const match = protoHostPortRegex.exec(uri);
52
+ if (!match?.groups)
53
+ return undefined;
54
+ return {
55
+ scheme: match.groups.scheme,
56
+ hostname: match.groups.ipv6 ?? match.groups.hostname,
57
+ port: match.groups.port !== undefined ? Number(match.groups.port) : undefined,
58
+ };
59
+ }
60
+ exports.splitProtoHostPort = splitProtoHostPort;
61
+ function joinProtoHostPort(components) {
62
+ const { scheme, hostname, port } = components;
63
+ const schemeText = scheme ? `${scheme}:` : '';
64
+ const hostnameText = hostname.includes(':') ? `[${hostname}]` : hostname;
65
+ const portText = port !== undefined ? `:${port}` : '';
66
+ return `${schemeText}${hostnameText}${portText}`;
67
+ }
68
+ exports.joinProtoHostPort = joinProtoHostPort;
69
+ /**
70
+ * Parse the address for the gRPC endpoint of a Temporal server.
71
+ *
72
+ * - The URI may only contain a hostname and a port.
73
+ * - Port is optional; it defaults to 7233.
74
+ *
75
+ * Examples of valid URIs:
76
+ *
77
+ * ```
78
+ * 127.0.0.1:7233 => { host: '192.168.0.1', port: 7233 }
79
+ * my.temporal.service.com:7233 => { host: 'my.temporal.service.com', port: 7233 }
80
+ * [::ffff:192.0.2.128]:8080 => { host: '[::ffff:192.0.2.128]', port: 8080 }
81
+ * ```
82
+ */
83
+ function normalizeTemporalGrpcEndpointAddress(uri) {
84
+ const splitted = splitProtoHostPort(uri);
85
+ if (!splitted || splitted.scheme !== undefined) {
86
+ throw new TypeError(`Invalid address for Temporal gRPC endpoint: expected URI of the form 'hostname' or 'hostname:port'; got '${uri}'`);
87
+ }
88
+ splitted.port ??= 7233;
89
+ return joinProtoHostPort(splitted);
90
+ }
91
+ exports.normalizeTemporalGrpcEndpointAddress = normalizeTemporalGrpcEndpointAddress;
92
+ //# sourceMappingURL=parse-host-uri.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-host-uri.js","sourceRoot":"","sources":["../../src/internal-non-workflow/parse-host-uri.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH;;;GAGG;AACH,MAAM,MAAM,GAAG,2CAA2C,CAAC;AAE3D;;;GAGG;AACH,MAAM,YAAY,GAAG,gCAAgC,CAAC;AAEtD;;;GAGG;AACH,MAAM,YAAY,GAAG,mCAAmC,CAAC;AAEzD,oCAAoC;AACpC,mCAAmC;AACnC,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC,MAAM,QAAQ,GAAG,MAAM,YAAY,IAAI,YAAY,IAAI,WAAW,GAAG,CAAC;AAEtE,sEAAsE;AACtE,mDAAmD;AACnD,MAAM,IAAI,GAAG,oBAAoB,CAAC;AAElC,MAAM,kBAAkB,GAAG,IAAI,MAAM,CAAC,IAAI,MAAM,iBAAiB,QAAQ,IAAI,IAAI,IAAI,CAAC,CAAC;AAQvF;;;;;;;;;;;;;GAaG;AACH,SAAgB,kBAAkB,CAAC,GAAW;IAC5C,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,EAAE,MAAM;QAAE,OAAO,SAAS,CAAC;IACrC,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC3B,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ;QACpD,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAC9E,CAAC;AACJ,CAAC;AARD,gDAQC;AAED,SAAgB,iBAAiB,CAAC,UAAyB;IACzD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC;IAC9C,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;IACzE,MAAM,QAAQ,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,OAAO,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,EAAE,CAAC;AACnD,CAAC;AAND,8CAMC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAgB,oCAAoC,CAAC,GAAW;IAC9D,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/C,MAAM,IAAI,SAAS,CACjB,4GAA4G,GAAG,GAAG,CACnH,CAAC;IACJ,CAAC;IACD,QAAQ,CAAC,IAAI,KAAK,IAAI,CAAC;IACvB,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AACrC,CAAC;AATD,oFASC"}
@@ -0,0 +1,43 @@
1
+ import { ProtoHostPort } from './parse-host-uri';
2
+ /**
3
+ * Configuration for HTTP CONNECT proxying.
4
+ */
5
+ export interface HttpConnectProxyConfig {
6
+ type: 'http-connect';
7
+ /**
8
+ * Address of the HTTP CONNECT proxy server, in either `hostname:port` or `http://hostname:port` formats.
9
+ *
10
+ * Port is required, and only the `http` scheme is supported. Raw IPv6 addresses must be wrapped in square brackets
11
+ * (e.g. `[ipv6]:port`).
12
+ */
13
+ targetHost: string;
14
+ /**
15
+ * Basic auth for the HTTP CONNECT proxy, if any.
16
+ *
17
+ * Neither username nor password may contain `:` or `@`.
18
+ *
19
+ * Note that these credentials will be exposed through environment variables, and will be exchanged in non-encrypted
20
+ * form ovrer the network. The connection to the proxy server is not encrypted.
21
+ */
22
+ basicAuth?: {
23
+ username: string;
24
+ password: string;
25
+ };
26
+ }
27
+ export type ProxyConfig = HttpConnectProxyConfig;
28
+ /**
29
+ * Parse the address of a HTTP CONNECT proxy endpoint.
30
+ *
31
+ * - The URI may only contain a scheme, a hostname, and a port;
32
+ * - If specified, scheme must be 'http';
33
+ * - Port is required.
34
+ *
35
+ * Examples of valid URIs:
36
+ *
37
+ * ```
38
+ * 127.0.0.1:8080 => { scheme: 'http', host: '192.168.0.1', port: 8080 }
39
+ * my.temporal.service.com:8888 => { scheme: 'http', host: 'my.temporal.service.com', port: 8888 }
40
+ * [::ffff:192.0.2.128]:8080 => { scheme: 'http', host: '::ffff:192.0.2.128', port: 8080 }
41
+ * ```
42
+ */
43
+ export declare function parseHttpConnectProxyAddress(target: string): ProtoHostPort;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseHttpConnectProxyAddress = void 0;
4
+ const parse_host_uri_1 = require("./parse-host-uri");
5
+ /**
6
+ * Parse the address of a HTTP CONNECT proxy endpoint.
7
+ *
8
+ * - The URI may only contain a scheme, a hostname, and a port;
9
+ * - If specified, scheme must be 'http';
10
+ * - Port is required.
11
+ *
12
+ * Examples of valid URIs:
13
+ *
14
+ * ```
15
+ * 127.0.0.1:8080 => { scheme: 'http', host: '192.168.0.1', port: 8080 }
16
+ * my.temporal.service.com:8888 => { scheme: 'http', host: 'my.temporal.service.com', port: 8888 }
17
+ * [::ffff:192.0.2.128]:8080 => { scheme: 'http', host: '::ffff:192.0.2.128', port: 8080 }
18
+ * ```
19
+ */
20
+ function parseHttpConnectProxyAddress(target) {
21
+ const match = (0, parse_host_uri_1.splitProtoHostPort)(target);
22
+ if (!match)
23
+ throw new TypeError(`Invalid address for HTTP CONNECT proxy: expected 'hostname:port' or '[ipv6 address]:port'; got '${target}'`);
24
+ const { scheme = 'http', hostname: host, port } = match;
25
+ if (scheme !== 'http')
26
+ throw new TypeError(`Invalid address for HTTP CONNECT proxy: scheme must be http'; got '${target}'`);
27
+ if (port === undefined)
28
+ throw new TypeError(`Invalid address for HTTP CONNECT proxy: port is required; got '${target}'`);
29
+ return { scheme, hostname: host, port };
30
+ }
31
+ exports.parseHttpConnectProxyAddress = parseHttpConnectProxyAddress;
32
+ //# sourceMappingURL=proxy-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-config.js","sourceRoot":"","sources":["../../src/internal-non-workflow/proxy-config.ts"],"names":[],"mappings":";;;AAAA,qDAAqE;AAgCrE;;;;;;;;;;;;;;GAcG;AACH,SAAgB,4BAA4B,CAAC,MAAc;IACzD,MAAM,KAAK,GAAG,IAAA,mCAAkB,EAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK;QACR,MAAM,IAAI,SAAS,CACjB,mGAAmG,MAAM,GAAG,CAC7G,CAAC;IACJ,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACxD,IAAI,MAAM,KAAK,MAAM;QACnB,MAAM,IAAI,SAAS,CAAC,sEAAsE,MAAM,GAAG,CAAC,CAAC;IACvG,IAAI,IAAI,KAAK,SAAS;QACpB,MAAM,IAAI,SAAS,CAAC,kEAAkE,MAAM,GAAG,CAAC,CAAC;IACnG,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC1C,CAAC;AAZD,oEAYC"}
package/lib/logger.d.ts CHANGED
@@ -11,3 +11,39 @@ export interface Logger {
11
11
  warn(message: string, meta?: LogMetadata): any;
12
12
  error(message: string, meta?: LogMetadata): any;
13
13
  }
14
+ /**
15
+ * Possible values of the `sdkComponent` meta attributes on log messages. This
16
+ * attribute indicates which subsystem emitted the log message; this may for
17
+ * example be used to implement fine-grained filtering of log messages.
18
+ *
19
+ * Note that there is no guarantee that this list will remain stable in the
20
+ * future; values may be added or removed, and messages that are currently
21
+ * emitted with some `sdkComponent` value may use a different value in the future.
22
+ */
23
+ export declare enum SdkComponent {
24
+ /**
25
+ * Component name for messages emited from Workflow code, using the {@link Workflow context logger|workflow.log}.
26
+ * The SDK itself never publishes messages with this component name.
27
+ */
28
+ workflow = "workflow",
29
+ /**
30
+ * Component name for messages emited from an activity, using the {@link activity context logger|Context.log}.
31
+ * The SDK itself never publishes messages with this component name.
32
+ */
33
+ activity = "activity",
34
+ /**
35
+ * Component name for messages emited from a Temporal Worker instance.
36
+ *
37
+ * This notably includes:
38
+ * - Issues with Worker or runtime configuration, or the JS execution environment;
39
+ * - Worker's, Activity's, and Workflow's lifecycle events;
40
+ * - Workflow Activation and Activity Task processing events;
41
+ * - Workflow bundling messages;
42
+ * - Sink processing issues.
43
+ */
44
+ worker = "worker",
45
+ /**
46
+ * Component name for all messages emitted by the Rust Core SDK library.
47
+ */
48
+ core = "core"
49
+ }
package/lib/logger.js CHANGED
@@ -1,3 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SdkComponent = void 0;
4
+ /**
5
+ * Possible values of the `sdkComponent` meta attributes on log messages. This
6
+ * attribute indicates which subsystem emitted the log message; this may for
7
+ * example be used to implement fine-grained filtering of log messages.
8
+ *
9
+ * Note that there is no guarantee that this list will remain stable in the
10
+ * future; values may be added or removed, and messages that are currently
11
+ * emitted with some `sdkComponent` value may use a different value in the future.
12
+ */
13
+ var SdkComponent;
14
+ (function (SdkComponent) {
15
+ /**
16
+ * Component name for messages emited from Workflow code, using the {@link Workflow context logger|workflow.log}.
17
+ * The SDK itself never publishes messages with this component name.
18
+ */
19
+ SdkComponent["workflow"] = "workflow";
20
+ /**
21
+ * Component name for messages emited from an activity, using the {@link activity context logger|Context.log}.
22
+ * The SDK itself never publishes messages with this component name.
23
+ */
24
+ SdkComponent["activity"] = "activity";
25
+ /**
26
+ * Component name for messages emited from a Temporal Worker instance.
27
+ *
28
+ * This notably includes:
29
+ * - Issues with Worker or runtime configuration, or the JS execution environment;
30
+ * - Worker's, Activity's, and Workflow's lifecycle events;
31
+ * - Workflow Activation and Activity Task processing events;
32
+ * - Workflow bundling messages;
33
+ * - Sink processing issues.
34
+ */
35
+ SdkComponent["worker"] = "worker";
36
+ /**
37
+ * Component name for all messages emitted by the Rust Core SDK library.
38
+ */
39
+ SdkComponent["core"] = "core";
40
+ })(SdkComponent || (exports.SdkComponent = SdkComponent = {}));
3
41
  //# sourceMappingURL=logger.js.map
package/lib/logger.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":";;;AAgBA;;;;;;;;GAQG;AACH,IAAY,YA6BX;AA7BD,WAAY,YAAY;IACtB;;;OAGG;IACH,qCAAqB,CAAA;IAErB;;;OAGG;IACH,qCAAqB,CAAA;IAErB;;;;;;;;;OASG;IACH,iCAAiB,CAAA;IAEjB;;OAEG;IACH,6BAAa,CAAA;AACf,CAAC,EA7BW,YAAY,4BAAZ,YAAY,QA6BvB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temporalio/common",
3
- "version": "1.9.3",
3
+ "version": "1.10.0",
4
4
  "description": "Common library for code that's used across the Client, Worker, and/or Workflow",
5
5
  "main": "lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  "author": "Temporal Technologies Inc. <sdk@temporal.io>",
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
- "@temporalio/proto": "1.9.3",
15
+ "@temporalio/proto": "1.10.0",
16
16
  "long": "^5.2.3",
17
17
  "ms": "^3.0.0-canary.1",
18
18
  "proto3-json-serializer": "^2.0.0"
@@ -36,5 +36,5 @@
36
36
  "src",
37
37
  "lib"
38
38
  ],
39
- "gitHead": "e7e46639c1ba23b86f367a051fb54d736c5f21ce"
39
+ "gitHead": "39d702af7ae5d7e33ee90651292aa77fa07d33ca"
40
40
  }
@@ -4,19 +4,37 @@ import { FailureConverter } from '../converter/failure-converter';
4
4
  import { errorCode, hasOwnProperty, isRecord } from '../type-helpers';
5
5
  import { ValueError } from '../errors';
6
6
 
7
- const isValidPayloadConverter = (converter: unknown): converter is PayloadConverter =>
8
- typeof converter === 'object' &&
9
- converter !== null &&
10
- ['toPayload', 'fromPayload'].every((method) => typeof (converter as Record<string, unknown>)[method] === 'function');
7
+ const isValidPayloadConverter = (converter: unknown, path: string): asserts converter is PayloadConverter => {
8
+ const isValid =
9
+ typeof converter === 'object' &&
10
+ converter !== null &&
11
+ ['toPayload', 'fromPayload'].every(
12
+ (method) => typeof (converter as Record<string, unknown>)[method] === 'function'
13
+ );
14
+ if (!isValid) {
15
+ throw new ValueError(`payloadConverter export at ${path} must be an object with toPayload and fromPayload methods`);
16
+ }
17
+ };
11
18
 
12
- const isValidFailureConverter = (converter: unknown): converter is FailureConverter =>
13
- typeof converter === 'object' &&
14
- converter !== null &&
15
- ['errorToFailure', 'failureToError'].every(
16
- (method) => typeof (converter as Record<string, unknown>)[method] === 'function'
17
- );
19
+ const isValidFailureConverter = (converter: unknown, path: string): asserts converter is FailureConverter => {
20
+ const isValid =
21
+ typeof converter === 'object' &&
22
+ converter !== null &&
23
+ ['errorToFailure', 'failureToError'].every(
24
+ (method) => typeof (converter as Record<string, unknown>)[method] === 'function'
25
+ );
26
+ if (!isValid) {
27
+ throw new ValueError(
28
+ `failureConverter export at ${path} must be an object with errorToFailure and failureToError methods`
29
+ );
30
+ }
31
+ };
18
32
 
19
- function requireConverter<T>(path: string, type: string, validator: (converter: unknown) => converter is T): T {
33
+ function requireConverter<T>(
34
+ path: string,
35
+ type: string,
36
+ validator: (converter: unknown, path: string) => asserts converter is T
37
+ ): T {
20
38
  let module;
21
39
  try {
22
40
  module = require(path); // eslint-disable-line @typescript-eslint/no-var-requires
@@ -29,15 +47,10 @@ function requireConverter<T>(path: string, type: string, validator: (converter:
29
47
 
30
48
  if (isRecord(module) && hasOwnProperty(module, type)) {
31
49
  const converter = module[type];
32
- if (validator(converter)) {
33
- return converter;
34
- } else {
35
- throw new ValueError(
36
- `payloadConverter export at ${path} must be an object with toPayload and fromPayload methods`
37
- );
38
- }
50
+ validator(converter, path);
51
+ return converter;
39
52
  } else {
40
- throw new ValueError(`Module ${path} does not have a \`payloadConverter\` named export`);
53
+ throw new ValueError(`Module ${path} does not have a \`${type}\` named export`);
41
54
  }
42
55
  }
43
56
 
@@ -6,5 +6,7 @@
6
6
  export * from './codec-helpers';
7
7
  export * from './codec-types';
8
8
  export * from './data-converter-helpers';
9
+ export * from './parse-host-uri';
10
+ export * from './proxy-config';
9
11
  export * from './tls-config';
10
12
  export * from './utils';
@@ -0,0 +1,102 @@
1
+ /**
2
+ * This file contain helper functions to parse specific subsets of URIs.
3
+ *
4
+ * The ECMAScript-compliant URL class don't properly handle some syntaxes that
5
+ * we care about, such as not providing a protocol (e.g. '127.0.0.1:7233'), and
6
+ * performs some normalizations that are not desirable for our use cases
7
+ * (e.g. parsing 'http://127.0.0.1:7233' adds a '/' path). On the other side,
8
+ * simply using `split(':')` breaks on IPv6 addresses. Hence these helpers.
9
+ */
10
+
11
+ /**
12
+ * Scheme. Requires but doesn't capture the ':' or '://' separator that follows.
13
+ * e.g. `http:` will be captured as 'http'.
14
+ */
15
+ const scheme = '(?:(?<scheme>[a-z][a-z0-9]+):(?:\\/\\/)?)';
16
+
17
+ /**
18
+ * IPv4-style hostname. Not captured.
19
+ * e.g.: `192.168.1.100`.
20
+ */
21
+ const ipv4Hostname = '(?:\\d{1,3}(?:\\.\\d{1,3}){3})';
22
+
23
+ /**
24
+ * IPv6-style hostname; must be enclosed in square brackets. Not captured.
25
+ * e.g.: `[::1]`, `[2001:db8::1]`, `[::FFFF:129.144.52.38]`, etc.
26
+ */
27
+ const ipv6Hostname = '(?:\\[(?<ipv6>[0-9a-fA-F.:]+)\\])';
28
+
29
+ // DNS-style hostname. Not captured.
30
+ // e.g.: `test.com` or `localhost`.
31
+ const dnsHostname = '(?:[^:/]+)';
32
+
33
+ const hostname = `(?:${ipv4Hostname}|${ipv6Hostname}|${dnsHostname})`;
34
+
35
+ // Port number. Requires but don't capture a preceeding ':' separator.
36
+ // For example, `:7233` will be captured as `7233`.
37
+ const port = '(?::(?<port>\\d+))';
38
+
39
+ const protoHostPortRegex = new RegExp(`^${scheme}??(?<hostname>${hostname})${port}?$`);
40
+
41
+ export interface ProtoHostPort {
42
+ scheme?: string;
43
+ hostname: string;
44
+ port?: number;
45
+ }
46
+
47
+ /**
48
+ * Split a URI composed only of a scheme, a hostname, and port.
49
+ * The scheme and port are optional.
50
+ *
51
+ * Examples of valid URIs for HTTP CONNECT proxies:
52
+ *
53
+ * ```
54
+ * http://test.com:8080 => { scheme: 'http', host: 'test.com', port: 8080 }
55
+ * http://192.168.0.1:8080 => { scheme: 'http', host: '192.168.0.1', port: 8080 }
56
+ * [::1]:8080 => { scheme: 'http', host: '::1', port: 8080 }
57
+ * [::ffff:192.0.2.128]:8080 => { scheme: 'http', host: '::ffff:192.0.2.128', port: 8080 }
58
+ * 192.168.0.1:8080 => { scheme: 'http', host: '192.168.0.1', port: 8080 }
59
+ * ```
60
+ */
61
+ export function splitProtoHostPort(uri: string): ProtoHostPort | undefined {
62
+ const match = protoHostPortRegex.exec(uri);
63
+ if (!match?.groups) return undefined;
64
+ return {
65
+ scheme: match.groups.scheme,
66
+ hostname: match.groups.ipv6 ?? match.groups.hostname,
67
+ port: match.groups.port !== undefined ? Number(match.groups.port) : undefined,
68
+ };
69
+ }
70
+
71
+ export function joinProtoHostPort(components: ProtoHostPort): string {
72
+ const { scheme, hostname, port } = components;
73
+ const schemeText = scheme ? `${scheme}:` : '';
74
+ const hostnameText = hostname.includes(':') ? `[${hostname}]` : hostname;
75
+ const portText = port !== undefined ? `:${port}` : '';
76
+ return `${schemeText}${hostnameText}${portText}`;
77
+ }
78
+
79
+ /**
80
+ * Parse the address for the gRPC endpoint of a Temporal server.
81
+ *
82
+ * - The URI may only contain a hostname and a port.
83
+ * - Port is optional; it defaults to 7233.
84
+ *
85
+ * Examples of valid URIs:
86
+ *
87
+ * ```
88
+ * 127.0.0.1:7233 => { host: '192.168.0.1', port: 7233 }
89
+ * my.temporal.service.com:7233 => { host: 'my.temporal.service.com', port: 7233 }
90
+ * [::ffff:192.0.2.128]:8080 => { host: '[::ffff:192.0.2.128]', port: 8080 }
91
+ * ```
92
+ */
93
+ export function normalizeTemporalGrpcEndpointAddress(uri: string): string {
94
+ const splitted = splitProtoHostPort(uri);
95
+ if (!splitted || splitted.scheme !== undefined) {
96
+ throw new TypeError(
97
+ `Invalid address for Temporal gRPC endpoint: expected URI of the form 'hostname' or 'hostname:port'; got '${uri}'`
98
+ );
99
+ }
100
+ splitted.port ??= 7233;
101
+ return joinProtoHostPort(splitted);
102
+ }
@@ -0,0 +1,60 @@
1
+ import { ProtoHostPort, splitProtoHostPort } from './parse-host-uri';
2
+
3
+ /**
4
+ * Configuration for HTTP CONNECT proxying.
5
+ */
6
+ export interface HttpConnectProxyConfig {
7
+ type: 'http-connect';
8
+
9
+ /**
10
+ * Address of the HTTP CONNECT proxy server, in either `hostname:port` or `http://hostname:port` formats.
11
+ *
12
+ * Port is required, and only the `http` scheme is supported. Raw IPv6 addresses must be wrapped in square brackets
13
+ * (e.g. `[ipv6]:port`).
14
+ */
15
+ targetHost: string;
16
+
17
+ /**
18
+ * Basic auth for the HTTP CONNECT proxy, if any.
19
+ *
20
+ * Neither username nor password may contain `:` or `@`.
21
+ *
22
+ * Note that these credentials will be exposed through environment variables, and will be exchanged in non-encrypted
23
+ * form ovrer the network. The connection to the proxy server is not encrypted.
24
+ */
25
+ basicAuth?: {
26
+ username: string;
27
+ password: string;
28
+ };
29
+ }
30
+
31
+ export type ProxyConfig = HttpConnectProxyConfig;
32
+
33
+ /**
34
+ * Parse the address of a HTTP CONNECT proxy endpoint.
35
+ *
36
+ * - The URI may only contain a scheme, a hostname, and a port;
37
+ * - If specified, scheme must be 'http';
38
+ * - Port is required.
39
+ *
40
+ * Examples of valid URIs:
41
+ *
42
+ * ```
43
+ * 127.0.0.1:8080 => { scheme: 'http', host: '192.168.0.1', port: 8080 }
44
+ * my.temporal.service.com:8888 => { scheme: 'http', host: 'my.temporal.service.com', port: 8888 }
45
+ * [::ffff:192.0.2.128]:8080 => { scheme: 'http', host: '::ffff:192.0.2.128', port: 8080 }
46
+ * ```
47
+ */
48
+ export function parseHttpConnectProxyAddress(target: string): ProtoHostPort {
49
+ const match = splitProtoHostPort(target);
50
+ if (!match)
51
+ throw new TypeError(
52
+ `Invalid address for HTTP CONNECT proxy: expected 'hostname:port' or '[ipv6 address]:port'; got '${target}'`
53
+ );
54
+ const { scheme = 'http', hostname: host, port } = match;
55
+ if (scheme !== 'http')
56
+ throw new TypeError(`Invalid address for HTTP CONNECT proxy: scheme must be http'; got '${target}'`);
57
+ if (port === undefined)
58
+ throw new TypeError(`Invalid address for HTTP CONNECT proxy: port is required; got '${target}'`);
59
+ return { scheme, hostname: host, port };
60
+ }
package/src/logger.ts CHANGED
@@ -13,3 +13,43 @@ export interface Logger {
13
13
  warn(message: string, meta?: LogMetadata): any;
14
14
  error(message: string, meta?: LogMetadata): any;
15
15
  }
16
+
17
+ /**
18
+ * Possible values of the `sdkComponent` meta attributes on log messages. This
19
+ * attribute indicates which subsystem emitted the log message; this may for
20
+ * example be used to implement fine-grained filtering of log messages.
21
+ *
22
+ * Note that there is no guarantee that this list will remain stable in the
23
+ * future; values may be added or removed, and messages that are currently
24
+ * emitted with some `sdkComponent` value may use a different value in the future.
25
+ */
26
+ export enum SdkComponent {
27
+ /**
28
+ * Component name for messages emited from Workflow code, using the {@link Workflow context logger|workflow.log}.
29
+ * The SDK itself never publishes messages with this component name.
30
+ */
31
+ workflow = 'workflow',
32
+
33
+ /**
34
+ * Component name for messages emited from an activity, using the {@link activity context logger|Context.log}.
35
+ * The SDK itself never publishes messages with this component name.
36
+ */
37
+ activity = 'activity',
38
+
39
+ /**
40
+ * Component name for messages emited from a Temporal Worker instance.
41
+ *
42
+ * This notably includes:
43
+ * - Issues with Worker or runtime configuration, or the JS execution environment;
44
+ * - Worker's, Activity's, and Workflow's lifecycle events;
45
+ * - Workflow Activation and Activity Task processing events;
46
+ * - Workflow bundling messages;
47
+ * - Sink processing issues.
48
+ */
49
+ worker = 'worker',
50
+
51
+ /**
52
+ * Component name for all messages emitted by the Rust Core SDK library.
53
+ */
54
+ core = 'core',
55
+ }