@webpieces/core-util 0.3.380 → 0.3.382

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/core-util",
3
- "version": "0.3.380",
3
+ "version": "0.3.382",
4
4
  "description": "Utility functions for WebPieces - works in browser and Node.js",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -1,44 +1,113 @@
1
+ import { ErrorTranslation, ErrorWireForm } from './ErrorTranslation';
2
+ import { ProtocolError } from './errors';
1
3
  /**
2
- * ClientRegistry - the process-global `svcName -> base URL` table of per-environment URL overrides
3
- * used to route outbound client calls.
4
+ * Derives a base URL for a service name that has NO registered mapping — the pluggable half of
5
+ * {@link ClientRegistry} resolution. One per environment, installed once at startup:
4
6
  *
5
- * On GCP a client resolves a peer's base URL deterministically from `K_SERVICE` + project + region
6
- * (see gcp-identity `resolveServiceUrl`), so same-project/same-region services need NO registration.
7
- * Everything the derivation cannot describe a localhost port, another region, another project, a
8
- * host that is not Cloud Run at all, or a browser that cannot read `K_SERVICE` is registered here
9
- * instead. Each environment populates the registry (from its own per-env config) with the URLs it
10
- * needs; a registered mapping WINS over GCP derivation.
7
+ * - `gcpCloudRunDeriver()` (@webpieces/gcp-identity) the Cloud Run formula, read from the metadata
8
+ * server. For code running ON GCP.
9
+ * - `gcpCloudRunDeriver(new GcpCloudRunTarget(projectNumber, region))`the SAME formula with the
10
+ * values supplied from config, for code running OFF GCP that still calls Cloud Run (a CLI, CI).
11
+ * - {@link templateDeriver} (this package, browser-safe) pure string substitution, for AWS or
12
+ * anything else with predictable DNS.
13
+ * - none at all — a browser goes same-origin, and localhost/tests hand-register their mappings.
14
+ */
15
+ export type ServiceUrlDeriver = (svcName: string) => Promise<string>;
16
+ /**
17
+ * ClientRegistry - the ONE place a `svcName` becomes a base URL, for every outbound client in every
18
+ * environment. A client is built once but a URL is per-environment, so the URL belongs here rather
19
+ * than on the client: clients carry ONLY a svcName.
20
+ *
21
+ * Resolution is one precedence chain, identical in the browser, in node, and in Cloud Tasks:
11
22
  *
12
- * - **http-client-node / cloudtasks-client** resolve through gcp-identity `resolveServiceUrl`: it
13
- * checks {@link ClientRegistry.tryLookup} first (override), then derives on GCP, then throws
14
- * off-GCP if unregistered a missing mapping is a setup bug, not a silent mis-route.
15
- * - **http-client-browser** cannot derive GCP URLs, so it resolves purely via
16
- * {@link ClientRegistry.lookup}; the Angular app registers each svcName at startup.
23
+ * 1. a registered mapping wins the localhost port table, AWS, an external API, another region
24
+ * or project, a host that is not Cloud Run at all. Populated at startup from per-env config.
25
+ * 2. else the installed {@link ServiceUrlDeriver}, if any GCP's built-in, a DNS template, or
26
+ * your own. OPTIONAL on purpose: localhost is inherently a TABLE (helper-fsdb -> :8401,
27
+ * helper-portal -> :8201 have per-service ports), so explicit mappings must stay sufficient on
28
+ * their own.
29
+ * 3. else the caller's fallback: the BROWSER goes relative (same origin — see
30
+ * {@link ClientRegistry.tryResolve}), while node THROWS (see {@link ClientRegistry.resolve}),
31
+ * because a server has no "own origin" and an unresolvable peer is a setup bug.
17
32
  *
18
33
  * Configured like {@link HeaderRegistry} / LogManager — populated once at startup, then globally
19
34
  * accessible with NO DI wiring. It is browser-safe (no `process.env`, no node-only deps), which is
20
35
  * why it lives in core-util rather than gcp-identity.
21
36
  *
22
37
  * ```ts
23
- * // startup, populated from the current environment's config:
38
+ * // startup, from the current environment's config:
24
39
  * ClientRegistry.addMapping('server2', 8202); // -> http://localhost:8202
25
40
  * ClientRegistry.addUrlMapping('email', 'https://email.other-region.example');
41
+ * ClientRegistry.setDeriver(gcpCloudRunDeriver()); // everything else, on GCP
26
42
  * ```
27
43
  */
28
44
  export declare class ClientRegistry {
29
45
  /** svcName -> resolved base URL. Process-global; populated at startup per environment. */
30
46
  private static readonly mappings;
47
+ /** The fallback for svcNames with no mapping. Undefined = no derivation in this environment. */
48
+ private static deriver;
49
+ /**
50
+ * App-supplied error translations, consulted BEFORE webpieces' built-in error mapping (both
51
+ * directions). Process-global, populated once at startup on the SERVER and in the BROWSER — the
52
+ * same no-DI pattern as {@link ClientRegistry.mappings} above. Consulted in registration order,
53
+ * first match wins, so a later-registered app type AND an override of a built-in status both
54
+ * work. See {@link ErrorTranslation}.
55
+ */
56
+ private static readonly errorTranslations;
31
57
  /** Map a service name to `http://localhost:<port>`. */
32
58
  static addMapping(svcName: string, port: number): void;
33
- /** Map a service name to an explicit base URL (any host / any environment). */
59
+ /**
60
+ * Map a service name to an explicit base URL (any host / any environment).
61
+ *
62
+ * The EMPTY STRING is a legal, meaningful mapping: it makes the service relative, i.e.
63
+ * same-origin, because the client builds its URL as `${baseUrl}${route.path}`.
64
+ */
34
65
  static addUrlMapping(svcName: string, url: string): void;
35
- /** The registered override for `svcName`, or undefined if none. Non-throwing. */
66
+ /**
67
+ * Install the environment's {@link ServiceUrlDeriver} — how to resolve a svcName that has NO
68
+ * mapping. Optional: an environment that registers every svcName it calls needs none.
69
+ */
70
+ static setDeriver(fn: ServiceUrlDeriver): void;
71
+ /** The registered override for `svcName`, or undefined if none. Registry only — no derivation. */
36
72
  static tryLookup(svcName: string): string | undefined;
37
73
  /**
38
- * Resolve `svcName` to its registered base URL. THROWS with actionable guidance if the service
39
- * was never registered used where there is no GCP-derivation fallback (the browser).
74
+ * Resolve `svcName` to its registered base URL. THROWS if the service was never registered.
75
+ * Registry onlyit does NOT consult the deriver; prefer {@link ClientRegistry.resolve}.
40
76
  */
41
77
  static lookup(svcName: string): string;
42
- /** Reset the registry. For tests, so the process-global map does not leak across specs. */
78
+ /**
79
+ * Steps 1 + 2 of the chain: the registered mapping, else the installed deriver, else undefined.
80
+ * Non-throwing — the BROWSER uses this and treats undefined as "" (relative → same origin),
81
+ * which is what a browser app calling the backend it was served from wants by default.
82
+ *
83
+ * Note the `!== undefined` guard: an empty-string mapping is a legal answer ("this service is
84
+ * same-origin"), so it must NOT fall through to derivation.
85
+ */
86
+ static tryResolve(svcName: string): Promise<string | undefined>;
87
+ /**
88
+ * The full chain, with node's fallback: mapping, else deriver, else THROW. A server has no "own
89
+ * origin" to go relative to, so an unresolvable peer is a setup bug and must fail loudly — and
90
+ * say how to fix it.
91
+ */
92
+ static resolve(svcName: string): Promise<string>;
93
+ /**
94
+ * Register an app error translation. Consulted BEFORE webpieces' built-in mapping, in
95
+ * registration order (first match wins), so later app types AND overrides of built-ins both
96
+ * work. Call ONCE at startup — on the server AND in the browser — mirroring
97
+ * {@link ClientRegistry.addMapping}. See {@link ErrorTranslation}.
98
+ */
99
+ static addErrorTranslation(translation: ErrorTranslation): void;
100
+ /**
101
+ * exception → wire (SERVER side). The first registered translation that claims `error` wins;
102
+ * `undefined` if none does — the caller then falls through to the generic webpieces mapping.
103
+ */
104
+ static tryTranslateToWire(error: Error): ErrorWireForm | undefined;
105
+ /**
106
+ * wire → exception (CLIENT side). The first registered translation that claims
107
+ * `(statusCode, protocolError)` wins; `undefined` if none does — the caller then falls through
108
+ * to the generic webpieces switch.
109
+ */
110
+ static tryTranslateFromWire(statusCode: number, protocolError: ProtocolError): Error | undefined;
111
+ /** Reset mappings, the deriver, AND error translations. For tests, so the process-globals do not leak across specs. */
43
112
  static clear(): void;
44
113
  }
@@ -2,53 +2,77 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ClientRegistry = void 0;
4
4
  /**
5
- * ClientRegistry - the process-global `svcName -> base URL` table of per-environment URL overrides
6
- * used to route outbound client calls.
5
+ * ClientRegistry - the ONE place a `svcName` becomes a base URL, for every outbound client in every
6
+ * environment. A client is built once but a URL is per-environment, so the URL belongs here rather
7
+ * than on the client: clients carry ONLY a svcName.
7
8
  *
8
- * On GCP a client resolves a peer's base URL deterministically from `K_SERVICE` + project + region
9
- * (see gcp-identity `resolveServiceUrl`), so same-project/same-region services need NO registration.
10
- * Everything the derivation cannot describe — a localhost port, another region, another project, a
11
- * host that is not Cloud Run at all, or a browser that cannot read `K_SERVICE` — is registered here
12
- * instead. Each environment populates the registry (from its own per-env config) with the URLs it
13
- * needs; a registered mapping WINS over GCP derivation.
9
+ * Resolution is one precedence chain, identical in the browser, in node, and in Cloud Tasks:
14
10
  *
15
- * - **http-client-node / cloudtasks-client** resolve through gcp-identity `resolveServiceUrl`: it
16
- * checks {@link ClientRegistry.tryLookup} first (override), then derives on GCP, then throws
17
- * off-GCP if unregistered a missing mapping is a setup bug, not a silent mis-route.
18
- * - **http-client-browser** cannot derive GCP URLs, so it resolves purely via
19
- * {@link ClientRegistry.lookup}; the Angular app registers each svcName at startup.
11
+ * 1. a registered mapping wins the localhost port table, AWS, an external API, another region
12
+ * or project, a host that is not Cloud Run at all. Populated at startup from per-env config.
13
+ * 2. else the installed {@link ServiceUrlDeriver}, if any GCP's built-in, a DNS template, or
14
+ * your own. OPTIONAL on purpose: localhost is inherently a TABLE (helper-fsdb -> :8401,
15
+ * helper-portal -> :8201 have per-service ports), so explicit mappings must stay sufficient on
16
+ * their own.
17
+ * 3. else the caller's fallback: the BROWSER goes relative (same origin — see
18
+ * {@link ClientRegistry.tryResolve}), while node THROWS (see {@link ClientRegistry.resolve}),
19
+ * because a server has no "own origin" and an unresolvable peer is a setup bug.
20
20
  *
21
21
  * Configured like {@link HeaderRegistry} / LogManager — populated once at startup, then globally
22
22
  * accessible with NO DI wiring. It is browser-safe (no `process.env`, no node-only deps), which is
23
23
  * why it lives in core-util rather than gcp-identity.
24
24
  *
25
25
  * ```ts
26
- * // startup, populated from the current environment's config:
26
+ * // startup, from the current environment's config:
27
27
  * ClientRegistry.addMapping('server2', 8202); // -> http://localhost:8202
28
28
  * ClientRegistry.addUrlMapping('email', 'https://email.other-region.example');
29
+ * ClientRegistry.setDeriver(gcpCloudRunDeriver()); // everything else, on GCP
29
30
  * ```
30
31
  */
31
32
  class ClientRegistry {
32
33
  /** svcName -> resolved base URL. Process-global; populated at startup per environment. */
33
34
  static mappings = new Map();
35
+ /** The fallback for svcNames with no mapping. Undefined = no derivation in this environment. */
36
+ static deriver;
37
+ /**
38
+ * App-supplied error translations, consulted BEFORE webpieces' built-in error mapping (both
39
+ * directions). Process-global, populated once at startup on the SERVER and in the BROWSER — the
40
+ * same no-DI pattern as {@link ClientRegistry.mappings} above. Consulted in registration order,
41
+ * first match wins, so a later-registered app type AND an override of a built-in status both
42
+ * work. See {@link ErrorTranslation}.
43
+ */
44
+ static errorTranslations = [];
34
45
  /** Map a service name to `http://localhost:<port>`. */
35
46
  // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
36
47
  static addMapping(svcName, port) {
37
48
  ClientRegistry.mappings.set(svcName, `http://localhost:${port}`);
38
49
  }
39
- /** Map a service name to an explicit base URL (any host / any environment). */
50
+ /**
51
+ * Map a service name to an explicit base URL (any host / any environment).
52
+ *
53
+ * The EMPTY STRING is a legal, meaningful mapping: it makes the service relative, i.e.
54
+ * same-origin, because the client builds its URL as `${baseUrl}${route.path}`.
55
+ */
40
56
  // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
41
57
  static addUrlMapping(svcName, url) {
42
58
  ClientRegistry.mappings.set(svcName, url);
43
59
  }
44
- /** The registered override for `svcName`, or undefined if none. Non-throwing. */
60
+ /**
61
+ * Install the environment's {@link ServiceUrlDeriver} — how to resolve a svcName that has NO
62
+ * mapping. Optional: an environment that registers every svcName it calls needs none.
63
+ */
64
+ // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
65
+ static setDeriver(fn) {
66
+ ClientRegistry.deriver = fn;
67
+ }
68
+ /** The registered override for `svcName`, or undefined if none. Registry only — no derivation. */
45
69
  // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
46
70
  static tryLookup(svcName) {
47
71
  return ClientRegistry.mappings.get(svcName);
48
72
  }
49
73
  /**
50
- * Resolve `svcName` to its registered base URL. THROWS with actionable guidance if the service
51
- * was never registered used where there is no GCP-derivation fallback (the browser).
74
+ * Resolve `svcName` to its registered base URL. THROWS if the service was never registered.
75
+ * Registry onlyit does NOT consult the deriver; prefer {@link ClientRegistry.resolve}.
52
76
  */
53
77
  // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
54
78
  static lookup(svcName) {
@@ -60,10 +84,87 @@ class ClientRegistry {
60
84
  }
61
85
  return url;
62
86
  }
63
- /** Reset the registry. For tests, so the process-global map does not leak across specs. */
87
+ /**
88
+ * Steps 1 + 2 of the chain: the registered mapping, else the installed deriver, else undefined.
89
+ * Non-throwing — the BROWSER uses this and treats undefined as "" (relative → same origin),
90
+ * which is what a browser app calling the backend it was served from wants by default.
91
+ *
92
+ * Note the `!== undefined` guard: an empty-string mapping is a legal answer ("this service is
93
+ * same-origin"), so it must NOT fall through to derivation.
94
+ */
95
+ // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
96
+ static tryResolve(svcName) {
97
+ const override = ClientRegistry.tryLookup(svcName);
98
+ if (override !== undefined) {
99
+ return Promise.resolve(override);
100
+ }
101
+ if (!ClientRegistry.deriver) {
102
+ return Promise.resolve(undefined);
103
+ }
104
+ return ClientRegistry.deriver(svcName);
105
+ }
106
+ /**
107
+ * The full chain, with node's fallback: mapping, else deriver, else THROW. A server has no "own
108
+ * origin" to go relative to, so an unresolvable peer is a setup bug and must fail loudly — and
109
+ * say how to fix it.
110
+ */
111
+ // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
112
+ static async resolve(svcName) {
113
+ const url = await ClientRegistry.tryResolve(svcName);
114
+ if (url === undefined) {
115
+ throw new Error(`No URL for service "${svcName}".\n` +
116
+ ` - localhost/AWS: ClientRegistry.addMapping('${svcName}', 8401)\n` +
117
+ ` or ClientRegistry.addUrlMapping('${svcName}', 'https://...')\n` +
118
+ ` - GCP: install a deriver — ClientRegistry.setDeriver(gcpCloudRunDeriver())\n` +
119
+ ` - typo? svcName must be the CLOUD RUN service name, not your project/module name`);
120
+ }
121
+ return url;
122
+ }
123
+ /**
124
+ * Register an app error translation. Consulted BEFORE webpieces' built-in mapping, in
125
+ * registration order (first match wins), so later app types AND overrides of built-ins both
126
+ * work. Call ONCE at startup — on the server AND in the browser — mirroring
127
+ * {@link ClientRegistry.addMapping}. See {@link ErrorTranslation}.
128
+ */
129
+ // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
130
+ static addErrorTranslation(translation) {
131
+ ClientRegistry.errorTranslations.push(translation);
132
+ }
133
+ /**
134
+ * exception → wire (SERVER side). The first registered translation that claims `error` wins;
135
+ * `undefined` if none does — the caller then falls through to the generic webpieces mapping.
136
+ */
137
+ // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
138
+ static tryTranslateToWire(error) {
139
+ for (const translation of ClientRegistry.errorTranslations) {
140
+ const wire = translation.toWire(error);
141
+ if (wire !== undefined) {
142
+ return wire;
143
+ }
144
+ }
145
+ return undefined;
146
+ }
147
+ /**
148
+ * wire → exception (CLIENT side). The first registered translation that claims
149
+ * `(statusCode, protocolError)` wins; `undefined` if none does — the caller then falls through
150
+ * to the generic webpieces switch.
151
+ */
152
+ // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
153
+ static tryTranslateFromWire(statusCode, protocolError) {
154
+ for (const translation of ClientRegistry.errorTranslations) {
155
+ const err = translation.fromWire(statusCode, protocolError);
156
+ if (err !== undefined) {
157
+ return err;
158
+ }
159
+ }
160
+ return undefined;
161
+ }
162
+ /** Reset mappings, the deriver, AND error translations. For tests, so the process-globals do not leak across specs. */
64
163
  // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected
65
164
  static clear() {
66
165
  ClientRegistry.mappings.clear();
166
+ ClientRegistry.deriver = undefined;
167
+ ClientRegistry.errorTranslations.length = 0;
67
168
  }
68
169
  }
69
170
  exports.ClientRegistry = ClientRegistry;
@@ -1 +1 @@
1
- {"version":3,"file":"ClientRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ClientRegistry.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAa,cAAc;IACvB,0FAA0F;IAClF,MAAM,CAAU,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE7D,uDAAuD;IACvD,wJAAwJ;IACxJ,MAAM,CAAC,UAAU,CAAC,OAAe,EAAE,IAAY;QAC3C,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,oBAAoB,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,+EAA+E;IAC/E,wJAAwJ;IACxJ,MAAM,CAAC,aAAa,CAAC,OAAe,EAAE,GAAW;QAC7C,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED,iFAAiF;IACjF,wJAAwJ;IACxJ,MAAM,CAAC,SAAS,CAAC,OAAe;QAC5B,OAAO,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;IAED;;;OAGG;IACH,wJAAwJ;IACxJ,MAAM,CAAC,MAAM,CAAC,OAAe;QACzB,MAAM,GAAG,GAAG,cAAc,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACX,kCAAkC,OAAO,6BAA6B;gBACtE,oEAAoE;gBACpE,iEAAiE,CACpE,CAAC;QACN,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED,2FAA2F;IAC3F,wJAAwJ;IACxJ,MAAM,CAAC,KAAK;QACR,cAAc,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACpC,CAAC;;AA3CL,wCA4CC","sourcesContent":["/**\n * ClientRegistry - the process-global `svcName -> base URL` table of per-environment URL overrides\n * used to route outbound client calls.\n *\n * On GCP a client resolves a peer's base URL deterministically from `K_SERVICE` + project + region\n * (see gcp-identity `resolveServiceUrl`), so same-project/same-region services need NO registration.\n * Everything the derivation cannot describe — a localhost port, another region, another project, a\n * host that is not Cloud Run at all, or a browser that cannot read `K_SERVICE` — is registered here\n * instead. Each environment populates the registry (from its own per-env config) with the URLs it\n * needs; a registered mapping WINS over GCP derivation.\n *\n * - **http-client-node / cloudtasks-client** resolve through gcp-identity `resolveServiceUrl`: it\n * checks {@link ClientRegistry.tryLookup} first (override), then derives on GCP, then throws\n * off-GCP if unregistered — a missing mapping is a setup bug, not a silent mis-route.\n * - **http-client-browser** cannot derive GCP URLs, so it resolves purely via\n * {@link ClientRegistry.lookup}; the Angular app registers each svcName at startup.\n *\n * Configured like {@link HeaderRegistry} / LogManager — populated once at startup, then globally\n * accessible with NO DI wiring. It is browser-safe (no `process.env`, no node-only deps), which is\n * why it lives in core-util rather than gcp-identity.\n *\n * ```ts\n * // startup, populated from the current environment's config:\n * ClientRegistry.addMapping('server2', 8202); // -> http://localhost:8202\n * ClientRegistry.addUrlMapping('email', 'https://email.other-region.example');\n * ```\n */\nexport class ClientRegistry {\n /** svcName -> resolved base URL. Process-global; populated at startup per environment. */\n private static readonly mappings = new Map<string, string>();\n\n /** Map a service name to `http://localhost:<port>`. */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static addMapping(svcName: string, port: number): void {\n ClientRegistry.mappings.set(svcName, `http://localhost:${port}`);\n }\n\n /** Map a service name to an explicit base URL (any host / any environment). */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static addUrlMapping(svcName: string, url: string): void {\n ClientRegistry.mappings.set(svcName, url);\n }\n\n /** The registered override for `svcName`, or undefined if none. Non-throwing. */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static tryLookup(svcName: string): string | undefined {\n return ClientRegistry.mappings.get(svcName);\n }\n\n /**\n * Resolve `svcName` to its registered base URL. THROWS with actionable guidance if the service\n * was never registered — used where there is no GCP-derivation fallback (the browser).\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static lookup(svcName: string): string {\n const url = ClientRegistry.tryLookup(svcName);\n if (url === undefined) {\n throw new Error(\n `No URL registered for service \"${svcName}\". Register it at startup: ` +\n `ClientRegistry.addMapping(svcName, port) for a localhost port, or ` +\n `ClientRegistry.addUrlMapping(svcName, url) for an explicit URL.`,\n );\n }\n return url;\n }\n\n /** Reset the registry. For tests, so the process-global map does not leak across specs. */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static clear(): void {\n ClientRegistry.mappings.clear();\n }\n}\n"]}
1
+ {"version":3,"file":"ClientRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ClientRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { ErrorTranslation, ErrorWireForm } from './ErrorTranslation';\nimport { ProtocolError } from './errors';\n\n/**\n * Derives a base URL for a service name that has NO registered mapping — the pluggable half of\n * {@link ClientRegistry} resolution. One per environment, installed once at startup:\n *\n * - `gcpCloudRunDeriver()` (@webpieces/gcp-identity) — the Cloud Run formula, read from the metadata\n * server. For code running ON GCP.\n * - `gcpCloudRunDeriver(new GcpCloudRunTarget(projectNumber, region))` — the SAME formula with the\n * values supplied from config, for code running OFF GCP that still calls Cloud Run (a CLI, CI).\n * - {@link templateDeriver} (this package, browser-safe) — pure string substitution, for AWS or\n * anything else with predictable DNS.\n * - none at all — a browser goes same-origin, and localhost/tests hand-register their mappings.\n */\nexport type ServiceUrlDeriver = (svcName: string) => Promise<string>;\n\n/**\n * ClientRegistry - the ONE place a `svcName` becomes a base URL, for every outbound client in every\n * environment. A client is built once but a URL is per-environment, so the URL belongs here rather\n * than on the client: clients carry ONLY a svcName.\n *\n * Resolution is one precedence chain, identical in the browser, in node, and in Cloud Tasks:\n *\n * 1. a registered mapping wins — the localhost port table, AWS, an external API, another region\n * or project, a host that is not Cloud Run at all. Populated at startup from per-env config.\n * 2. else the installed {@link ServiceUrlDeriver}, if any — GCP's built-in, a DNS template, or\n * your own. OPTIONAL on purpose: localhost is inherently a TABLE (helper-fsdb -> :8401,\n * helper-portal -> :8201 have per-service ports), so explicit mappings must stay sufficient on\n * their own.\n * 3. else the caller's fallback: the BROWSER goes relative (same origin — see\n * {@link ClientRegistry.tryResolve}), while node THROWS (see {@link ClientRegistry.resolve}),\n * because a server has no \"own origin\" and an unresolvable peer is a setup bug.\n *\n * Configured like {@link HeaderRegistry} / LogManager — populated once at startup, then globally\n * accessible with NO DI wiring. It is browser-safe (no `process.env`, no node-only deps), which is\n * why it lives in core-util rather than gcp-identity.\n *\n * ```ts\n * // startup, from the current environment's config:\n * ClientRegistry.addMapping('server2', 8202); // -> http://localhost:8202\n * ClientRegistry.addUrlMapping('email', 'https://email.other-region.example');\n * ClientRegistry.setDeriver(gcpCloudRunDeriver()); // everything else, on GCP\n * ```\n */\nexport class ClientRegistry {\n /** svcName -> resolved base URL. Process-global; populated at startup per environment. */\n private static readonly mappings = new Map<string, string>();\n\n /** The fallback for svcNames with no mapping. Undefined = no derivation in this environment. */\n private static deriver: ServiceUrlDeriver | undefined;\n\n /**\n * App-supplied error translations, consulted BEFORE webpieces' built-in error mapping (both\n * directions). Process-global, populated once at startup on the SERVER and in the BROWSER — the\n * same no-DI pattern as {@link ClientRegistry.mappings} above. Consulted in registration order,\n * first match wins, so a later-registered app type AND an override of a built-in status both\n * work. See {@link ErrorTranslation}.\n */\n private static readonly errorTranslations: ErrorTranslation[] = [];\n\n /** Map a service name to `http://localhost:<port>`. */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static addMapping(svcName: string, port: number): void {\n ClientRegistry.mappings.set(svcName, `http://localhost:${port}`);\n }\n\n /**\n * Map a service name to an explicit base URL (any host / any environment).\n *\n * The EMPTY STRING is a legal, meaningful mapping: it makes the service relative, i.e.\n * same-origin, because the client builds its URL as `${baseUrl}${route.path}`.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static addUrlMapping(svcName: string, url: string): void {\n ClientRegistry.mappings.set(svcName, url);\n }\n\n /**\n * Install the environment's {@link ServiceUrlDeriver} — how to resolve a svcName that has NO\n * mapping. Optional: an environment that registers every svcName it calls needs none.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static setDeriver(fn: ServiceUrlDeriver): void {\n ClientRegistry.deriver = fn;\n }\n\n /** The registered override for `svcName`, or undefined if none. Registry only — no derivation. */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static tryLookup(svcName: string): string | undefined {\n return ClientRegistry.mappings.get(svcName);\n }\n\n /**\n * Resolve `svcName` to its registered base URL. THROWS if the service was never registered.\n * Registry only — it does NOT consult the deriver; prefer {@link ClientRegistry.resolve}.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static lookup(svcName: string): string {\n const url = ClientRegistry.tryLookup(svcName);\n if (url === undefined) {\n throw new Error(\n `No URL registered for service \"${svcName}\". Register it at startup: ` +\n `ClientRegistry.addMapping(svcName, port) for a localhost port, or ` +\n `ClientRegistry.addUrlMapping(svcName, url) for an explicit URL.`,\n );\n }\n return url;\n }\n\n /**\n * Steps 1 + 2 of the chain: the registered mapping, else the installed deriver, else undefined.\n * Non-throwing — the BROWSER uses this and treats undefined as \"\" (relative → same origin),\n * which is what a browser app calling the backend it was served from wants by default.\n *\n * Note the `!== undefined` guard: an empty-string mapping is a legal answer (\"this service is\n * same-origin\"), so it must NOT fall through to derivation.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static tryResolve(svcName: string): Promise<string | undefined> {\n const override = ClientRegistry.tryLookup(svcName);\n if (override !== undefined) {\n return Promise.resolve(override);\n }\n if (!ClientRegistry.deriver) {\n return Promise.resolve(undefined);\n }\n return ClientRegistry.deriver(svcName);\n }\n\n /**\n * The full chain, with node's fallback: mapping, else deriver, else THROW. A server has no \"own\n * origin\" to go relative to, so an unresolvable peer is a setup bug and must fail loudly — and\n * say how to fix it.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static async resolve(svcName: string): Promise<string> {\n const url = await ClientRegistry.tryResolve(svcName);\n if (url === undefined) {\n throw new Error(\n `No URL for service \"${svcName}\".\\n` +\n ` - localhost/AWS: ClientRegistry.addMapping('${svcName}', 8401)\\n` +\n ` or ClientRegistry.addUrlMapping('${svcName}', 'https://...')\\n` +\n ` - GCP: install a deriver — ClientRegistry.setDeriver(gcpCloudRunDeriver())\\n` +\n ` - typo? svcName must be the CLOUD RUN service name, not your project/module name`,\n );\n }\n return url;\n }\n\n /**\n * Register an app error translation. Consulted BEFORE webpieces' built-in mapping, in\n * registration order (first match wins), so later app types AND overrides of built-ins both\n * work. Call ONCE at startup — on the server AND in the browser — mirroring\n * {@link ClientRegistry.addMapping}. See {@link ErrorTranslation}.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static addErrorTranslation(translation: ErrorTranslation): void {\n ClientRegistry.errorTranslations.push(translation);\n }\n\n /**\n * exception → wire (SERVER side). The first registered translation that claims `error` wins;\n * `undefined` if none does — the caller then falls through to the generic webpieces mapping.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static tryTranslateToWire(error: Error): ErrorWireForm | undefined {\n for (const translation of ClientRegistry.errorTranslations) {\n const wire = translation.toWire(error);\n if (wire !== undefined) {\n return wire;\n }\n }\n return undefined;\n }\n\n /**\n * wire → exception (CLIENT side). The first registered translation that claims\n * `(statusCode, protocolError)` wins; `undefined` if none does — the caller then falls through\n * to the generic webpieces switch.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static tryTranslateFromWire(statusCode: number, protocolError: ProtocolError): Error | undefined {\n for (const translation of ClientRegistry.errorTranslations) {\n const err = translation.fromWire(statusCode, protocolError);\n if (err !== undefined) {\n return err;\n }\n }\n return undefined;\n }\n\n /** Reset mappings, the deriver, AND error translations. For tests, so the process-globals do not leak across specs. */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/LogManager); populated once at startup, never DI-injected\n static clear(): void {\n ClientRegistry.mappings.clear();\n ClientRegistry.deriver = undefined;\n ClientRegistry.errorTranslations.length = 0;\n }\n}\n"]}
@@ -0,0 +1,47 @@
1
+ import { ProtocolError } from './errors';
2
+ /**
3
+ * ErrorWireForm - the wire representation an error translates to: the HTTP status code plus the
4
+ * {@link ProtocolError} body fields. This is exactly what the server writes and the client reads,
5
+ * so the two directions of a translation agree on the same shape.
6
+ *
7
+ * Data-only structure (a class, not an inline object literal, per the webpieces guidelines) so
8
+ * every producer constructs it explicitly.
9
+ */
10
+ export declare class ErrorWireForm {
11
+ readonly statusCode: number;
12
+ readonly protocolError: ProtocolError;
13
+ constructor(statusCode: number, protocolError: ProtocolError);
14
+ }
15
+ /**
16
+ * ErrorTranslation - a bidirectional, app-supplied translation between one (or more) exception
17
+ * types and their wire form. An app registers translations ONCE at startup (server AND browser)
18
+ * via {@link ClientRegistry.addErrorTranslation}; they are consulted BEFORE the built-in webpieces
19
+ * mapping (the hard-coded status-code switch on the client, the `instanceof HttpError` ladder on
20
+ * the server).
21
+ *
22
+ * BOTH methods return `undefined` to mean "not mine — fall through to the next registered
23
+ * translation, then to generic webpieces." This single rule is what lets translations be additive
24
+ * (an app ADDS a new error type, e.g. a custom 460) AND override-capable (an app REPLACES how a
25
+ * built-in status like 400 is written/reconstructed): a translation that returns a value wins;
26
+ * one that returns undefined steps aside.
27
+ *
28
+ * Symmetry: `toWire` runs on the SERVER (exception → JSON) and `fromWire` runs on the CLIENT
29
+ * (JSON → exception). Because {@link ErrorTranslation} and {@link ClientRegistry} live in core-util
30
+ * (browser-safe, zero node deps), the identical translation object serves the node server and the
31
+ * Angular/browser client — the same halves the app supplies together.
32
+ *
33
+ * This is a business-logic contract (methods, not data), so it is an interface per the webpieces
34
+ * guidelines.
35
+ */
36
+ export interface ErrorTranslation {
37
+ /**
38
+ * exception → JSON. Return the wire form for `error`, or `undefined` if this translation does
39
+ * not handle `error` (→ fall through to the next translation, then to generic webpieces).
40
+ */
41
+ toWire(error: Error): ErrorWireForm | undefined;
42
+ /**
43
+ * JSON → exception. Return the reconstructed, typed error for `(statusCode, protocolError)`, or
44
+ * `undefined` to fall through to the next translation, then to the generic webpieces switch.
45
+ */
46
+ fromWire(statusCode: number, protocolError: ProtocolError): Error | undefined;
47
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ErrorWireForm = void 0;
4
+ /**
5
+ * ErrorWireForm - the wire representation an error translates to: the HTTP status code plus the
6
+ * {@link ProtocolError} body fields. This is exactly what the server writes and the client reads,
7
+ * so the two directions of a translation agree on the same shape.
8
+ *
9
+ * Data-only structure (a class, not an inline object literal, per the webpieces guidelines) so
10
+ * every producer constructs it explicitly.
11
+ */
12
+ class ErrorWireForm {
13
+ statusCode;
14
+ protocolError;
15
+ constructor(statusCode, protocolError) {
16
+ this.statusCode = statusCode;
17
+ this.protocolError = protocolError;
18
+ }
19
+ }
20
+ exports.ErrorWireForm = ErrorWireForm;
21
+ //# sourceMappingURL=ErrorTranslation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorTranslation.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ErrorTranslation.ts"],"names":[],"mappings":";;;AAEA;;;;;;;GAOG;AACH,MAAa,aAAa;IAEF;IACA;IAFpB,YACoB,UAAkB,EAClB,aAA4B;QAD5B,eAAU,GAAV,UAAU,CAAQ;QAClB,kBAAa,GAAb,aAAa,CAAe;IAC7C,CAAC;CACP;AALD,sCAKC","sourcesContent":["import { ProtocolError } from './errors';\n\n/**\n * ErrorWireForm - the wire representation an error translates to: the HTTP status code plus the\n * {@link ProtocolError} body fields. This is exactly what the server writes and the client reads,\n * so the two directions of a translation agree on the same shape.\n *\n * Data-only structure (a class, not an inline object literal, per the webpieces guidelines) so\n * every producer constructs it explicitly.\n */\nexport class ErrorWireForm {\n constructor(\n public readonly statusCode: number,\n public readonly protocolError: ProtocolError,\n ) {}\n}\n\n/**\n * ErrorTranslation - a bidirectional, app-supplied translation between one (or more) exception\n * types and their wire form. An app registers translations ONCE at startup (server AND browser)\n * via {@link ClientRegistry.addErrorTranslation}; they are consulted BEFORE the built-in webpieces\n * mapping (the hard-coded status-code switch on the client, the `instanceof HttpError` ladder on\n * the server).\n *\n * BOTH methods return `undefined` to mean \"not mine — fall through to the next registered\n * translation, then to generic webpieces.\" This single rule is what lets translations be additive\n * (an app ADDS a new error type, e.g. a custom 460) AND override-capable (an app REPLACES how a\n * built-in status like 400 is written/reconstructed): a translation that returns a value wins;\n * one that returns undefined steps aside.\n *\n * Symmetry: `toWire` runs on the SERVER (exception → JSON) and `fromWire` runs on the CLIENT\n * (JSON → exception). Because {@link ErrorTranslation} and {@link ClientRegistry} live in core-util\n * (browser-safe, zero node deps), the identical translation object serves the node server and the\n * Angular/browser client — the same halves the app supplies together.\n *\n * This is a business-logic contract (methods, not data), so it is an interface per the webpieces\n * guidelines.\n */\nexport interface ErrorTranslation {\n /**\n * exception → JSON. Return the wire form for `error`, or `undefined` if this translation does\n * not handle `error` (→ fall through to the next translation, then to generic webpieces).\n */\n toWire(error: Error): ErrorWireForm | undefined;\n\n /**\n * JSON → exception. Return the reconstructed, typed error for `(statusCode, protocolError)`, or\n * `undefined` to fall through to the next translation, then to the generic webpieces switch.\n */\n fromWire(statusCode: number, protocolError: ProtocolError): Error | undefined;\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import { ServiceUrlDeriver } from './ClientRegistry';
2
+ /**
3
+ * A {@link ServiceUrlDeriver} for any environment whose service URLs are a PREDICTABLE FORMULA —
4
+ * AWS behind Route53, a k8s cluster, a corp DNS zone, anything not Cloud Run. Pure string
5
+ * substitution, so it is browser-safe and pulls in no cloud SDK.
6
+ *
7
+ * `{svc}` is the service name being resolved; every other `{key}` comes from `vars`:
8
+ *
9
+ * ```ts
10
+ * ClientRegistry.setDeriver(templateDeriver('https://{svc}.example.com')); // prod
11
+ * ClientRegistry.setDeriver(templateDeriver('https://{svc}.{env}.example.com', { env: 'qa' })); // per-env
12
+ * ```
13
+ *
14
+ * Nothing about this is GCP-specific, which is the point: an AWS deployment installs THIS (or no
15
+ * deriver at all, and registers its mappings) and never pulls in gcp-metadata.
16
+ *
17
+ * @throws Error at DERIVE time if the pattern still holds an unsubstituted `{key}` — a typo in the
18
+ * pattern (or a var you forgot to pass) must not silently ship a bogus hostname.
19
+ */
20
+ export declare function templateDeriver(pattern: string, vars?: Record<string, string>): ServiceUrlDeriver;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.templateDeriver = templateDeriver;
4
+ /**
5
+ * A {@link ServiceUrlDeriver} for any environment whose service URLs are a PREDICTABLE FORMULA —
6
+ * AWS behind Route53, a k8s cluster, a corp DNS zone, anything not Cloud Run. Pure string
7
+ * substitution, so it is browser-safe and pulls in no cloud SDK.
8
+ *
9
+ * `{svc}` is the service name being resolved; every other `{key}` comes from `vars`:
10
+ *
11
+ * ```ts
12
+ * ClientRegistry.setDeriver(templateDeriver('https://{svc}.example.com')); // prod
13
+ * ClientRegistry.setDeriver(templateDeriver('https://{svc}.{env}.example.com', { env: 'qa' })); // per-env
14
+ * ```
15
+ *
16
+ * Nothing about this is GCP-specific, which is the point: an AWS deployment installs THIS (or no
17
+ * deriver at all, and registers its mappings) and never pulls in gcp-metadata.
18
+ *
19
+ * @throws Error at DERIVE time if the pattern still holds an unsubstituted `{key}` — a typo in the
20
+ * pattern (or a var you forgot to pass) must not silently ship a bogus hostname.
21
+ */
22
+ // webpieces-disable no-function-outside-class -- a deriver IS a function; this is the factory that closes over the pattern (see ServiceUrlDeriver)
23
+ function templateDeriver(pattern, vars = {}) {
24
+ // async, so a bad pattern REJECTS like every other resolution failure rather than throwing
25
+ // synchronously out of ClientRegistry.tryResolve.
26
+ return async (svcName) => {
27
+ const substitutions = { ...vars, svc: svcName };
28
+ const url = pattern.replace(/\{(\w+)\}/g, (placeholder, key) => {
29
+ const value = substitutions[key];
30
+ return value === undefined ? placeholder : value;
31
+ });
32
+ const leftover = /\{(\w+)\}/.exec(url);
33
+ if (leftover) {
34
+ throw new Error(`templateDeriver('${pattern}') cannot resolve "${svcName}": no value for {${leftover[1]}}. ` +
35
+ `Pass it in the vars argument, e.g. templateDeriver(pattern, { ${leftover[1]}: '...' }).`);
36
+ }
37
+ return url;
38
+ };
39
+ }
40
+ //# sourceMappingURL=templateDeriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templateDeriver.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/templateDeriver.ts"],"names":[],"mappings":";;AAqBA,0CAmBC;AAtCD;;;;;;;;;;;;;;;;;GAiBG;AACH,mJAAmJ;AACnJ,SAAgB,eAAe,CAAC,OAAe,EAAE,OAA+B,EAAE;IAC9E,2FAA2F;IAC3F,kDAAkD;IAClD,OAAO,KAAK,EAAE,OAAe,EAAmB,EAAE;QAC9C,MAAM,aAAa,GAA2B,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;QACxE,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,WAAmB,EAAE,GAAW,EAAU,EAAE;YACnF,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;YACjC,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,QAAQ,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACX,oBAAoB,OAAO,sBAAsB,OAAO,oBAAoB,QAAQ,CAAC,CAAC,CAAC,KAAK;gBAC5F,iEAAiE,QAAQ,CAAC,CAAC,CAAC,aAAa,CAC5F,CAAC;QACN,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC,CAAC;AACN,CAAC","sourcesContent":["import { ServiceUrlDeriver } from './ClientRegistry';\n\n/**\n * A {@link ServiceUrlDeriver} for any environment whose service URLs are a PREDICTABLE FORMULA —\n * AWS behind Route53, a k8s cluster, a corp DNS zone, anything not Cloud Run. Pure string\n * substitution, so it is browser-safe and pulls in no cloud SDK.\n *\n * `{svc}` is the service name being resolved; every other `{key}` comes from `vars`:\n *\n * ```ts\n * ClientRegistry.setDeriver(templateDeriver('https://{svc}.example.com')); // prod\n * ClientRegistry.setDeriver(templateDeriver('https://{svc}.{env}.example.com', { env: 'qa' })); // per-env\n * ```\n *\n * Nothing about this is GCP-specific, which is the point: an AWS deployment installs THIS (or no\n * deriver at all, and registers its mappings) and never pulls in gcp-metadata.\n *\n * @throws Error at DERIVE time if the pattern still holds an unsubstituted `{key}` — a typo in the\n * pattern (or a var you forgot to pass) must not silently ship a bogus hostname.\n */\n// webpieces-disable no-function-outside-class -- a deriver IS a function; this is the factory that closes over the pattern (see ServiceUrlDeriver)\nexport function templateDeriver(pattern: string, vars: Record<string, string> = {}): ServiceUrlDeriver {\n // async, so a bad pattern REJECTS like every other resolution failure rather than throwing\n // synchronously out of ClientRegistry.tryResolve.\n return async (svcName: string): Promise<string> => {\n const substitutions: Record<string, string> = { ...vars, svc: svcName };\n const url = pattern.replace(/\\{(\\w+)\\}/g, (placeholder: string, key: string): string => {\n const value = substitutions[key];\n return value === undefined ? placeholder : value;\n });\n\n const leftover = /\\{(\\w+)\\}/.exec(url);\n if (leftover) {\n throw new Error(\n `templateDeriver('${pattern}') cannot resolve \"${svcName}\": no value for {${leftover[1]}}. ` +\n `Pass it in the vars argument, e.g. templateDeriver(pattern, { ${leftover[1]}: '...' }).`,\n );\n }\n return url;\n };\n}\n"]}
package/src/index.d.ts CHANGED
@@ -23,6 +23,10 @@ export { ProtocolError, HttpError, HttpNotFoundError, EndpointNotFoundError, Htt
23
23
  export { InstantDto, DateDto, TimeDto, DateTimeDto, InstantUtil, DateUtil, TimeUtil, DateTimeUtil, } from './http/datetime';
24
24
  export { HeaderRegistry } from './http/HeaderRegistry';
25
25
  export { ClientRegistry } from './http/ClientRegistry';
26
+ export type { ServiceUrlDeriver } from './http/ClientRegistry';
27
+ export { ErrorWireForm } from './http/ErrorTranslation';
28
+ export type { ErrorTranslation } from './http/ErrorTranslation';
29
+ export { templateDeriver } from './http/templateDeriver';
26
30
  export { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';
27
31
  export { ContextReader } from './http/ContextReader';
28
32
  export type { ContextRead } from './http/ContextReader';
package/src/index.js CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.ENTITY_NOT_FOUND = exports.HttpUserError = exports.HttpVendorError = exports.HttpInternalServerError = exports.HttpGatewayTimeoutError = exports.HttpBadGatewayError = exports.HttpTimeoutError = exports.HttpForbiddenError = exports.HttpUnauthorizedError = exports.HttpBadRequestError = exports.EndpointNotFoundError = exports.HttpNotFoundError = exports.HttpError = exports.ProtocolError = exports.Secrets = exports.METADATA_KEYS = exports.RouteMetadata = exports.AuthMeta = exports.validateNoConflictingDecorators = exports.getQueueName = exports.assertPubSubConventions = exports.assertApiKind = exports.getApiKind = exports.assertEveryEndpointHasAuthMode = exports.getAuthMode = exports.getAuthMeta = exports.isApiPath = exports.getEndpoints = exports.getApiPath = exports.Queue = exports.PubSub = exports.Rpc = exports.AuthSharedSecret = exports.AuthOidc = exports.Auth = exports.AuthJwt = exports.Public = exports.AuthenticationConfig = exports.Authentication = exports.Endpoint = exports.ApiPath = exports.LogManager = exports.ConsoleLoggerFactory = exports.ConsoleLogger = exports.DESIGN_METADATA_KEYS = exports.isDocumentDesign = exports.DocumentDesign = exports.ContextTuple = exports.ContextKey = exports.toError = void 0;
12
- exports.SerializedError = exports.SerializedMap = exports.RecordSerializer = exports.getDoNotRecordFields = exports.DoNotRecord = exports.RecordedTestCase = exports.RecordedError = exports.RecordedEndpoint = exports.RecorderKeys = exports.LogApiCall = exports.ContextMgr = exports.WebpiecesCoreHeaders = exports.ClientRegistry = exports.HeaderRegistry = exports.DateTimeUtil = exports.TimeUtil = exports.DateUtil = exports.InstantUtil = exports.NO_REG_CODE = exports.WRONG_COMPANY = exports.WRONG_DOMAIN = exports.EMAIL_NOT_CONFIRMED = exports.NOT_APPROVED = exports.WRONG_LOGIN = exports.WRONG_LOGIN_TYPE = void 0;
12
+ exports.SerializedError = exports.SerializedMap = exports.RecordSerializer = exports.getDoNotRecordFields = exports.DoNotRecord = exports.RecordedTestCase = exports.RecordedError = exports.RecordedEndpoint = exports.RecorderKeys = exports.LogApiCall = exports.ContextMgr = exports.WebpiecesCoreHeaders = exports.templateDeriver = exports.ErrorWireForm = exports.ClientRegistry = exports.HeaderRegistry = exports.DateTimeUtil = exports.TimeUtil = exports.DateUtil = exports.InstantUtil = exports.NO_REG_CODE = exports.WRONG_COMPANY = exports.WRONG_DOMAIN = exports.EMAIL_NOT_CONFIRMED = exports.NOT_APPROVED = exports.WRONG_LOGIN = exports.WRONG_LOGIN_TYPE = void 0;
13
13
  var errorUtils_1 = require("./lib/errorUtils");
14
14
  Object.defineProperty(exports, "toError", { enumerable: true, get: function () { return errorUtils_1.toError; } });
15
15
  var ContextKey_1 = require("./ContextKey");
@@ -102,6 +102,12 @@ var HeaderRegistry_1 = require("./http/HeaderRegistry");
102
102
  Object.defineProperty(exports, "HeaderRegistry", { enumerable: true, get: function () { return HeaderRegistry_1.HeaderRegistry; } });
103
103
  var ClientRegistry_1 = require("./http/ClientRegistry");
104
104
  Object.defineProperty(exports, "ClientRegistry", { enumerable: true, get: function () { return ClientRegistry_1.ClientRegistry; } });
105
+ // Pluggable, bidirectional error translation (app exception <-> wire form). Registered on
106
+ // ClientRegistry at startup; consulted before the built-in webpieces mapping on BOTH sides.
107
+ var ErrorTranslation_1 = require("./http/ErrorTranslation");
108
+ Object.defineProperty(exports, "ErrorWireForm", { enumerable: true, get: function () { return ErrorTranslation_1.ErrorWireForm; } });
109
+ var templateDeriver_1 = require("./http/templateDeriver");
110
+ Object.defineProperty(exports, "templateDeriver", { enumerable: true, get: function () { return templateDeriver_1.templateDeriver; } });
105
111
  var WebpiecesCoreHeaders_1 = require("./http/WebpiecesCoreHeaders");
106
112
  Object.defineProperty(exports, "WebpiecesCoreHeaders", { enumerable: true, get: function () { return WebpiecesCoreHeaders_1.WebpiecesCoreHeaders; } });
107
113
  // BROWSER-ONLY outbound-header propagation (app-held store + registry -> outbound HTTP headers).
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/core/core-util/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @webpieces/core-util\n *\n * Utility functions for WebPieces applications.\n * This package works in both browser and Node.js environments.\n *\n * @packageDocumentation\n */\n\nexport { toError } from './lib/errorUtils';\nexport { ContextKey } from './ContextKey';\nexport { ContextTuple } from './ContextTuple';\n\n// @DocumentDesign — DI-design-root marker. Applies to ANY project kind (server\n// controllers AND library impl classes), so it lives here (browser + Node) rather\n// than in a server-only routing package.\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './DocumentDesign';\n\n// Logging (merged from former @webpieces/wp-logging).\n// Pluggable logging interface + a browser-safe console default; apps plug in\n// bunyan/winston/pino/etc. via LogManager.setFactory(...). Browser + Node.\nexport type { Logger, LogLevel } from './logging/Logger';\nexport type { LoggerFactory } from './logging/LoggerFactory';\nexport { ConsoleLogger } from './logging/ConsoleLogger';\nexport { ConsoleLoggerFactory } from './logging/ConsoleLoggerFactory';\nexport { LogManager } from './logging/LogManager';\n\n// HTTP API contract (merged from former @webpieces/http-api).\n// Shared HTTP API definition consumed by both client and server: REST\n// decorators, the HttpError hierarchy, datetime DTOs, platform-header\n// registry/readers, ValidateImplementation, and the test-case recorder\n// contract. Pure definitions — express-free, browser + Node safe.\n\n// API definition decorators\nexport {\n ApiPath,\n Endpoint,\n Authentication,\n AuthenticationConfig,\n // Auth mode decorators (clean service-to-service + user JWT model)\n Public,\n AuthJwt,\n Auth,\n AuthOidc,\n AuthSharedSecret,\n // API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n Rpc,\n PubSub,\n Queue,\n getApiPath,\n getEndpoints,\n isApiPath,\n getAuthMeta,\n getAuthMode,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n validateNoConflictingDecorators,\n AuthMeta,\n RouteMetadata,\n METADATA_KEYS,\n} from './http/decorators';\nexport type { AuthMode, ApiKind, JwtRequirement } from './http/decorators';\n// Client-side shared-secret store (the value THIS service sends per @AuthSharedSecret key).\nexport { Secrets } from './http/Secrets';\n\n// Type validators\nexport { ValidateImplementation } from './http/validators';\n\n// HTTP errors\nexport {\n ProtocolError,\n HttpError,\n HttpNotFoundError,\n EndpointNotFoundError,\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpTimeoutError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n HttpInternalServerError,\n HttpVendorError,\n HttpUserError,\n // Error subtype constants\n ENTITY_NOT_FOUND,\n WRONG_LOGIN_TYPE,\n WRONG_LOGIN,\n NOT_APPROVED,\n EMAIL_NOT_CONFIRMED,\n WRONG_DOMAIN,\n WRONG_COMPANY,\n NO_REG_CODE,\n} from './http/errors';\n\n// Date/Time DTOs and Utilities (inspired by Java Time / JSR-310)\nexport {\n InstantDto,\n DateDto,\n TimeDto,\n DateTimeDto,\n InstantUtil,\n DateUtil,\n TimeUtil,\n DateTimeUtil,\n} from './http/datetime';\n\n// Context keys + registry (the global magic-context header system)\nexport { HeaderRegistry } from './http/HeaderRegistry';\nexport { ClientRegistry } from './http/ClientRegistry';\nexport { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';\nexport { ContextReader } from './http/ContextReader';\nexport type { ContextRead } from './http/ContextReader';\n\n// BROWSER-ONLY outbound-header propagation (app-held store + registry -> outbound HTTP headers).\n// Only @webpieces/http-client-browser may name it; the server reads RequestContext directly via\n// RequestContextHeaders in the Node-only @webpieces/core-context.\nexport { ContextMgr } from './http/ContextMgr';\n\n// API-call logging helper (uses LogManager above)\nexport { LogApiCall } from './http/LogApiCall';\n\n// Test-case recording contract (impl lives in http-server; hooks in http-client)\nexport { TestCaseRecorder, RecorderKeys } from './http/recorder/TestCaseRecorder';\nexport { RecordedEndpoint, RecordedError, RecordedTestCase } from './http/recorder/RecordedEndpoint';\nexport { DoNotRecord, getDoNotRecordFields } from './http/recorder/DoNotRecord';\nexport { RecordSerializer, SerializedMap, SerializedError } from './http/recorder/RecordSerializer';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/core/core-util/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;AAEH,+CAA2C;AAAlC,qGAAA,OAAO,OAAA;AAChB,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,+EAA+E;AAC/E,kFAAkF;AAClF,yCAAyC;AACzC,mDAA0F;AAAjF,gHAAA,cAAc,OAAA;AAAE,kHAAA,gBAAgB,OAAA;AAAE,sHAAA,oBAAoB,OAAA;AAO/D,yDAAwD;AAA/C,8GAAA,aAAa,OAAA;AACtB,uEAAsE;AAA7D,4HAAA,oBAAoB,OAAA;AAC7B,mDAAkD;AAAzC,wGAAA,UAAU,OAAA;AAEnB,8DAA8D;AAC9D,sEAAsE;AACtE,sEAAsE;AACtE,uEAAuE;AACvE,kEAAkE;AAElE,4BAA4B;AAC5B,gDA6B2B;AA5BvB,qGAAA,OAAO,OAAA;AACP,sGAAA,QAAQ,OAAA;AACR,4GAAA,cAAc,OAAA;AACd,kHAAA,oBAAoB,OAAA;AACpB,mEAAmE;AACnE,oGAAA,MAAM,OAAA;AACN,qGAAA,OAAO,OAAA;AACP,kGAAA,IAAI,OAAA;AACJ,sGAAA,QAAQ,OAAA;AACR,8GAAA,gBAAgB,OAAA;AAChB,sDAAsD;AACtD,iGAAA,GAAG,OAAA;AACH,oGAAA,MAAM,OAAA;AACN,mGAAA,KAAK,OAAA;AACL,wGAAA,UAAU,OAAA;AACV,0GAAA,YAAY,OAAA;AACZ,uGAAA,SAAS,OAAA;AACT,yGAAA,WAAW,OAAA;AACX,yGAAA,WAAW,OAAA;AACX,4HAAA,8BAA8B,OAAA;AAC9B,wGAAA,UAAU,OAAA;AACV,2GAAA,aAAa,OAAA;AACb,qHAAA,uBAAuB,OAAA;AACvB,0GAAA,YAAY,OAAA;AACZ,6HAAA,+BAA+B,OAAA;AAC/B,sGAAA,QAAQ,OAAA;AACR,2GAAA,aAAa,OAAA;AACb,2GAAA,aAAa,OAAA;AAGjB,4FAA4F;AAC5F,0CAAyC;AAAhC,kGAAA,OAAO,OAAA;AAKhB,cAAc;AACd,wCAuBuB;AAtBnB,uGAAA,aAAa,OAAA;AACb,mGAAA,SAAS,OAAA;AACT,2GAAA,iBAAiB,OAAA;AACjB,+GAAA,qBAAqB,OAAA;AACrB,6GAAA,mBAAmB,OAAA;AACnB,+GAAA,qBAAqB,OAAA;AACrB,4GAAA,kBAAkB,OAAA;AAClB,0GAAA,gBAAgB,OAAA;AAChB,6GAAA,mBAAmB,OAAA;AACnB,iHAAA,uBAAuB,OAAA;AACvB,iHAAA,uBAAuB,OAAA;AACvB,yGAAA,eAAe,OAAA;AACf,uGAAA,aAAa,OAAA;AACb,0BAA0B;AAC1B,0GAAA,gBAAgB,OAAA;AAChB,0GAAA,gBAAgB,OAAA;AAChB,qGAAA,WAAW,OAAA;AACX,sGAAA,YAAY,OAAA;AACZ,6GAAA,mBAAmB,OAAA;AACnB,sGAAA,YAAY,OAAA;AACZ,uGAAA,aAAa,OAAA;AACb,qGAAA,WAAW,OAAA;AAGf,iEAAiE;AACjE,4CASyB;AAJrB,uGAAA,WAAW,OAAA;AACX,oGAAA,QAAQ,OAAA;AACR,oGAAA,QAAQ,OAAA;AACR,wGAAA,YAAY,OAAA;AAGhB,mEAAmE;AACnE,wDAAuD;AAA9C,gHAAA,cAAc,OAAA;AACvB,wDAAuD;AAA9C,gHAAA,cAAc,OAAA;AAEvB,0FAA0F;AAC1F,4FAA4F;AAC5F,4DAAwD;AAA/C,iHAAA,aAAa,OAAA;AAEtB,0DAAyD;AAAhD,kHAAA,eAAe,OAAA;AACxB,oEAAmE;AAA1D,4HAAA,oBAAoB,OAAA;AAI7B,iGAAiG;AACjG,gGAAgG;AAChG,kEAAkE;AAClE,gDAA+C;AAAtC,wGAAA,UAAU,OAAA;AAEnB,kDAAkD;AAClD,gDAA+C;AAAtC,wGAAA,UAAU,OAAA;AAEnB,iFAAiF;AACjF,qEAAkF;AAAvD,gHAAA,YAAY,OAAA;AACvC,qEAAqG;AAA5F,oHAAA,gBAAgB,OAAA;AAAE,iHAAA,aAAa,OAAA;AAAE,oHAAA,gBAAgB,OAAA;AAC1D,2DAAgF;AAAvE,0GAAA,WAAW,OAAA;AAAE,mHAAA,oBAAoB,OAAA;AAC1C,qEAAoG;AAA3F,oHAAA,gBAAgB,OAAA;AAAE,iHAAA,aAAa,OAAA;AAAE,mHAAA,eAAe,OAAA","sourcesContent":["/**\n * @webpieces/core-util\n *\n * Utility functions for WebPieces applications.\n * This package works in both browser and Node.js environments.\n *\n * @packageDocumentation\n */\n\nexport { toError } from './lib/errorUtils';\nexport { ContextKey } from './ContextKey';\nexport { ContextTuple } from './ContextTuple';\n\n// @DocumentDesign — DI-design-root marker. Applies to ANY project kind (server\n// controllers AND library impl classes), so it lives here (browser + Node) rather\n// than in a server-only routing package.\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './DocumentDesign';\n\n// Logging (merged from former @webpieces/wp-logging).\n// Pluggable logging interface + a browser-safe console default; apps plug in\n// bunyan/winston/pino/etc. via LogManager.setFactory(...). Browser + Node.\nexport type { Logger, LogLevel } from './logging/Logger';\nexport type { LoggerFactory } from './logging/LoggerFactory';\nexport { ConsoleLogger } from './logging/ConsoleLogger';\nexport { ConsoleLoggerFactory } from './logging/ConsoleLoggerFactory';\nexport { LogManager } from './logging/LogManager';\n\n// HTTP API contract (merged from former @webpieces/http-api).\n// Shared HTTP API definition consumed by both client and server: REST\n// decorators, the HttpError hierarchy, datetime DTOs, platform-header\n// registry/readers, ValidateImplementation, and the test-case recorder\n// contract. Pure definitions — express-free, browser + Node safe.\n\n// API definition decorators\nexport {\n ApiPath,\n Endpoint,\n Authentication,\n AuthenticationConfig,\n // Auth mode decorators (clean service-to-service + user JWT model)\n Public,\n AuthJwt,\n Auth,\n AuthOidc,\n AuthSharedSecret,\n // API kind (RPC vs PubSub/Cloud Tasks) + queue naming\n Rpc,\n PubSub,\n Queue,\n getApiPath,\n getEndpoints,\n isApiPath,\n getAuthMeta,\n getAuthMode,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n validateNoConflictingDecorators,\n AuthMeta,\n RouteMetadata,\n METADATA_KEYS,\n} from './http/decorators';\nexport type { AuthMode, ApiKind, JwtRequirement } from './http/decorators';\n// Client-side shared-secret store (the value THIS service sends per @AuthSharedSecret key).\nexport { Secrets } from './http/Secrets';\n\n// Type validators\nexport { ValidateImplementation } from './http/validators';\n\n// HTTP errors\nexport {\n ProtocolError,\n HttpError,\n HttpNotFoundError,\n EndpointNotFoundError,\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpTimeoutError,\n HttpBadGatewayError,\n HttpGatewayTimeoutError,\n HttpInternalServerError,\n HttpVendorError,\n HttpUserError,\n // Error subtype constants\n ENTITY_NOT_FOUND,\n WRONG_LOGIN_TYPE,\n WRONG_LOGIN,\n NOT_APPROVED,\n EMAIL_NOT_CONFIRMED,\n WRONG_DOMAIN,\n WRONG_COMPANY,\n NO_REG_CODE,\n} from './http/errors';\n\n// Date/Time DTOs and Utilities (inspired by Java Time / JSR-310)\nexport {\n InstantDto,\n DateDto,\n TimeDto,\n DateTimeDto,\n InstantUtil,\n DateUtil,\n TimeUtil,\n DateTimeUtil,\n} from './http/datetime';\n\n// Context keys + registry (the global magic-context header system)\nexport { HeaderRegistry } from './http/HeaderRegistry';\nexport { ClientRegistry } from './http/ClientRegistry';\nexport type { ServiceUrlDeriver } from './http/ClientRegistry';\n// Pluggable, bidirectional error translation (app exception <-> wire form). Registered on\n// ClientRegistry at startup; consulted before the built-in webpieces mapping on BOTH sides.\nexport { ErrorWireForm } from './http/ErrorTranslation';\nexport type { ErrorTranslation } from './http/ErrorTranslation';\nexport { templateDeriver } from './http/templateDeriver';\nexport { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';\nexport { ContextReader } from './http/ContextReader';\nexport type { ContextRead } from './http/ContextReader';\n\n// BROWSER-ONLY outbound-header propagation (app-held store + registry -> outbound HTTP headers).\n// Only @webpieces/http-client-browser may name it; the server reads RequestContext directly via\n// RequestContextHeaders in the Node-only @webpieces/core-context.\nexport { ContextMgr } from './http/ContextMgr';\n\n// API-call logging helper (uses LogManager above)\nexport { LogApiCall } from './http/LogApiCall';\n\n// Test-case recording contract (impl lives in http-server; hooks in http-client)\nexport { TestCaseRecorder, RecorderKeys } from './http/recorder/TestCaseRecorder';\nexport { RecordedEndpoint, RecordedError, RecordedTestCase } from './http/recorder/RecordedEndpoint';\nexport { DoNotRecord, getDoNotRecordFields } from './http/recorder/DoNotRecord';\nexport { RecordSerializer, SerializedMap, SerializedError } from './http/recorder/RecordSerializer';\n"]}