@c15t/node-sdk 3.0.0-alpha.3 → 3.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -45
- package/dist/160.js +68 -0
- package/dist/index.js +1016 -349
- package/dist/testing.js +42 -87
- package/dist-types/client.d.ts +112 -705
- package/dist-types/configuration-error.d.ts +17 -0
- package/dist-types/contract.d.ts +868 -0
- package/dist-types/errors.d.ts +143 -0
- package/dist-types/index.d.ts +13 -40
- package/dist-types/options.d.ts +152 -0
- package/dist-types/path.d.ts +24 -0
- package/dist-types/result.d.ts +77 -0
- package/dist-types/testing.d.ts +81 -90
- package/dist-types/transport.d.ts +19 -0
- package/dist-types/version.d.ts +1 -1
- package/package.json +6 -12
- package/dist-types/endpoints/consent.d.ts +0 -16
- package/dist-types/endpoints/index.d.ts +0 -4
- package/dist-types/endpoints/init.d.ts +0 -15
- package/dist-types/endpoints/status.d.ts +0 -15
- package/dist-types/endpoints/subjects.d.ts +0 -45
- package/dist-types/error.d.ts +0 -85
- package/dist-types/fetcher.d.ts +0 -37
- package/dist-types/headers.d.ts +0 -3
- package/dist-types/types.d.ts +0 -234
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes and the error classes the client returns or throws.
|
|
3
|
+
*
|
|
4
|
+
* API codes are the backend's own, copied verbatim from `cause.code` in its
|
|
5
|
+
* error body. Client codes describe failures that happen on this side of the
|
|
6
|
+
* wire. Both stay SCREAMING_CASE so a caller sees one vocabulary.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Every code the c15t backend can put in `cause.code`.
|
|
10
|
+
*
|
|
11
|
+
* Each method narrows this to the codes its endpoint can actually return.
|
|
12
|
+
*/
|
|
13
|
+
export declare const C15T_API_ERROR_CODES: readonly ['BAD_REQUEST', 'CHOICE_OUT_OF_SCOPE', 'CHOICE_PREFERENCE_MISMATCH', 'CONFLICT', 'DATABASE_ERROR', 'EXTERNAL_ID_REQUIRED', 'INPUT_VALIDATION_FAILED', 'NOT_FOUND', 'POLICY_SNAPSHOT_EXPIRED', 'POLICY_SNAPSHOT_INVALID', 'POLICY_SNAPSHOT_REQUIRED', 'PURPOSE_NOT_ALLOWED', 'SERVICE_UNAVAILABLE', 'STALE_POLICY', 'SUBJECT_CONFLICT', 'TYPE_REQUIRED', 'UNAUTHORIZED'];
|
|
14
|
+
/** A code the c15t backend can return. */
|
|
15
|
+
export type C15tApiErrorCode = (typeof C15T_API_ERROR_CODES)[number];
|
|
16
|
+
/**
|
|
17
|
+
* Failures every method can return, whichever endpoint it calls.
|
|
18
|
+
*
|
|
19
|
+
* - `INVALID_INPUT`: the input failed validation; nothing was sent.
|
|
20
|
+
* - `MISSING_API_KEY`: the endpoint needs an API key and the client has none;
|
|
21
|
+
* nothing was sent.
|
|
22
|
+
* - `NETWORK_ERROR`: the request failed before a response arrived.
|
|
23
|
+
* - `TIMEOUT`: one attempt exceeded `timeoutMs`, body read included.
|
|
24
|
+
* - `ABORTED`: the caller's `signal` aborted the call.
|
|
25
|
+
* - `UNEXPECTED_RESPONSE`: a response this endpoint does not document, such as
|
|
26
|
+
* a proxy's HTML error page, an undeclared error code (kept in
|
|
27
|
+
* `serverCode`), or a body that could not be decoded.
|
|
28
|
+
*/
|
|
29
|
+
export declare const C15T_CLIENT_ERROR_CODES: readonly ['ABORTED', 'INVALID_INPUT', 'MISSING_API_KEY', 'NETWORK_ERROR', 'TIMEOUT', 'UNEXPECTED_RESPONSE'];
|
|
30
|
+
/** A failure produced by the client rather than the backend. */
|
|
31
|
+
export type C15tClientErrorCode = (typeof C15T_CLIENT_ERROR_CODES)[number];
|
|
32
|
+
/** Any code a {@link C15tError} can carry. */
|
|
33
|
+
export type C15tErrorCode = C15tApiErrorCode | C15tClientErrorCode;
|
|
34
|
+
/** Why the backend refused a consent as `STALE_POLICY`. */
|
|
35
|
+
export type StalePolicyReason = 'decision-mismatch' | 'incomplete-inputs' | 'policy-changed';
|
|
36
|
+
/**
|
|
37
|
+
* Narrows a wire value to a documented stale-policy reason.
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare const isStalePolicyReason: (value: unknown) => value is StalePolicyReason;
|
|
41
|
+
/** One input validation problem, as the schema reported it. */
|
|
42
|
+
export interface C15tIssue {
|
|
43
|
+
readonly message: string;
|
|
44
|
+
/** Path into the input, such as `['preferences', 'analytics']`. */
|
|
45
|
+
readonly path?: readonly PropertyKey[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* `reason` is only ever set for `STALE_POLICY`, so it is typed that way: a
|
|
49
|
+
* `C15tError<'NOT_FOUND'>` has no reason to read.
|
|
50
|
+
*/
|
|
51
|
+
type ReasonFor<Code extends string> = Code extends 'STALE_POLICY' ? StalePolicyReason | undefined : undefined;
|
|
52
|
+
/** Fields used to build a {@link C15tError}. */
|
|
53
|
+
export interface C15tErrorInit<Code extends string> {
|
|
54
|
+
readonly code: Code;
|
|
55
|
+
readonly message: string;
|
|
56
|
+
readonly status?: number | undefined;
|
|
57
|
+
readonly reason?: StalePolicyReason | undefined;
|
|
58
|
+
readonly requestId?: string | undefined;
|
|
59
|
+
readonly retryable?: boolean | undefined;
|
|
60
|
+
readonly issues?: readonly C15tIssue[] | undefined;
|
|
61
|
+
readonly serverCode?: string | undefined;
|
|
62
|
+
readonly cause?: unknown;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A failed call.
|
|
66
|
+
*
|
|
67
|
+
* Methods return it inside `{ ok: false, error }` rather than throwing it;
|
|
68
|
+
* {@link unwrap} throws it. `Code` is the union of codes the failing method
|
|
69
|
+
* can produce, so a `switch (error.code)` is checked for exhaustiveness.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const result = await c15t.subjects.get(id);
|
|
74
|
+
* if (!result.ok) {
|
|
75
|
+
* switch (result.error.code) {
|
|
76
|
+
* case 'NOT_FOUND':
|
|
77
|
+
* return null;
|
|
78
|
+
* default:
|
|
79
|
+
* throw result.error;
|
|
80
|
+
* }
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare class C15tError<Code extends string = C15tErrorCode> extends Error {
|
|
85
|
+
readonly name = "C15tError";
|
|
86
|
+
/** Stable, machine-readable failure code. */
|
|
87
|
+
readonly code: Code;
|
|
88
|
+
/** HTTP status, when a response arrived. */
|
|
89
|
+
readonly status: number | undefined;
|
|
90
|
+
/** Refinement of `STALE_POLICY`; `undefined` for every other code. */
|
|
91
|
+
readonly reason: ReasonFor<Code>;
|
|
92
|
+
/**
|
|
93
|
+
* The `x-request-id` the call sent, for matching backend logs. Absent when
|
|
94
|
+
* nothing was sent (`INVALID_INPUT`, `MISSING_API_KEY`).
|
|
95
|
+
*/
|
|
96
|
+
readonly requestId: string | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Whether repeating the same call could succeed. The client already
|
|
99
|
+
* retried idempotent endpoints before returning this.
|
|
100
|
+
*/
|
|
101
|
+
readonly retryable: boolean;
|
|
102
|
+
/** Validation problems, set for `INVALID_INPUT`. */
|
|
103
|
+
readonly issues: readonly C15tIssue[] | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* The raw `cause.code` when the backend sent one this endpoint does not
|
|
106
|
+
* document. Set only for `UNEXPECTED_RESPONSE`.
|
|
107
|
+
*/
|
|
108
|
+
readonly serverCode: string | undefined;
|
|
109
|
+
constructor(init: C15tErrorInit<Code>);
|
|
110
|
+
/** Plain object for logging and serialization. `cause` is left out. */
|
|
111
|
+
toJSON(): {
|
|
112
|
+
readonly name: 'C15tError';
|
|
113
|
+
readonly code: Code;
|
|
114
|
+
readonly message: string;
|
|
115
|
+
readonly status: number | undefined;
|
|
116
|
+
readonly reason: ReasonFor<Code>;
|
|
117
|
+
readonly requestId: string | undefined;
|
|
118
|
+
readonly retryable: boolean;
|
|
119
|
+
readonly issues: readonly C15tIssue[] | undefined;
|
|
120
|
+
readonly serverCode: string | undefined;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Checks whether a value is a {@link C15tError}, optionally with one of the
|
|
125
|
+
* given codes.
|
|
126
|
+
*
|
|
127
|
+
* @param error - Any caught value.
|
|
128
|
+
* @param codes - Codes to match. With none, any `C15tError` matches.
|
|
129
|
+
* @returns `true` when `error` is a `C15tError` whose code is in `codes`.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* try {
|
|
134
|
+
* unwrap(await c15t.subjects.get(id));
|
|
135
|
+
* } catch (error) {
|
|
136
|
+
* if (isC15tError(error, 'NOT_FOUND')) {
|
|
137
|
+
* // error.code is 'NOT_FOUND'
|
|
138
|
+
* }
|
|
139
|
+
* }
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export declare function isC15tError<const Codes extends readonly C15tErrorCode[]>(error: unknown, ...codes: Codes): error is C15tError<Codes extends readonly [] ? C15tErrorCode : Codes[number]>;
|
|
143
|
+
export {};
|
package/dist-types/index.d.ts
CHANGED
|
@@ -1,43 +1,16 @@
|
|
|
1
|
-
import { C15TClient } from './client.js';
|
|
2
|
-
import type { C15TClientOptions } from './types.js';
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* Typed client for the c15t consent API, for server code.
|
|
5
3
|
*
|
|
6
|
-
* @
|
|
7
|
-
* falls back to C15T_API_URL and C15T_API_TOKEN environment variables.
|
|
8
|
-
* @returns A C15TClient instance for interacting with the C15T API
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```typescript
|
|
12
|
-
* // Auto-configure from environment variables
|
|
13
|
-
* const client = c15tClient();
|
|
14
|
-
*
|
|
15
|
-
* // Or provide explicit options
|
|
16
|
-
* const client = c15tClient({
|
|
17
|
-
* baseUrl: "https://api.example.com",
|
|
18
|
-
* token: "your-auth-token"
|
|
19
|
-
* });
|
|
20
|
-
*
|
|
21
|
-
* // Check API status
|
|
22
|
-
* const status = await client.meta.status();
|
|
23
|
-
*
|
|
24
|
-
* // Initialize consent manager
|
|
25
|
-
* const init = await client.meta.init();
|
|
26
|
-
*
|
|
27
|
-
* // Create a subject with consent
|
|
28
|
-
* const subject = await client.subjects.create({
|
|
29
|
-
* type: 'cookie_banner',
|
|
30
|
-
* subjectId: 'sub_123',
|
|
31
|
-
* domain: 'example.com',
|
|
32
|
-
* preferences: { analytics: true },
|
|
33
|
-
* givenAt: Date.now(),
|
|
34
|
-
* });
|
|
35
|
-
* ```
|
|
4
|
+
* @packageDocumentation
|
|
36
5
|
*/
|
|
37
|
-
export
|
|
38
|
-
export type {
|
|
39
|
-
export {
|
|
40
|
-
export {
|
|
41
|
-
export type {
|
|
42
|
-
export {
|
|
43
|
-
export type {
|
|
6
|
+
export { createC15tClient } from './client.js';
|
|
7
|
+
export type { C15tClient, C15tConsents, C15tExperiments, C15tLegalDocuments, C15tPublicClient, C15tPublicSubjects, C15tSubjects, } from './client.js';
|
|
8
|
+
export type { C15tCheckConsentInput, C15tCheckConsentOutput, C15tCreateSubjectInput, C15tExperimentSummaryRequest, C15tGetSubjectRequest, C15tIdentifySubjectInput, C15tInitRequest, C15tManifestRequest, C15tManifestResult, C15tPublishLegalDocumentInput, } from './contract.js';
|
|
9
|
+
export { C15tConfigurationError } from './configuration-error.js';
|
|
10
|
+
export type { C15tConfigurationIssue } from './configuration-error.js';
|
|
11
|
+
export { C15T_API_ERROR_CODES, C15T_CLIENT_ERROR_CODES, C15tError, isC15tError, } from './errors.js';
|
|
12
|
+
export type { C15tApiErrorCode, C15tClientErrorCode, C15tErrorCode, C15tIssue, StalePolicyReason, } from './errors.js';
|
|
13
|
+
export type { C15tCallOptions, C15tClientOptions, C15tRequestEvent, C15tRetryOptions, } from './options.js';
|
|
14
|
+
export { unwrap } from './result.js';
|
|
15
|
+
export type { C15tDataOf, C15tErrorCodeOf, C15tFailure, C15tResult, C15tSuccess, } from './result.js';
|
|
16
|
+
export type { ConsentCheckResult, ConsentItem, ConsentManifest, ConsentPolicyType, ExperimentArmSummary, ExperimentSummaryOutput, GetSubjectOutput, InitOutput, LegalDocumentCurrentOutput, LegalDocumentPolicyType, ListSubjectsOutput, PatchSubjectOutput, PostSubjectOutput, StatusOutput, SubjectItem, } from '@c15t/schema/types';
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { C15tConfigurationIssue } from './configuration-error.js';
|
|
2
|
+
import type { C15tError } from './errors.js';
|
|
3
|
+
/** Retry behaviour for idempotent endpoints. */
|
|
4
|
+
export interface C15tRetryOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Attempts after the first, for retryable failures only.
|
|
7
|
+
* @default 2
|
|
8
|
+
*/
|
|
9
|
+
readonly maxRetries?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Upper bound of the first backoff, in milliseconds. Each retry doubles it,
|
|
12
|
+
* and the actual delay is a random value below the bound (full jitter).
|
|
13
|
+
* Must be an integer from 0 to 2147483647.
|
|
14
|
+
* @default 200
|
|
15
|
+
*/
|
|
16
|
+
readonly initialDelayMs?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Longest the client waits before a retry, in milliseconds. A
|
|
19
|
+
* `Retry-After` longer than this ends retrying instead.
|
|
20
|
+
* Must be an integer from 0 to 2147483647.
|
|
21
|
+
* @default 5000
|
|
22
|
+
*/
|
|
23
|
+
readonly maxDelayMs?: number;
|
|
24
|
+
}
|
|
25
|
+
/** What happened during one call, for logging and metrics. */
|
|
26
|
+
export type C15tRequestEvent = {
|
|
27
|
+
readonly type: 'request';
|
|
28
|
+
readonly method: string;
|
|
29
|
+
readonly path: string;
|
|
30
|
+
readonly requestId: string;
|
|
31
|
+
/** 0 for the first attempt. */
|
|
32
|
+
readonly attempt: number;
|
|
33
|
+
} | {
|
|
34
|
+
readonly type: 'response';
|
|
35
|
+
readonly method: string;
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly requestId: string;
|
|
38
|
+
readonly attempt: number;
|
|
39
|
+
readonly status: number;
|
|
40
|
+
readonly durationMs: number;
|
|
41
|
+
} | {
|
|
42
|
+
readonly type: 'retry';
|
|
43
|
+
readonly method: string;
|
|
44
|
+
readonly path: string;
|
|
45
|
+
readonly requestId: string;
|
|
46
|
+
/** The attempt that failed. */
|
|
47
|
+
readonly attempt: number;
|
|
48
|
+
readonly delayMs: number;
|
|
49
|
+
readonly error: C15tError;
|
|
50
|
+
};
|
|
51
|
+
/** Options for {@link createC15tClient}. */
|
|
52
|
+
export interface C15tClientOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Where the c15t backend is mounted, path included, such as
|
|
55
|
+
* `https://example.com/api/c15t` or your hosted instance URL.
|
|
56
|
+
*/
|
|
57
|
+
readonly baseUrl: string | URL;
|
|
58
|
+
/**
|
|
59
|
+
* API key, sent as `Authorization: Bearer`. Required for
|
|
60
|
+
* `subjects.list`, `experiments.summary` and `legalDocuments.publish`;
|
|
61
|
+
* a client created without one does not have those methods.
|
|
62
|
+
*
|
|
63
|
+
* The client reads no environment variables. Pass the key explicitly.
|
|
64
|
+
*/
|
|
65
|
+
readonly apiKey?: string | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* `fetch` implementation. Defaults to `globalThis.fetch`. Use it for
|
|
68
|
+
* tracing, proxies or tests.
|
|
69
|
+
*/
|
|
70
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
71
|
+
/** Headers sent with every request. */
|
|
72
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
73
|
+
/**
|
|
74
|
+
* Deadline for one attempt in milliseconds, response body included.
|
|
75
|
+
* Must be an integer from 1 to 2147483647.
|
|
76
|
+
* @default 10000
|
|
77
|
+
*/
|
|
78
|
+
readonly timeoutMs?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Retry behaviour for idempotent endpoints, or `false` to never retry.
|
|
81
|
+
*/
|
|
82
|
+
readonly retry?: C15tRetryOptions | false;
|
|
83
|
+
/**
|
|
84
|
+
* Called for each request, response and retry. Errors it throws are
|
|
85
|
+
* ignored.
|
|
86
|
+
*/
|
|
87
|
+
readonly onEvent?: (event: C15tRequestEvent) => void;
|
|
88
|
+
}
|
|
89
|
+
/** Options accepted by every method as its last argument. */
|
|
90
|
+
export interface C15tCallOptions {
|
|
91
|
+
/** Aborts the call, including a pending retry. Resolves as `ABORTED`. */
|
|
92
|
+
readonly signal?: AbortSignal;
|
|
93
|
+
/** Overrides the client's `timeoutMs` for this call. */
|
|
94
|
+
readonly timeoutMs?: number;
|
|
95
|
+
/** Headers for this call, merged over the client's. */
|
|
96
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
97
|
+
/** Overrides the client's retry behaviour for this call. */
|
|
98
|
+
readonly retry?: C15tRetryOptions | false;
|
|
99
|
+
/**
|
|
100
|
+
* `x-request-id` to send, such as the id of the request being handled.
|
|
101
|
+
* Defaults to a random UUID.
|
|
102
|
+
*/
|
|
103
|
+
readonly requestId?: string;
|
|
104
|
+
}
|
|
105
|
+
/** Retry settings with every default applied. */
|
|
106
|
+
export interface ResolvedRetry {
|
|
107
|
+
readonly maxRetries: number;
|
|
108
|
+
readonly initialDelayMs: number;
|
|
109
|
+
readonly maxDelayMs: number;
|
|
110
|
+
}
|
|
111
|
+
/** Client options after decoding. */
|
|
112
|
+
export interface ResolvedOptions {
|
|
113
|
+
readonly baseUrl: URL;
|
|
114
|
+
readonly apiKey: string | undefined;
|
|
115
|
+
readonly fetch: typeof globalThis.fetch;
|
|
116
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
117
|
+
readonly timeoutMs: number;
|
|
118
|
+
readonly retry: ResolvedRetry;
|
|
119
|
+
readonly onEvent: ((event: C15tRequestEvent) => void) | undefined;
|
|
120
|
+
}
|
|
121
|
+
export declare const DEFAULT_TIMEOUT_MS = 10000;
|
|
122
|
+
export declare const MAX_RETRIES_LIMIT = 10;
|
|
123
|
+
export declare const NO_RETRY: ResolvedRetry;
|
|
124
|
+
/**
|
|
125
|
+
* Decodes retry options. Shared with per-call options, which is why it takes
|
|
126
|
+
* the option path to report.
|
|
127
|
+
*/
|
|
128
|
+
export declare const decodeRetry: (value: unknown, option: string, issues: C15tConfigurationIssue[], fallback?: ResolvedRetry) => ResolvedRetry;
|
|
129
|
+
/**
|
|
130
|
+
* Decodes client options from `unknown`.
|
|
131
|
+
*
|
|
132
|
+
* TypeScript catches most mistakes at the call site, but JavaScript callers
|
|
133
|
+
* and options assembled at runtime reach the same code, so every option is
|
|
134
|
+
* checked here and every problem is reported in one throw.
|
|
135
|
+
*
|
|
136
|
+
* @throws {C15tConfigurationError} When any option is invalid.
|
|
137
|
+
*/
|
|
138
|
+
export declare const decodeOptions: (options: unknown) => ResolvedOptions;
|
|
139
|
+
/** Call options after decoding, with client defaults applied. */
|
|
140
|
+
export interface ResolvedCallOptions {
|
|
141
|
+
readonly signal: AbortSignal | undefined;
|
|
142
|
+
readonly timeoutMs: number;
|
|
143
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
144
|
+
readonly retry: ResolvedRetry;
|
|
145
|
+
readonly requestId: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Decodes per-call options. Unlike client options, a bad value here is
|
|
149
|
+
* reported as an `INVALID_INPUT` result rather than thrown, because methods
|
|
150
|
+
* never throw.
|
|
151
|
+
*/
|
|
152
|
+
export declare const decodeCallOptions: (value: unknown, client: ResolvedOptions, issues: C15tConfigurationIssue[]) => ResolvedCallOptions;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Names of the `:param` segments in a route template.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* type Names = PathParamNames<'/experiments/:id/summary'>; // 'id'
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export type PathParamNames<Path extends string> = Path extends `${string}:${infer Param}/${infer Rest}` ? Param | PathParamNames<`/${Rest}`> : Path extends `${string}:${infer Param}` ? Param : never;
|
|
10
|
+
/** The values a route template needs, one string per `:param`. */
|
|
11
|
+
export type PathParams<Path extends string> = {
|
|
12
|
+
readonly [Name in PathParamNames<Path>]: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Fills a route template's `:param` segments.
|
|
16
|
+
*
|
|
17
|
+
* Every value is percent-encoded, so an id containing `/`, `?` or `#` stays
|
|
18
|
+
* one path segment instead of changing the route.
|
|
19
|
+
*
|
|
20
|
+
* @param template - Route template, such as `/subjects/:id`.
|
|
21
|
+
* @param params - One string per `:param`.
|
|
22
|
+
* @returns The path with every segment filled.
|
|
23
|
+
*/
|
|
24
|
+
export declare const buildPath: <Path extends string>(template: Path, params: PathParams<Path>) => string;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { C15tClientErrorCode, C15tError } from './errors.js';
|
|
2
|
+
/** A call that succeeded. */
|
|
3
|
+
export interface C15tSuccess<Data> {
|
|
4
|
+
readonly ok: true;
|
|
5
|
+
readonly data: Data;
|
|
6
|
+
/** HTTP status of the response. */
|
|
7
|
+
readonly status: number;
|
|
8
|
+
/** The `x-request-id` the call sent. */
|
|
9
|
+
readonly requestId: string;
|
|
10
|
+
readonly headers: Headers;
|
|
11
|
+
}
|
|
12
|
+
/** A call that failed. */
|
|
13
|
+
export interface C15tFailure<Code extends string> {
|
|
14
|
+
readonly ok: false;
|
|
15
|
+
readonly error: C15tError<Code>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* What every client method resolves to. Methods never reject.
|
|
19
|
+
*
|
|
20
|
+
* `ApiCode` lists the backend codes the method's endpoint can return; the
|
|
21
|
+
* client's own codes are always included.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const result = await c15t.subjects.get(id);
|
|
26
|
+
* if (result.ok) {
|
|
27
|
+
* result.data.consents; // narrowed: data exists, error does not
|
|
28
|
+
* } else {
|
|
29
|
+
* result.error.code; // 'NOT_FOUND' | 'DATABASE_ERROR' | C15tClientErrorCode
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export type C15tResult<Data, ApiCode extends string = never> = C15tSuccess<Data> | C15tFailure<ApiCode | C15tClientErrorCode>;
|
|
34
|
+
/** Anything that returns a {@link C15tResult}, sync or async. */
|
|
35
|
+
type ResultReturning = (...args: never[]) => C15tResult<unknown, string> | Promise<C15tResult<unknown, string>>;
|
|
36
|
+
/**
|
|
37
|
+
* The `data` type of a client method.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* type Subject = C15tDataOf<typeof c15t.subjects.get>;
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export type C15tDataOf<Method extends ResultReturning> = Extract<Awaited<ReturnType<Method>>, {
|
|
45
|
+
ok: true;
|
|
46
|
+
}> extends C15tSuccess<infer Data> ? Data : never;
|
|
47
|
+
/**
|
|
48
|
+
* Every error code a client method can return.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* type GetSubjectError = C15tErrorCodeOf<typeof c15t.subjects.get>;
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export type C15tErrorCodeOf<Method extends ResultReturning> = Extract<Awaited<ReturnType<Method>>, {
|
|
56
|
+
ok: false;
|
|
57
|
+
}> extends C15tFailure<infer Code> ? Code : never;
|
|
58
|
+
/**
|
|
59
|
+
* Returns the data of a successful result, or throws its {@link C15tError}.
|
|
60
|
+
*
|
|
61
|
+
* Use it where a failure should abort the surrounding work, such as a job
|
|
62
|
+
* that has its own retry.
|
|
63
|
+
*
|
|
64
|
+
* @param result - A client method's result.
|
|
65
|
+
* @returns The result's `data`.
|
|
66
|
+
* @throws {C15tError} The result's error, with its code narrowed to the
|
|
67
|
+
* method's codes.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const { results } = unwrap(
|
|
72
|
+
* await c15t.consents.check({ externalId, types: ['marketing_communications'] })
|
|
73
|
+
* );
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare const unwrap: <Data, Code extends string>(result: C15tSuccess<Data> | C15tFailure<Code>) => Data;
|
|
77
|
+
export {};
|
package/dist-types/testing.d.ts
CHANGED
|
@@ -1,110 +1,101 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Test helpers for code that uses `@c15t/node-sdk`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* `createMockC15tClient` returns a full {@link C15tClient} whose methods call
|
|
5
|
+
* the handlers you pass. Handler types come from the client, so a handler
|
|
6
|
+
* that returns the wrong data or an error code its method cannot produce is
|
|
7
|
+
* a type error.
|
|
5
8
|
*
|
|
6
9
|
* @example
|
|
7
|
-
* ```
|
|
8
|
-
* import {
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { createMockC15tClient, err, ok } from '@c15t/node-sdk/testing';
|
|
9
12
|
*
|
|
10
|
-
* const
|
|
11
|
-
*
|
|
13
|
+
* const c15t = createMockC15tClient({
|
|
14
|
+
* consents: {
|
|
15
|
+
* check: ({ types }) =>
|
|
16
|
+
* ok({
|
|
17
|
+
* results: Object.fromEntries(
|
|
18
|
+
* types.map((type) => [type, { hasConsent: true, isLatestPolicy: true }])
|
|
19
|
+
* ),
|
|
20
|
+
* }),
|
|
21
|
+
* },
|
|
22
|
+
* subjects: { get: () => err('NOT_FOUND') },
|
|
12
23
|
* });
|
|
13
|
-
*
|
|
14
|
-
* // Use mockClient in your tests
|
|
15
|
-
* const result = await mockClient.getSubject('sub_123');
|
|
16
|
-
* expect(result.data?.id).toBe('sub_123');
|
|
17
24
|
* ```
|
|
18
25
|
*/
|
|
19
|
-
import type {
|
|
26
|
+
import type { ConsentPolicyType } from '@c15t/schema/types';
|
|
27
|
+
import type { C15tClient, C15tExperiments, C15tLegalDocuments, C15tSubjects } from './client.js';
|
|
28
|
+
import type { C15tCheckConsentInput } from './contract.js';
|
|
29
|
+
import type { C15tErrorCode, C15tIssue, StalePolicyReason } from './errors.js';
|
|
30
|
+
import type { C15tCallOptions } from './options.js';
|
|
31
|
+
import type { C15tFailure, C15tResult, C15tSuccess } from './result.js';
|
|
32
|
+
type Awaitable<Value> = Value | Promise<Value>;
|
|
33
|
+
/** A handler with the same arguments as `Method`, sync or async. */
|
|
34
|
+
export type C15tMockHandler<Method> = Method extends (...args: infer Args) => Promise<infer Result> ? (...args: Args) => Awaitable<Result> : never;
|
|
20
35
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* @param options - Optional configuration for the mock response
|
|
25
|
-
* @returns A ResponseContext object
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```typescript
|
|
29
|
-
* const response = createMockResponse({ id: 'sub_123' });
|
|
30
|
-
* expect(response.ok).toBe(true);
|
|
31
|
-
* expect(response.data?.id).toBe('sub_123');
|
|
32
|
-
* ```
|
|
36
|
+
* `consents.check` is generic over the requested types, which a plain
|
|
37
|
+
* handler cannot be. Its handler returns results keyed by string; the mock
|
|
38
|
+
* hands them back under the caller's narrowed type.
|
|
33
39
|
*/
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
export type C15tMockCheckConsentHandler = (input: C15tCheckConsentInput<readonly ConsentPolicyType[]>, options?: C15tCallOptions) => Awaitable<C15tResult<{
|
|
41
|
+
readonly results: Readonly<Record<string, {
|
|
42
|
+
readonly hasConsent: boolean;
|
|
43
|
+
readonly isLatestPolicy: boolean;
|
|
44
|
+
}>>;
|
|
45
|
+
}, 'DATABASE_ERROR' | 'EXTERNAL_ID_REQUIRED' | 'TYPE_REQUIRED'>>;
|
|
46
|
+
/** Handlers for {@link createMockC15tClient}. Any method can be left out. */
|
|
47
|
+
export interface C15tMockHandlers {
|
|
48
|
+
readonly status?: C15tMockHandler<C15tClient['status']>;
|
|
49
|
+
readonly init?: C15tMockHandler<C15tClient['init']>;
|
|
50
|
+
readonly manifest?: C15tMockHandler<C15tClient['manifest']>;
|
|
51
|
+
readonly subjects?: {
|
|
52
|
+
readonly [Name in keyof C15tSubjects]?: C15tMockHandler<C15tSubjects[Name]>;
|
|
53
|
+
};
|
|
54
|
+
readonly consents?: {
|
|
55
|
+
readonly check?: C15tMockCheckConsentHandler;
|
|
41
56
|
};
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
readonly experiments?: {
|
|
58
|
+
readonly [Name in keyof C15tExperiments]?: C15tMockHandler<C15tExperiments[Name]>;
|
|
59
|
+
};
|
|
60
|
+
readonly legalDocuments?: {
|
|
61
|
+
readonly [Name in keyof C15tLegalDocuments]?: C15tMockHandler<C15tLegalDocuments[Name]>;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
44
64
|
/**
|
|
45
|
-
*
|
|
65
|
+
* Builds a successful result.
|
|
46
66
|
*
|
|
47
|
-
* @param
|
|
48
|
-
* @
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* ```typescript
|
|
52
|
-
* const response = createMockErrorResponse({
|
|
53
|
-
* message: 'Not found',
|
|
54
|
-
* status: 404,
|
|
55
|
-
* code: 'NOT_FOUND',
|
|
56
|
-
* });
|
|
57
|
-
* expect(response.ok).toBe(false);
|
|
58
|
-
* expect(response.error?.status).toBe(404);
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
|
-
export declare const createMockErrorResponse: <T = unknown>(error: {
|
|
62
|
-
message: string;
|
|
63
|
-
status: number;
|
|
64
|
-
code?: string;
|
|
65
|
-
details?: Record<string, unknown> | null;
|
|
66
|
-
}) => ResponseContext<T>;
|
|
67
|
-
/**
|
|
68
|
-
* Type for mock method implementations
|
|
67
|
+
* @param data - The result's data.
|
|
68
|
+
* @param init - Optional status, request id and headers.
|
|
69
69
|
*/
|
|
70
|
-
export
|
|
70
|
+
export declare const ok: <Data>(data: Data, init?: {
|
|
71
|
+
readonly status?: number;
|
|
72
|
+
readonly requestId?: string;
|
|
73
|
+
readonly headers?: ConstructorParameters<typeof Headers>[0];
|
|
74
|
+
}) => C15tSuccess<Data>;
|
|
71
75
|
/**
|
|
72
|
-
*
|
|
76
|
+
* Builds a failed result. The code is checked against the method the
|
|
77
|
+
* handler belongs to.
|
|
78
|
+
*
|
|
79
|
+
* @param code - Error code, such as `NOT_FOUND`.
|
|
80
|
+
* @param init - Optional message, status and other error fields.
|
|
73
81
|
*/
|
|
74
|
-
export
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
82
|
+
export declare const err: <const Code extends C15tErrorCode>(code: Code, init?: {
|
|
83
|
+
readonly message?: string;
|
|
84
|
+
readonly status?: number;
|
|
85
|
+
readonly reason?: Code extends 'STALE_POLICY' ? StalePolicyReason : never;
|
|
86
|
+
readonly requestId?: string;
|
|
87
|
+
readonly retryable?: boolean;
|
|
88
|
+
readonly issues?: readonly C15tIssue[];
|
|
89
|
+
readonly serverCode?: string;
|
|
90
|
+
}) => C15tFailure<Code>;
|
|
83
91
|
/**
|
|
84
|
-
* Creates a
|
|
92
|
+
* Creates a client whose methods call the given handlers.
|
|
85
93
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
94
|
+
* A method without a handler rejects with an error naming it, so a test
|
|
95
|
+
* fails loudly when code calls something the test did not expect.
|
|
88
96
|
*
|
|
89
|
-
* @
|
|
90
|
-
*
|
|
91
|
-
* const mockClient = createMockClient({
|
|
92
|
-
* getSubject: async (id) => createMockResponse({
|
|
93
|
-
* id,
|
|
94
|
-
* externalId: 'user_123',
|
|
95
|
-
* consents: [],
|
|
96
|
-
* }),
|
|
97
|
-
* checkConsent: async () => createMockResponse({
|
|
98
|
-
* results: { analytics: { hasConsent: true } },
|
|
99
|
-
* }),
|
|
100
|
-
* });
|
|
101
|
-
*
|
|
102
|
-
* // Use in tests
|
|
103
|
-
* const result = await mockClient.getSubject('sub_123');
|
|
104
|
-
* ```
|
|
105
|
-
*/
|
|
106
|
-
export declare const createMockClient: (overrides?: MockClientOverrides) => any;
|
|
107
|
-
/**
|
|
108
|
-
* Type representing the mock client returned by createMockClient
|
|
97
|
+
* @param handlers - Handlers by namespace and method name.
|
|
98
|
+
* @returns A full {@link C15tClient}.
|
|
109
99
|
*/
|
|
110
|
-
export
|
|
100
|
+
export declare const createMockC15tClient: (handlers?: C15tMockHandlers) => C15tClient;
|
|
101
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sends one endpoint call: validation, auth, retries, timeouts and response
|
|
3
|
+
* classification. Every outcome resolves as a result; nothing here throws to
|
|
4
|
+
* the caller.
|
|
5
|
+
*/
|
|
6
|
+
import type { Endpoint } from './contract.js';
|
|
7
|
+
import type { C15tApiErrorCode } from './errors.js';
|
|
8
|
+
import type { ResolvedOptions } from './options.js';
|
|
9
|
+
import type { C15tResult } from './result.js';
|
|
10
|
+
/**
|
|
11
|
+
* Calls one endpoint.
|
|
12
|
+
*
|
|
13
|
+
* @param options - Decoded client options.
|
|
14
|
+
* @param endpoint - The route to call.
|
|
15
|
+
* @param input - The endpoint's input, validated before anything is sent.
|
|
16
|
+
* @param rawCallOptions - The caller's per-call options, decoded here.
|
|
17
|
+
* @returns The call's result. Never rejects.
|
|
18
|
+
*/
|
|
19
|
+
export declare const send: <Path extends string, Input, Data, Code extends C15tApiErrorCode>(options: ResolvedOptions, endpoint: Endpoint<Path, Input, Data, Code>, input: Input, rawCallOptions: unknown) => Promise<C15tResult<Data, Code>>;
|
package/dist-types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "3.0.0-alpha.
|
|
1
|
+
export declare const version = "3.0.0-alpha.5";
|