@naulon/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.
Files changed (157) hide show
  1. package/dist/cli/check.d.ts +23 -0
  2. package/dist/cli/check.d.ts.map +1 -0
  3. package/dist/cli/check.js +100 -0
  4. package/dist/cli/check.js.map +1 -0
  5. package/dist/cli/crawl.d.ts +30 -0
  6. package/dist/cli/crawl.d.ts.map +1 -0
  7. package/dist/cli/crawl.js +180 -0
  8. package/dist/cli/crawl.js.map +1 -0
  9. package/dist/cli/doctor.d.ts +11 -0
  10. package/dist/cli/doctor.d.ts.map +1 -0
  11. package/dist/cli/doctor.js +69 -0
  12. package/dist/cli/doctor.js.map +1 -0
  13. package/dist/cli/init.d.ts +23 -0
  14. package/dist/cli/init.d.ts.map +1 -0
  15. package/dist/cli/init.js +217 -0
  16. package/dist/cli/init.js.map +1 -0
  17. package/dist/cli/naulon.d.ts +3 -0
  18. package/dist/cli/naulon.d.ts.map +1 -0
  19. package/dist/cli/naulon.js +50 -0
  20. package/dist/cli/naulon.js.map +1 -0
  21. package/dist/contract/credits.d.ts +53 -0
  22. package/dist/contract/credits.d.ts.map +1 -0
  23. package/dist/contract/credits.js +56 -0
  24. package/dist/contract/credits.js.map +1 -0
  25. package/dist/contract/index.d.ts +4 -0
  26. package/dist/contract/index.d.ts.map +1 -0
  27. package/dist/contract/index.js +4 -0
  28. package/dist/contract/index.js.map +1 -0
  29. package/dist/contract/settlement.d.ts +67 -0
  30. package/dist/contract/settlement.d.ts.map +1 -0
  31. package/dist/contract/settlement.js +52 -0
  32. package/dist/contract/settlement.js.map +1 -0
  33. package/dist/contract/wallet.d.ts +23 -0
  34. package/dist/contract/wallet.d.ts.map +1 -0
  35. package/dist/contract/wallet.js +26 -0
  36. package/dist/contract/wallet.js.map +1 -0
  37. package/dist/crawl/adapters/rss.d.ts +12 -0
  38. package/dist/crawl/adapters/rss.d.ts.map +1 -0
  39. package/dist/crawl/adapters/rss.js +119 -0
  40. package/dist/crawl/adapters/rss.js.map +1 -0
  41. package/dist/crawl/adapters/sitemap.d.ts +12 -0
  42. package/dist/crawl/adapters/sitemap.d.ts.map +1 -0
  43. package/dist/crawl/adapters/sitemap.js +103 -0
  44. package/dist/crawl/adapters/sitemap.js.map +1 -0
  45. package/dist/crawl/adapters/wordpress.d.ts +19 -0
  46. package/dist/crawl/adapters/wordpress.d.ts.map +1 -0
  47. package/dist/crawl/adapters/wordpress.js +89 -0
  48. package/dist/crawl/adapters/wordpress.js.map +1 -0
  49. package/dist/crawl/authors.d.ts +27 -0
  50. package/dist/crawl/authors.d.ts.map +1 -0
  51. package/dist/crawl/authors.js +13 -0
  52. package/dist/crawl/authors.js.map +1 -0
  53. package/dist/crawl/crawl.d.ts +35 -0
  54. package/dist/crawl/crawl.d.ts.map +1 -0
  55. package/dist/crawl/crawl.js +28 -0
  56. package/dist/crawl/crawl.js.map +1 -0
  57. package/dist/crawl/credits-file.d.ts +39 -0
  58. package/dist/crawl/credits-file.d.ts.map +1 -0
  59. package/dist/crawl/credits-file.js +52 -0
  60. package/dist/crawl/credits-file.js.map +1 -0
  61. package/dist/crawl/fetcher.d.ts +15 -0
  62. package/dist/crawl/fetcher.d.ts.map +1 -0
  63. package/dist/crawl/fetcher.js +106 -0
  64. package/dist/crawl/fetcher.js.map +1 -0
  65. package/dist/crawl/glob.d.ts +19 -0
  66. package/dist/crawl/glob.d.ts.map +1 -0
  67. package/dist/crawl/glob.js +59 -0
  68. package/dist/crawl/glob.js.map +1 -0
  69. package/dist/crawl/net.d.ts +12 -0
  70. package/dist/crawl/net.d.ts.map +1 -0
  71. package/dist/crawl/net.js +93 -0
  72. package/dist/crawl/net.js.map +1 -0
  73. package/dist/crawl/registry.d.ts +17 -0
  74. package/dist/crawl/registry.d.ts.map +1 -0
  75. package/dist/crawl/registry.js +19 -0
  76. package/dist/crawl/registry.js.map +1 -0
  77. package/dist/crawl/slug.d.ts +15 -0
  78. package/dist/crawl/slug.d.ts.map +1 -0
  79. package/dist/crawl/slug.js +42 -0
  80. package/dist/crawl/slug.js.map +1 -0
  81. package/dist/crawl/types.d.ts +96 -0
  82. package/dist/crawl/types.d.ts.map +1 -0
  83. package/dist/crawl/types.js +21 -0
  84. package/dist/crawl/types.js.map +1 -0
  85. package/dist/crawl/xml.d.ts +9 -0
  86. package/dist/crawl/xml.d.ts.map +1 -0
  87. package/dist/crawl/xml.js +44 -0
  88. package/dist/crawl/xml.js.map +1 -0
  89. package/dist/crypto/fixture.d.ts +13 -0
  90. package/dist/crypto/fixture.d.ts.map +1 -0
  91. package/dist/crypto/fixture.js +39 -0
  92. package/dist/crypto/fixture.js.map +1 -0
  93. package/dist/crypto/sign.d.ts +7 -0
  94. package/dist/crypto/sign.d.ts.map +1 -0
  95. package/dist/crypto/sign.js +15 -0
  96. package/dist/crypto/sign.js.map +1 -0
  97. package/dist/crypto/verify.d.ts +21 -0
  98. package/dist/crypto/verify.d.ts.map +1 -0
  99. package/dist/crypto/verify.js +58 -0
  100. package/dist/crypto/verify.js.map +1 -0
  101. package/dist/doctor/doctor.d.ts +27 -0
  102. package/dist/doctor/doctor.d.ts.map +1 -0
  103. package/dist/doctor/doctor.js +149 -0
  104. package/dist/doctor/doctor.js.map +1 -0
  105. package/dist/express/_bridge.d.ts +32 -0
  106. package/dist/express/_bridge.d.ts.map +1 -0
  107. package/dist/express/_bridge.js +34 -0
  108. package/dist/express/_bridge.js.map +1 -0
  109. package/dist/express/credits-route.d.ts +6 -0
  110. package/dist/express/credits-route.d.ts.map +1 -0
  111. package/dist/express/credits-route.js +23 -0
  112. package/dist/express/credits-route.js.map +1 -0
  113. package/dist/express/index.d.ts +10 -0
  114. package/dist/express/index.d.ts.map +1 -0
  115. package/dist/express/index.js +9 -0
  116. package/dist/express/index.js.map +1 -0
  117. package/dist/express/settlement-receiver.d.ts +14 -0
  118. package/dist/express/settlement-receiver.d.ts.map +1 -0
  119. package/dist/express/settlement-receiver.js +31 -0
  120. package/dist/express/settlement-receiver.js.map +1 -0
  121. package/dist/idempotency.d.ts +23 -0
  122. package/dist/idempotency.d.ts.map +1 -0
  123. package/dist/idempotency.js +19 -0
  124. package/dist/idempotency.js.map +1 -0
  125. package/dist/index.d.ts +18 -0
  126. package/dist/index.d.ts.map +1 -0
  127. package/dist/index.js +18 -0
  128. package/dist/index.js.map +1 -0
  129. package/dist/init/plan.d.ts +76 -0
  130. package/dist/init/plan.d.ts.map +1 -0
  131. package/dist/init/plan.js +132 -0
  132. package/dist/init/plan.js.map +1 -0
  133. package/dist/next/credits-route.d.ts +20 -0
  134. package/dist/next/credits-route.d.ts.map +1 -0
  135. package/dist/next/credits-route.js +18 -0
  136. package/dist/next/credits-route.js.map +1 -0
  137. package/dist/next/index.d.ts +10 -0
  138. package/dist/next/index.d.ts.map +1 -0
  139. package/dist/next/index.js +10 -0
  140. package/dist/next/index.js.map +1 -0
  141. package/dist/next/settlement-receiver.d.ts +12 -0
  142. package/dist/next/settlement-receiver.d.ts.map +1 -0
  143. package/dist/next/settlement-receiver.js +42 -0
  144. package/dist/next/settlement-receiver.js.map +1 -0
  145. package/dist/resolver/fixture.d.ts +10 -0
  146. package/dist/resolver/fixture.d.ts.map +1 -0
  147. package/dist/resolver/fixture.js +33 -0
  148. package/dist/resolver/fixture.js.map +1 -0
  149. package/dist/resolver/http.d.ts +3 -0
  150. package/dist/resolver/http.d.ts.map +1 -0
  151. package/dist/resolver/http.js +23 -0
  152. package/dist/resolver/http.js.map +1 -0
  153. package/dist/resolver/types.d.ts +11 -0
  154. package/dist/resolver/types.d.ts.map +1 -0
  155. package/dist/resolver/types.js +2 -0
  156. package/dist/resolver/types.js.map +1 -0
  157. package/package.json +67 -0
@@ -0,0 +1,13 @@
1
+ import type { SettlementBody } from "../contract/settlement.ts";
2
+ export declare function makeSignedSettlementFixture(opts: {
3
+ secret: string;
4
+ body?: SettlementBody;
5
+ now?: number;
6
+ }): {
7
+ rawBody: string;
8
+ headers: {
9
+ "x-naulon-timestamp": string;
10
+ "x-naulon-signature": string;
11
+ };
12
+ };
13
+ //# sourceMappingURL=fixture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../src/crypto/fixture.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAwBhE,wBAAgB,2BAA2B,CAAC,IAAI,EAAE;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE;QAAE,oBAAoB,EAAE,MAAM,CAAC;QAAC,oBAAoB,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAS/F"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Offline conformance helper. A publisher feeds the output into THEIR own receiver
3
+ * in THEIR test harness and asserts a 200 + a written payout — proving their
4
+ * integration without ever POSTing to production (there is no dry-run path; a
5
+ * money receiver gets no public "pretend" mode). The CLI prints this fixture.
6
+ */
7
+ import { signSettlement } from "./sign.js";
8
+ /** A valid sample body (Σ splits === gross, exactly one primary) — passes settlementBodySchema. */
9
+ const SAMPLE_BODY = {
10
+ eventId: "11111111-2222-4333-8444-555555555555",
11
+ slug: "on-stillness",
12
+ txHash: "0xfeed",
13
+ chainId: 5042002,
14
+ currency: "USDC",
15
+ grossAmount: "5000",
16
+ paidTo: "0x1111111111111111111111111111111111111111",
17
+ payer: "0x3333333333333333333333333333333333333333",
18
+ settledAt: "2023-11-14T22:13:20.000Z",
19
+ splits: [
20
+ {
21
+ authorId: "mira",
22
+ wallet: "0x1111111111111111111111111111111111111111",
23
+ amount: "5000",
24
+ weight: 1000,
25
+ primary: true,
26
+ },
27
+ ],
28
+ };
29
+ export function makeSignedSettlementFixture(opts) {
30
+ const body = opts.body ?? SAMPLE_BODY;
31
+ const rawBody = JSON.stringify(body);
32
+ const now = opts.now ?? Math.floor(Date.now() / 1000);
33
+ const { timestamp, signature } = signSettlement(rawBody, opts.secret, now);
34
+ return {
35
+ rawBody,
36
+ headers: { "x-naulon-timestamp": timestamp, "x-naulon-signature": signature },
37
+ };
38
+ }
39
+ //# sourceMappingURL=fixture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture.js","sourceRoot":"","sources":["../../src/crypto/fixture.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C,mGAAmG;AACnG,MAAM,WAAW,GAAmB;IAClC,OAAO,EAAE,sCAAsC;IAC/C,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,MAAM;IAChB,WAAW,EAAE,MAAM;IACnB,MAAM,EAAE,4CAAwE;IAChF,KAAK,EAAE,4CAAwE;IAC/E,SAAS,EAAE,0BAA0B;IACrC,MAAM,EAAE;QACN;YACE,QAAQ,EAAE,MAAM;YAChB,MAAM,EAAE,4CAAwE;YAChF,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,IAAI;SACd;KACF;CACF,CAAC;AAEF,MAAM,UAAU,2BAA2B,CAAC,IAI3C;IACC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACtD,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3E,OAAO;QACL,OAAO;QACP,OAAO,EAAE,EAAE,oBAAoB,EAAE,SAAS,EAAE,oBAAoB,EAAE,SAAS,EAAE;KAC9E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ /** Headers the receiver verifies: a unix-seconds timestamp and the HMAC. */
2
+ export interface SignedSettlement {
3
+ timestamp: string;
4
+ signature: string;
5
+ }
6
+ export declare function signSettlement(rawBody: string, secret: string, unixSeconds: number): SignedSettlement;
7
+ //# sourceMappingURL=sign.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../src/crypto/sign.ts"],"names":[],"mappings":"AAUA,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,gBAAgB,CAKlB"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * HMAC signing of the settlement body — the producer half of the trust boundary.
3
+ *
4
+ * Pure: no fetch, no ambient clock. The caller passes the unix-seconds timestamp
5
+ * and signs the EXACT raw body bytes it will send — serialize once, sign that
6
+ * string, send that string. The receiver recomputes HMAC-SHA256(`${ts}.${body}`)
7
+ * and rejects a mismatch (401) or a timestamp skewed past the window.
8
+ */
9
+ import { createHmac } from "node:crypto";
10
+ export function signSettlement(rawBody, secret, unixSeconds) {
11
+ const timestamp = String(unixSeconds);
12
+ const signature = "sha256=" + createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
13
+ return { timestamp, signature };
14
+ }
15
+ //# sourceMappingURL=sign.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign.js","sourceRoot":"","sources":["../../src/crypto/sign.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAQzC,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,MAAc,EACd,WAAmB;IAEnB,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IACtC,MAAM,SAAS,GACb,SAAS,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3F,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { type SettlementBody } from "../contract/settlement.ts";
2
+ /** Max clock skew (seconds) between the signer and the receiver. */
3
+ export declare const MAX_SKEW_SECONDS = 300;
4
+ export type VerifyResult = {
5
+ ok: true;
6
+ event: SettlementBody;
7
+ } | {
8
+ ok: false;
9
+ status: 400 | 401;
10
+ reason: string;
11
+ };
12
+ export declare function verifySettlement(opts: {
13
+ rawBody: string;
14
+ timestampHeader: string | null;
15
+ signatureHeader: string | null;
16
+ /** 1..n secrets; >1 during a rotation overlap. */
17
+ secrets: string[];
18
+ /** Injectable clock (unix seconds) for tests; defaults to now. */
19
+ now?: number;
20
+ }): VerifyResult;
21
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/crypto/verify.ts"],"names":[],"mappings":"AAcA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEtF,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,GACnC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAsBrD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kDAAkD;IAClD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,YAAY,CA4Bf"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * HMAC verification of the settlement body — the verify half, written once here
3
+ * instead of hand-rolled per publisher. Authenticity + freshness + shape, in the
4
+ * order the gate's retry semantics depend on:
5
+ *
6
+ * 401 = TRANSIENT (the gate retries) — signature/clock problems.
7
+ * 400 = PERMANENT (the gate gives up) — a malformed body; retrying won't help.
8
+ *
9
+ * Get those status codes wrong and you change the gate's retry behavior, so they
10
+ * are pinned by tests. This proves AUTHENTICITY only; exactly-once PERSISTENCE is
11
+ * the receiver's job (see IdempotencyStore) — the skew window means an authentic
12
+ * POST is replayable for its whole duration.
13
+ */
14
+ import { createHmac, timingSafeEqual } from "node:crypto";
15
+ import { settlementBodySchema } from "../contract/settlement.js";
16
+ /** Max clock skew (seconds) between the signer and the receiver. */
17
+ export const MAX_SKEW_SECONDS = 300;
18
+ /** Constant-time compare of the provided signature against each candidate secret
19
+ * (the array is what makes secret rotation safe — accept old+new during a roll). */
20
+ function signatureMatches(timestamp, rawBody, header, secrets) {
21
+ const provided = Buffer.from(header);
22
+ for (const secret of secrets) {
23
+ const expected = Buffer.from("sha256=" + createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex"));
24
+ if (provided.length === expected.length && timingSafeEqual(provided, expected)) {
25
+ return true;
26
+ }
27
+ }
28
+ return false;
29
+ }
30
+ export function verifySettlement(opts) {
31
+ const { rawBody, timestampHeader, signatureHeader, secrets } = opts;
32
+ if (secrets.length === 0) {
33
+ throw new Error("verifySettlement: at least one secret is required");
34
+ }
35
+ const now = opts.now ?? Math.floor(Date.now() / 1000);
36
+ if (!timestampHeader || !/^\d+$/.test(timestampHeader)) {
37
+ return { ok: false, status: 401, reason: "bad-timestamp" };
38
+ }
39
+ if (Math.abs(now - Number(timestampHeader)) > MAX_SKEW_SECONDS) {
40
+ return { ok: false, status: 401, reason: "stale-timestamp" };
41
+ }
42
+ if (!signatureHeader || !signatureMatches(timestampHeader, rawBody, signatureHeader, secrets)) {
43
+ return { ok: false, status: 401, reason: "bad-signature" };
44
+ }
45
+ let parsed;
46
+ try {
47
+ parsed = JSON.parse(rawBody);
48
+ }
49
+ catch {
50
+ return { ok: false, status: 400, reason: "bad-json" };
51
+ }
52
+ const result = settlementBodySchema.safeParse(parsed);
53
+ if (!result.success) {
54
+ return { ok: false, status: 400, reason: "invalid-event" };
55
+ }
56
+ return { ok: true, event: result.data };
57
+ }
58
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/crypto/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAuB,MAAM,2BAA2B,CAAC;AAEtF,oEAAoE;AACpE,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAMpC;qFACqF;AACrF,SAAS,gBAAgB,CACvB,SAAiB,EACjB,OAAe,EACf,MAAc,EACd,OAAiB;IAEjB,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAC1B,SAAS,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACzF,CAAC;QACF,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;YAC/E,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAQhC;IACC,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACpE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAEtD,IAAI,CAAC,eAAe,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;QACvD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC7D,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC,GAAG,gBAAgB,EAAE,CAAC;QAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC/D,CAAC;IACD,IAAI,CAAC,eAAe,IAAI,CAAC,gBAAgB,CAAC,eAAe,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,CAAC;QAC9F,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC7D,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACxD,CAAC;IACD,MAAM,MAAM,GAAG,oBAAoB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC7D,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAAiC,EAAE,CAAC;AACvE,CAAC"}
@@ -0,0 +1,27 @@
1
+ export type Level = "pass" | "warn" | "fail";
2
+ export interface DoctorCheck {
3
+ name: string;
4
+ level: Level;
5
+ detail: string;
6
+ }
7
+ export interface DoctorInputs {
8
+ /** `.env` file contents, or null if it's missing. */
9
+ envText: string | null;
10
+ fileExists: (path: string) => boolean;
11
+ readFile: (path: string) => string;
12
+ /** Absolute dir the `.env` lives in — resolves a relative CREDITS_FIXTURES. */
13
+ cwd: string;
14
+ /** Provide to run live gate probes; omit to skip them (e.g. in a pure unit test). */
15
+ fetchImpl?: typeof fetch;
16
+ /** Override where the gate is; defaults to http://localhost:${TOLLGATE_PORT}. */
17
+ gateUrl?: string;
18
+ }
19
+ export interface DoctorOutcome {
20
+ checks: DoctorCheck[];
21
+ /** true when no check FAILED (warnings are allowed). */
22
+ ok: boolean;
23
+ }
24
+ /** Parse a dotenv-style file into a flat record. Comments + blanks ignored. */
25
+ export declare function parseEnvFile(text: string): Record<string, string>;
26
+ export declare function runDoctor(inp: DoctorInputs): Promise<DoctorOutcome>;
27
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/doctor/doctor.ts"],"names":[],"mappings":"AAgCA,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAC7C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,+EAA+E;IAC/E,GAAG,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,wDAAwD;IACxD,EAAE,EAAE,OAAO,CAAC;CACb;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOjE;AAOD,wBAAsB,SAAS,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAsGzE"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * `naulon doctor` — a health check for YOUR OWN gate.
3
+ *
4
+ * `naulon-kit check` validates a *remote* publisher's `/credits` endpoint. `doctor` is the
5
+ * mirror for the operator: is *my* local setup coherent and would it toll correctly? It reads
6
+ * the `.env`, validates it against the gate's own `configSchema`, confirms the credits source
7
+ * resolves, sanity-checks the settlement mode, and — if the gate is running — probes it: a human
8
+ * UA must read free, an agent UA must get a 402. All the logic is here (pure, with injected fs +
9
+ * fetch) so it is unit-testable without a real filesystem or a running server, matching the
10
+ * `runCheck` / `check` split.
11
+ */
12
+ import { z } from "zod";
13
+ import { parseCredits } from "../contract/credits.js";
14
+ /**
15
+ * The subset of the gate's env that `doctor` reasons about. Deliberately NOT the gate's full
16
+ * `configSchema` (in `@naulon/shared`, which imports this package — importing it back would
17
+ * cycle). The gate itself fails loud on deeper config errors at boot; the live probe below
18
+ * catches real misbehavior. This validates the handful of vars doctor's checks depend on.
19
+ */
20
+ const doctorEnvSchema = z.object({
21
+ PAYMENT_MODE: z.enum(["mock", "gateway"]).default("mock"),
22
+ ORIGIN_URL: z.string().url().default("http://localhost:3000"),
23
+ TOLLGATE_PORT: z.coerce.number().int().positive().default(8402),
24
+ ARTICLE_PATH_PREFIXES: z.string().default("essays,articles,posts"),
25
+ SETTLEMENT_NETWORK: z.enum(["arcTestnet", "baseSepolia", "base"]).default("arcTestnet"),
26
+ CREDITS_API_URL: z.string().url().optional(),
27
+ CREDITS_FIXTURES: z.string().optional(),
28
+ RELAYER_PRIVATE_KEY: z.string().optional(),
29
+ CIRCLE_API_KEY: z.string().optional(),
30
+ });
31
+ /** Parse a dotenv-style file into a flat record. Comments + blanks ignored. */
32
+ export function parseEnvFile(text) {
33
+ const env = {};
34
+ for (const line of text.split("\n")) {
35
+ const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)$/);
36
+ if (m && m[1] && !line.trimStart().startsWith("#"))
37
+ env[m[1]] = (m[2] ?? "").trim();
38
+ }
39
+ return env;
40
+ }
41
+ function joinPath(cwd, p) {
42
+ if (p.startsWith("/"))
43
+ return p;
44
+ return `${cwd.replace(/\/$/, "")}/${p.replace(/^\.\//, "")}`;
45
+ }
46
+ export async function runDoctor(inp) {
47
+ const checks = [];
48
+ // 1. .env present + valid against the gate's OWN config schema.
49
+ if (inp.envText === null) {
50
+ checks.push({ name: "env", level: "fail", detail: "no .env found — run `naulon init`" });
51
+ return { checks, ok: false };
52
+ }
53
+ const parsed = doctorEnvSchema.safeParse(parseEnvFile(inp.envText));
54
+ if (!parsed.success) {
55
+ const first = parsed.error.issues[0];
56
+ checks.push({
57
+ name: "env",
58
+ level: "fail",
59
+ detail: `.env invalid: ${first ? `${first.path.join(".")} — ${first.message}` : "malformed"}`,
60
+ });
61
+ return { checks, ok: false };
62
+ }
63
+ const cfg = parsed.data;
64
+ checks.push({
65
+ name: "env",
66
+ level: "pass",
67
+ detail: `valid — mode=${cfg.PAYMENT_MODE} origin=${cfg.ORIGIN_URL} port=${cfg.TOLLGATE_PORT}`,
68
+ });
69
+ // 2. Credits source resolves.
70
+ let firstSlug;
71
+ if (cfg.CREDITS_API_URL) {
72
+ checks.push({
73
+ name: "credits",
74
+ level: "pass",
75
+ detail: `API mode → ${cfg.CREDITS_API_URL} (conformance: \`naulon check ${cfg.CREDITS_API_URL} --slug <s>\`)`,
76
+ });
77
+ }
78
+ else if (!cfg.CREDITS_FIXTURES) {
79
+ checks.push({
80
+ name: "credits",
81
+ level: "warn",
82
+ detail: "neither CREDITS_FIXTURES nor CREDITS_API_URL set — the gate falls back to its bundled default; run `naulon init`",
83
+ });
84
+ }
85
+ else {
86
+ const path = joinPath(inp.cwd, cfg.CREDITS_FIXTURES);
87
+ if (!inp.fileExists(path)) {
88
+ checks.push({ name: "credits", level: "fail", detail: `CREDITS_FIXTURES not found: ${cfg.CREDITS_FIXTURES}` });
89
+ }
90
+ else {
91
+ try {
92
+ const map = JSON.parse(inp.readFile(path));
93
+ const slugs = Object.keys(map);
94
+ for (const s of slugs)
95
+ parseCredits(map[s], `credits "${s}"`);
96
+ firstSlug = slugs[0];
97
+ checks.push({
98
+ name: "credits",
99
+ level: slugs.length > 0 ? "pass" : "warn",
100
+ detail: slugs.length > 0 ? `${slugs.length} valid entr${slugs.length === 1 ? "y" : "ies"}` : "file is empty — no articles to toll yet",
101
+ });
102
+ }
103
+ catch (e) {
104
+ checks.push({ name: "credits", level: "fail", detail: `CREDITS_FIXTURES invalid: ${e instanceof Error ? e.message : String(e)}` });
105
+ }
106
+ }
107
+ }
108
+ // 3. Settlement-mode coherence.
109
+ if (cfg.PAYMENT_MODE === "mock") {
110
+ checks.push({ name: "settlement", level: "warn", detail: "mock — payments signed offline, no real USDC. Fine for dev." });
111
+ }
112
+ else {
113
+ const hasRelayer = Boolean(cfg.RELAYER_PRIVATE_KEY);
114
+ const hasCircle = Boolean(cfg.CIRCLE_API_KEY);
115
+ const isArcMemo = cfg.SETTLEMENT_NETWORK === "arcTestnet"; // memo self-relay network
116
+ if (isArcMemo && !hasRelayer) {
117
+ checks.push({ name: "settlement", level: "warn", detail: `gateway on ${cfg.SETTLEMENT_NETWORK} but RELAYER_PRIVATE_KEY unset — real settle will error at settle-time` });
118
+ }
119
+ else if (!isArcMemo && !hasCircle) {
120
+ checks.push({ name: "settlement", level: "warn", detail: `gateway on ${cfg.SETTLEMENT_NETWORK} but CIRCLE_API_KEY unset` });
121
+ }
122
+ else {
123
+ checks.push({ name: "settlement", level: "pass", detail: `gateway on ${cfg.SETTLEMENT_NETWORK} — settlement creds present` });
124
+ }
125
+ }
126
+ // 4. Live gate probes (only if a fetch impl was provided).
127
+ if (inp.fetchImpl) {
128
+ const base = (inp.gateUrl ?? `http://localhost:${cfg.TOLLGATE_PORT}`).replace(/\/$/, "");
129
+ const prefix = cfg.ARTICLE_PATH_PREFIXES.split(",")[0]?.trim() || "essays";
130
+ const slug = firstSlug ?? "example";
131
+ const url = `${base}/${prefix}/${encodeURIComponent(slug)}`;
132
+ try {
133
+ const human = await inp.fetchImpl(url, { headers: { "user-agent": "Mozilla/5.0", accept: "text/html" }, redirect: "manual" });
134
+ checks.push(human.status === 402
135
+ ? { name: "gate:human", level: "fail", detail: `a human UA was tolled (402) on ${url} — humans must read free` }
136
+ : { name: "gate:human", level: "pass", detail: `human UA → not tolled (HTTP ${human.status})` });
137
+ const agent = await inp.fetchImpl(url, { headers: { "user-agent": "python-requests/2.31" }, redirect: "manual" });
138
+ const challenged = agent.status === 402 && (agent.headers.get("payment-required") !== null || agent.headers.get("link") !== null);
139
+ checks.push(challenged
140
+ ? { name: "gate:agent", level: "pass", detail: `agent UA → 402 + payment challenge on /${prefix}/${slug}` }
141
+ : { name: "gate:agent", level: "warn", detail: `agent UA got HTTP ${agent.status} (expected 402) on /${prefix}/${slug} — check ARTICLE_PATH_PREFIXES + credits` });
142
+ }
143
+ catch {
144
+ checks.push({ name: "gate", level: "warn", detail: `gate not reachable at ${base} — start it: \`make dev\`` });
145
+ }
146
+ }
147
+ return { checks, ok: checks.every((c) => c.level !== "fail") };
148
+ }
149
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/doctor/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACzD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,uBAAuB,CAAC;IAC7D,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAC/D,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,uBAAuB,CAAC;IAClE,kBAAkB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;IACvF,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC5C,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACvC,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1C,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACtC,CAAC,CAAC;AA4BH,+EAA+E;AAC/E,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QACrD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW,EAAE,CAAS;IACtC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IAChC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAiB;IAC/C,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,gEAAgE;IAChE,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,mCAAmC,EAAE,CAAC,CAAC;QACzF,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAC/B,CAAC;IACD,MAAM,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,KAAK;YACX,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,iBAAiB,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE;SAC9F,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAC/B,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC;IACxB,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,MAAM;QACb,MAAM,EAAE,gBAAgB,GAAG,CAAC,YAAY,WAAW,GAAG,CAAC,UAAU,SAAS,GAAG,CAAC,aAAa,EAAE;KAC9F,CAAC,CAAC;IAEH,8BAA8B;IAC9B,IAAI,SAA6B,CAAC;IAClC,IAAI,GAAG,CAAC,eAAe,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,cAAc,GAAG,CAAC,eAAe,iCAAiC,GAAG,CAAC,eAAe,gBAAgB;SAC9G,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,kHAAkH;SAC3H,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;QACrD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,+BAA+B,GAAG,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;QACjH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAA4B,CAAC;gBACtE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC/B,KAAK,MAAM,CAAC,IAAI,KAAK;oBAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC;gBAC9D,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACrB,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,SAAS;oBACf,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;oBACzC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,cAAc,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,yCAAyC;iBACvI,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,6BAA6B,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACrI,CAAC;QACH,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,IAAI,GAAG,CAAC,YAAY,KAAK,MAAM,EAAE,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,6DAA6D,EAAE,CAAC,CAAC;IAC5H,CAAC;SAAM,CAAC;QACN,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,GAAG,CAAC,kBAAkB,KAAK,YAAY,CAAC,CAAC,0BAA0B;QACrF,IAAI,SAAS,IAAI,CAAC,UAAU,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,CAAC,kBAAkB,wEAAwE,EAAE,CAAC,CAAC;QAC3K,CAAC;aAAM,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,CAAC,kBAAkB,2BAA2B,EAAE,CAAC,CAAC;QAC9H,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,CAAC,kBAAkB,6BAA6B,EAAE,CAAC,CAAC;QAChI,CAAC;IACH,CAAC;IAED,2DAA2D;IAC3D,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;QAClB,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,oBAAoB,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACzF,MAAM,MAAM,GAAG,GAAG,CAAC,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC;QAC3E,MAAM,IAAI,GAAG,SAAS,IAAI,SAAS,CAAC;QACpC,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,MAAM,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5D,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC9H,MAAM,CAAC,IAAI,CACT,KAAK,CAAC,MAAM,KAAK,GAAG;gBAClB,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,kCAAkC,GAAG,0BAA0B,EAAE;gBAChH,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,+BAA+B,KAAK,CAAC,MAAM,GAAG,EAAE,CAClG,CAAC;YACF,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,sBAAsB,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;YAClH,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;YAClI,MAAM,CAAC,IAAI,CACT,UAAU;gBACR,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,0CAA0C,MAAM,IAAI,IAAI,EAAE,EAAE;gBAC3G,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,KAAK,CAAC,MAAM,uBAAuB,MAAM,IAAI,IAAI,0CAA0C,EAAE,CACpK,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,yBAAyB,IAAI,2BAA2B,EAAE,CAAC,CAAC;QACjH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,EAAE,CAAC;AACjE,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Express ⇄ web-standard bridge. The Express adapters are deliberately thin: they
3
+ * translate an Express `(req, res)` into the web-standard `Request`/`Response` the
4
+ * Next adapters already speak, then run the SAME handler. One implementation of the
5
+ * contract logic, two framework skins — no second copy to drift (the SDK's reason
6
+ * for existing). These structural types avoid a hard `@types/express` dependency:
7
+ * the adapters work against any Express-shaped req/res.
8
+ */
9
+ export interface ExpressReqLike {
10
+ headers: Record<string, string | string[] | undefined>;
11
+ params: Record<string, string | undefined>;
12
+ /** Raw request body — a Buffer when the route is mounted with `express.raw()`. */
13
+ body?: unknown;
14
+ }
15
+ export interface ExpressResLike {
16
+ status(code: number): ExpressResLike;
17
+ setHeader(name: string, value: string): void;
18
+ send(body: string): void;
19
+ }
20
+ export type ExpressHandler = (req: ExpressReqLike, res: ExpressResLike) => Promise<void>;
21
+ /** A single header value (Express folds duplicates to an array; take the first). */
22
+ export declare function headerValue(v: string | string[] | undefined): string | undefined;
23
+ /**
24
+ * The settlement HMAC is computed over the EXACT request bytes, so the receiver
25
+ * needs the raw body. `express.raw()` leaves it a Buffer; `express.json()` parses
26
+ * and discards the bytes (re-serializing changes them and every signature fails).
27
+ * Fail loud with the fix rather than silently 401 every valid call.
28
+ */
29
+ export declare function rawBodyOf(body: unknown, who: string): string;
30
+ /** Write a web-standard Response back onto an Express response, headers and all. */
31
+ export declare function pipeResponse(response: Response, res: ExpressResLike): Promise<void>;
32
+ //# sourceMappingURL=_bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_bridge.d.ts","sourceRoot":"","sources":["../../src/express/_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC3C,kFAAkF;IAClF,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC;IACrC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEzF,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEhF;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED,oFAAoF;AACpF,wBAAsB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzF"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Express ⇄ web-standard bridge. The Express adapters are deliberately thin: they
3
+ * translate an Express `(req, res)` into the web-standard `Request`/`Response` the
4
+ * Next adapters already speak, then run the SAME handler. One implementation of the
5
+ * contract logic, two framework skins — no second copy to drift (the SDK's reason
6
+ * for existing). These structural types avoid a hard `@types/express` dependency:
7
+ * the adapters work against any Express-shaped req/res.
8
+ */
9
+ /** A single header value (Express folds duplicates to an array; take the first). */
10
+ export function headerValue(v) {
11
+ return Array.isArray(v) ? v[0] : v;
12
+ }
13
+ /**
14
+ * The settlement HMAC is computed over the EXACT request bytes, so the receiver
15
+ * needs the raw body. `express.raw()` leaves it a Buffer; `express.json()` parses
16
+ * and discards the bytes (re-serializing changes them and every signature fails).
17
+ * Fail loud with the fix rather than silently 401 every valid call.
18
+ */
19
+ export function rawBodyOf(body, who) {
20
+ if (Buffer.isBuffer(body))
21
+ return body.toString("utf8");
22
+ if (typeof body === "string")
23
+ return body;
24
+ throw new Error(`${who}: req.body must be the raw request bytes. Mount this route with ` +
25
+ `express.raw({ type: "*/*" }) — the HMAC signs the exact bytes, so express.json() ` +
26
+ `(which parses then discards them) breaks verification.`);
27
+ }
28
+ /** Write a web-standard Response back onto an Express response, headers and all. */
29
+ export async function pipeResponse(response, res) {
30
+ res.status(response.status);
31
+ response.headers.forEach((value, name) => res.setHeader(name, value));
32
+ res.send(await response.text());
33
+ }
34
+ //# sourceMappingURL=_bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_bridge.js","sourceRoot":"","sources":["../../src/express/_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiBH,oFAAoF;AACpF,MAAM,UAAU,WAAW,CAAC,CAAgC;IAC1D,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,IAAa,EAAE,GAAW;IAClD,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,IAAI,KAAK,CACb,GAAG,GAAG,kEAAkE;QACtE,mFAAmF;QACnF,wDAAwD,CAC3D,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAkB,EAAE,GAAmB;IACxE,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACtE,GAAG,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { CreditsResolver } from "../resolver/types.ts";
2
+ import { type ExpressHandler } from "./_bridge.ts";
3
+ export declare function createExpressCreditsRoute(resolver: CreditsResolver, opts?: {
4
+ token?: string;
5
+ }): ExpressHandler;
6
+ //# sourceMappingURL=credits-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credits-route.d.ts","sourceRoot":"","sources":["../../src/express/credits-route.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,KAAK,cAAc,EAA6B,MAAM,cAAc,CAAC;AAE9E,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,eAAe,EACzB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACxB,cAAc,CAWhB"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Express credits route — the same minimal `CreditsResolver` framing as the Next
3
+ * adapter (`../next/credits-route.ts`), wrapped for an Express `(req, res)` route.
4
+ * Pins the one thing publishers get wrong: `404 = free read`, distinct from a
5
+ * `200` with credits. Wire it as:
6
+ * app.get("/api/credits/:slug", createExpressCreditsRoute(resolver));
7
+ */
8
+ import { createCreditsRoute } from "../next/credits-route.js";
9
+ import { headerValue, pipeResponse } from "./_bridge.js";
10
+ export function createExpressCreditsRoute(resolver, opts) {
11
+ const handler = createCreditsRoute(resolver, opts);
12
+ return async (req, res) => {
13
+ const slug = req.params.slug ?? "";
14
+ const headers = new Headers();
15
+ const auth = headerValue(req.headers["authorization"]);
16
+ if (auth !== undefined)
17
+ headers.set("authorization", auth);
18
+ const request = new Request(`http://credits.local/${encodeURIComponent(slug)}`, { headers });
19
+ const response = await handler(request, { params: Promise.resolve({ slug }) });
20
+ await pipeResponse(response, res);
21
+ };
22
+ }
23
+ //# sourceMappingURL=credits-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credits-route.js","sourceRoot":"","sources":["../../src/express/credits-route.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAuB,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE9E,MAAM,UAAU,yBAAyB,CACvC,QAAyB,EACzB,IAAyB;IAEzB,MAAM,OAAO,GAAG,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACnD,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;QACvD,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,wBAAwB,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QAC7F,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/E,MAAM,YAAY,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `@naulon/sdk/express` — Express adapters for publishers on Express instead
3
+ * of Next. Thin bridges over the framework-neutral handlers, so the contract logic
4
+ * is identical to `@naulon/sdk/next`. `express` is an optional peer dep; the
5
+ * adapters use structural req/res types so they need no `@types/express` to build.
6
+ */
7
+ export { createExpressCreditsRoute } from "./credits-route.ts";
8
+ export { createExpressSettlementReceiver } from "./settlement-receiver.ts";
9
+ export type { ExpressReqLike, ExpressResLike, ExpressHandler } from "./_bridge.ts";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/express/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;AAC3E,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@naulon/sdk/express` — Express adapters for publishers on Express instead
3
+ * of Next. Thin bridges over the framework-neutral handlers, so the contract logic
4
+ * is identical to `@naulon/sdk/next`. `express` is an optional peer dep; the
5
+ * adapters use structural req/res types so they need no `@types/express` to build.
6
+ */
7
+ export { createExpressCreditsRoute } from "./credits-route.js";
8
+ export { createExpressSettlementReceiver } from "./settlement-receiver.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/express/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,+BAA+B,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { SettlementBody } from "../contract/settlement.ts";
2
+ import type { IdempotencyStore } from "../idempotency.ts";
3
+ import { type ExpressHandler } from "./_bridge.ts";
4
+ export declare function createExpressSettlementReceiver(opts: {
5
+ /** 1..n shared HMAC secrets; >1 only during a rotation overlap. */
6
+ secrets: string[];
7
+ /** Persist the payout here. Runs at most once per eventId (idempotency-gated). */
8
+ onEvent: (event: SettlementBody) => Promise<void>;
9
+ /** REQUIRED. memoryIdempotencyStore() is dev-only — back it with a DB unique
10
+ * constraint on eventId in production (the 300s skew window makes an authentic
11
+ * POST replayable for five minutes). */
12
+ idempotency: IdempotencyStore;
13
+ }): ExpressHandler;
14
+ //# sourceMappingURL=settlement-receiver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settlement-receiver.d.ts","sourceRoot":"","sources":["../../src/express/settlement-receiver.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EACL,KAAK,cAAc,EAIpB,MAAM,cAAc,CAAC;AAEtB,wBAAgB,+BAA+B,CAAC,IAAI,EAAE;IACpD,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kFAAkF;IAClF,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD;;6CAEyC;IACzC,WAAW,EAAE,gBAAgB,CAAC;CAC/B,GAAG,cAAc,CAajB"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Express settlement receiver — the same logic as the Next receiver
3
+ * (`../next/settlement-receiver.ts`), wrapped for an Express `(req, res)` route.
4
+ * It runs the web-standard handler verbatim, so HMAC verification, the 401/400
5
+ * retry semantics, and the mandatory exactly-once `IdempotencyStore` behave
6
+ * identically across both frameworks.
7
+ *
8
+ * Mount it with `express.raw()` so the body arrives as raw bytes:
9
+ * app.post("/api/credits/settlement",
10
+ * express.raw({ type: "*\/*" }),
11
+ * createExpressSettlementReceiver({ secrets, onEvent, idempotency }));
12
+ */
13
+ import { createSettlementReceiver } from "../next/settlement-receiver.js";
14
+ import { headerValue, pipeResponse, rawBodyOf, } from "./_bridge.js";
15
+ export function createExpressSettlementReceiver(opts) {
16
+ // Reuse the web handler — including its empty-secrets guard (throws here).
17
+ const handler = createSettlementReceiver(opts);
18
+ return async (req, res) => {
19
+ const rawBody = rawBodyOf(req.body, "createExpressSettlementReceiver");
20
+ const headers = new Headers();
21
+ const ts = headerValue(req.headers["x-naulon-timestamp"]);
22
+ const sig = headerValue(req.headers["x-naulon-signature"]);
23
+ if (ts !== undefined)
24
+ headers.set("x-naulon-timestamp", ts);
25
+ if (sig !== undefined)
26
+ headers.set("x-naulon-signature", sig);
27
+ const request = new Request("http://settlement.local", { method: "POST", headers, body: rawBody });
28
+ await pipeResponse(await handler(request), res);
29
+ };
30
+ }
31
+ //# sourceMappingURL=settlement-receiver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settlement-receiver.js","sourceRoot":"","sources":["../../src/express/settlement-receiver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAG1E,OAAO,EAEL,WAAW,EACX,YAAY,EACZ,SAAS,GACV,MAAM,cAAc,CAAC;AAEtB,MAAM,UAAU,+BAA+B,CAAC,IAS/C;IACC,2EAA2E;IAC3E,MAAM,OAAO,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC/C,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,iCAAiC,CAAC,CAAC;QACvE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QAC9B,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC;QAC1D,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC;QAC3D,IAAI,EAAE,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;QAC5D,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;QAC9D,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,yBAAyB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACnG,MAAM,YAAY,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Exactly-once persistence for the settlement receiver — a SEPARATE concern from
3
+ * `verifySettlement` (which proves authenticity only). The skew window means an
4
+ * authentic POST is replayable for its whole duration (5 minutes), so a money
5
+ * receiver without dedupe is a double-payout defect, not a choice. The receiver
6
+ * adapter therefore REQUIRES one of these.
7
+ */
8
+ export interface IdempotencyStore {
9
+ /**
10
+ * Atomically claim `eventId`. Returns `true` the first time (proceed with the
11
+ * payout), `false` if it was already seen (a replay — short-circuit to 200).
12
+ */
13
+ claim(eventId: string): Promise<boolean>;
14
+ }
15
+ /**
16
+ * A process-local default so the type is always satisfiable in dev/tests.
17
+ *
18
+ * NOT DURABLE. It is lost on restart and useless across instances — any real
19
+ * deployment MUST back `claim` with its database (a unique constraint on the event
20
+ * id). Using this in production is a double-payout footgun.
21
+ */
22
+ export declare function memoryIdempotencyStore(): IdempotencyStore;
23
+ //# sourceMappingURL=idempotency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,gBAAgB,CASzD"}