@lucern/sdk 1.0.11 → 1.0.13
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/CHANGELOG.md +10 -0
- package/README.md +24 -27
- package/dist/.generated +2 -0
- package/dist/accessControl.d.ts +19 -26
- package/dist/accessControl.js +195 -1423
- package/dist/adminClient.d.ts +52 -59
- package/dist/adminClient.js +364 -1142
- package/dist/answersClient.d.ts +5 -14
- package/dist/answersClient.js +19 -737
- package/dist/audience/index.d.ts +18 -18
- package/dist/audience/index.js +87 -90
- package/dist/audiencesClient.d.ts +19 -27
- package/dist/audiencesClient.js +107 -868
- package/dist/auditClient.d.ts +8 -15
- package/dist/auditClient.js +18 -791
- package/dist/authContext.d.ts +11 -16
- package/dist/authContext.js +122 -154
- package/dist/authDeviceClient.d.ts +8 -17
- package/dist/authDeviceClient.js +113 -102
- package/dist/beliefs/index.d.ts +16 -67
- package/dist/beliefs/index.js +20 -10181
- package/dist/beliefs/lifecycle.d.ts +10 -11
- package/dist/beliefs/lifecycle.js +78 -80
- package/dist/beliefsClient.d.ts +30 -35
- package/dist/beliefsClient.js +238 -994
- package/dist/boundaryClientSurface.d.ts +11 -16
- package/dist/boundaryClientSurface.js +49 -68
- package/dist/client.d.ts +82 -113
- package/dist/client.js +232 -10155
- package/dist/clientAssemblyTypes.d.ts +3 -3
- package/dist/clientAssemblyTypes.js +1 -2
- package/dist/clientConfig.d.ts +45 -59
- package/dist/clientConfig.js +1 -2
- package/dist/clientEvidenceCompat.d.ts +24 -14
- package/dist/clientEvidenceCompat.js +56 -64
- package/dist/clientGraphNamespaces.d.ts +3 -5
- package/dist/clientGraphNamespaces.js +170 -245
- package/dist/clientHelpers.d.ts +20 -25
- package/dist/clientHelpers.js +104 -127
- package/dist/clientKnowledgeNamespaces.d.ts +24 -54
- package/dist/clientKnowledgeNamespaces.js +506 -506
- package/dist/clientLocalHelpers.d.ts +11 -56
- package/dist/clientLocalHelpers.js +503 -732
- package/dist/clientPlatformNamespaces.d.ts +5 -53
- package/dist/clientPlatformNamespaces.js +229 -323
- package/dist/clientRuntime.d.ts +5 -53
- package/dist/clientRuntime.js +26 -30
- package/dist/clientWorkflowNamespaces.d.ts +6 -15
- package/dist/clientWorkflowNamespaces.js +529 -596
- package/dist/contextClient.d.ts +9 -17
- package/dist/contextClient.js +92 -805
- package/dist/contextFacade.d.ts +11 -2
- package/dist/contextFacade.js +10 -81
- package/dist/contextPackCompiler.d.ts +10 -11
- package/dist/contextPackCompiler.js +494 -1040
- package/dist/contextPackPolicy.d.ts +14 -15
- package/dist/contextPackPolicy.js +227 -305
- package/dist/contextPackSchema.d.ts +3 -3
- package/dist/contextPackSchema.js +169 -176
- package/dist/contextTypes.d.ts +14 -15
- package/dist/contextTypes.js +1 -2
- package/dist/contracts/api-enums.contract.d.ts +29 -30
- package/dist/contracts/api-enums.contract.js +162 -88
- package/dist/contracts/auth-session.contract.d.ts +13 -14
- package/dist/contracts/auth-session.contract.js +55 -52
- package/dist/contracts/context-pack.contract.d.ts +54 -55
- package/dist/contracts/context-pack.contract.js +160 -88
- package/dist/contracts/contextPack.d.ts +2 -1
- package/dist/contracts/contextPack.js +1 -97
- package/dist/contracts/index.d.ts +11 -12
- package/dist/contracts/index.js +10 -854
- package/dist/contracts/lens-filter.contract.d.ts +9 -10
- package/dist/contracts/lens-filter.contract.js +82 -58
- package/dist/contracts/lens-workflow.contract.d.ts +21 -23
- package/dist/contracts/lens-workflow.contract.js +48 -117
- package/dist/contracts/lensFilter.d.ts +2 -1
- package/dist/contracts/lensFilter.js +1 -71
- package/dist/contracts/lensWorkflow.d.ts +2 -2
- package/dist/contracts/lensWorkflow.js +1 -123
- package/dist/contracts/mcpTools.d.ts +16 -18
- package/dist/contracts/mcpTools.js +89 -123
- package/dist/contracts/prompt.contract.d.ts +4 -5
- package/dist/contracts/prompt.contract.js +23 -10
- package/dist/contracts/prompt.d.ts +2 -1
- package/dist/contracts/prompt.js +1 -11
- package/dist/contracts/sdk-tools.contract.d.ts +2 -1
- package/dist/contracts/sdk-tools.contract.js +1 -2
- package/dist/contracts/sdkTools.d.ts +2 -1
- package/dist/contracts/sdkTools.js +1 -26
- package/dist/contracts/tool-contracts.d.ts +2 -1
- package/dist/contracts/tool-contracts.js +1 -2
- package/dist/contracts/workflow-runtime.contract.d.ts +45 -46
- package/dist/contracts/workflow-runtime.contract.js +241 -228
- package/dist/contracts/workflowRuntime.d.ts +2 -1
- package/dist/contracts/workflowRuntime.js +1 -244
- package/dist/contradictions/index.d.ts +8 -60
- package/dist/contradictions/index.js +11 -10175
- package/dist/control-plane.d.ts +17 -24
- package/dist/control-plane.js +124 -840
- package/dist/controlObjectOwnership.d.ts +19 -20
- package/dist/controlObjectOwnership.js +207 -201
- package/dist/coreClient.d.ts +23 -28
- package/dist/coreClient.js +567 -692
- package/dist/customTools.d.ts +17 -21
- package/dist/customTools.js +221 -221
- package/dist/decisions/index.d.ts +7 -58
- package/dist/decisions/index.js +14 -10177
- package/dist/decisionsClient.d.ts +25 -32
- package/dist/decisionsClient.js +113 -913
- package/dist/domainContext.d.ts +2 -1
- package/dist/domainContext.js +1 -2
- package/dist/edges/index.d.ts +21 -73
- package/dist/edges/index.js +12 -10176
- package/dist/embeddingsClient.d.ts +22 -30
- package/dist/embeddingsClient.js +73 -922
- package/dist/eventingClient.d.ts +23 -31
- package/dist/eventingClient.js +89 -918
- package/dist/events.d.ts +48 -49
- package/dist/events.js +257 -241
- package/dist/eventsCore.d.ts +20 -29
- package/dist/eventsCore.js +86 -830
- package/dist/evidence/index.d.ts +42 -61
- package/dist/evidence/index.js +13 -10176
- package/dist/evidenceClient.d.ts +13 -22
- package/dist/evidenceClient.js +34 -751
- package/dist/facade/context.d.ts +7 -8
- package/dist/facade/context.js +73 -72
- package/dist/functionSurface.d.ts +2 -156
- package/dist/functionSurface.js +1 -1460
- package/dist/functionSurfaceClient.d.ts +2 -9
- package/dist/functionSurfaceClient.js +1 -1460
- package/dist/gatewayFacades.d.ts +93 -296
- package/dist/gatewayFacades.factories.d.ts +209 -14
- package/dist/gatewayFacades.factories.js +545 -2228
- package/dist/gatewayFacades.js +284 -2627
- package/dist/generated/functionSurface.d.ts +149 -0
- package/dist/generated/functionSurface.js +749 -0
- package/dist/graphAnalysisClient.d.ts +41 -49
- package/dist/graphAnalysisClient.js +185 -974
- package/dist/graphClient.d.ts +53 -60
- package/dist/graphClient.js +219 -1090
- package/dist/graphIntel.d.ts +2 -4
- package/dist/graphIntel.js +1 -2
- package/dist/graphIntelligence.d.ts +4 -2
- package/dist/graphIntelligence.js +2 -46
- package/dist/graphRecommendationsClient.d.ts +15 -23
- package/dist/graphRecommendationsClient.js +70 -849
- package/dist/graphStateClassifierClient.d.ts +17 -25
- package/dist/graphStateClassifierClient.js +67 -908
- package/dist/harnessClient.d.ts +40 -47
- package/dist/harnessClient.js +198 -993
- package/dist/identityClient.d.ts +25 -33
- package/dist/identityClient.js +245 -1186
- package/dist/index.d.ts +73 -69
- package/dist/index.js +72 -13313
- package/dist/infisicalRuntime.d.ts +12 -14
- package/dist/infisicalRuntime.js +290 -297
- package/dist/jobsClient.d.ts +24 -32
- package/dist/jobsClient.js +101 -916
- package/dist/learningClient.d.ts +8 -16
- package/dist/learningClient.js +45 -809
- package/dist/lenses/index.d.ts +13 -65
- package/dist/lenses/index.js +11 -10175
- package/dist/mcpClient.d.ts +14 -23
- package/dist/mcpClient.js +115 -856
- package/dist/modelRuntimeClient.d.ts +18 -26
- package/dist/modelRuntimeClient.js +74 -894
- package/dist/nodes/index.d.ts +7 -58
- package/dist/nodes/index.js +14 -10177
- package/dist/ontologies/index.d.ts +21 -73
- package/dist/ontologies/index.js +14 -10178
- package/dist/ontologyClient.d.ts +23 -31
- package/dist/ontologyClient.js +138 -924
- package/dist/ontologyLinksClient.d.ts +16 -24
- package/dist/ontologyLinksClient.js +76 -886
- package/dist/opinion.d.ts +5 -6
- package/dist/opinion.js +21 -25
- package/dist/orgGraphSearchClient.d.ts +19 -27
- package/dist/orgGraphSearchClient.js +89 -857
- package/dist/packRuntime.d.ts +2 -2
- package/dist/packRuntime.js +1 -2
- package/dist/packsClient.d.ts +30 -37
- package/dist/packsClient.js +131 -906
- package/dist/policyClient.d.ts +21 -29
- package/dist/policyClient.js +267 -1026
- package/dist/proof-attestation.json +1 -1
- package/dist/questions/index.d.ts +9 -60
- package/dist/questions/index.js +15 -10178
- package/dist/realtime/index.d.ts +20 -16
- package/dist/realtime/index.js +30 -19
- package/dist/realtime/refs.d.ts +4 -6
- package/dist/realtime/refs.js +12 -7
- package/dist/realtime-refs.d.ts +1 -0
- package/dist/realtime-refs.js +1 -0
- package/dist/realtime.d.ts +1 -0
- package/dist/realtime.js +1 -0
- package/dist/reportsClient.d.ts +10 -19
- package/dist/reportsClient.js +48 -836
- package/dist/schemaClient.d.ts +16 -23
- package/dist/schemaClient.js +62 -832
- package/dist/sdkSurface.d.ts +18 -25
- package/dist/sdkSurface.js +135 -106
- package/dist/secrets.d.ts +2 -1
- package/dist/secrets.js +1 -2
- package/dist/sourcesClient.d.ts +11 -18
- package/dist/sourcesClient.js +18 -741
- package/dist/telemetryClient.d.ts +22 -30
- package/dist/telemetryClient.js +107 -931
- package/dist/toolRegistryClient.d.ts +27 -35
- package/dist/toolRegistryClient.js +116 -954
- package/dist/topics/index.d.ts +13 -64
- package/dist/topics/index.js +15 -10178
- package/dist/topicsClient.d.ts +19 -27
- package/dist/topicsClient.js +106 -894
- package/dist/types.d.ts +84 -87
- package/dist/types.js +1 -2
- package/dist/version.d.ts +2 -3
- package/dist/version.js +2 -5
- package/dist/workflowClient.d.ts +60 -65
- package/dist/workflowClient.js +343 -1219
- package/dist/worktrees/index.d.ts +16 -68
- package/dist/worktrees/index.js +14 -10178
- package/package.json +6 -6
- package/dist/accessControl.js.map +0 -1
- package/dist/adminClient.js.map +0 -1
- package/dist/answersClient.js.map +0 -1
- package/dist/audience/index.js.map +0 -1
- package/dist/audiencesClient.js.map +0 -1
- package/dist/auditClient.js.map +0 -1
- package/dist/authContext.js.map +0 -1
- package/dist/authDeviceClient.js.map +0 -1
- package/dist/beliefs/index.js.map +0 -1
- package/dist/beliefs/lifecycle.js.map +0 -1
- package/dist/beliefsClient.js.map +0 -1
- package/dist/boundaryClientSurface.js.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/clientAssemblyTypes.js.map +0 -1
- package/dist/clientConfig.js.map +0 -1
- package/dist/clientEvidenceCompat.js.map +0 -1
- package/dist/clientGraphNamespaces.js.map +0 -1
- package/dist/clientHelpers.js.map +0 -1
- package/dist/clientKnowledgeNamespaces.js.map +0 -1
- package/dist/clientLocalHelpers.js.map +0 -1
- package/dist/clientPlatformNamespaces.js.map +0 -1
- package/dist/clientRuntime.js.map +0 -1
- package/dist/clientWorkflowNamespaces.js.map +0 -1
- package/dist/contextClient.js.map +0 -1
- package/dist/contextFacade.js.map +0 -1
- package/dist/contextPackCompiler.js.map +0 -1
- package/dist/contextPackPolicy.js.map +0 -1
- package/dist/contextPackSchema.js.map +0 -1
- package/dist/contextTypes.js.map +0 -1
- package/dist/contracts/api-enums.contract.js.map +0 -1
- package/dist/contracts/auth-session.contract.js.map +0 -1
- package/dist/contracts/context-pack.contract.js.map +0 -1
- package/dist/contracts/contextPack.js.map +0 -1
- package/dist/contracts/index.js.map +0 -1
- package/dist/contracts/lens-filter.contract.js.map +0 -1
- package/dist/contracts/lens-workflow.contract.js.map +0 -1
- package/dist/contracts/lensFilter.js.map +0 -1
- package/dist/contracts/lensWorkflow.js.map +0 -1
- package/dist/contracts/mcpTools.js.map +0 -1
- package/dist/contracts/prompt.contract.js.map +0 -1
- package/dist/contracts/prompt.js.map +0 -1
- package/dist/contracts/sdk-tools.contract.js.map +0 -1
- package/dist/contracts/sdkTools.js.map +0 -1
- package/dist/contracts/tool-contracts.js.map +0 -1
- package/dist/contracts/workflow-runtime.contract.js.map +0 -1
- package/dist/contracts/workflowRuntime.js.map +0 -1
- package/dist/contradictions/index.js.map +0 -1
- package/dist/control-plane.js.map +0 -1
- package/dist/controlObjectOwnership.js.map +0 -1
- package/dist/coreClient.js.map +0 -1
- package/dist/customTools.js.map +0 -1
- package/dist/decisions/index.js.map +0 -1
- package/dist/decisionsClient.js.map +0 -1
- package/dist/domainContext.js.map +0 -1
- package/dist/edges/index.js.map +0 -1
- package/dist/embeddingsClient.js.map +0 -1
- package/dist/eventingClient.js.map +0 -1
- package/dist/events.js.map +0 -1
- package/dist/eventsCore.js.map +0 -1
- package/dist/evidence/index.js.map +0 -1
- package/dist/evidenceClient.js.map +0 -1
- package/dist/facade/context.js.map +0 -1
- package/dist/functionSurface.js.map +0 -1
- package/dist/functionSurfaceClient.js.map +0 -1
- package/dist/gatewayFacades.factories.js.map +0 -1
- package/dist/gatewayFacades.js.map +0 -1
- package/dist/graphAnalysisClient.js.map +0 -1
- package/dist/graphClient.js.map +0 -1
- package/dist/graphIntel.js.map +0 -1
- package/dist/graphIntelligence.js.map +0 -1
- package/dist/graphRecommendationsClient.js.map +0 -1
- package/dist/graphStateClassifierClient.js.map +0 -1
- package/dist/harnessClient.js.map +0 -1
- package/dist/identityClient.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/infisicalRuntime.js.map +0 -1
- package/dist/jobsClient.js.map +0 -1
- package/dist/learningClient.js.map +0 -1
- package/dist/lenses/index.js.map +0 -1
- package/dist/mcpClient.js.map +0 -1
- package/dist/modelRuntimeClient.js.map +0 -1
- package/dist/nodes/index.js.map +0 -1
- package/dist/ontologies/index.js.map +0 -1
- package/dist/ontologyClient.js.map +0 -1
- package/dist/ontologyLinksClient.js.map +0 -1
- package/dist/opinion.js.map +0 -1
- package/dist/orgGraphSearchClient.js.map +0 -1
- package/dist/packRuntime.js.map +0 -1
- package/dist/packsClient.js.map +0 -1
- package/dist/policyClient.js.map +0 -1
- package/dist/questions/index.js.map +0 -1
- package/dist/realtime/index.js.map +0 -1
- package/dist/realtime/refs.js.map +0 -1
- package/dist/reportsClient.js.map +0 -1
- package/dist/schemaClient.js.map +0 -1
- package/dist/sdk-tools.contract-B4c1Zr1o.d.ts +0 -22
- package/dist/sdkSurface.js.map +0 -1
- package/dist/secrets.js.map +0 -1
- package/dist/sourcesClient.js.map +0 -1
- package/dist/telemetryClient.js.map +0 -1
- package/dist/tool-contracts-BUiL9P6z.d.ts +0 -22
- package/dist/toolRegistryClient.js.map +0 -1
- package/dist/topics/index.js.map +0 -1
- package/dist/topicsClient.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/version.js.map +0 -1
- package/dist/workflowClient.js.map +0 -1
- package/dist/worktrees/index.js.map +0 -1
package/dist/coreClient.js
CHANGED
|
@@ -1,747 +1,622 @@
|
|
|
1
|
-
import { createTelemetryExporterFromEnv, emitTelemetrySignal } from
|
|
2
|
-
import { redactDiagnosticValue } from
|
|
3
|
-
import { classifyRetry } from
|
|
4
|
-
import { Effect, Exit
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return normalized ? normalized : void 0;
|
|
20
|
-
}
|
|
21
|
-
function cleanStringList(values) {
|
|
22
|
-
if (!values) {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
return values.map((value) => value.trim()).filter(
|
|
26
|
-
(value, index, list) => value.length > 0 && list.indexOf(value) === index
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
function requireString(value, reason, label) {
|
|
30
|
-
const normalized = cleanString(value);
|
|
31
|
-
if (!normalized) {
|
|
32
|
-
throw new LucernSdkAuthContextError(
|
|
33
|
-
reason,
|
|
34
|
-
`Canonical Lucern SDK auth context is missing ${label}.`
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
return normalized;
|
|
38
|
-
}
|
|
39
|
-
function requirePrincipalType(principalType) {
|
|
40
|
-
if (!principalType) {
|
|
41
|
-
throw new LucernSdkAuthContextError(
|
|
42
|
-
"principal_missing",
|
|
43
|
-
"Canonical Lucern SDK auth context is missing principalType."
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
return principalType;
|
|
47
|
-
}
|
|
48
|
-
function requireAuthMode(authMode) {
|
|
49
|
-
if (!authMode) {
|
|
50
|
-
throw new LucernSdkAuthContextError(
|
|
51
|
-
"principal_missing",
|
|
52
|
-
"Canonical Lucern SDK auth context is missing authMode."
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
return authMode;
|
|
56
|
-
}
|
|
57
|
-
function ensurePermitMatch(args) {
|
|
58
|
-
const actual = cleanString(args.actual);
|
|
59
|
-
if (actual && actual !== args.expected) {
|
|
60
|
-
throw new LucernSdkAuthContextError(
|
|
61
|
-
"policy_denied",
|
|
62
|
-
`Canonical Lucern SDK auth context has conflicting Permit ${args.field}.`
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
function normalizeCanonicalLucernAuthContext(input) {
|
|
67
|
-
if (!input) {
|
|
68
|
-
throw new LucernSdkAuthContextError(
|
|
69
|
-
"principal_missing",
|
|
70
|
-
"Canonical Lucern SDK auth context is required."
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
if (input.policyDecision === "deny") {
|
|
74
|
-
throw new LucernSdkAuthContextError(
|
|
75
|
-
"policy_denied",
|
|
76
|
-
"Canonical Lucern SDK auth context carries a denied policy decision."
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
const principalId = requireString(
|
|
80
|
-
input.principalId,
|
|
81
|
-
"principal_missing",
|
|
82
|
-
"principalId"
|
|
83
|
-
);
|
|
84
|
-
const tenantId = requireString(input.tenantId, "tenant_missing", "tenantId");
|
|
85
|
-
const workspaceId = requireString(
|
|
86
|
-
input.workspaceId,
|
|
87
|
-
"workspace_missing",
|
|
88
|
-
"workspaceId"
|
|
89
|
-
);
|
|
90
|
-
const roles = cleanStringList(input.roles);
|
|
91
|
-
const scopes = cleanStringList(input.scopes);
|
|
92
|
-
const principalType = requirePrincipalType(input.principalType);
|
|
93
|
-
const authMode = requireAuthMode(input.authMode);
|
|
94
|
-
const roleBasedInteractiveAuth = authMode === "interactive_user" && roles.length > 0;
|
|
95
|
-
if (roles.length === 0 || scopes.length === 0 && !roleBasedInteractiveAuth) {
|
|
96
|
-
throw new LucernSdkAuthContextError(
|
|
97
|
-
"membership_missing",
|
|
98
|
-
"Canonical Lucern SDK auth context requires non-empty roles and scopes."
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
const subject = cleanString(input.permit?.subject) ?? principalId;
|
|
102
|
-
const tenant = cleanString(input.permit?.tenant) ?? tenantId;
|
|
103
|
-
const workspace = cleanString(input.permit?.workspace) ?? workspaceId;
|
|
104
|
-
ensurePermitMatch({
|
|
105
|
-
field: "subject",
|
|
106
|
-
expected: principalId,
|
|
107
|
-
actual: subject
|
|
108
|
-
});
|
|
109
|
-
ensurePermitMatch({ field: "tenant", expected: tenantId, actual: tenant });
|
|
110
|
-
ensurePermitMatch({
|
|
111
|
-
field: "workspace",
|
|
112
|
-
expected: workspaceId,
|
|
113
|
-
actual: workspace
|
|
114
|
-
});
|
|
115
|
-
const context = input.permit?.context ? { ...input.permit.context } : void 0;
|
|
116
|
-
return {
|
|
117
|
-
clerkId: cleanString(input.clerkId),
|
|
118
|
-
principalId,
|
|
119
|
-
tenantId,
|
|
120
|
-
workspaceId,
|
|
121
|
-
principalType,
|
|
122
|
-
authMode,
|
|
123
|
-
roles,
|
|
124
|
-
scopes,
|
|
125
|
-
delegationChain: input.delegationChain ? [...input.delegationChain] : [],
|
|
126
|
-
policyTraceId: cleanString(input.policyTraceId),
|
|
127
|
-
correlationId: cleanString(input.correlationId),
|
|
128
|
-
membershipId: cleanString(input.membershipId),
|
|
129
|
-
permit: {
|
|
130
|
-
subject,
|
|
131
|
-
tenant,
|
|
132
|
-
workspace,
|
|
133
|
-
resource: cleanString(input.permit?.resource),
|
|
134
|
-
action: cleanString(input.permit?.action),
|
|
135
|
-
relation: cleanString(input.permit?.relation),
|
|
136
|
-
context
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
function createCanonicalAuthHeaders(authContext) {
|
|
141
|
-
const headers = {
|
|
142
|
-
"x-lucern-principal-id": authContext.principalId,
|
|
143
|
-
"x-lucern-principal-type": authContext.principalType,
|
|
144
|
-
"x-lucern-tenant": authContext.tenantId,
|
|
145
|
-
"x-lucern-tenant-id": authContext.tenantId,
|
|
146
|
-
"x-lucern-workspace": authContext.workspaceId,
|
|
147
|
-
"x-lucern-workspace-id": authContext.workspaceId,
|
|
148
|
-
"x-lucern-auth-mode": authContext.authMode,
|
|
149
|
-
"x-lucern-roles": authContext.roles.join(","),
|
|
150
|
-
"x-lucern-scopes": authContext.scopes.join(","),
|
|
151
|
-
"x-lucern-permit-context": JSON.stringify(authContext.permit)
|
|
152
|
-
};
|
|
153
|
-
if (authContext.clerkId) {
|
|
154
|
-
headers["x-lucern-clerk-id"] = authContext.clerkId;
|
|
155
|
-
headers["x-lucern-user-id"] = authContext.clerkId;
|
|
156
|
-
}
|
|
157
|
-
if (authContext.delegationChain.length > 0) {
|
|
158
|
-
headers["x-lucern-delegation-chain"] = JSON.stringify(
|
|
159
|
-
authContext.delegationChain
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
if (authContext.policyTraceId) {
|
|
163
|
-
headers["x-lucern-policy-trace-id"] = authContext.policyTraceId;
|
|
164
|
-
}
|
|
165
|
-
if (authContext.correlationId) {
|
|
166
|
-
headers["x-correlation-id"] = authContext.correlationId;
|
|
167
|
-
headers["x-lucern-correlation-id"] = authContext.correlationId;
|
|
168
|
-
}
|
|
169
|
-
if (authContext.membershipId) {
|
|
170
|
-
headers["x-lucern-membership-id"] = authContext.membershipId;
|
|
171
|
-
}
|
|
172
|
-
return headers;
|
|
1
|
+
import { createTelemetryExporterFromEnv, emitTelemetrySignal, } from "@lucern/transport-core";
|
|
2
|
+
import { redactDiagnosticValue } from "@lucern/transport-core/redaction";
|
|
3
|
+
import { classifyRetry } from "@lucern/transport-core/transport";
|
|
4
|
+
import { Cause, Effect, Exit } from "effect";
|
|
5
|
+
import { createCanonicalAuthHeaders, LucernSdkAuthContextError, normalizeCanonicalLucernAuthContext, } from "./authContext.js";
|
|
6
|
+
const DEFAULT_GATEWAY_TIMEOUT_MS = 15_000;
|
|
7
|
+
const DEFAULT_GATEWAY_MAX_RETRIES = 2;
|
|
8
|
+
const DEFAULT_ENV_TIMEOUT_MS = "LUCERN_REQUEST_TIMEOUT_MS";
|
|
9
|
+
const DEFAULT_ENV_MAX_RETRIES = "LUCERN_GATEWAY_MAX_RETRIES";
|
|
10
|
+
const ENV_TIMEOUT_BY_METHOD_PREFIX = "LUCERN_REQUEST_TIMEOUT_MS_";
|
|
11
|
+
export class GatewayTimeoutError extends Error {
|
|
12
|
+
retryable = true;
|
|
13
|
+
timeoutMs;
|
|
14
|
+
constructor(timeoutMs) {
|
|
15
|
+
super(`Request timed out after ${timeoutMs}ms`);
|
|
16
|
+
this.name = "AbortError";
|
|
17
|
+
this.timeoutMs = timeoutMs;
|
|
18
|
+
}
|
|
173
19
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
timeoutMs;
|
|
184
|
-
constructor(timeoutMs) {
|
|
185
|
-
super(`Request timed out after ${timeoutMs}ms`);
|
|
186
|
-
this.name = "AbortError";
|
|
187
|
-
this.timeoutMs = timeoutMs;
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
var GatewayTransportError = class extends Error {
|
|
191
|
-
retryable;
|
|
192
|
-
cause;
|
|
193
|
-
constructor(message, options) {
|
|
194
|
-
super(message);
|
|
195
|
-
this.name = "GatewayTransportError";
|
|
196
|
-
this.retryable = options?.retryable ?? true;
|
|
197
|
-
this.cause = options?.cause;
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
function isGatewayRecoverableError(error) {
|
|
201
|
-
return error instanceof GatewayTimeoutError || error instanceof GatewayTransportError;
|
|
20
|
+
export class GatewayTransportError extends Error {
|
|
21
|
+
retryable;
|
|
22
|
+
cause;
|
|
23
|
+
constructor(message, options) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "GatewayTransportError";
|
|
26
|
+
this.retryable = options?.retryable ?? true;
|
|
27
|
+
this.cause = options?.cause;
|
|
28
|
+
}
|
|
202
29
|
}
|
|
203
|
-
function
|
|
204
|
-
|
|
30
|
+
export function isGatewayRecoverableError(error) {
|
|
31
|
+
return error instanceof GatewayTimeoutError || error instanceof GatewayTransportError;
|
|
32
|
+
}
|
|
33
|
+
export function isGatewayRetryableError(error) {
|
|
34
|
+
return ((error instanceof GatewayTimeoutError && error.retryable) ||
|
|
35
|
+
(error instanceof GatewayTransportError && error.retryable) ||
|
|
36
|
+
false);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Structured error thrown when a platform API request fails.
|
|
40
|
+
*
|
|
41
|
+
* Includes HTTP status, machine-readable error code, optional invariant
|
|
42
|
+
* reference, and actionable suggestions for common failure modes.
|
|
43
|
+
*/
|
|
44
|
+
export class LucernApiError extends Error {
|
|
45
|
+
code;
|
|
46
|
+
status;
|
|
47
|
+
invariant;
|
|
48
|
+
suggestion;
|
|
49
|
+
details;
|
|
50
|
+
requestId;
|
|
51
|
+
correlationId;
|
|
52
|
+
policyTraceId;
|
|
53
|
+
constructor(args) {
|
|
54
|
+
super(args.message);
|
|
55
|
+
this.name = "LucernApiError";
|
|
56
|
+
this.code = args.code;
|
|
57
|
+
this.status = args.status;
|
|
58
|
+
this.invariant = args.invariant;
|
|
59
|
+
this.suggestion = args.suggestion;
|
|
60
|
+
this.details = args.details;
|
|
61
|
+
this.requestId = args.requestId;
|
|
62
|
+
this.correlationId = args.correlationId;
|
|
63
|
+
this.policyTraceId = args.policyTraceId;
|
|
64
|
+
}
|
|
205
65
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
};
|
|
228
|
-
function toQueryString(scope) {
|
|
229
|
-
const params = new URLSearchParams();
|
|
230
|
-
if (scope.tenantId) {
|
|
231
|
-
params.set("tenantId", scope.tenantId);
|
|
232
|
-
}
|
|
233
|
-
if (scope.workspaceId) {
|
|
234
|
-
params.set("workspaceId", scope.workspaceId);
|
|
235
|
-
}
|
|
236
|
-
for (const [key, value] of Object.entries(scope)) {
|
|
237
|
-
if (key === "tenantId" || key === "workspaceId") {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (value === void 0) {
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
params.set(key, String(value));
|
|
244
|
-
}
|
|
245
|
-
const serialized = params.toString();
|
|
246
|
-
return serialized.length > 0 ? `?${serialized}` : "";
|
|
66
|
+
/**
|
|
67
|
+
* Serialize a gateway query object into a URL query string.
|
|
68
|
+
*/
|
|
69
|
+
export function toQueryString(scope) {
|
|
70
|
+
const params = new URLSearchParams();
|
|
71
|
+
if (scope.tenantId) {
|
|
72
|
+
params.set("tenantId", scope.tenantId);
|
|
73
|
+
}
|
|
74
|
+
if (scope.workspaceId) {
|
|
75
|
+
params.set("workspaceId", scope.workspaceId);
|
|
76
|
+
}
|
|
77
|
+
for (const [key, value] of Object.entries(scope)) {
|
|
78
|
+
if (key === "tenantId" || key === "workspaceId") {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (value === undefined) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
params.set(key, String(value));
|
|
85
|
+
}
|
|
86
|
+
const serialized = params.toString();
|
|
87
|
+
return serialized.length > 0 ? `?${serialized}` : "";
|
|
247
88
|
}
|
|
248
89
|
function fillRandomBytes(length) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
90
|
+
const bytes = new Uint8Array(length);
|
|
91
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") {
|
|
92
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
93
|
+
return bytes;
|
|
94
|
+
}
|
|
95
|
+
for (let index = 0; index < length; index += 1) {
|
|
96
|
+
bytes[index] = Math.floor(Math.random() * 256);
|
|
97
|
+
}
|
|
252
98
|
return bytes;
|
|
253
|
-
}
|
|
254
|
-
for (let index = 0; index < length; index += 1) {
|
|
255
|
-
bytes[index] = Math.floor(Math.random() * 256);
|
|
256
|
-
}
|
|
257
|
-
return bytes;
|
|
258
99
|
}
|
|
259
100
|
function generatePortableRequestId() {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
6,
|
|
269
|
-
8
|
|
270
|
-
).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
|
|
101
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
102
|
+
return globalThis.crypto.randomUUID();
|
|
103
|
+
}
|
|
104
|
+
const bytes = fillRandomBytes(16);
|
|
105
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
106
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
107
|
+
const hex = Array.from(bytes, (value) => value.toString(16).padStart(2, "0"));
|
|
108
|
+
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
|
|
271
109
|
}
|
|
272
110
|
function resolveEnvironment() {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
111
|
+
const processEnv = typeof globalThis === "object" &&
|
|
112
|
+
globalThis !== null &&
|
|
113
|
+
"process" in globalThis
|
|
114
|
+
? globalThis.process
|
|
115
|
+
: undefined;
|
|
116
|
+
const env = processEnv !== undefined &&
|
|
117
|
+
typeof processEnv === "object" &&
|
|
118
|
+
processEnv !== null &&
|
|
119
|
+
typeof processEnv.env === "object"
|
|
120
|
+
? processEnv.env
|
|
121
|
+
: undefined;
|
|
122
|
+
return {
|
|
123
|
+
get: (name) => {
|
|
124
|
+
const value = env?.[name];
|
|
125
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
281
128
|
}
|
|
282
129
|
function telemetryEnvironmentRecord(environment) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
names.map((name) => [name, environment.get(name)])
|
|
302
|
-
);
|
|
130
|
+
const names = [
|
|
131
|
+
"LUCERN_TELEMETRY_ENABLED",
|
|
132
|
+
"AXIOM_TELEMETRY_ENABLED",
|
|
133
|
+
"LUCERN_AXIOM_TOKEN",
|
|
134
|
+
"AXIOM_TOKEN",
|
|
135
|
+
"LUCERN_AXIOM_EVENTS_DATASET",
|
|
136
|
+
"LUCERN_AXIOM_DATASET",
|
|
137
|
+
"AXIOM_EVENTS_DATASET",
|
|
138
|
+
"AXIOM_DATASET",
|
|
139
|
+
"LUCERN_AXIOM_API_URL",
|
|
140
|
+
"AXIOM_URL",
|
|
141
|
+
"LUCERN_ENVIRONMENT",
|
|
142
|
+
"NODE_ENV",
|
|
143
|
+
"LUCERN_RELEASE",
|
|
144
|
+
"SENTRY_RELEASE",
|
|
145
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
146
|
+
];
|
|
147
|
+
return Object.fromEntries(names.map((name) => [name, environment.get(name)]));
|
|
303
148
|
}
|
|
304
149
|
function resolveRequestProfile(config, environment) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
config.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
return {
|
|
329
|
-
maxRetries: parsedMaxRetries ?? DEFAULT_GATEWAY_MAX_RETRIES,
|
|
330
|
-
timeoutMs: parsedTimeoutMs ?? DEFAULT_GATEWAY_TIMEOUT_MS,
|
|
331
|
-
timeoutMsByMethod: methodTimeouts,
|
|
332
|
-
requestIdFactory
|
|
333
|
-
};
|
|
150
|
+
const requestIdFactory = config.requestIdFactory ?? (() => generatePortableRequestId());
|
|
151
|
+
const parsedMaxRetries = parseIntegerFromString(config.maxRetries, environment.get(DEFAULT_ENV_MAX_RETRIES));
|
|
152
|
+
const parsedTimeoutMs = parseIntegerFromString(config.timeoutMs, environment.get(DEFAULT_ENV_TIMEOUT_MS));
|
|
153
|
+
const methodTimeouts = {
|
|
154
|
+
...config.timeoutMsByMethod,
|
|
155
|
+
};
|
|
156
|
+
for (const method of ["GET", "POST", "PUT", "PATCH", "DELETE"]) {
|
|
157
|
+
const envKey = `${ENV_TIMEOUT_BY_METHOD_PREFIX}${method}`;
|
|
158
|
+
const raw = environment.get(envKey);
|
|
159
|
+
if (!raw || methodTimeouts[method] !== undefined) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const parsed = parseIntegerFromString(undefined, raw);
|
|
163
|
+
if (typeof parsed === "number") {
|
|
164
|
+
methodTimeouts[method] = parsed;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
maxRetries: parsedMaxRetries ?? DEFAULT_GATEWAY_MAX_RETRIES,
|
|
169
|
+
timeoutMs: parsedTimeoutMs ?? DEFAULT_GATEWAY_TIMEOUT_MS,
|
|
170
|
+
timeoutMsByMethod: methodTimeouts,
|
|
171
|
+
requestIdFactory,
|
|
172
|
+
};
|
|
334
173
|
}
|
|
335
174
|
function createGatewayRuntime(config, environment) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
175
|
+
return {
|
|
176
|
+
fetch: config.fetchImpl ?? fetch,
|
|
177
|
+
now: () => Date.now(),
|
|
178
|
+
sleep: (ms) => delay(ms),
|
|
179
|
+
env: environment,
|
|
180
|
+
redaction: resolveRequestRedactionValue,
|
|
181
|
+
profile: resolveRequestProfile(config, environment),
|
|
182
|
+
};
|
|
344
183
|
}
|
|
345
184
|
function parseIntegerFromString(value, rawValue) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
185
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
|
|
186
|
+
return value;
|
|
187
|
+
}
|
|
188
|
+
if (typeof rawValue !== "string" || !rawValue.trim()) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
const parsed = Number.parseInt(rawValue, 10);
|
|
192
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : undefined;
|
|
354
193
|
}
|
|
355
194
|
function resolveRequestRedactionValue(value) {
|
|
356
|
-
|
|
195
|
+
return redactDiagnosticValue(value);
|
|
357
196
|
}
|
|
358
197
|
function resolveGatewayBaseUrl(configBaseUrl, environment) {
|
|
359
|
-
|
|
360
|
-
|
|
198
|
+
const envBaseUrl = environment.get("LUCERN_API_URL") ??
|
|
199
|
+
environment.get("LUCERN_BASE_URL") ??
|
|
200
|
+
environment.get("LUCERN_GATEWAY_BASE_URL");
|
|
201
|
+
return (configBaseUrl ?? envBaseUrl ?? "").replace(/\/+$/, "");
|
|
361
202
|
}
|
|
362
203
|
function normalizeGatewayEnvironment(value) {
|
|
363
|
-
|
|
204
|
+
return value === "sandbox" || value === "production" ? value : undefined;
|
|
364
205
|
}
|
|
365
|
-
|
|
206
|
+
/**
|
|
207
|
+
* Generate a random idempotency key for retry-safe writes.
|
|
208
|
+
*/
|
|
209
|
+
export const randomIdempotencyKey = generatePortableRequestId;
|
|
366
210
|
function fallbackErrorCode(status) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
211
|
+
if (status === 401) {
|
|
212
|
+
return "AUTHENTICATION_REQUIRED";
|
|
213
|
+
}
|
|
214
|
+
if (status === 403) {
|
|
215
|
+
return "FORBIDDEN";
|
|
216
|
+
}
|
|
217
|
+
if (status === 404) {
|
|
218
|
+
return "NOT_FOUND";
|
|
219
|
+
}
|
|
220
|
+
if (status === 408) {
|
|
221
|
+
return "UPSTREAM_ERROR";
|
|
222
|
+
}
|
|
223
|
+
if (status === 409) {
|
|
224
|
+
return "CONFLICT";
|
|
225
|
+
}
|
|
226
|
+
if (status === 429) {
|
|
227
|
+
return "RATE_LIMIT_EXCEEDED";
|
|
228
|
+
}
|
|
229
|
+
if (status >= 500) {
|
|
230
|
+
return "UPSTREAM_ERROR";
|
|
231
|
+
}
|
|
232
|
+
return "INTERNAL_ERROR";
|
|
389
233
|
}
|
|
390
234
|
function delay(ms) {
|
|
391
|
-
|
|
235
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
392
236
|
}
|
|
393
237
|
function computeRetryDelayMs(args) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return baseDelay + Math.round(Math.random() * jitterWindow);
|
|
238
|
+
const baseDelay = args.status === 429
|
|
239
|
+
? Math.max(args.retryAfterMs ?? 0, Math.min(1000 * 2 ** args.attempt, 10_000))
|
|
240
|
+
: Math.min(1000 * 2 ** args.attempt, 4000);
|
|
241
|
+
if (args.status !== 429) {
|
|
242
|
+
return baseDelay;
|
|
243
|
+
}
|
|
244
|
+
const jitterWindow = Math.max(250, Math.round(baseDelay * 0.25));
|
|
245
|
+
return baseDelay + Math.round(Math.random() * jitterWindow);
|
|
403
246
|
}
|
|
404
247
|
function classifyGatewayErrorForRetry(error) {
|
|
405
|
-
|
|
248
|
+
return isGatewayRetryableError(error) || classifyRetry({ error }).retryable;
|
|
406
249
|
}
|
|
407
250
|
function isRecord(value) {
|
|
408
|
-
|
|
251
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
409
252
|
}
|
|
410
253
|
function readPolicySummaryFromDetails(details) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
254
|
+
if (!isRecord(details)) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
const directSummary = details.summary;
|
|
258
|
+
if (typeof directSummary === "string" && directSummary.trim().length > 0) {
|
|
259
|
+
return directSummary.trim();
|
|
260
|
+
}
|
|
261
|
+
const policy = details.policy;
|
|
262
|
+
if (!isRecord(policy)) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
const explanation = policy.explanation;
|
|
266
|
+
if (!isRecord(explanation)) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const nestedSummary = explanation.summary;
|
|
270
|
+
if (typeof nestedSummary === "string" && nestedSummary.trim().length > 0) {
|
|
271
|
+
return nestedSummary.trim();
|
|
272
|
+
}
|
|
424
273
|
return null;
|
|
425
|
-
}
|
|
426
|
-
const nestedSummary = explanation.summary;
|
|
427
|
-
if (typeof nestedSummary === "string" && nestedSummary.trim().length > 0) {
|
|
428
|
-
return nestedSummary.trim();
|
|
429
|
-
}
|
|
430
|
-
return null;
|
|
431
274
|
}
|
|
432
275
|
function redactJsonDiagnosticValue(value) {
|
|
433
|
-
|
|
276
|
+
return value === undefined
|
|
277
|
+
? undefined
|
|
278
|
+
: redactDiagnosticValue(value);
|
|
434
279
|
}
|
|
435
280
|
async function resolveConfiguredAuthContext(authContext) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
281
|
+
if (typeof authContext === "function") {
|
|
282
|
+
return await authContext();
|
|
283
|
+
}
|
|
284
|
+
return authContext;
|
|
440
285
|
}
|
|
441
286
|
function mergeHeaderRecord(base, addition) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
headers.set(key, value);
|
|
452
|
-
}
|
|
453
|
-
return Object.fromEntries(headers.entries());
|
|
287
|
+
const headers = new Headers(base);
|
|
288
|
+
for (const [key, value] of Object.entries(addition)) {
|
|
289
|
+
const existing = headers.get(key);
|
|
290
|
+
if (existing !== null && existing !== value) {
|
|
291
|
+
throw new LucernSdkAuthContextError("policy_denied", `Canonical Lucern SDK auth context conflicts with existing ${key} header.`);
|
|
292
|
+
}
|
|
293
|
+
headers.set(key, value);
|
|
294
|
+
}
|
|
295
|
+
return Object.fromEntries(headers.entries());
|
|
454
296
|
}
|
|
455
297
|
function cleanHeaderValue(value) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
async function fetchWithTimeout(url, init, timeoutMs) {
|
|
498
|
-
const normalizeTransportError = (error, isTimeout) => {
|
|
499
|
-
if (isTimeout) {
|
|
500
|
-
return new GatewayTimeoutError(timeoutMs);
|
|
501
|
-
}
|
|
502
|
-
return error instanceof GatewayTimeoutError || error instanceof GatewayTransportError ? error : new GatewayTransportError(
|
|
503
|
-
error instanceof Error ? error.message : "Gateway transport error",
|
|
504
|
-
{
|
|
505
|
-
cause: error,
|
|
506
|
-
retryable: classifyGatewayErrorForRetry(error)
|
|
298
|
+
const normalized = value?.trim();
|
|
299
|
+
return normalized ? normalized : undefined;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Create the transport client used by all SDK modules.
|
|
303
|
+
*/
|
|
304
|
+
export function createGatewayRequestClient(config = {}) {
|
|
305
|
+
const env = resolveEnvironment();
|
|
306
|
+
const runtime = createGatewayRuntime(config, env);
|
|
307
|
+
const baseUrl = resolveGatewayBaseUrl(config.baseUrl, env);
|
|
308
|
+
const maxRetries = runtime.profile.maxRetries;
|
|
309
|
+
const requestIdFactory = runtime.profile.requestIdFactory;
|
|
310
|
+
const requestTimeoutByMethod = runtime.profile.timeoutMsByMethod;
|
|
311
|
+
const defaultRequestTimeoutMs = runtime.profile.timeoutMs;
|
|
312
|
+
const normalizedEnvironment = normalizeGatewayEnvironment(config.environment);
|
|
313
|
+
const telemetryExporter = config.telemetryEnabled === false
|
|
314
|
+
? null
|
|
315
|
+
: (config.telemetryExporter ??
|
|
316
|
+
createTelemetryExporterFromEnv(telemetryEnvironmentRecord(env), {
|
|
317
|
+
service: "lucern-sdk",
|
|
318
|
+
environment: normalizedEnvironment,
|
|
319
|
+
}));
|
|
320
|
+
async function resolveAuthHeaders() {
|
|
321
|
+
const provided = config.getAuthHeaders ? await config.getAuthHeaders() : {};
|
|
322
|
+
const headers = new Headers(provided);
|
|
323
|
+
const setIfAbsent = (name, value) => {
|
|
324
|
+
const normalized = cleanHeaderValue(value);
|
|
325
|
+
if (normalized && !headers.has(name)) {
|
|
326
|
+
headers.set(name, normalized);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
setIfAbsent("x-lucern-key", config.apiKey);
|
|
330
|
+
setIfAbsent("x-lucern-session-token", config.userToken);
|
|
331
|
+
setIfAbsent("x-lucern-environment", normalizedEnvironment);
|
|
332
|
+
setIfAbsent("x-lucern-clerk-id", config.clerkId);
|
|
333
|
+
setIfAbsent("x-lucern-user-id", config.userId ?? config.clerkId);
|
|
334
|
+
setIfAbsent("x-lucern-deployment-host", config.deploymentHost);
|
|
335
|
+
const base = Object.fromEntries(headers.entries());
|
|
336
|
+
const authContextInput = await resolveConfiguredAuthContext(config.authContext);
|
|
337
|
+
if (!authContextInput && !config.requireCanonicalAuthContext) {
|
|
338
|
+
return base;
|
|
507
339
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
tenantId: context.headers.get("x-lucern-tenant-id") ?? context.headers.get("x-lucern-tenant") ?? void 0,
|
|
547
|
-
workspaceId: context.headers.get("x-lucern-workspace-id") ?? context.headers.get("x-lucern-workspace") ?? void 0,
|
|
548
|
-
attributes: {
|
|
549
|
-
service: "lucern-sdk",
|
|
550
|
-
operation: "gateway.request",
|
|
551
|
-
path: context.path,
|
|
552
|
-
httpMethod: context.method,
|
|
553
|
-
httpStatus: context.status,
|
|
554
|
-
attempt: context.attempt,
|
|
555
|
-
maxRetries: context.maxRetries,
|
|
556
|
-
retryReason: retry.reason,
|
|
557
|
-
retryAfterMs: context.retryAfterMs ?? retry.retryAfterMs,
|
|
558
|
-
willRetry: context.willRetry,
|
|
559
|
-
retryable: retry.retryable,
|
|
560
|
-
errorName: context.error instanceof Error ? context.error.name : void 0,
|
|
561
|
-
errorMessage: context.error instanceof Error ? context.error.message : void 0
|
|
562
|
-
}
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
async function parsePayload(response) {
|
|
566
|
-
const text = await response.text();
|
|
567
|
-
if (!text) {
|
|
568
|
-
return null;
|
|
569
|
-
}
|
|
570
|
-
const parsed = tryParseGatewayEnvelopeJson(text);
|
|
571
|
-
if (!parsed.ok) {
|
|
572
|
-
return null;
|
|
573
|
-
}
|
|
574
|
-
return isRecord(parsed.value) ? parsed.value : null;
|
|
575
|
-
}
|
|
576
|
-
function resolveTimeoutMs(method, requestTimeoutMs) {
|
|
577
|
-
if (typeof requestTimeoutMs === "number") {
|
|
578
|
-
return requestTimeoutMs;
|
|
579
|
-
}
|
|
580
|
-
const methodTimeoutMs = requestTimeoutByMethod?.[method];
|
|
581
|
-
if (typeof methodTimeoutMs === "number") {
|
|
582
|
-
return methodTimeoutMs;
|
|
583
|
-
}
|
|
584
|
-
return defaultRequestTimeoutMs;
|
|
585
|
-
}
|
|
586
|
-
function tryParseGatewayEnvelopeJson(text) {
|
|
587
|
-
const trimmed = text.trim();
|
|
588
|
-
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
589
|
-
return { ok: false, reason: "non-json" };
|
|
590
|
-
}
|
|
591
|
-
try {
|
|
592
|
-
return { ok: true, value: JSON.parse(trimmed) };
|
|
593
|
-
} catch (error) {
|
|
594
|
-
if (error instanceof SyntaxError) {
|
|
595
|
-
return { ok: false, reason: "invalid-json", error };
|
|
596
|
-
}
|
|
597
|
-
throw error;
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
function buildApiError(args) {
|
|
601
|
-
const failure = args.failure;
|
|
602
|
-
const legacyError = failure && isRecord(failure.error) ? failure.error : failure?.legacyError;
|
|
603
|
-
const correlationId = failure?.correlationId ?? args.response.headers.get("x-lucern-correlation-id")?.trim() ?? args.requestId;
|
|
604
|
-
const policyTraceId = failure?.policyTraceId ?? args.response.headers.get("x-lucern-policy-trace-id")?.trim() ?? null;
|
|
605
|
-
const details = runtime.redaction(
|
|
606
|
-
redactJsonDiagnosticValue(failure?.details ?? legacyError?.details)
|
|
607
|
-
);
|
|
608
|
-
const policySummary = readPolicySummaryFromDetails(details);
|
|
609
|
-
const failureMessage = typeof failure?.error === "string" ? failure.error : legacyError?.message;
|
|
610
|
-
return new LucernApiError({
|
|
611
|
-
code: failure?.code ?? legacyError?.code ?? fallbackErrorCode(args.response.status),
|
|
612
|
-
message: policySummary ?? failureMessage ?? (args.response.ok ? "Platform API returned an invalid success payload." : "Platform API request failed."),
|
|
613
|
-
status: args.response.status,
|
|
614
|
-
invariant: failure?.invariant,
|
|
615
|
-
suggestion: failure?.suggestion,
|
|
616
|
-
details,
|
|
617
|
-
requestId: args.requestId,
|
|
618
|
-
correlationId,
|
|
619
|
-
policyTraceId
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
async function request(args) {
|
|
623
|
-
const authHeaders = await resolveAuthHeaders();
|
|
624
|
-
const method = args.method ?? "GET";
|
|
625
|
-
const timeoutMs = resolveTimeoutMs(method, args.timeoutMs);
|
|
626
|
-
const headers = new Headers({
|
|
627
|
-
"content-type": "application/json",
|
|
628
|
-
...authHeaders
|
|
629
|
-
});
|
|
630
|
-
if (args.idempotencyKey) {
|
|
631
|
-
headers.set("idempotency-key", args.idempotencyKey);
|
|
632
|
-
}
|
|
633
|
-
const requestId = headers.get("x-correlation-id")?.trim() || headers.get("x-request-id")?.trim() || args.requestId || requestIdFactory();
|
|
634
|
-
if (!headers.has("x-correlation-id") && !headers.has("x-request-id")) {
|
|
635
|
-
headers.set("x-correlation-id", requestId);
|
|
636
|
-
}
|
|
637
|
-
const url = `${baseUrl}${args.path}`;
|
|
638
|
-
const serializedBody = args.body ? JSON.stringify(args.body) : void 0;
|
|
639
|
-
const init = {
|
|
640
|
-
method,
|
|
641
|
-
headers,
|
|
642
|
-
body: serializedBody
|
|
643
|
-
};
|
|
644
|
-
let lastError;
|
|
645
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
646
|
-
const hookRequestContext = {
|
|
647
|
-
requestId,
|
|
648
|
-
attempt,
|
|
649
|
-
maxRetries,
|
|
650
|
-
method,
|
|
651
|
-
path: args.path,
|
|
652
|
-
url,
|
|
653
|
-
headers: new Headers(headers),
|
|
654
|
-
body: serializedBody,
|
|
655
|
-
timeoutMs
|
|
656
|
-
};
|
|
657
|
-
await config.onRequest?.(hookRequestContext);
|
|
658
|
-
const startedAt = Date.now();
|
|
659
|
-
try {
|
|
660
|
-
const response = await fetchWithTimeout(url, init, timeoutMs);
|
|
661
|
-
const responseClone = response.clone();
|
|
662
|
-
const payload = await parsePayload(response);
|
|
340
|
+
const authContext = normalizeCanonicalLucernAuthContext(authContextInput);
|
|
341
|
+
return mergeHeaderRecord(base, createCanonicalAuthHeaders(authContext));
|
|
342
|
+
}
|
|
343
|
+
async function fetchWithTimeout(url, init, timeoutMs) {
|
|
344
|
+
const normalizeTransportError = (error, isTimeout) => {
|
|
345
|
+
if (isTimeout) {
|
|
346
|
+
return new GatewayTimeoutError(timeoutMs);
|
|
347
|
+
}
|
|
348
|
+
return error instanceof GatewayTimeoutError ||
|
|
349
|
+
error instanceof GatewayTransportError
|
|
350
|
+
? error
|
|
351
|
+
: new GatewayTransportError(error instanceof Error ? error.message : "Gateway transport error", {
|
|
352
|
+
cause: error,
|
|
353
|
+
retryable: classifyGatewayErrorForRetry(error),
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
const controller = new AbortController();
|
|
357
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
358
|
+
const requestEffect = Effect.tryPromise({
|
|
359
|
+
try: () => runtime.fetch(url, { ...init, signal: controller.signal }),
|
|
360
|
+
catch: (error) => normalizeTransportError(error, controller.signal.aborted),
|
|
361
|
+
});
|
|
362
|
+
try {
|
|
363
|
+
const exit = await Effect.runPromiseExit(requestEffect);
|
|
364
|
+
if (Exit.isSuccess(exit)) {
|
|
365
|
+
return exit.value;
|
|
366
|
+
}
|
|
367
|
+
const failure = Array.from(Cause.failures(exit.cause))[0];
|
|
368
|
+
if (failure !== undefined) {
|
|
369
|
+
throw failure;
|
|
370
|
+
}
|
|
371
|
+
throw Cause.squash(exit.cause);
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
clearTimeout(timer);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async function emitSdkResponseTelemetry(context) {
|
|
663
378
|
const retry = classifyRetry({
|
|
664
|
-
|
|
665
|
-
|
|
379
|
+
status: context.status,
|
|
380
|
+
error: context.error,
|
|
381
|
+
retryAfter: context.retryAfterMs !== null && context.retryAfterMs !== undefined
|
|
382
|
+
? String(context.retryAfterMs / 1000)
|
|
383
|
+
: undefined,
|
|
666
384
|
});
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
attempt,
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
385
|
+
await emitTelemetrySignal(telemetryExporter, {
|
|
386
|
+
signalType: "trace",
|
|
387
|
+
surface: "sdk-retry",
|
|
388
|
+
eventName: context.willRetry
|
|
389
|
+
? "sdk.retry"
|
|
390
|
+
: context.error
|
|
391
|
+
? "sdk.request.error"
|
|
392
|
+
: "sdk.request.complete",
|
|
393
|
+
severity: context.error ? (context.willRetry ? "warn" : "error") : "info",
|
|
394
|
+
durationMs: context.durationMs,
|
|
395
|
+
metricName: "sdk.request.duration_ms",
|
|
396
|
+
metricValue: context.durationMs,
|
|
397
|
+
correlationId: context.correlationId ?? context.requestId,
|
|
398
|
+
policyTraceId: context.policyTraceId ?? null,
|
|
399
|
+
tenantId: context.headers.get("x-lucern-tenant-id") ??
|
|
400
|
+
context.headers.get("x-lucern-tenant") ??
|
|
401
|
+
undefined,
|
|
402
|
+
workspaceId: context.headers.get("x-lucern-workspace-id") ??
|
|
403
|
+
context.headers.get("x-lucern-workspace") ??
|
|
404
|
+
undefined,
|
|
405
|
+
attributes: {
|
|
406
|
+
service: "lucern-sdk",
|
|
407
|
+
operation: "gateway.request",
|
|
408
|
+
path: context.path,
|
|
409
|
+
httpMethod: context.method,
|
|
410
|
+
httpStatus: context.status,
|
|
411
|
+
attempt: context.attempt,
|
|
412
|
+
maxRetries: context.maxRetries,
|
|
413
|
+
retryReason: retry.reason,
|
|
414
|
+
retryAfterMs: context.retryAfterMs ?? retry.retryAfterMs,
|
|
415
|
+
willRetry: context.willRetry,
|
|
416
|
+
retryable: retry.retryable,
|
|
417
|
+
errorName: context.error instanceof Error ? context.error.name : undefined,
|
|
418
|
+
errorMessage: context.error instanceof Error ? context.error.message : undefined,
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async function parsePayload(response) {
|
|
423
|
+
const text = await response.text();
|
|
424
|
+
if (!text) {
|
|
425
|
+
return null;
|
|
701
426
|
}
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
427
|
+
const parsed = tryParseGatewayEnvelopeJson(text);
|
|
428
|
+
if (!parsed.ok) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
return isRecord(parsed.value)
|
|
432
|
+
? parsed.value
|
|
433
|
+
: null;
|
|
434
|
+
}
|
|
435
|
+
function resolveTimeoutMs(method, requestTimeoutMs) {
|
|
436
|
+
if (typeof requestTimeoutMs === "number") {
|
|
437
|
+
return requestTimeoutMs;
|
|
438
|
+
}
|
|
439
|
+
const methodTimeoutMs = requestTimeoutByMethod?.[method];
|
|
440
|
+
if (typeof methodTimeoutMs === "number") {
|
|
441
|
+
return methodTimeoutMs;
|
|
442
|
+
}
|
|
443
|
+
return defaultRequestTimeoutMs;
|
|
444
|
+
}
|
|
445
|
+
function tryParseGatewayEnvelopeJson(text) {
|
|
446
|
+
const trimmed = text.trim();
|
|
447
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
448
|
+
return { ok: false, reason: "non-json" };
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
return { ok: true, value: JSON.parse(trimmed) };
|
|
720
452
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
453
|
+
catch (error) {
|
|
454
|
+
if (error instanceof SyntaxError) {
|
|
455
|
+
return { ok: false, reason: "invalid-json", error };
|
|
456
|
+
}
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function buildApiError(args) {
|
|
461
|
+
const failure = args.failure;
|
|
462
|
+
const legacyError = failure && isRecord(failure.error) ? failure.error : failure?.legacyError;
|
|
463
|
+
const correlationId = failure?.correlationId ??
|
|
464
|
+
args.response.headers.get("x-lucern-correlation-id")?.trim() ??
|
|
465
|
+
args.requestId;
|
|
466
|
+
const policyTraceId = failure?.policyTraceId ??
|
|
467
|
+
args.response.headers.get("x-lucern-policy-trace-id")?.trim() ??
|
|
468
|
+
null;
|
|
469
|
+
const details = runtime.redaction(redactJsonDiagnosticValue(failure?.details ?? legacyError?.details));
|
|
470
|
+
const policySummary = readPolicySummaryFromDetails(details);
|
|
471
|
+
const failureMessage = typeof failure?.error === "string"
|
|
472
|
+
? failure.error
|
|
473
|
+
: legacyError?.message;
|
|
474
|
+
return new LucernApiError({
|
|
475
|
+
code: failure?.code ??
|
|
476
|
+
legacyError?.code ??
|
|
477
|
+
fallbackErrorCode(args.response.status),
|
|
478
|
+
message: policySummary ??
|
|
479
|
+
failureMessage ??
|
|
480
|
+
(args.response.ok
|
|
481
|
+
? "Platform API returned an invalid success payload."
|
|
482
|
+
: "Platform API request failed."),
|
|
483
|
+
status: args.response.status,
|
|
484
|
+
invariant: failure?.invariant,
|
|
485
|
+
suggestion: failure?.suggestion,
|
|
486
|
+
details,
|
|
487
|
+
requestId: args.requestId,
|
|
488
|
+
correlationId,
|
|
489
|
+
policyTraceId,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
async function request(args) {
|
|
493
|
+
const authHeaders = await resolveAuthHeaders();
|
|
494
|
+
const method = args.method ?? "GET";
|
|
495
|
+
const timeoutMs = resolveTimeoutMs(method, args.timeoutMs);
|
|
496
|
+
const headers = new Headers({
|
|
497
|
+
"content-type": "application/json",
|
|
498
|
+
...authHeaders,
|
|
499
|
+
});
|
|
500
|
+
if (args.idempotencyKey) {
|
|
501
|
+
headers.set("idempotency-key", args.idempotencyKey);
|
|
502
|
+
}
|
|
503
|
+
const requestId = headers.get("x-correlation-id")?.trim() ||
|
|
504
|
+
headers.get("x-request-id")?.trim() ||
|
|
505
|
+
args.requestId ||
|
|
506
|
+
requestIdFactory();
|
|
507
|
+
if (!headers.has("x-correlation-id") && !headers.has("x-request-id")) {
|
|
508
|
+
headers.set("x-correlation-id", requestId);
|
|
509
|
+
}
|
|
510
|
+
const url = `${baseUrl}${args.path}`;
|
|
511
|
+
const serializedBody = args.body ? JSON.stringify(args.body) : undefined;
|
|
512
|
+
const init = {
|
|
513
|
+
method,
|
|
514
|
+
headers,
|
|
515
|
+
body: serializedBody,
|
|
729
516
|
};
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
517
|
+
let lastError;
|
|
518
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
519
|
+
const hookRequestContext = {
|
|
520
|
+
requestId,
|
|
521
|
+
attempt,
|
|
522
|
+
maxRetries,
|
|
523
|
+
method,
|
|
524
|
+
path: args.path,
|
|
525
|
+
url,
|
|
526
|
+
headers: new Headers(headers),
|
|
527
|
+
body: serializedBody,
|
|
528
|
+
timeoutMs,
|
|
529
|
+
};
|
|
530
|
+
await config.onRequest?.(hookRequestContext);
|
|
531
|
+
const startedAt = Date.now();
|
|
532
|
+
try {
|
|
533
|
+
const response = await fetchWithTimeout(url, init, timeoutMs);
|
|
534
|
+
const responseClone = response.clone();
|
|
535
|
+
const payload = await parsePayload(response);
|
|
536
|
+
const retry = classifyRetry({
|
|
537
|
+
status: response.status,
|
|
538
|
+
retryAfter: response.headers.get("Retry-After"),
|
|
539
|
+
});
|
|
540
|
+
const retryAfterMs = retry.retryAfterMs ?? null;
|
|
541
|
+
if (!response.ok || !payload?.success) {
|
|
542
|
+
const failure = payload && !payload.success ? payload : null;
|
|
543
|
+
const apiError = buildApiError({
|
|
544
|
+
requestId,
|
|
545
|
+
response,
|
|
546
|
+
failure,
|
|
547
|
+
});
|
|
548
|
+
const willRetry = attempt < maxRetries && retry.retryable;
|
|
549
|
+
const responseContext = {
|
|
550
|
+
...hookRequestContext,
|
|
551
|
+
durationMs: Date.now() - startedAt,
|
|
552
|
+
status: response.status,
|
|
553
|
+
response: responseClone,
|
|
554
|
+
error: apiError,
|
|
555
|
+
correlationId: apiError.correlationId ?? requestId,
|
|
556
|
+
policyTraceId: apiError.policyTraceId ?? null,
|
|
557
|
+
retryAfterMs,
|
|
558
|
+
willRetry,
|
|
559
|
+
};
|
|
560
|
+
await config.onResponse?.(responseContext);
|
|
561
|
+
await emitSdkResponseTelemetry(responseContext);
|
|
562
|
+
if (willRetry) {
|
|
563
|
+
lastError = apiError;
|
|
564
|
+
await delay(computeRetryDelayMs({
|
|
565
|
+
attempt,
|
|
566
|
+
status: response.status,
|
|
567
|
+
retryAfterMs,
|
|
568
|
+
}));
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
throw apiError;
|
|
572
|
+
}
|
|
573
|
+
const successPayload = payload;
|
|
574
|
+
const responseContext = {
|
|
575
|
+
...hookRequestContext,
|
|
576
|
+
durationMs: Date.now() - startedAt,
|
|
577
|
+
status: response.status,
|
|
578
|
+
response: responseClone,
|
|
579
|
+
correlationId: successPayload.correlationId ??
|
|
580
|
+
response.headers.get("x-lucern-correlation-id")?.trim() ??
|
|
581
|
+
requestId,
|
|
582
|
+
policyTraceId: successPayload.policyTraceId ??
|
|
583
|
+
response.headers.get("x-lucern-policy-trace-id")?.trim() ??
|
|
584
|
+
null,
|
|
585
|
+
idempotentReplay: successPayload.idempotentReplay,
|
|
586
|
+
retryAfterMs,
|
|
587
|
+
willRetry: false,
|
|
588
|
+
};
|
|
589
|
+
await config.onResponse?.(responseContext);
|
|
590
|
+
await emitSdkResponseTelemetry(responseContext);
|
|
591
|
+
return successPayload;
|
|
592
|
+
}
|
|
593
|
+
catch (fetchError) {
|
|
594
|
+
if (fetchError instanceof LucernApiError) {
|
|
595
|
+
throw fetchError;
|
|
596
|
+
}
|
|
597
|
+
const willRetry = attempt < maxRetries && classifyGatewayErrorForRetry(fetchError);
|
|
598
|
+
const responseContext = {
|
|
599
|
+
...hookRequestContext,
|
|
600
|
+
durationMs: Date.now() - startedAt,
|
|
601
|
+
error: fetchError,
|
|
602
|
+
correlationId: requestId,
|
|
603
|
+
policyTraceId: null,
|
|
604
|
+
willRetry,
|
|
605
|
+
};
|
|
606
|
+
await config.onResponse?.(responseContext);
|
|
607
|
+
await emitSdkResponseTelemetry(responseContext);
|
|
608
|
+
lastError = fetchError;
|
|
609
|
+
if (willRetry) {
|
|
610
|
+
await delay(computeRetryDelayMs({ attempt }));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
735
613
|
}
|
|
736
|
-
|
|
614
|
+
throw lastError instanceof Error
|
|
615
|
+
? lastError
|
|
616
|
+
: new Error("Platform API request failed after retries.");
|
|
737
617
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
request
|
|
742
|
-
};
|
|
618
|
+
return {
|
|
619
|
+
request,
|
|
620
|
+
};
|
|
743
621
|
}
|
|
744
|
-
|
|
745
|
-
export { GatewayTimeoutError, GatewayTransportError, LucernApiError, createGatewayRequestClient, isGatewayRecoverableError, isGatewayRetryableError, randomIdempotencyKey, toQueryString };
|
|
746
|
-
//# sourceMappingURL=coreClient.js.map
|
|
747
622
|
//# sourceMappingURL=coreClient.js.map
|