@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.
@@ -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 {};
@@ -1,43 +1,16 @@
1
- import { C15TClient } from './client.js';
2
- import type { C15TClientOptions } from './types.js';
3
1
  /**
4
- * Creates a type-safe C15T client instance
2
+ * Typed client for the c15t consent API, for server code.
5
3
  *
6
- * @param options - Configuration options for the client. If not provided,
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 declare const c15tClient: (options?: C15TClientOptions) => C15TClient;
38
- export type { CheckConsentOutput, CheckConsentQuery, ConsentCheckResult, ConsentItem, GetSubjectInput, GetSubjectOutput, GetSubjectParams, GetSubjectQuery, InitOutput, ListSubjectsOutput, ListSubjectsQuery, PatchSubjectFullInput, PatchSubjectOutput, PostSubjectInput, PostSubjectOutput, StatusOutput, SubjectItem, } from '@c15t/schema/types';
39
- export { C15TClient } from './client.js';
40
- export { C15TError, isC15TError } from './error.js';
41
- export type { FetcherContext } from './fetcher.js';
42
- export { createResponseContext, fetcher, resolveUrl } from './fetcher.js';
43
- export type { C15TClientOptions, FetchOptions, ResponseContext, RetryConfig, } from './types.js';
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 {};
@@ -1,110 +1,101 @@
1
1
  /**
2
- * Testing utilities for @c15t/node-sdk
2
+ * Test helpers for code that uses `@c15t/node-sdk`.
3
3
  *
4
- * These utilities help users mock the C15T client in their tests.
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
- * ```typescript
8
- * import { createMockClient, createMockResponse } from '@c15t/node-sdk/testing';
10
+ * ```ts
11
+ * import { createMockC15tClient, err, ok } from '@c15t/node-sdk/testing';
9
12
  *
10
- * const mockClient = createMockClient({
11
- * getSubject: async () => createMockResponse({ id: 'sub_123', consents: [] }),
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 { ResponseContext } from './types.js';
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
- * Creates a mock ResponseContext for testing
22
- *
23
- * @param data - The data to return in the response
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 declare const createMockResponse: <T>(data: T, options?: {
35
- ok?: boolean;
36
- error?: {
37
- message: string;
38
- status: number;
39
- code?: string;
40
- details?: Record<string, unknown> | null;
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
- response?: Response;
43
- }) => ResponseContext<T>;
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
- * Creates a mock error ResponseContext for testing
65
+ * Builds a successful result.
46
66
  *
47
- * @param error - The error details
48
- * @returns A ResponseContext object representing an error
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 type MockMethodImplementation<TInput, TOutput> = (input: TInput) => ResponseContext<TOutput> | Promise<ResponseContext<TOutput>>;
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
- * Type for mock client overrides
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 interface MockClientOverrides {
75
- status?: MockMethodImplementation<void, unknown>;
76
- init?: MockMethodImplementation<void, unknown>;
77
- checkConsent?: MockMethodImplementation<unknown, unknown>;
78
- createSubject?: MockMethodImplementation<unknown, unknown>;
79
- getSubject?: MockMethodImplementation<string, unknown>;
80
- patchSubject?: MockMethodImplementation<unknown, unknown>;
81
- listSubjects?: MockMethodImplementation<unknown, unknown>;
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 mock C15T client for testing
92
+ * Creates a client whose methods call the given handlers.
85
93
  *
86
- * @param overrides - Method implementations to override
87
- * @returns A mock client object
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
- * @example
90
- * ```typescript
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 type MockC15TClient = ReturnType<typeof createMockClient>;
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>>;
@@ -1 +1 @@
1
- export declare const version = "3.0.0-alpha.3";
1
+ export declare const version = "3.0.0-alpha.5";