@webpieces/core-util 0.4.386 → 0.4.387

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.4.386",
3
+ "version": "0.4.387",
4
4
  "description": "Utility functions for WebPieces - works in browser and Node.js",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -1,5 +1,14 @@
1
1
  import { RouteMetadata } from "./decorators";
2
2
  import { ApiSide } from "./ApiCallInfo";
3
+ /**
4
+ * Options for {@link LogApiCallImpl.execute}. `allowVoidResponse` opts OUT of the strict
5
+ * falsy-response guard for callers that legitimately return void/undefined (e.g. a local firestore
6
+ * wrapper). Defaults to strict so RPC callers keep the safety net.
7
+ */
8
+ export declare class LogApiCallOptions {
9
+ allowVoidResponse?: boolean;
10
+ constructor(allowVoidResponse?: boolean);
11
+ }
3
12
  /**
4
13
  * LogApiCall - Generic API call logging utility, used by BOTH server-side (LogApiFilter) and
5
14
  * client-side (ProxyClient) for one consistent logging shape across the framework.
@@ -33,6 +42,11 @@ export declare class LogApiCallImpl {
33
42
  * @param meta - Route metadata with controllerClassName and methodName
34
43
  * @param requestDto - The request DTO
35
44
  * @param method - The method to execute
45
+ * @param options - `allowVoidResponse: true` opts OUT of the strict falsy-response guard, for
46
+ * callers that legitimately return void/undefined (e.g. a local firestore wrapper whose
47
+ * `setDocument` returns void, or a `getDocById` miss returning undefined). Defaults to strict,
48
+ * so RPC callers (LogApiFilter, ProxyClient) keep the safety net: an HTTP endpoint returning
49
+ * nothing is almost always a bug.
36
50
  *
37
51
  * Correlation fields (requestId, tenantId, ...) are NOT stamped here — a logging BACKEND owns that,
38
52
  * reading RequestContext on every record. What IS stamped here is the per-call `api` tag, and only
@@ -41,7 +55,7 @@ export declare class LogApiCallImpl {
41
55
  * it. Cost: only the `[API-*]` req/resp lines carry `api`, not lines emitted mid-call — which is
42
56
  * exactly what the GCP filters (`jsonPayload.api.*`) want.
43
57
  */
44
- execute(side: ApiSide, meta: RouteMetadata, requestDto: any, method: (dto: any) => Promise<any>): Promise<any>;
58
+ execute(side: ApiSide, meta: RouteMetadata, requestDto: any, method: (dto: any) => Promise<any>, options?: LogApiCallOptions): Promise<any>;
45
59
  /**
46
60
  * Is this error a NON-failure for HEALTH/METRICS — the process working CORRECTLY (log OTHER, api
47
61
  * result:'success') — rather than a real failure to surface (log FAIL, result:'failure')?
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LogApiCall = exports.LogApiCallImpl = void 0;
3
+ exports.LogApiCall = exports.LogApiCallImpl = exports.LogApiCallOptions = void 0;
4
4
  const errors_1 = require("./errors");
5
5
  const errorUtils_1 = require("../lib/errorUtils");
6
6
  const LogManager_1 = require("../logging/LogManager");
@@ -8,6 +8,18 @@ const ApiCallInfo_1 = require("./ApiCallInfo");
8
8
  const ApiCallContext_1 = require("./ApiCallContext");
9
9
  const WebpiecesCoreHeaders_1 = require("./WebpiecesCoreHeaders");
10
10
  const log = LogManager_1.LogManager.getLogger('LogApiCall');
11
+ /**
12
+ * Options for {@link LogApiCallImpl.execute}. `allowVoidResponse` opts OUT of the strict
13
+ * falsy-response guard for callers that legitimately return void/undefined (e.g. a local firestore
14
+ * wrapper). Defaults to strict so RPC callers keep the safety net.
15
+ */
16
+ class LogApiCallOptions {
17
+ allowVoidResponse;
18
+ constructor(allowVoidResponse) {
19
+ this.allowVoidResponse = allowVoidResponse;
20
+ }
21
+ }
22
+ exports.LogApiCallOptions = LogApiCallOptions;
11
23
  /**
12
24
  * LogApiCall - Generic API call logging utility, used by BOTH server-side (LogApiFilter) and
13
25
  * client-side (ProxyClient) for one consistent logging shape across the framework.
@@ -41,6 +53,11 @@ class LogApiCallImpl {
41
53
  * @param meta - Route metadata with controllerClassName and methodName
42
54
  * @param requestDto - The request DTO
43
55
  * @param method - The method to execute
56
+ * @param options - `allowVoidResponse: true` opts OUT of the strict falsy-response guard, for
57
+ * callers that legitimately return void/undefined (e.g. a local firestore wrapper whose
58
+ * `setDocument` returns void, or a `getDocById` miss returning undefined). Defaults to strict,
59
+ * so RPC callers (LogApiFilter, ProxyClient) keep the safety net: an HTTP endpoint returning
60
+ * nothing is almost always a bug.
44
61
  *
45
62
  * Correlation fields (requestId, tenantId, ...) are NOT stamped here — a logging BACKEND owns that,
46
63
  * reading RequestContext on every record. What IS stamped here is the per-call `api` tag, and only
@@ -53,7 +70,7 @@ class LogApiCallImpl {
53
70
  // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary (matches ProxyClient)
54
71
  requestDto,
55
72
  // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary
56
- method
73
+ method, options
57
74
  // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary
58
75
  ) {
59
76
  // Throws if no ApiCallContext was installed at startup, or there is no active scope to stamp
@@ -79,7 +96,7 @@ class LogApiCallImpl {
79
96
  if (!requestDto)
80
97
  throw new Error(`Request cannot be null and was from ${cls}.${meta.methodName}`);
81
98
  const response = await method(requestDto);
82
- if (!response)
99
+ if (!options?.allowVoidResponse && !response)
83
100
  throw new Error(`Response cannot be null and was from ${cls}.${meta.methodName}`);
84
101
  stamp(new ApiCallInfo_1.ApiCallInfo(side, 'response', 'success', meta.path, meta.methodName, cls), () => log.info(`[API-${side}-resp-SUCCESS] ${cls}.${meta.methodName} response=${JSON.stringify(response)}`));
85
102
  return response;
@@ -1 +1 @@
1
- {"version":3,"file":"LogApiCall.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/LogApiCall.ts"],"names":[],"mappings":"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","sourcesContent":["import {RouteMetadata} from \"./decorators\";\nimport {\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpUserError,\n} from './errors';\nimport {toError} from \"../lib/errorUtils\";\nimport {LogManager} from \"../logging/LogManager\";\nimport {ApiCallInfo, ApiSide} from \"./ApiCallInfo\";\nimport {ApiCallContextHolder} from \"./ApiCallContext\";\nimport {WebpiecesCoreHeaders} from \"./WebpiecesCoreHeaders\";\n\nconst log = LogManager.getLogger('LogApiCall');\n\n\n/**\n * LogApiCall - Generic API call logging utility, used by BOTH server-side (LogApiFilter) and\n * client-side (ProxyClient) for one consistent logging shape across the framework.\n *\n * TWO things happen around each call:\n * 1. Text lines are emitted (the human-readable `[API-...]` patterns below).\n * 2. A structured {@link ApiCallInfo} tag is stamped into the ambient request context via the\n * {@link ApiCallContextHolder} seam, so EVERY log line emitted during the call (not just the\n * req/resp lines) inherits a filterable `api` object — surfacing in GCP as\n * `jsonPayload.api.{side,type,result,path,method}`.\n *\n * BROWSER-SAFE: this lives in core-util and runs in the browser bundle (via ProxyClient →\n * BrowserProxyClient), so it MUST NOT import `RequestContext` (Node async_hooks, and a circular dep).\n * It stamps through the {@link ApiCallContext} seam instead: `setupRuntime` installs a\n * RequestContext-backed impl on a Node server, and `ClientHttpBrowserFactory` a module-global impl in a\n * browser. If neither ran, {@link ApiCallContextHolder.get} throws (loud misconfiguration).\n *\n * Singleton, mirroring `RequestContext`: use the exported {@link LogApiCall} constant, not `new`.\n *\n * Logging format patterns:\n * - [API-{side}-req] ClassName.methodName request={...}\n * - [API-{side}-resp-SUCCESS] ClassName.methodName response={...}\n * - [API-{side}-resp-OTHER] ClassName.methodName errorType={...} (user errors)\n * - [API-{side}-resp-FAIL] ClassName.methodName error={...} (server errors)\n */\nexport class LogApiCallImpl {\n\n /**\n * Execute an API call with logging + `api` context-tagging around it.\n *\n * @param side - 'client' (outbound call this process made) or 'server' (inbound call it handled)\n * @param meta - Route metadata with controllerClassName and methodName\n * @param requestDto - The request DTO\n * @param method - The method to execute\n *\n * Correlation fields (requestId, tenantId, ...) are NOT stamped here — a logging BACKEND owns that,\n * reading RequestContext on every record. What IS stamped here is the per-call `api` tag, and only\n * for the SYNCHRONOUS span of each log line: set → log → remove. Because the tag is never held across\n * `await method(...)`, a concurrent browser call (single-threaded, one global slot) can never clobber\n * it. Cost: only the `[API-*]` req/resp lines carry `api`, not lines emitted mid-call — which is\n * exactly what the GCP filters (`jsonPayload.api.*`) want.\n */\n public async execute(\n side: ApiSide,\n meta: RouteMetadata,\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary (matches ProxyClient)\n requestDto: any,\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary\n method: (dto: any) => Promise<any>\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary\n ): Promise<any> {\n // Throws if no ApiCallContext was installed at startup, or there is no active scope to stamp\n // into (loud misconfiguration — an api call with nowhere to tag is a bug).\n const ctx = ApiCallContextHolder.get();\n if (!ctx.isActive()) {\n throw new Error(\n 'LogApiCall requires an ACTIVE ApiCallContext. On a Node server, run inside the ' +\n 'RequestContext.run(...) a server filter opens; in a browser, build ClientHttpBrowserFactory first.',\n );\n }\n const key = WebpiecesCoreHeaders.API_CALL_INFO;\n const server = side === 'server';\n const cls = meta.controllerClassName;\n // set → emit → remove, as ONE synchronous span: the tag is live only while the logger reads it,\n // never across an await, so a single browser global slot can never be clobbered by a concurrent call.\n const stamp = (info: ApiCallInfo, emit: () => void): void => {\n ctx.set(key, info);\n emit();\n ctx.remove(key);\n };\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- LogApiCall logs errors before re-throwing to caller\n try {\n stamp(new ApiCallInfo(side, 'request', undefined, meta.path, meta.methodName, cls), () =>\n log.info(`[API-${side}-req] ${cls}.${meta.methodName} ${meta.path} request=${JSON.stringify(requestDto)}`));\n\n if(!requestDto)\n throw new Error(`Request cannot be null and was from ${cls}.${meta.methodName}`);\n\n const response = await method(requestDto);\n\n if(!response)\n throw new Error(`Response cannot be null and was from ${cls}.${meta.methodName}`);\n\n stamp(new ApiCallInfo(side, 'response', 'success', meta.path, meta.methodName, cls), () =>\n log.info(`[API-${side}-resp-SUCCESS] ${cls}.${meta.methodName} response=${JSON.stringify(response)}`));\n\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n const errorType = error.constructor.name;\n const errorMessage = error.message;\n // Side-dependent: a 4xx the SERVER raised is a handled non-failure (OTHER); the same 4xx a\n // CLIENT receives means its call FAILED. HttpUserError (266) is never a failure, either side.\n const isUser = this.isUserError(error, server);\n\n stamp(new ApiCallInfo(side, 'response', isUser ? 'success' : 'failure', meta.path, meta.methodName, cls), () =>\n isUser\n ? log.warn(`[API-${side}-resp-OTHER] ${cls}.${meta.methodName} errorType=${errorType}`)\n : log.error(`[API-${side}-resp-FAIL] ${cls}.${meta.methodName} errorType=${errorType} error=${errorMessage}`));\n throw error;\n }\n }\n\n /**\n * Is this error a NON-failure for HEALTH/METRICS — the process working CORRECTLY (log OTHER, api\n * result:'success') — rather than a real failure to surface (log FAIL, result:'failure')?\n *\n * The question is \"are things WORKING?\", NOT \"was it an HTTP 4xx vs 5xx\" — and the two are not the\n * same (see 408 below). Classification is by portable Error TYPE, never by transport: LogApiCall runs\n * deep in the stack (in-process calls, pubsub/queue handlers, HTTP — one code path) and only ever\n * sees a thrown Error. The Http* classes are poorly named for that (the `Http` prefix is historical),\n * but they ARE portable error types that travel with the throw anywhere, so matching the TYPE works\n * with or without any HTTP in the picture. Do NOT swap this for an `error.code` 4xx range check — that\n * both re-couples to HTTP AND would bury 408.\n *\n * SERVER — a healthy server correctly rejecting a CLIENT'S mistake is metrics NOISE, not a failure:\n * - HttpBadRequestError (400) \"your request is malformed\"\n * - HttpUnauthorizedError (401) \"you're not authenticated\"\n * - HttpForbiddenError (403) \"authenticated, but not allowed\"\n * - HttpNotFoundError (404) \"wrong url / no such entity\" (EndpointNotFoundError is a subclass)\n * → the server is fine, the caller erred → result:'success', logged OTHER.\n *\n * SERVER — something may actually be WRONG, so SURFACE it (result:'failure', logged FAIL):\n * - HttpTimeoutError (408): a 4xx, but the client may NEVER have seen the response — deliberately\n * absent from the list below so it counts as a failure.\n * - 500 / 502 / 504 / 598, and any non-Http Error: real failures.\n *\n * HttpUserError (266): ALWAYS a non-failure, server OR client — an expected \"user made a mistake\" signal.\n * CLIENT: receiving ANY error except 266 means the outbound call FAILED → result:'failure'.\n *\n * @param error - The already-normalized error (callers pass toError(err), never a raw catch value)\n * @param server - True when this side is the SERVER handling an inbound call; false for a CLIENT's outbound call\n * @returns true if this should be treated as a non-failure (OTHER / result:'success')\n */\n isUserError(error: Error, server: boolean): boolean {\n // 266 is the one error that is never a failure, on either side.\n if (error instanceof HttpUserError) {\n return true;\n }\n // A client that RECEIVED any error (except the 266 above) made a failed call.\n if (!server) {\n return false;\n }\n // SERVER: only a healthy rejection of the caller's mistake is a non-failure. Note 408\n // (HttpTimeoutError) is intentionally NOT here — the client may never have seen the response.\n return (\n error instanceof HttpBadRequestError ||\n error instanceof HttpUnauthorizedError ||\n error instanceof HttpForbiddenError ||\n error instanceof HttpNotFoundError\n );\n }\n}\n\n/**\n * The process-wide {@link LogApiCallImpl} singleton — mirrors the `RequestContext` export pattern.\n * Callers use `LogApiCall.execute(...)`, never `new`.\n */\nexport const LogApiCall = new LogApiCallImpl();\n"]}
1
+ {"version":3,"file":"LogApiCall.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/LogApiCall.ts"],"names":[],"mappings":"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","sourcesContent":["import {RouteMetadata} from \"./decorators\";\nimport {\n HttpBadRequestError,\n HttpUnauthorizedError,\n HttpForbiddenError,\n HttpNotFoundError,\n HttpUserError,\n} from './errors';\nimport {toError} from \"../lib/errorUtils\";\nimport {LogManager} from \"../logging/LogManager\";\nimport {ApiCallInfo, ApiSide} from \"./ApiCallInfo\";\nimport {ApiCallContextHolder} from \"./ApiCallContext\";\nimport {WebpiecesCoreHeaders} from \"./WebpiecesCoreHeaders\";\n\nconst log = LogManager.getLogger('LogApiCall');\n\n/**\n * Options for {@link LogApiCallImpl.execute}. `allowVoidResponse` opts OUT of the strict\n * falsy-response guard for callers that legitimately return void/undefined (e.g. a local firestore\n * wrapper). Defaults to strict so RPC callers keep the safety net.\n */\nexport class LogApiCallOptions {\n allowVoidResponse?: boolean;\n\n constructor(allowVoidResponse?: boolean) {\n this.allowVoidResponse = allowVoidResponse;\n }\n}\n\n/**\n * LogApiCall - Generic API call logging utility, used by BOTH server-side (LogApiFilter) and\n * client-side (ProxyClient) for one consistent logging shape across the framework.\n *\n * TWO things happen around each call:\n * 1. Text lines are emitted (the human-readable `[API-...]` patterns below).\n * 2. A structured {@link ApiCallInfo} tag is stamped into the ambient request context via the\n * {@link ApiCallContextHolder} seam, so EVERY log line emitted during the call (not just the\n * req/resp lines) inherits a filterable `api` object — surfacing in GCP as\n * `jsonPayload.api.{side,type,result,path,method}`.\n *\n * BROWSER-SAFE: this lives in core-util and runs in the browser bundle (via ProxyClient →\n * BrowserProxyClient), so it MUST NOT import `RequestContext` (Node async_hooks, and a circular dep).\n * It stamps through the {@link ApiCallContext} seam instead: `setupRuntime` installs a\n * RequestContext-backed impl on a Node server, and `ClientHttpBrowserFactory` a module-global impl in a\n * browser. If neither ran, {@link ApiCallContextHolder.get} throws (loud misconfiguration).\n *\n * Singleton, mirroring `RequestContext`: use the exported {@link LogApiCall} constant, not `new`.\n *\n * Logging format patterns:\n * - [API-{side}-req] ClassName.methodName request={...}\n * - [API-{side}-resp-SUCCESS] ClassName.methodName response={...}\n * - [API-{side}-resp-OTHER] ClassName.methodName errorType={...} (user errors)\n * - [API-{side}-resp-FAIL] ClassName.methodName error={...} (server errors)\n */\nexport class LogApiCallImpl {\n\n /**\n * Execute an API call with logging + `api` context-tagging around it.\n *\n * @param side - 'client' (outbound call this process made) or 'server' (inbound call it handled)\n * @param meta - Route metadata with controllerClassName and methodName\n * @param requestDto - The request DTO\n * @param method - The method to execute\n * @param options - `allowVoidResponse: true` opts OUT of the strict falsy-response guard, for\n * callers that legitimately return void/undefined (e.g. a local firestore wrapper whose\n * `setDocument` returns void, or a `getDocById` miss returning undefined). Defaults to strict,\n * so RPC callers (LogApiFilter, ProxyClient) keep the safety net: an HTTP endpoint returning\n * nothing is almost always a bug.\n *\n * Correlation fields (requestId, tenantId, ...) are NOT stamped here — a logging BACKEND owns that,\n * reading RequestContext on every record. What IS stamped here is the per-call `api` tag, and only\n * for the SYNCHRONOUS span of each log line: set → log → remove. Because the tag is never held across\n * `await method(...)`, a concurrent browser call (single-threaded, one global slot) can never clobber\n * it. Cost: only the `[API-*]` req/resp lines carry `api`, not lines emitted mid-call — which is\n * exactly what the GCP filters (`jsonPayload.api.*`) want.\n */\n public async execute(\n side: ApiSide,\n meta: RouteMetadata,\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary (matches ProxyClient)\n requestDto: any,\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary\n method: (dto: any) => Promise<any>,\n options?: LogApiCallOptions\n // webpieces-disable no-any-unknown -- DTO types are erased at the api/proxy boundary\n ): Promise<any> {\n // Throws if no ApiCallContext was installed at startup, or there is no active scope to stamp\n // into (loud misconfiguration — an api call with nowhere to tag is a bug).\n const ctx = ApiCallContextHolder.get();\n if (!ctx.isActive()) {\n throw new Error(\n 'LogApiCall requires an ACTIVE ApiCallContext. On a Node server, run inside the ' +\n 'RequestContext.run(...) a server filter opens; in a browser, build ClientHttpBrowserFactory first.',\n );\n }\n const key = WebpiecesCoreHeaders.API_CALL_INFO;\n const server = side === 'server';\n const cls = meta.controllerClassName;\n // set → emit → remove, as ONE synchronous span: the tag is live only while the logger reads it,\n // never across an await, so a single browser global slot can never be clobbered by a concurrent call.\n const stamp = (info: ApiCallInfo, emit: () => void): void => {\n ctx.set(key, info);\n emit();\n ctx.remove(key);\n };\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- LogApiCall logs errors before re-throwing to caller\n try {\n stamp(new ApiCallInfo(side, 'request', undefined, meta.path, meta.methodName, cls), () =>\n log.info(`[API-${side}-req] ${cls}.${meta.methodName} ${meta.path} request=${JSON.stringify(requestDto)}`));\n\n if(!requestDto)\n throw new Error(`Request cannot be null and was from ${cls}.${meta.methodName}`);\n\n const response = await method(requestDto);\n\n if(!options?.allowVoidResponse && !response)\n throw new Error(`Response cannot be null and was from ${cls}.${meta.methodName}`);\n\n stamp(new ApiCallInfo(side, 'response', 'success', meta.path, meta.methodName, cls), () =>\n log.info(`[API-${side}-resp-SUCCESS] ${cls}.${meta.methodName} response=${JSON.stringify(response)}`));\n\n return response;\n } catch (err: unknown) {\n const error = toError(err);\n const errorType = error.constructor.name;\n const errorMessage = error.message;\n // Side-dependent: a 4xx the SERVER raised is a handled non-failure (OTHER); the same 4xx a\n // CLIENT receives means its call FAILED. HttpUserError (266) is never a failure, either side.\n const isUser = this.isUserError(error, server);\n\n stamp(new ApiCallInfo(side, 'response', isUser ? 'success' : 'failure', meta.path, meta.methodName, cls), () =>\n isUser\n ? log.warn(`[API-${side}-resp-OTHER] ${cls}.${meta.methodName} errorType=${errorType}`)\n : log.error(`[API-${side}-resp-FAIL] ${cls}.${meta.methodName} errorType=${errorType} error=${errorMessage}`));\n throw error;\n }\n }\n\n /**\n * Is this error a NON-failure for HEALTH/METRICS — the process working CORRECTLY (log OTHER, api\n * result:'success') — rather than a real failure to surface (log FAIL, result:'failure')?\n *\n * The question is \"are things WORKING?\", NOT \"was it an HTTP 4xx vs 5xx\" — and the two are not the\n * same (see 408 below). Classification is by portable Error TYPE, never by transport: LogApiCall runs\n * deep in the stack (in-process calls, pubsub/queue handlers, HTTP — one code path) and only ever\n * sees a thrown Error. The Http* classes are poorly named for that (the `Http` prefix is historical),\n * but they ARE portable error types that travel with the throw anywhere, so matching the TYPE works\n * with or without any HTTP in the picture. Do NOT swap this for an `error.code` 4xx range check — that\n * both re-couples to HTTP AND would bury 408.\n *\n * SERVER — a healthy server correctly rejecting a CLIENT'S mistake is metrics NOISE, not a failure:\n * - HttpBadRequestError (400) \"your request is malformed\"\n * - HttpUnauthorizedError (401) \"you're not authenticated\"\n * - HttpForbiddenError (403) \"authenticated, but not allowed\"\n * - HttpNotFoundError (404) \"wrong url / no such entity\" (EndpointNotFoundError is a subclass)\n * → the server is fine, the caller erred → result:'success', logged OTHER.\n *\n * SERVER — something may actually be WRONG, so SURFACE it (result:'failure', logged FAIL):\n * - HttpTimeoutError (408): a 4xx, but the client may NEVER have seen the response — deliberately\n * absent from the list below so it counts as a failure.\n * - 500 / 502 / 504 / 598, and any non-Http Error: real failures.\n *\n * HttpUserError (266): ALWAYS a non-failure, server OR client — an expected \"user made a mistake\" signal.\n * CLIENT: receiving ANY error except 266 means the outbound call FAILED → result:'failure'.\n *\n * @param error - The already-normalized error (callers pass toError(err), never a raw catch value)\n * @param server - True when this side is the SERVER handling an inbound call; false for a CLIENT's outbound call\n * @returns true if this should be treated as a non-failure (OTHER / result:'success')\n */\n isUserError(error: Error, server: boolean): boolean {\n // 266 is the one error that is never a failure, on either side.\n if (error instanceof HttpUserError) {\n return true;\n }\n // A client that RECEIVED any error (except the 266 above) made a failed call.\n if (!server) {\n return false;\n }\n // SERVER: only a healthy rejection of the caller's mistake is a non-failure. Note 408\n // (HttpTimeoutError) is intentionally NOT here — the client may never have seen the response.\n return (\n error instanceof HttpBadRequestError ||\n error instanceof HttpUnauthorizedError ||\n error instanceof HttpForbiddenError ||\n error instanceof HttpNotFoundError\n );\n }\n}\n\n/**\n * The process-wide {@link LogApiCallImpl} singleton — mirrors the `RequestContext` export pattern.\n * Callers use `LogApiCall.execute(...)`, never `new`.\n */\nexport const LogApiCall = new LogApiCallImpl();\n"]}
package/src/index.d.ts CHANGED
@@ -31,7 +31,7 @@ export { WebpiecesCoreHeaders } from './http/WebpiecesCoreHeaders';
31
31
  export { ContextReader } from './http/ContextReader';
32
32
  export type { ContextRead, StructuredContextRead } from './http/ContextReader';
33
33
  export { ContextMgr } from './http/ContextMgr';
34
- export { LogApiCall, LogApiCallImpl } from './http/LogApiCall';
34
+ export { LogApiCall, LogApiCallImpl, LogApiCallOptions } from './http/LogApiCall';
35
35
  export { ApiCallInfo } from './http/ApiCallInfo';
36
36
  export type { ApiSide, ApiType, ApiResult } from './http/ApiCallInfo';
37
37
  export { ApiCallContextHolder } from './http/ApiCallContext';
package/src/index.js CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  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.isFormPost = exports.getEndpointOptions = 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.ApiCallContextHolder = exports.ApiCallInfo = exports.LogApiCallImpl = 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 = exports.ENTITY_NOT_FOUND = exports.HttpUserError = void 0;
12
+ exports.SerializedError = exports.SerializedMap = exports.RecordSerializer = exports.getDoNotRecordFields = exports.DoNotRecord = exports.RecordedTestCase = exports.RecordedError = exports.RecordedEndpoint = exports.RecorderKeys = exports.ApiCallContextHolder = exports.ApiCallInfo = exports.LogApiCallOptions = exports.LogApiCallImpl = 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 = exports.ENTITY_NOT_FOUND = exports.HttpUserError = 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");
@@ -121,6 +121,7 @@ Object.defineProperty(exports, "ContextMgr", { enumerable: true, get: function (
121
121
  var LogApiCall_1 = require("./http/LogApiCall");
122
122
  Object.defineProperty(exports, "LogApiCall", { enumerable: true, get: function () { return LogApiCall_1.LogApiCall; } });
123
123
  Object.defineProperty(exports, "LogApiCallImpl", { enumerable: true, get: function () { return LogApiCall_1.LogApiCallImpl; } });
124
+ Object.defineProperty(exports, "LogApiCallOptions", { enumerable: true, get: function () { return LogApiCall_1.LogApiCallOptions; } });
124
125
  // The structured `api` tag + the context-writer seam LogApiCall stamps through. The Node
125
126
  // RequestContext-backed impl is installed by @webpieces/core-context; the browser gets the no-op.
126
127
  var ApiCallInfo_1 = require("./http/ApiCallInfo");
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 getEndpointOptions,\n isFormPost,\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, EndpointOptions } 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, StructuredContextRead } 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). Singleton: use the LogApiCall constant, not `new`.\nexport { LogApiCall, LogApiCallImpl } from './http/LogApiCall';\n\n// The structured `api` tag + the context-writer seam LogApiCall stamps through. The Node\n// RequestContext-backed impl is installed by @webpieces/core-context; the browser gets the no-op.\nexport { ApiCallInfo } from './http/ApiCallInfo';\nexport type { ApiSide, ApiType, ApiResult } from './http/ApiCallInfo';\nexport { ApiCallContextHolder } from './http/ApiCallContext';\nexport type { ApiCallContext } from './http/ApiCallContext';\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":"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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 getEndpointOptions,\n isFormPost,\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, EndpointOptions } 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, StructuredContextRead } 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). Singleton: use the LogApiCall constant, not `new`.\nexport { LogApiCall, LogApiCallImpl, LogApiCallOptions } from './http/LogApiCall';\n\n// The structured `api` tag + the context-writer seam LogApiCall stamps through. The Node\n// RequestContext-backed impl is installed by @webpieces/core-context; the browser gets the no-op.\nexport { ApiCallInfo } from './http/ApiCallInfo';\nexport type { ApiSide, ApiType, ApiResult } from './http/ApiCallInfo';\nexport { ApiCallContextHolder } from './http/ApiCallContext';\nexport type { ApiCallContext } from './http/ApiCallContext';\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"]}