@chitmark/sdk 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,601 @@
1
+ 'use strict';
2
+
3
+ // src/errors.ts
4
+ var ChitmarkError = class extends Error {
5
+ name = "ChitmarkError";
6
+ status;
7
+ code;
8
+ body;
9
+ constructor(message, options) {
10
+ super(message);
11
+ this.status = options?.status;
12
+ this.code = options?.code;
13
+ this.body = options?.body;
14
+ }
15
+ };
16
+ var ChitmarkApiError = class extends ChitmarkError {
17
+ name = "ChitmarkApiError";
18
+ constructor(status, body, message) {
19
+ const msg = message ?? (typeof body === "string" ? body : body.message || body.error || `Chitmark API error (${status})`);
20
+ super(msg, {
21
+ status,
22
+ code: typeof body === "object" ? body.code ?? body.error : void 0,
23
+ body
24
+ });
25
+ }
26
+ };
27
+ function degradedChallengeVerdict(reason, session) {
28
+ return {
29
+ decision: "challenge",
30
+ actorType: "unknown",
31
+ confidence: 0,
32
+ reasons: [reason],
33
+ principal: {
34
+ resolved: false,
35
+ identityBasis: ["degraded"]
36
+ },
37
+ verdictToken: "",
38
+ eventId: `evt_degraded_${session ?? "unknown"}`,
39
+ degraded: true,
40
+ enforcementMode: "enforce"
41
+ };
42
+ }
43
+ function enforceDegradedNeverAllow(verdict) {
44
+ if (verdict.degraded && verdict.decision === "allow") {
45
+ return {
46
+ ...verdict,
47
+ decision: "challenge",
48
+ reasons: [
49
+ ...verdict.reasons,
50
+ "Client enforced: degraded decision must not be allow"
51
+ ]
52
+ };
53
+ }
54
+ return verdict;
55
+ }
56
+
57
+ // src/internal/constants.ts
58
+ var DEFAULT_BASE_URLS = {
59
+ production: "https://api.chitmark.com",
60
+ staging: "https://api.staging.chitmark.com",
61
+ development: "http://127.0.0.1:8787"
62
+ };
63
+ var DEFAULT_TIMEOUT_MS = 800;
64
+ var SDK_VERSION = "0.1.0";
65
+ function resolveBaseUrl(env = "production", baseUrl) {
66
+ return (baseUrl ?? DEFAULT_BASE_URLS[env]).replace(/\/$/, "");
67
+ }
68
+ function envBaseUrl() {
69
+ if (typeof process === "undefined") return void 0;
70
+ const value = process.env?.CHITMARK_BASE_URL;
71
+ return value ? value.trim() : void 0;
72
+ }
73
+
74
+ // src/pii.ts
75
+ async function hashEmailSha256(email) {
76
+ const normalized = email.trim().toLowerCase();
77
+ const data = new TextEncoder().encode(normalized);
78
+ const digest = await crypto.subtle.digest("SHA-256", data);
79
+ return bufferToHex(digest);
80
+ }
81
+ function truncateIp(ip) {
82
+ const trimmed = ip.trim();
83
+ const first = trimmed.split(",")[0]?.trim() ?? trimmed;
84
+ if (first.includes(".") && !first.includes(":")) {
85
+ const parts = first.split(".");
86
+ if (parts.length === 4) {
87
+ return `${parts[0]}.${parts[1]}.${parts[2]}.0/24`;
88
+ }
89
+ }
90
+ if (first.includes(":")) {
91
+ const hextets = first.split(":").filter(Boolean);
92
+ const head = hextets.slice(0, 3).join(":");
93
+ return `${head}::/48`;
94
+ }
95
+ return first;
96
+ }
97
+ function minimizeFormData(formData) {
98
+ if (!formData) return void 0;
99
+ const allowed = /* @__PURE__ */ new Set([
100
+ "companyDomain",
101
+ "company_domain",
102
+ "domain",
103
+ "plan",
104
+ "referrer",
105
+ "utm_source",
106
+ "utm_medium",
107
+ "utm_campaign"
108
+ ]);
109
+ const out = {};
110
+ for (const [key, value] of Object.entries(formData)) {
111
+ if (allowed.has(key) && value != null) {
112
+ out[key] = value;
113
+ }
114
+ }
115
+ return Object.keys(out).length > 0 ? out : void 0;
116
+ }
117
+ function bufferToHex(buffer) {
118
+ const bytes = new Uint8Array(buffer);
119
+ let hex = "";
120
+ for (const b of bytes) {
121
+ hex += b.toString(16).padStart(2, "0");
122
+ }
123
+ return hex;
124
+ }
125
+
126
+ // src/internal/prepare.ts
127
+ async function prepareSubject(subject, piiMode) {
128
+ if (piiMode === "raw") {
129
+ return subject;
130
+ }
131
+ if (piiMode === "none") {
132
+ return {
133
+ userAgent: subject.userAgent,
134
+ headers: pickWebBotAuthHeaders(subject.headers)
135
+ };
136
+ }
137
+ const prepared = {
138
+ userAgent: subject.userAgent,
139
+ headers: subject.headers
140
+ };
141
+ if (subject.emailSha256) {
142
+ prepared.emailSha256 = subject.emailSha256;
143
+ } else if (subject.email) {
144
+ prepared.emailSha256 = await hashEmailSha256(subject.email);
145
+ }
146
+ if (subject.ipTrunc) {
147
+ prepared.ipTrunc = subject.ipTrunc;
148
+ } else if (subject.ip) {
149
+ prepared.ipTrunc = truncateIp(subject.ip);
150
+ }
151
+ const hashedForm = subject.formDataHashed ?? minimizeFormData(subject.formData);
152
+ if (hashedForm) {
153
+ prepared.formDataHashed = hashedForm;
154
+ }
155
+ return prepared;
156
+ }
157
+ function pickWebBotAuthHeaders(headers) {
158
+ if (!headers) return void 0;
159
+ const keys = ["signature-agent", "signature-input", "signature", "signature-agent"];
160
+ const out = {};
161
+ for (const [k, v] of Object.entries(headers)) {
162
+ const lower = k.toLowerCase();
163
+ if (keys.includes(lower) && typeof v === "string") {
164
+ out[lower] = v;
165
+ } else if (keys.includes(lower) && Array.isArray(v) && v[0]) {
166
+ out[lower] = v[0];
167
+ }
168
+ }
169
+ return Object.keys(out).length > 0 ? out : void 0;
170
+ }
171
+ function flattenHeaders(headers) {
172
+ if (!headers) return void 0;
173
+ if (headers instanceof Headers) {
174
+ const out2 = {};
175
+ headers.forEach((value, key) => {
176
+ out2[key.toLowerCase()] = value;
177
+ });
178
+ return out2;
179
+ }
180
+ const out = {};
181
+ for (const [k, v] of Object.entries(headers)) {
182
+ if (typeof v === "string") out[k.toLowerCase()] = v;
183
+ else if (Array.isArray(v) && v[0]) out[k.toLowerCase()] = v[0];
184
+ }
185
+ return out;
186
+ }
187
+
188
+ // src/verify-token.ts
189
+ var VerdictTokenError = class extends ChitmarkError {
190
+ name = "VerdictTokenError";
191
+ constructor(code, message) {
192
+ super(message, { code });
193
+ }
194
+ };
195
+ var DEFAULT_JWKS_URL = "https://api.chitmark.com/.well-known/jwks.json";
196
+ var DEFAULT_LEEWAY_SECONDS = 30;
197
+ async function verifyVerdictToken(token, options = {}) {
198
+ const nowMs = options.nowMs ?? Date.now();
199
+ const leewaySeconds = options.leewaySeconds ?? DEFAULT_LEEWAY_SECONDS;
200
+ const parts = token.split(".");
201
+ if (parts.length !== 3) {
202
+ throw new VerdictTokenError("malformed_token", "verdictToken must be a JWT");
203
+ }
204
+ const [headerB64, payloadB64, signatureB64] = parts;
205
+ let header;
206
+ let payload;
207
+ try {
208
+ header = JSON.parse(decodeBase64Url(headerB64));
209
+ payload = JSON.parse(decodeBase64Url(payloadB64));
210
+ } catch {
211
+ throw new VerdictTokenError("malformed_token", "verdictToken is not valid JWT JSON");
212
+ }
213
+ if (header["alg"] !== "ES256") {
214
+ throw new VerdictTokenError(
215
+ "wrong_algorithm",
216
+ `verdictToken must use ES256, got ${String(header["alg"])}`
217
+ );
218
+ }
219
+ const kid = typeof header["kid"] === "string" ? header["kid"] : "";
220
+ if (!kid) {
221
+ throw new VerdictTokenError("unknown_kid", "verdictToken is missing kid");
222
+ }
223
+ const exp = typeof payload["exp"] === "number" ? payload["exp"] : NaN;
224
+ const iat = typeof payload["iat"] === "number" ? payload["iat"] : NaN;
225
+ const nowSeconds = Math.floor(nowMs / 1e3);
226
+ if (Number.isFinite(exp) && exp < nowSeconds - leewaySeconds) {
227
+ throw new VerdictTokenError("expired", "verdictToken has expired");
228
+ }
229
+ if (Number.isFinite(iat) && iat > nowSeconds + leewaySeconds) {
230
+ throw new VerdictTokenError("not_yet_valid", "verdictToken iat is in the future");
231
+ }
232
+ const expectedSession = options.session;
233
+ if (expectedSession !== void 0 && payload["sub"] !== expectedSession) {
234
+ throw new VerdictTokenError(
235
+ "wrong_session",
236
+ "verdictToken was minted for a different session"
237
+ );
238
+ }
239
+ const expectedAud = options.aud;
240
+ if (expectedAud !== void 0 && payload["aud"] !== expectedAud) {
241
+ throw new VerdictTokenError(
242
+ "wrong_aud",
243
+ "verdictToken was minted for a different origin"
244
+ );
245
+ }
246
+ const jwk = await findJwk(kid, options);
247
+ if (!jwk) {
248
+ throw new VerdictTokenError("unknown_kid", `JWKS has no key for kid ${kid}`);
249
+ }
250
+ let publicKey;
251
+ try {
252
+ publicKey = await crypto.subtle.importKey(
253
+ "jwk",
254
+ jwk,
255
+ { name: "ECDSA", namedCurve: "P-256" },
256
+ false,
257
+ ["verify"]
258
+ );
259
+ } catch {
260
+ throw new VerdictTokenError(
261
+ "invalid_signature",
262
+ "JWKS key is not a valid P-256 public key"
263
+ );
264
+ }
265
+ const signatureBytes = decodeBase64UrlBytes(signatureB64);
266
+ const signatureBuffer = new ArrayBuffer(signatureBytes.byteLength);
267
+ new Uint8Array(signatureBuffer).set(signatureBytes);
268
+ const valid = await crypto.subtle.verify(
269
+ { name: "ECDSA", hash: "SHA-256" },
270
+ publicKey,
271
+ signatureBuffer,
272
+ new TextEncoder().encode(`${headerB64}.${payloadB64}`)
273
+ );
274
+ if (!valid) {
275
+ throw new VerdictTokenError(
276
+ "invalid_signature",
277
+ "verdictToken signature failed verification"
278
+ );
279
+ }
280
+ return payload;
281
+ }
282
+ async function findJwk(kid, options) {
283
+ let jwks = options.jwks;
284
+ if (!jwks) {
285
+ const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;
286
+ try {
287
+ const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
288
+ const res = await fetchImpl(jwksUrl);
289
+ if (!res.ok) {
290
+ throw new Error(`JWKS fetch failed with ${res.status}`);
291
+ }
292
+ jwks = await res.json();
293
+ } catch (err) {
294
+ throw new VerdictTokenError(
295
+ "jwks_unavailable",
296
+ `Failed to load JWKS from ${jwksUrl}: ${err instanceof Error ? err.message : "unknown error"}`
297
+ );
298
+ }
299
+ }
300
+ if (!Array.isArray(jwks.keys)) {
301
+ throw new VerdictTokenError("jwks_unavailable", "JWKS response has no keys array");
302
+ }
303
+ return jwks.keys.find((key) => key["kid"] === kid);
304
+ }
305
+ function decodeBase64Url(value) {
306
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
307
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
308
+ const binary = atob(padded);
309
+ return new TextDecoder().decode(Uint8Array.from(binary, (ch) => ch.charCodeAt(0)));
310
+ }
311
+ function decodeBase64UrlBytes(value) {
312
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
313
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
314
+ const binary = atob(padded);
315
+ const out = new Uint8Array(binary.length);
316
+ for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
317
+ return out;
318
+ }
319
+
320
+ // src/client.ts
321
+ var Chitmark = class {
322
+ apiKey;
323
+ baseUrl;
324
+ timeoutMs;
325
+ piiMode;
326
+ onDegraded;
327
+ fetchImpl;
328
+ clientName;
329
+ constructor(options) {
330
+ if (!options.apiKey) {
331
+ throw new ChitmarkError("Chitmark apiKey is required");
332
+ }
333
+ if (options.onDegraded && options.onDegraded !== "challenge") {
334
+ throw new ChitmarkError(
335
+ 'onDegraded must be "challenge" \u2014 fail to challenge, never to allow'
336
+ );
337
+ }
338
+ this.apiKey = options.apiKey;
339
+ this.baseUrl = resolveBaseUrl(options.env, options.baseUrl ?? envBaseUrl());
340
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
341
+ this.piiMode = options.piiMode ?? "hashed";
342
+ this.onDegraded = "challenge";
343
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
344
+ this.clientName = options.clientName ?? `chitmark-node/${SDK_VERSION}`;
345
+ }
346
+ /**
347
+ * Score + route an inbound action (signup / trial / …).
348
+ * Hot path at the edge; client budget defaults to 800ms.
349
+ * On timeout/transport error — or a malformed verdict shape from the API —
350
+ * returns a degraded challenge verdict (never allow, never crashes).
351
+ *
352
+ * Persist `v.eventId` on the account row: feedback joins only on that id.
353
+ */
354
+ async verify(request) {
355
+ const subject = await prepareSubject(
356
+ {
357
+ ...request.subject,
358
+ headers: flattenHeaders(request.subject.headers)
359
+ },
360
+ this.piiMode
361
+ );
362
+ const body = {
363
+ ...request,
364
+ subject
365
+ };
366
+ try {
367
+ const verdict = await this.request("/v1/verify", {
368
+ method: "POST",
369
+ body,
370
+ idempotencyKey: request.idempotencyKey
371
+ });
372
+ if (!isWellFormedVerdict(verdict)) {
373
+ return degradedChallengeVerdict(
374
+ "Degraded: verify returned a malformed verdict shape",
375
+ request.session
376
+ );
377
+ }
378
+ return enforceDegradedNeverAllow(verdict);
379
+ } catch (err) {
380
+ if (err instanceof ChitmarkApiError) {
381
+ throw err;
382
+ }
383
+ return degradedChallengeVerdict(
384
+ err instanceof Error ? `Degraded: ${err.message}` : "Degraded: verify failed",
385
+ request.session
386
+ );
387
+ }
388
+ }
389
+ /**
390
+ * Report a downstream outcome joined on the stored eventId from verify.
391
+ * This is the data moat — attribution is a schema contract, not a fuzzy match.
392
+ */
393
+ async feedback(request) {
394
+ return this.request("/v1/feedback", {
395
+ method: "POST",
396
+ body: request
397
+ });
398
+ }
399
+ /**
400
+ * Issue an asymmetric-cost challenge after decision === challenge | degraded.
401
+ */
402
+ async challenge(request) {
403
+ return this.request("/v1/challenge", {
404
+ method: "POST",
405
+ body: request
406
+ });
407
+ }
408
+ /**
409
+ * Complete a challenge by submitting its solved proof. Clears only the bound
410
+ * session, once. Re-verify with `context.challengeId` to have the next
411
+ * verify honor the cleared session.
412
+ */
413
+ async completeChallenge(request) {
414
+ return this.request("/v1/challenge", {
415
+ method: "POST",
416
+ body: request
417
+ });
418
+ }
419
+ /**
420
+ * Verify a verdictToken's signature against the API's JWKS and its session /
421
+ * origin binding (MVP-016). Rejects expired tokens, unknown kids, and
422
+ * invalid signatures with `VerdictTokenError`. Pass `session` to enforce
423
+ * session binding (recommended) and `aud` to enforce origin binding.
424
+ */
425
+ async verifyVerdictToken(token, options = {}) {
426
+ return verifyVerdictToken(token, {
427
+ ...options,
428
+ jwksUrl: `${this.baseUrl}/.well-known/jwks.json`,
429
+ fetch: this.fetchImpl
430
+ });
431
+ }
432
+ /**
433
+ * Low-level HTTP helper. Prefer verify / feedback / challenge.
434
+ */
435
+ async request(path, init) {
436
+ const controller = new AbortController();
437
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
438
+ try {
439
+ const headers = {
440
+ Authorization: `Bearer ${this.apiKey}`,
441
+ "Content-Type": "application/json",
442
+ Accept: "application/json",
443
+ "User-Agent": this.clientName,
444
+ "X-Chitmark-Client": this.clientName
445
+ };
446
+ if (init.idempotencyKey) {
447
+ headers["Idempotency-Key"] = init.idempotencyKey;
448
+ }
449
+ const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
450
+ method: init.method,
451
+ headers,
452
+ body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
453
+ signal: controller.signal
454
+ });
455
+ const text = await res.text();
456
+ let parsed = void 0;
457
+ if (text) {
458
+ try {
459
+ parsed = JSON.parse(text);
460
+ } catch {
461
+ parsed = text;
462
+ }
463
+ }
464
+ if (!res.ok) {
465
+ const errBody = typeof parsed === "object" && parsed !== null ? parsed : text;
466
+ const normalized = typeof errBody === "string" ? errBody : {
467
+ error: errBody.error ?? `http_${res.status}`,
468
+ message: errBody.message,
469
+ code: errBody.code
470
+ };
471
+ throw new ChitmarkApiError(res.status, normalized);
472
+ }
473
+ return parsed;
474
+ } catch (err) {
475
+ if (err instanceof ChitmarkApiError) throw err;
476
+ if (err instanceof Error && err.name === "AbortError") {
477
+ throw new ChitmarkError(`Request timed out after ${this.timeoutMs}ms`, {
478
+ code: "timeout"
479
+ });
480
+ }
481
+ throw err instanceof Error ? new ChitmarkError(err.message, { code: "transport" }) : new ChitmarkError("Unknown transport error", { code: "transport" });
482
+ } finally {
483
+ clearTimeout(timer);
484
+ }
485
+ }
486
+ /** Public accessors for inspection / tests. */
487
+ get config() {
488
+ return {
489
+ baseUrl: this.baseUrl,
490
+ timeoutMs: this.timeoutMs,
491
+ piiMode: this.piiMode,
492
+ onDegraded: this.onDegraded
493
+ };
494
+ }
495
+ };
496
+ var VERDICT_DECISIONS = /* @__PURE__ */ new Set(["allow", "challenge", "deny"]);
497
+ function isWellFormedVerdict(value) {
498
+ if (typeof value !== "object" || value === null) return false;
499
+ const v = value;
500
+ return typeof v.decision === "string" && VERDICT_DECISIONS.has(v.decision) && typeof v.eventId === "string" && v.eventId.length > 0 && typeof v.degraded === "boolean";
501
+ }
502
+
503
+ // src/http-status.ts
504
+ var CHITMARK_HTTP_STATUS = {
505
+ ok: 200,
506
+ created: 201,
507
+ badRequest: 400,
508
+ unauthorized: 401,
509
+ forbidden: 403,
510
+ notFound: 404,
511
+ conflict: 409,
512
+ preconditionRequired: 428,
513
+ tooManyRequests: 429
514
+ };
515
+ var CHITMARK_HTTP_STATUS_MEANING = {
516
+ ok: "Request succeeded. For verify gates, continue only after an explicit non-degraded allow.",
517
+ created: "Resource was created after an explicit non-degraded allow.",
518
+ badRequest: "Required request fields are missing or malformed.",
519
+ unauthorized: "API key is missing or invalid.",
520
+ forbidden: "Verify returned deny; block the action.",
521
+ notFound: "Referenced Chitmark resource was not found.",
522
+ conflict: "Idempotency key was reused with a materially different payload.",
523
+ preconditionRequired: "Verify returned challenge or degraded; require friction before continuing.",
524
+ tooManyRequests: "Request was rate limited."
525
+ };
526
+ function verdictToHttpStatus(verdict) {
527
+ if (verdict.degraded || verdict.decision === "challenge") {
528
+ return CHITMARK_HTTP_STATUS.preconditionRequired;
529
+ }
530
+ if (verdict.decision === "deny") return CHITMARK_HTTP_STATUS.forbidden;
531
+ return CHITMARK_HTTP_STATUS.ok;
532
+ }
533
+
534
+ // src/middleware/express.ts
535
+ function chitmarkGate(client, action, options = {}) {
536
+ return async (req, res, next) => {
537
+ try {
538
+ const surface = options.surface?.(req) ?? `${req.hostname ?? ""}${req.path ?? ""}`;
539
+ const session = options.session?.(req);
540
+ const v = await client.verify({
541
+ action,
542
+ surface,
543
+ session,
544
+ subject: {
545
+ email: req.body?.email,
546
+ ip: req.ip,
547
+ userAgent: (typeof req.headers["user-agent"] === "string" ? req.headers["user-agent"] : void 0) ?? "",
548
+ headers: req.headers,
549
+ formData: req.body
550
+ }
551
+ });
552
+ res.locals.chitmark = v;
553
+ if (v.degraded || v.decision === "challenge") {
554
+ res.status(CHITMARK_HTTP_STATUS.preconditionRequired).json({ challenge: true, eventId: v.eventId });
555
+ return;
556
+ }
557
+ if (v.decision === "deny") {
558
+ res.status(CHITMARK_HTTP_STATUS.forbidden).json({ error: "blocked", eventId: v.eventId });
559
+ return;
560
+ }
561
+ next();
562
+ } catch (err) {
563
+ next(err);
564
+ }
565
+ };
566
+ }
567
+
568
+ // src/middleware/next.ts
569
+ async function verifyNextRequest(client, req, options) {
570
+ const url = new URL(req.url);
571
+ return client.verify({
572
+ action: options.action,
573
+ surface: options.surface ?? `${url.host}${url.pathname}`,
574
+ session: options.session,
575
+ subject: {
576
+ email: options.body?.email,
577
+ ip: req.headers.get("x-forwarded-for") ?? "",
578
+ userAgent: req.headers.get("user-agent") ?? "",
579
+ headers: Object.fromEntries(req.headers),
580
+ formData: options.body
581
+ }
582
+ });
583
+ }
584
+
585
+ exports.CHITMARK_HTTP_STATUS = CHITMARK_HTTP_STATUS;
586
+ exports.CHITMARK_HTTP_STATUS_MEANING = CHITMARK_HTTP_STATUS_MEANING;
587
+ exports.Chitmark = Chitmark;
588
+ exports.ChitmarkApiError = ChitmarkApiError;
589
+ exports.ChitmarkError = ChitmarkError;
590
+ exports.VerdictTokenError = VerdictTokenError;
591
+ exports.chitmarkGate = chitmarkGate;
592
+ exports.degradedChallengeVerdict = degradedChallengeVerdict;
593
+ exports.enforceDegradedNeverAllow = enforceDegradedNeverAllow;
594
+ exports.hashEmailSha256 = hashEmailSha256;
595
+ exports.minimizeFormData = minimizeFormData;
596
+ exports.truncateIp = truncateIp;
597
+ exports.verdictToHttpStatus = verdictToHttpStatus;
598
+ exports.verifyNextRequest = verifyNextRequest;
599
+ exports.verifyVerdictToken = verifyVerdictToken;
600
+ //# sourceMappingURL=index.cjs.map
601
+ //# sourceMappingURL=index.cjs.map