@c15t/node-sdk 1.8.0 → 2.0.0-rc.0
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 +217 -13
- package/dist/client.d.ts +433 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/endpoints/consent.d.ts +17 -0
- package/dist/endpoints/consent.d.ts.map +1 -0
- package/dist/endpoints/index.d.ts +5 -0
- package/dist/endpoints/index.d.ts.map +1 -0
- package/dist/endpoints/init.d.ts +16 -0
- package/dist/endpoints/init.d.ts.map +1 -0
- package/dist/endpoints/status.d.ts +16 -0
- package/dist/endpoints/status.d.ts.map +1 -0
- package/dist/endpoints/subjects.d.ts +46 -0
- package/dist/endpoints/subjects.d.ts.map +1 -0
- package/dist/error.d.ts +86 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/fetcher.d.ts +38 -0
- package/dist/fetcher.d.ts.map +1 -0
- package/dist/index.cjs +386 -19
- package/dist/index.d.ts +31 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +365 -16
- package/dist/testing.d.ts +132 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/types.d.ts +235 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +67 -72
- package/LICENSE.md +0 -595
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,47 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
import type { JsonifiedClient } from '@orpc/openapi-client';
|
|
4
|
-
/**
|
|
5
|
-
* Configuration options for the C15T SDK client
|
|
6
|
-
*/
|
|
7
|
-
export interface C15TClientOptions {
|
|
8
|
-
/**
|
|
9
|
-
* Base URL for the API server
|
|
10
|
-
* @example "https://api.example.com"
|
|
11
|
-
*/
|
|
12
|
-
baseUrl: string;
|
|
13
|
-
/**
|
|
14
|
-
* Authentication token (if needed)
|
|
15
|
-
* @example "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
16
|
-
*/
|
|
17
|
-
token?: string;
|
|
18
|
-
/**
|
|
19
|
-
* Additional headers to include with each request
|
|
20
|
-
*/
|
|
21
|
-
headers?: Record<string, string>;
|
|
22
|
-
/**
|
|
23
|
-
* Prefix path for API endpoints
|
|
24
|
-
* @default "/"
|
|
25
|
-
*/
|
|
26
|
-
prefix?: string;
|
|
27
|
-
}
|
|
1
|
+
import { C15TClient } from './client';
|
|
2
|
+
import type { C15TClientOptions } from './types';
|
|
28
3
|
/**
|
|
29
4
|
* Creates a type-safe C15T client instance
|
|
30
5
|
*
|
|
31
|
-
* @param options - Configuration options for the client
|
|
32
|
-
*
|
|
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
|
|
33
9
|
*
|
|
34
10
|
* @example
|
|
35
11
|
* ```typescript
|
|
36
|
-
* //
|
|
37
|
-
* const client =
|
|
12
|
+
* // Auto-configure from environment variables
|
|
13
|
+
* const client = c15tClient();
|
|
14
|
+
*
|
|
15
|
+
* // Or provide explicit options
|
|
16
|
+
* const client = c15tClient({
|
|
38
17
|
* baseUrl: "https://api.example.com",
|
|
39
18
|
* token: "your-auth-token"
|
|
40
19
|
* });
|
|
41
20
|
*
|
|
42
|
-
* //
|
|
43
|
-
* const
|
|
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
|
+
* });
|
|
44
35
|
* ```
|
|
45
36
|
*/
|
|
46
|
-
export declare function c15tClient(options
|
|
37
|
+
export declare function 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';
|
|
40
|
+
export { C15TError, isC15TError } from './error';
|
|
41
|
+
export type { FetcherContext } from './fetcher';
|
|
42
|
+
export { createResponseContext, fetcher, resolveUrl } from './fetcher';
|
|
43
|
+
export type { C15TClientOptions, FetchOptions, ResponseContext, RetryConfig, } from './types';
|
|
47
44
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,UAAU,CAElE;AAGD,YAAY,EACX,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,WAAW,GACX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,qBAAqB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvE,YAAY,EACX,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,WAAW,GACX,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,368 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
class C15TError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
cause;
|
|
6
|
+
constructor(options){
|
|
7
|
+
super(options.message);
|
|
8
|
+
this.name = 'C15TError';
|
|
9
|
+
this.status = options.status;
|
|
10
|
+
this.code = options.code;
|
|
11
|
+
this.details = options.details;
|
|
12
|
+
this.cause = options.cause;
|
|
13
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, C15TError);
|
|
14
|
+
}
|
|
15
|
+
isStatus(status) {
|
|
16
|
+
return this.status === status;
|
|
17
|
+
}
|
|
18
|
+
isNotFound() {
|
|
19
|
+
return 404 === this.status || 'NOT_FOUND' === this.code;
|
|
20
|
+
}
|
|
21
|
+
isValidationError() {
|
|
22
|
+
return 400 === this.status || 'VALIDATION_ERROR' === this.code;
|
|
23
|
+
}
|
|
24
|
+
isUnauthorized() {
|
|
25
|
+
return 401 === this.status || 'UNAUTHORIZED' === this.code;
|
|
26
|
+
}
|
|
27
|
+
isForbidden() {
|
|
28
|
+
return 403 === this.status || 'FORBIDDEN' === this.code;
|
|
29
|
+
}
|
|
30
|
+
isServerError() {
|
|
31
|
+
return this.status >= 500 && this.status < 600;
|
|
32
|
+
}
|
|
33
|
+
isNetworkError() {
|
|
34
|
+
return 0 === this.status || 'NETWORK_ERROR' === this.code;
|
|
35
|
+
}
|
|
36
|
+
toJSON() {
|
|
37
|
+
return {
|
|
38
|
+
name: this.name,
|
|
39
|
+
message: this.message,
|
|
40
|
+
status: this.status,
|
|
41
|
+
code: this.code,
|
|
42
|
+
details: this.details
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function isC15TError(error) {
|
|
47
|
+
return error instanceof C15TError;
|
|
48
|
+
}
|
|
49
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
50
|
+
maxRetries: 3,
|
|
51
|
+
initialDelayMs: 100,
|
|
52
|
+
backoffFactor: 2,
|
|
53
|
+
retryableStatusCodes: [
|
|
54
|
+
500,
|
|
55
|
+
502,
|
|
56
|
+
503,
|
|
57
|
+
504
|
|
58
|
+
],
|
|
59
|
+
nonRetryableStatusCodes: [
|
|
60
|
+
400,
|
|
61
|
+
401,
|
|
62
|
+
403,
|
|
63
|
+
404
|
|
64
|
+
],
|
|
65
|
+
retryOnNetworkError: true
|
|
66
|
+
};
|
|
67
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
68
|
+
function debugLog(debug, method, path, durationMs, status) {
|
|
69
|
+
if (!debug) return;
|
|
70
|
+
const timestamp = new Date().toISOString();
|
|
71
|
+
console.log(`[c15t] ${timestamp} ${method} ${path} (${durationMs}ms) -> ${status}`);
|
|
72
|
+
}
|
|
73
|
+
const delay = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
74
|
+
function generateUUID() {
|
|
75
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c)=>{
|
|
76
|
+
const r = 16 * Math.random() | 0;
|
|
77
|
+
const v = 'x' === c ? r : 0x3 & r | 0x8;
|
|
78
|
+
return v.toString(16);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function createResponseContext(isSuccess, data = null, error = null, response = null) {
|
|
82
|
+
return {
|
|
83
|
+
data,
|
|
84
|
+
error,
|
|
85
|
+
ok: isSuccess,
|
|
86
|
+
response,
|
|
87
|
+
unwrap () {
|
|
88
|
+
if (!isSuccess || null === data) throw new C15TError({
|
|
89
|
+
message: error?.message || 'Request failed',
|
|
90
|
+
status: error?.status || 0,
|
|
91
|
+
code: error?.code,
|
|
92
|
+
details: error?.details,
|
|
93
|
+
cause: error?.cause
|
|
94
|
+
});
|
|
95
|
+
return data;
|
|
96
|
+
},
|
|
97
|
+
unwrapOr (defaultValue) {
|
|
98
|
+
if (!isSuccess || null === data) return defaultValue;
|
|
99
|
+
return data;
|
|
100
|
+
},
|
|
101
|
+
expect (message) {
|
|
102
|
+
if (!isSuccess || null === data) throw new C15TError({
|
|
103
|
+
message,
|
|
104
|
+
status: error?.status || 0,
|
|
105
|
+
code: error?.code,
|
|
106
|
+
details: error?.details,
|
|
107
|
+
cause: error?.cause
|
|
108
|
+
});
|
|
109
|
+
return data;
|
|
110
|
+
},
|
|
111
|
+
map (fn) {
|
|
112
|
+
if (!isSuccess || null === data) return createResponseContext(false, null, error, response);
|
|
113
|
+
return createResponseContext(true, fn(data), null, response);
|
|
15
114
|
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function resolveUrl(baseUrl, path) {
|
|
118
|
+
const cleanBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
119
|
+
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
|
|
120
|
+
return `${cleanBase}/${cleanPath}`;
|
|
121
|
+
}
|
|
122
|
+
async function fetcher(context, path, options) {
|
|
123
|
+
const finalRetryConfig = {
|
|
124
|
+
...DEFAULT_RETRY_CONFIG,
|
|
125
|
+
...context.retryConfig,
|
|
126
|
+
...options?.retryConfig || {}
|
|
127
|
+
};
|
|
128
|
+
const { maxRetries = 3, initialDelayMs = 100, backoffFactor = 2, retryableStatusCodes = [
|
|
129
|
+
500,
|
|
130
|
+
502,
|
|
131
|
+
503,
|
|
132
|
+
504
|
|
133
|
+
], nonRetryableStatusCodes = [
|
|
134
|
+
400,
|
|
135
|
+
401,
|
|
136
|
+
403,
|
|
137
|
+
404
|
|
138
|
+
], retryOnNetworkError = true } = finalRetryConfig;
|
|
139
|
+
let attemptsMade = 0;
|
|
140
|
+
let currentDelay = initialDelayMs;
|
|
141
|
+
let lastErrorResponse = null;
|
|
142
|
+
while(attemptsMade <= maxRetries){
|
|
143
|
+
const requestId = generateUUID();
|
|
144
|
+
const resolvedUrl = resolveUrl(context.baseUrl, path);
|
|
145
|
+
const url = new URL(resolvedUrl);
|
|
146
|
+
if (options?.query) {
|
|
147
|
+
for (const [key, value] of Object.entries(options.query))if (null != value) url.searchParams.append(key, String(value));
|
|
148
|
+
}
|
|
149
|
+
const timeoutMs = options?.timeout ?? context.timeout;
|
|
150
|
+
const controller = new AbortController();
|
|
151
|
+
let timeoutId;
|
|
152
|
+
if (timeoutMs > 0) timeoutId = setTimeout(()=>controller.abort(), timeoutMs);
|
|
153
|
+
const requestOptions = {
|
|
154
|
+
method: options?.method || 'GET',
|
|
155
|
+
headers: {
|
|
156
|
+
'Content-Type': 'application/json',
|
|
157
|
+
...context.headers,
|
|
158
|
+
'X-Request-ID': requestId,
|
|
159
|
+
...options?.headers
|
|
160
|
+
},
|
|
161
|
+
signal: controller.signal
|
|
162
|
+
};
|
|
163
|
+
if (options?.body && 'GET' !== requestOptions.method) requestOptions.body = JSON.stringify(options.body);
|
|
164
|
+
const startTime = Date.now();
|
|
165
|
+
try {
|
|
166
|
+
const response = await fetch(url.toString(), requestOptions);
|
|
167
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
168
|
+
const durationMs = Date.now() - startTime;
|
|
169
|
+
let data = null;
|
|
170
|
+
let parseError = null;
|
|
171
|
+
try {
|
|
172
|
+
const contentType = response.headers.get('content-type');
|
|
173
|
+
if (contentType?.includes('application/json') && 204 !== response.status && '0' !== response.headers.get('content-length')) data = await response.json();
|
|
174
|
+
else if (204 === response.status) data = null;
|
|
175
|
+
} catch (err) {
|
|
176
|
+
parseError = err;
|
|
177
|
+
}
|
|
178
|
+
if (parseError) {
|
|
179
|
+
const errorResponse = createResponseContext(false, null, {
|
|
180
|
+
message: 'Failed to parse response',
|
|
181
|
+
status: response.status,
|
|
182
|
+
code: 'PARSE_ERROR',
|
|
183
|
+
cause: parseError
|
|
184
|
+
}, response);
|
|
185
|
+
options?.onError?.(errorResponse, path);
|
|
186
|
+
if (options?.throw) throw new Error('Failed to parse response');
|
|
187
|
+
return errorResponse;
|
|
188
|
+
}
|
|
189
|
+
const isSuccess = response.status >= 200 && response.status < 300;
|
|
190
|
+
if (isSuccess) {
|
|
191
|
+
debugLog(context.debug, requestOptions.method || 'GET', path, durationMs, response.status);
|
|
192
|
+
const successResponse = createResponseContext(true, data, null, response);
|
|
193
|
+
options?.onSuccess?.(successResponse);
|
|
194
|
+
return successResponse;
|
|
195
|
+
}
|
|
196
|
+
const errorData = data;
|
|
197
|
+
const errorResponse = createResponseContext(false, null, {
|
|
198
|
+
message: errorData?.message || `Request failed with status ${response.status}`,
|
|
199
|
+
status: response.status,
|
|
200
|
+
code: errorData?.code || 'API_ERROR',
|
|
201
|
+
details: errorData?.details || null
|
|
202
|
+
}, response);
|
|
203
|
+
lastErrorResponse = errorResponse;
|
|
204
|
+
let shouldRetryThisRequest = false;
|
|
205
|
+
shouldRetryThisRequest = nonRetryableStatusCodes.includes(response.status) ? false : retryableStatusCodes.includes(response.status);
|
|
206
|
+
if (!shouldRetryThisRequest || attemptsMade >= maxRetries) {
|
|
207
|
+
debugLog(context.debug, requestOptions.method || 'GET', path, durationMs, response.status);
|
|
208
|
+
options?.onError?.(errorResponse, path);
|
|
209
|
+
if (options?.throw) throw new Error(errorResponse.error?.message || 'Request failed');
|
|
210
|
+
return errorResponse;
|
|
211
|
+
}
|
|
212
|
+
attemptsMade++;
|
|
213
|
+
await delay(currentDelay);
|
|
214
|
+
currentDelay *= backoffFactor;
|
|
215
|
+
} catch (fetchError) {
|
|
216
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
217
|
+
if (fetchError instanceof Error && 'Failed to parse response' === fetchError.message) throw fetchError;
|
|
218
|
+
const isAbortError = fetchError instanceof Error && 'AbortError' === fetchError.name;
|
|
219
|
+
const isNetworkError = !(fetchError instanceof Response);
|
|
220
|
+
const errorResponse = createResponseContext(false, null, {
|
|
221
|
+
message: isAbortError ? `Request timed out after ${timeoutMs}ms` : fetchError instanceof Error ? fetchError.message : String(fetchError),
|
|
222
|
+
status: 0,
|
|
223
|
+
code: isAbortError ? 'TIMEOUT' : 'NETWORK_ERROR',
|
|
224
|
+
cause: fetchError
|
|
225
|
+
}, null);
|
|
226
|
+
lastErrorResponse = errorResponse;
|
|
227
|
+
const shouldRetryThisRequest = isNetworkError && retryOnNetworkError;
|
|
228
|
+
if (!shouldRetryThisRequest || attemptsMade >= maxRetries) {
|
|
229
|
+
debugLog(context.debug, requestOptions.method || 'GET', path, Date.now() - startTime, 'ERROR');
|
|
230
|
+
options?.onError?.(errorResponse, path);
|
|
231
|
+
if (options?.throw) throw fetchError;
|
|
232
|
+
return errorResponse;
|
|
233
|
+
}
|
|
234
|
+
attemptsMade++;
|
|
235
|
+
await delay(currentDelay);
|
|
236
|
+
currentDelay *= backoffFactor;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const maxRetriesErrorResponse = lastErrorResponse || createResponseContext(false, null, {
|
|
240
|
+
message: `Request failed after ${maxRetries} retries`,
|
|
241
|
+
status: 0,
|
|
242
|
+
code: 'MAX_RETRIES_EXCEEDED'
|
|
243
|
+
}, null);
|
|
244
|
+
options?.onError?.(maxRetriesErrorResponse, path);
|
|
245
|
+
if (options?.throw) throw new Error(`Request failed after ${maxRetries} retries`);
|
|
246
|
+
return maxRetriesErrorResponse;
|
|
247
|
+
}
|
|
248
|
+
const CONSENT_CHECK_PATH = '/consents/check';
|
|
249
|
+
async function checkConsent(context, query, options) {
|
|
250
|
+
return fetcher(context, CONSENT_CHECK_PATH, {
|
|
251
|
+
method: 'GET',
|
|
252
|
+
query,
|
|
253
|
+
...options
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const INIT_PATH = '/init';
|
|
257
|
+
async function init(context, options) {
|
|
258
|
+
return fetcher(context, INIT_PATH, {
|
|
259
|
+
method: 'GET',
|
|
260
|
+
...options
|
|
16
261
|
});
|
|
17
|
-
return createORPCClient(link);
|
|
18
262
|
}
|
|
19
|
-
|
|
263
|
+
const STATUS_PATH = '/status';
|
|
264
|
+
async function status_status(context, options) {
|
|
265
|
+
return fetcher(context, STATUS_PATH, {
|
|
266
|
+
method: 'GET',
|
|
267
|
+
...options
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const SUBJECTS_PATH = '/subjects';
|
|
271
|
+
async function createSubject(context, input, options) {
|
|
272
|
+
return fetcher(context, SUBJECTS_PATH, {
|
|
273
|
+
method: 'POST',
|
|
274
|
+
body: input,
|
|
275
|
+
...options
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
async function getSubject(context, id, query, options) {
|
|
279
|
+
return fetcher(context, `${SUBJECTS_PATH}/${id}`, {
|
|
280
|
+
method: 'GET',
|
|
281
|
+
query,
|
|
282
|
+
...options
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async function patchSubject(context, id, input, options) {
|
|
286
|
+
return fetcher(context, `${SUBJECTS_PATH}/${id}`, {
|
|
287
|
+
method: 'PATCH',
|
|
288
|
+
body: input,
|
|
289
|
+
...options
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
async function listSubjects(context, query, options) {
|
|
293
|
+
return fetcher(context, SUBJECTS_PATH, {
|
|
294
|
+
method: 'GET',
|
|
295
|
+
query,
|
|
296
|
+
...options
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
class C15TClient {
|
|
300
|
+
context;
|
|
301
|
+
constructor(options = {}){
|
|
302
|
+
const baseUrlString = options.baseUrl || ('undefined' != typeof process ? process.env?.C15T_API_URL : void 0);
|
|
303
|
+
if (!baseUrlString) throw new TypeError('baseUrl is required. Provide it in options or set C15T_API_URL environment variable.');
|
|
304
|
+
const baseUrl = new URL(baseUrlString);
|
|
305
|
+
if (options.prefix) baseUrl.pathname = options.prefix;
|
|
306
|
+
const token = options.token || ('undefined' != typeof process ? process.env?.C15T_API_TOKEN : void 0);
|
|
307
|
+
const authHeaders = token ? {
|
|
308
|
+
Authorization: `Bearer ${token}`
|
|
309
|
+
} : {};
|
|
310
|
+
const retryConfig = {
|
|
311
|
+
...DEFAULT_RETRY_CONFIG,
|
|
312
|
+
...options.retryConfig
|
|
313
|
+
};
|
|
314
|
+
const debug = options.debug ?? ('undefined' != typeof process ? process.env?.C15T_DEBUG === 'true' : false);
|
|
315
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
316
|
+
this.context = {
|
|
317
|
+
baseUrl: baseUrl.toString(),
|
|
318
|
+
headers: {
|
|
319
|
+
...authHeaders,
|
|
320
|
+
...options.headers
|
|
321
|
+
},
|
|
322
|
+
retryConfig,
|
|
323
|
+
debug,
|
|
324
|
+
timeout
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
async status(options) {
|
|
328
|
+
return status_status(this.context, options);
|
|
329
|
+
}
|
|
330
|
+
async init(options) {
|
|
331
|
+
return init(this.context, options);
|
|
332
|
+
}
|
|
333
|
+
async createSubject(input, options) {
|
|
334
|
+
return createSubject(this.context, input, options);
|
|
335
|
+
}
|
|
336
|
+
async getSubject(id, query, options) {
|
|
337
|
+
return getSubject(this.context, id, query, options);
|
|
338
|
+
}
|
|
339
|
+
async patchSubject(id, input, options) {
|
|
340
|
+
return patchSubject(this.context, id, input, options);
|
|
341
|
+
}
|
|
342
|
+
async listSubjects(query, options) {
|
|
343
|
+
return listSubjects(this.context, query, options);
|
|
344
|
+
}
|
|
345
|
+
async checkConsent(query, options) {
|
|
346
|
+
return checkConsent(this.context, query, options);
|
|
347
|
+
}
|
|
348
|
+
async $fetch(path, options) {
|
|
349
|
+
return fetcher(this.context, path, options);
|
|
350
|
+
}
|
|
351
|
+
consent = {
|
|
352
|
+
check: (query, options)=>this.checkConsent(query, options)
|
|
353
|
+
};
|
|
354
|
+
subjects = {
|
|
355
|
+
create: (input, options)=>this.createSubject(input, options),
|
|
356
|
+
get: (id, query, options)=>this.getSubject(id, query, options),
|
|
357
|
+
patch: (id, input, options)=>this.patchSubject(id, input, options),
|
|
358
|
+
list: (query, options)=>this.listSubjects(query, options)
|
|
359
|
+
};
|
|
360
|
+
meta = {
|
|
361
|
+
status: (options)=>this.status(options),
|
|
362
|
+
init: (options)=>this.init(options)
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function c15tClient(options) {
|
|
366
|
+
return new C15TClient(options);
|
|
367
|
+
}
|
|
368
|
+
export { C15TClient, C15TError, c15tClient, createResponseContext, fetcher, isC15TError, resolveUrl };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testing utilities for @c15t/node-sdk
|
|
3
|
+
*
|
|
4
|
+
* These utilities help users mock the C15T client in their tests.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { createMockClient, createMockResponse } from '@c15t/node-sdk/testing';
|
|
9
|
+
*
|
|
10
|
+
* const mockClient = createMockClient({
|
|
11
|
+
* getSubject: async () => createMockResponse({ id: 'sub_123', consents: [] }),
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* // Use mockClient in your tests
|
|
15
|
+
* const result = await mockClient.getSubject('sub_123');
|
|
16
|
+
* expect(result.data?.id).toBe('sub_123');
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { ResponseContext } from './types';
|
|
20
|
+
/**
|
|
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
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function 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;
|
|
41
|
+
};
|
|
42
|
+
response?: Response;
|
|
43
|
+
}): ResponseContext<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Creates a mock error ResponseContext for testing
|
|
46
|
+
*
|
|
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 function 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
|
|
69
|
+
*/
|
|
70
|
+
export type MockMethodImplementation<TInput, TOutput> = (input: TInput) => ResponseContext<TOutput> | Promise<ResponseContext<TOutput>>;
|
|
71
|
+
/**
|
|
72
|
+
* Type for mock client overrides
|
|
73
|
+
*/
|
|
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
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Creates a mock C15T client for testing
|
|
85
|
+
*
|
|
86
|
+
* @param overrides - Method implementations to override
|
|
87
|
+
* @returns A mock client object
|
|
88
|
+
*
|
|
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 function createMockClient(overrides?: MockClientOverrides): {
|
|
107
|
+
status: () => Promise<ResponseContext<unknown>>;
|
|
108
|
+
init: () => Promise<ResponseContext<unknown>>;
|
|
109
|
+
checkConsent: (query: unknown) => Promise<ResponseContext<unknown>>;
|
|
110
|
+
createSubject: (input: unknown) => Promise<ResponseContext<unknown>>;
|
|
111
|
+
getSubject: (id: string) => Promise<ResponseContext<unknown>>;
|
|
112
|
+
patchSubject: (id: string, input: unknown) => Promise<ResponseContext<unknown>>;
|
|
113
|
+
listSubjects: (query?: unknown) => Promise<ResponseContext<unknown>>;
|
|
114
|
+
consent: {
|
|
115
|
+
check: (query: unknown) => Promise<ResponseContext<unknown>>;
|
|
116
|
+
};
|
|
117
|
+
subjects: {
|
|
118
|
+
create: (input: unknown) => Promise<ResponseContext<unknown>>;
|
|
119
|
+
get: (id: string) => Promise<ResponseContext<unknown>>;
|
|
120
|
+
patch: (id: string, input: unknown) => Promise<ResponseContext<unknown>>;
|
|
121
|
+
list: (query?: unknown) => Promise<ResponseContext<unknown>>;
|
|
122
|
+
};
|
|
123
|
+
meta: {
|
|
124
|
+
status: () => Promise<ResponseContext<unknown>>;
|
|
125
|
+
init: () => Promise<ResponseContext<unknown>>;
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Type representing the mock client returned by createMockClient
|
|
130
|
+
*/
|
|
131
|
+
export type MockC15TClient = ReturnType<typeof createMockClient>;
|
|
132
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EACnC,IAAI,EAAE,CAAC,EACP,OAAO,GAAE;IACR,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,KAAK,CAAC,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;KACzC,CAAC;IACF,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACf,GACJ,eAAe,CAAC,CAAC,CAAC,CA0CpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE;IAC3D,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACzC,GAAG,eAAe,CAAC,CAAC,CAAC,CAErB;AAED;;GAEG;AACH,MAAM,MAAM,wBAAwB,CAAC,MAAM,EAAE,OAAO,IAAI,CACvD,KAAK,EAAE,MAAM,KACT,eAAe,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,MAAM,CAAC,EAAE,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACjD,IAAI,CAAC,EAAE,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/C,YAAY,CAAC,EAAE,wBAAwB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC1D,aAAa,CAAC,EAAE,wBAAwB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3D,UAAU,CAAC,EAAE,wBAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvD,YAAY,CAAC,EAAE,wBAAwB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC1D,YAAY,CAAC,EAAE,wBAAwB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,GAAE,mBAAwB;;;0BAoB5C,OAAO;2BACN,OAAO;qBACb,MAAM;uBACJ,MAAM,SAAS,OAAO;2BAOlB,OAAO;;uBAId,OAAO;;;wBAGN,OAAO;kBACb,MAAM;oBACJ,MAAM,SAAS,OAAO;uBAOnB,OAAO;;;;;;EAOxB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|