@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.
- package/dist/cli/check.d.ts +23 -0
- package/dist/cli/check.d.ts.map +1 -0
- package/dist/cli/check.js +100 -0
- package/dist/cli/check.js.map +1 -0
- package/dist/cli/crawl.d.ts +30 -0
- package/dist/cli/crawl.d.ts.map +1 -0
- package/dist/cli/crawl.js +180 -0
- package/dist/cli/crawl.js.map +1 -0
- package/dist/cli/doctor.d.ts +11 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +69 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/init.d.ts +23 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +217 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/naulon.d.ts +3 -0
- package/dist/cli/naulon.d.ts.map +1 -0
- package/dist/cli/naulon.js +50 -0
- package/dist/cli/naulon.js.map +1 -0
- package/dist/contract/credits.d.ts +53 -0
- package/dist/contract/credits.d.ts.map +1 -0
- package/dist/contract/credits.js +56 -0
- package/dist/contract/credits.js.map +1 -0
- package/dist/contract/index.d.ts +4 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +4 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/settlement.d.ts +67 -0
- package/dist/contract/settlement.d.ts.map +1 -0
- package/dist/contract/settlement.js +52 -0
- package/dist/contract/settlement.js.map +1 -0
- package/dist/contract/wallet.d.ts +23 -0
- package/dist/contract/wallet.d.ts.map +1 -0
- package/dist/contract/wallet.js +26 -0
- package/dist/contract/wallet.js.map +1 -0
- package/dist/crawl/adapters/rss.d.ts +12 -0
- package/dist/crawl/adapters/rss.d.ts.map +1 -0
- package/dist/crawl/adapters/rss.js +119 -0
- package/dist/crawl/adapters/rss.js.map +1 -0
- package/dist/crawl/adapters/sitemap.d.ts +12 -0
- package/dist/crawl/adapters/sitemap.d.ts.map +1 -0
- package/dist/crawl/adapters/sitemap.js +103 -0
- package/dist/crawl/adapters/sitemap.js.map +1 -0
- package/dist/crawl/adapters/wordpress.d.ts +19 -0
- package/dist/crawl/adapters/wordpress.d.ts.map +1 -0
- package/dist/crawl/adapters/wordpress.js +89 -0
- package/dist/crawl/adapters/wordpress.js.map +1 -0
- package/dist/crawl/authors.d.ts +27 -0
- package/dist/crawl/authors.d.ts.map +1 -0
- package/dist/crawl/authors.js +13 -0
- package/dist/crawl/authors.js.map +1 -0
- package/dist/crawl/crawl.d.ts +35 -0
- package/dist/crawl/crawl.d.ts.map +1 -0
- package/dist/crawl/crawl.js +28 -0
- package/dist/crawl/crawl.js.map +1 -0
- package/dist/crawl/credits-file.d.ts +39 -0
- package/dist/crawl/credits-file.d.ts.map +1 -0
- package/dist/crawl/credits-file.js +52 -0
- package/dist/crawl/credits-file.js.map +1 -0
- package/dist/crawl/fetcher.d.ts +15 -0
- package/dist/crawl/fetcher.d.ts.map +1 -0
- package/dist/crawl/fetcher.js +106 -0
- package/dist/crawl/fetcher.js.map +1 -0
- package/dist/crawl/glob.d.ts +19 -0
- package/dist/crawl/glob.d.ts.map +1 -0
- package/dist/crawl/glob.js +59 -0
- package/dist/crawl/glob.js.map +1 -0
- package/dist/crawl/net.d.ts +12 -0
- package/dist/crawl/net.d.ts.map +1 -0
- package/dist/crawl/net.js +93 -0
- package/dist/crawl/net.js.map +1 -0
- package/dist/crawl/registry.d.ts +17 -0
- package/dist/crawl/registry.d.ts.map +1 -0
- package/dist/crawl/registry.js +19 -0
- package/dist/crawl/registry.js.map +1 -0
- package/dist/crawl/slug.d.ts +15 -0
- package/dist/crawl/slug.d.ts.map +1 -0
- package/dist/crawl/slug.js +42 -0
- package/dist/crawl/slug.js.map +1 -0
- package/dist/crawl/types.d.ts +96 -0
- package/dist/crawl/types.d.ts.map +1 -0
- package/dist/crawl/types.js +21 -0
- package/dist/crawl/types.js.map +1 -0
- package/dist/crawl/xml.d.ts +9 -0
- package/dist/crawl/xml.d.ts.map +1 -0
- package/dist/crawl/xml.js +44 -0
- package/dist/crawl/xml.js.map +1 -0
- package/dist/crypto/fixture.d.ts +13 -0
- package/dist/crypto/fixture.d.ts.map +1 -0
- package/dist/crypto/fixture.js +39 -0
- package/dist/crypto/fixture.js.map +1 -0
- package/dist/crypto/sign.d.ts +7 -0
- package/dist/crypto/sign.d.ts.map +1 -0
- package/dist/crypto/sign.js +15 -0
- package/dist/crypto/sign.js.map +1 -0
- package/dist/crypto/verify.d.ts +21 -0
- package/dist/crypto/verify.d.ts.map +1 -0
- package/dist/crypto/verify.js +58 -0
- package/dist/crypto/verify.js.map +1 -0
- package/dist/doctor/doctor.d.ts +27 -0
- package/dist/doctor/doctor.d.ts.map +1 -0
- package/dist/doctor/doctor.js +149 -0
- package/dist/doctor/doctor.js.map +1 -0
- package/dist/express/_bridge.d.ts +32 -0
- package/dist/express/_bridge.d.ts.map +1 -0
- package/dist/express/_bridge.js +34 -0
- package/dist/express/_bridge.js.map +1 -0
- package/dist/express/credits-route.d.ts +6 -0
- package/dist/express/credits-route.d.ts.map +1 -0
- package/dist/express/credits-route.js +23 -0
- package/dist/express/credits-route.js.map +1 -0
- package/dist/express/index.d.ts +10 -0
- package/dist/express/index.d.ts.map +1 -0
- package/dist/express/index.js +9 -0
- package/dist/express/index.js.map +1 -0
- package/dist/express/settlement-receiver.d.ts +14 -0
- package/dist/express/settlement-receiver.d.ts.map +1 -0
- package/dist/express/settlement-receiver.js +31 -0
- package/dist/express/settlement-receiver.js.map +1 -0
- package/dist/idempotency.d.ts +23 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +19 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/init/plan.d.ts +76 -0
- package/dist/init/plan.d.ts.map +1 -0
- package/dist/init/plan.js +132 -0
- package/dist/init/plan.js.map +1 -0
- package/dist/next/credits-route.d.ts +20 -0
- package/dist/next/credits-route.d.ts.map +1 -0
- package/dist/next/credits-route.js +18 -0
- package/dist/next/credits-route.js.map +1 -0
- package/dist/next/index.d.ts +10 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +10 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/settlement-receiver.d.ts +12 -0
- package/dist/next/settlement-receiver.d.ts.map +1 -0
- package/dist/next/settlement-receiver.js +42 -0
- package/dist/next/settlement-receiver.js.map +1 -0
- package/dist/resolver/fixture.d.ts +10 -0
- package/dist/resolver/fixture.d.ts.map +1 -0
- package/dist/resolver/fixture.js +33 -0
- package/dist/resolver/fixture.js.map +1 -0
- package/dist/resolver/http.d.ts +3 -0
- package/dist/resolver/http.d.ts.map +1 -0
- package/dist/resolver/http.js +23 -0
- package/dist/resolver/http.js.map +1 -0
- package/dist/resolver/types.d.ts +11 -0
- package/dist/resolver/types.d.ts.map +1 -0
- package/dist/resolver/types.js +2 -0
- package/dist/resolver/types.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A process-local default so the type is always satisfiable in dev/tests.
|
|
3
|
+
*
|
|
4
|
+
* NOT DURABLE. It is lost on restart and useless across instances — any real
|
|
5
|
+
* deployment MUST back `claim` with its database (a unique constraint on the event
|
|
6
|
+
* id). Using this in production is a double-payout footgun.
|
|
7
|
+
*/
|
|
8
|
+
export function memoryIdempotencyStore() {
|
|
9
|
+
const seen = new Set();
|
|
10
|
+
return {
|
|
11
|
+
async claim(eventId) {
|
|
12
|
+
if (seen.has(eventId))
|
|
13
|
+
return false;
|
|
14
|
+
seen.add(eventId);
|
|
15
|
+
return true;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAeA;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAO;YACjB,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,OAAO,KAAK,CAAC;YACpC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAClB,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @naulon/sdk — the naulon publisher contract.
|
|
3
|
+
*
|
|
4
|
+
* One source of truth for the credits + settlement wire: the types a publisher
|
|
5
|
+
* produces/consumes, the validators that guard the money-routing boundary, the
|
|
6
|
+
* HMAC sign/verify pair, and the credits resolvers. Runtime deps: `zod` +
|
|
7
|
+
* `node:crypto` only — self-contained, so an external publisher installs one
|
|
8
|
+
* thing. Framework adapters live behind subpath exports (added in a later phase).
|
|
9
|
+
*/
|
|
10
|
+
export * from "./contract/index.ts";
|
|
11
|
+
export * from "./crypto/sign.ts";
|
|
12
|
+
export * from "./crypto/verify.ts";
|
|
13
|
+
export * from "./crypto/fixture.ts";
|
|
14
|
+
export * from "./resolver/types.ts";
|
|
15
|
+
export * from "./resolver/http.ts";
|
|
16
|
+
export * from "./resolver/fixture.ts";
|
|
17
|
+
export * from "./idempotency.ts";
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @naulon/sdk — the naulon publisher contract.
|
|
3
|
+
*
|
|
4
|
+
* One source of truth for the credits + settlement wire: the types a publisher
|
|
5
|
+
* produces/consumes, the validators that guard the money-routing boundary, the
|
|
6
|
+
* HMAC sign/verify pair, and the credits resolvers. Runtime deps: `zod` +
|
|
7
|
+
* `node:crypto` only — self-contained, so an external publisher installs one
|
|
8
|
+
* thing. Framework adapters live behind subpath exports (added in a later phase).
|
|
9
|
+
*/
|
|
10
|
+
export * from "./contract/index.js";
|
|
11
|
+
export * from "./crypto/sign.js";
|
|
12
|
+
export * from "./crypto/verify.js";
|
|
13
|
+
export * from "./crypto/fixture.js";
|
|
14
|
+
export * from "./resolver/types.js";
|
|
15
|
+
export * from "./resolver/http.js";
|
|
16
|
+
export * from "./resolver/fixture.js";
|
|
17
|
+
export * from "./idempotency.js";
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `naulon init` — the pure, testable core.
|
|
3
|
+
*
|
|
4
|
+
* A self-hoster's setup cliff today is: copy a 180-line `.env.example`, hand-edit it,
|
|
5
|
+
* AND hand-author a credits source that satisfies the contract. `init` collapses that
|
|
6
|
+
* into a short prompt flow. This module is the part with no I/O and no prompts: it turns
|
|
7
|
+
* validated answers into the exact bytes of the two files the gate boots from — so it can
|
|
8
|
+
* be unit-tested with fixtures, never touching a TTY or the filesystem (the same split as
|
|
9
|
+
* `runCheck` vs the `check` CLI shell).
|
|
10
|
+
*
|
|
11
|
+
* Every generated `credits.json` entry is run through the real `creditsSchema` before it
|
|
12
|
+
* is returned, so `init` can never emit a file the gate would reject at boot.
|
|
13
|
+
*/
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
/** A clearly-fake but structurally-valid address, so a wallet-less starter still boots. */
|
|
16
|
+
export declare const PLACEHOLDER_WALLET = "0x0000000000000000000000000000000000000000";
|
|
17
|
+
/**
|
|
18
|
+
* The answers the wizard resolves (from flags, prompts, or defaults) before planning.
|
|
19
|
+
* Mirrors the env surface the gate actually reads (`shared/src/config.ts`) — the real
|
|
20
|
+
* public contract a self-hoster pins to.
|
|
21
|
+
*/
|
|
22
|
+
export declare const initAnswersSchema: z.ZodObject<{
|
|
23
|
+
originUrl: z.ZodString;
|
|
24
|
+
priceUsdc: z.ZodNumber;
|
|
25
|
+
citationMultiplier: z.ZodNumber;
|
|
26
|
+
paymentMode: z.ZodEnum<{
|
|
27
|
+
mock: "mock";
|
|
28
|
+
gateway: "gateway";
|
|
29
|
+
}>;
|
|
30
|
+
settlementNetwork: z.ZodDefault<z.ZodEnum<{
|
|
31
|
+
arcTestnet: "arcTestnet";
|
|
32
|
+
baseSepolia: "baseSepolia";
|
|
33
|
+
base: "base";
|
|
34
|
+
}>>;
|
|
35
|
+
tollgatePort: z.ZodNumber;
|
|
36
|
+
articlePrefixes: z.ZodString;
|
|
37
|
+
creditsPath: z.ZodString;
|
|
38
|
+
starterSlug: z.ZodString;
|
|
39
|
+
starterTitle: z.ZodString;
|
|
40
|
+
starterAuthorId: z.ZodString;
|
|
41
|
+
defaultWallet: z.ZodOptional<z.ZodString>;
|
|
42
|
+
}, z.core.$strict>;
|
|
43
|
+
export type InitAnswers = z.infer<typeof initAnswersSchema>;
|
|
44
|
+
/** Schema-level defaults, surfaced to the CLI so prompts can show them in brackets. */
|
|
45
|
+
export declare const INIT_DEFAULTS: {
|
|
46
|
+
readonly originUrl: "http://localhost:3000";
|
|
47
|
+
readonly priceUsdc: 0.001;
|
|
48
|
+
readonly citationMultiplier: 5;
|
|
49
|
+
readonly paymentMode: "mock";
|
|
50
|
+
readonly settlementNetwork: "arcTestnet";
|
|
51
|
+
readonly tollgatePort: 8402;
|
|
52
|
+
readonly articlePrefixes: "essays,articles,posts";
|
|
53
|
+
readonly creditsPath: "./credits.json";
|
|
54
|
+
readonly starterSlug: "welcome";
|
|
55
|
+
readonly starterTitle: "Welcome";
|
|
56
|
+
readonly starterAuthorId: "you";
|
|
57
|
+
};
|
|
58
|
+
export interface PlanFile {
|
|
59
|
+
/** Relative to the target directory. */
|
|
60
|
+
path: string;
|
|
61
|
+
contents: string;
|
|
62
|
+
}
|
|
63
|
+
export interface InitPlan {
|
|
64
|
+
env: PlanFile;
|
|
65
|
+
credits: PlanFile;
|
|
66
|
+
/** Non-fatal things the operator must know (mock mode, placeholder wallet, gateway creds). */
|
|
67
|
+
warnings: string[];
|
|
68
|
+
/** Printed after the files land — the "what now" a good scaffolder always ends with. */
|
|
69
|
+
nextSteps: string[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Turn validated answers into the two files the gate boots from. Pure: no I/O, no prompts.
|
|
73
|
+
* Throws only if a generated credits entry fails the contract (a guard, not an expected path).
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildInitPlan(answers: InitAnswers): InitPlan;
|
|
76
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/init/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,2FAA2F;AAC3F,eAAO,MAAM,kBAAkB,+CAA+C,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;kBAiBnB,CAAC;AAEZ,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,uFAAuF;AACvF,eAAO,MAAM,aAAa;;;;;;;;;;;;CAYhB,CAAC;AAEX,MAAM,WAAW,QAAQ;IACvB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,QAAQ,CAAC;IACd,OAAO,EAAE,QAAQ,CAAC;IAClB,8FAA8F;IAC9F,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wFAAwF;IACxF,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AA0DD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,QAAQ,CA8B5D"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `naulon init` — the pure, testable core.
|
|
3
|
+
*
|
|
4
|
+
* A self-hoster's setup cliff today is: copy a 180-line `.env.example`, hand-edit it,
|
|
5
|
+
* AND hand-author a credits source that satisfies the contract. `init` collapses that
|
|
6
|
+
* into a short prompt flow. This module is the part with no I/O and no prompts: it turns
|
|
7
|
+
* validated answers into the exact bytes of the two files the gate boots from — so it can
|
|
8
|
+
* be unit-tested with fixtures, never touching a TTY or the filesystem (the same split as
|
|
9
|
+
* `runCheck` vs the `check` CLI shell).
|
|
10
|
+
*
|
|
11
|
+
* Every generated `credits.json` entry is run through the real `creditsSchema` before it
|
|
12
|
+
* is returned, so `init` can never emit a file the gate would reject at boot.
|
|
13
|
+
*/
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { walletSchema, walletAddress } from "../contract/wallet.js";
|
|
16
|
+
import { parseCredits } from "../contract/credits.js";
|
|
17
|
+
/** A clearly-fake but structurally-valid address, so a wallet-less starter still boots. */
|
|
18
|
+
export const PLACEHOLDER_WALLET = "0x0000000000000000000000000000000000000000";
|
|
19
|
+
/**
|
|
20
|
+
* The answers the wizard resolves (from flags, prompts, or defaults) before planning.
|
|
21
|
+
* Mirrors the env surface the gate actually reads (`shared/src/config.ts`) — the real
|
|
22
|
+
* public contract a self-hoster pins to.
|
|
23
|
+
*/
|
|
24
|
+
export const initAnswersSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
originUrl: z.string().url(),
|
|
27
|
+
priceUsdc: z.number().positive(),
|
|
28
|
+
citationMultiplier: z.number().positive(),
|
|
29
|
+
paymentMode: z.enum(["mock", "gateway"]),
|
|
30
|
+
settlementNetwork: z.enum(["arcTestnet", "baseSepolia", "base"]).default("arcTestnet"),
|
|
31
|
+
tollgatePort: z.number().int().positive(),
|
|
32
|
+
articlePrefixes: z.string().min(1),
|
|
33
|
+
/** Where the starter credits map is written; also becomes CREDITS_FIXTURES. */
|
|
34
|
+
creditsPath: z.string().min(1),
|
|
35
|
+
/** The starter article + its author. Wallet-less → a placeholder + a loud warning. */
|
|
36
|
+
starterSlug: z.string().min(1),
|
|
37
|
+
starterTitle: z.string().min(1),
|
|
38
|
+
starterAuthorId: z.string().min(1),
|
|
39
|
+
defaultWallet: walletSchema.optional(),
|
|
40
|
+
})
|
|
41
|
+
.strict();
|
|
42
|
+
/** Schema-level defaults, surfaced to the CLI so prompts can show them in brackets. */
|
|
43
|
+
export const INIT_DEFAULTS = {
|
|
44
|
+
originUrl: "http://localhost:3000",
|
|
45
|
+
priceUsdc: 0.001,
|
|
46
|
+
citationMultiplier: 5,
|
|
47
|
+
paymentMode: "mock",
|
|
48
|
+
settlementNetwork: "arcTestnet",
|
|
49
|
+
tollgatePort: 8402,
|
|
50
|
+
articlePrefixes: "essays,articles,posts",
|
|
51
|
+
creditsPath: "./credits.json",
|
|
52
|
+
starterSlug: "welcome",
|
|
53
|
+
starterTitle: "Welcome",
|
|
54
|
+
starterAuthorId: "you",
|
|
55
|
+
};
|
|
56
|
+
function renderEnv(a) {
|
|
57
|
+
const gateway = a.paymentMode === "gateway";
|
|
58
|
+
const lines = [
|
|
59
|
+
"# naulon gate — generated by `naulon init`. Edit freely; re-run init to regenerate.",
|
|
60
|
+
"# The full annotated reference is .env.example.",
|
|
61
|
+
"",
|
|
62
|
+
"# ── Core toll ──",
|
|
63
|
+
`PAYMENT_MODE=${a.paymentMode}`,
|
|
64
|
+
`# The site the gate sits in front of. A machine request to a gateable path gets tolled;`,
|
|
65
|
+
`# a human request is proxied straight through, free.`,
|
|
66
|
+
`ORIGIN_URL=${a.originUrl}`,
|
|
67
|
+
`TOLLGATE_PORT=${a.tollgatePort}`,
|
|
68
|
+
`# Comma-separated path prefixes that count as gateable articles.`,
|
|
69
|
+
`ARTICLE_PATH_PREFIXES=${a.articlePrefixes}`,
|
|
70
|
+
"",
|
|
71
|
+
"# ── Price ──",
|
|
72
|
+
`# A single read costs this much USDC. A citation costs CITATION_MULTIPLIER× a read.`,
|
|
73
|
+
`DEFAULT_PRICE_USDC=${a.priceUsdc}`,
|
|
74
|
+
`CITATION_MULTIPLIER=${a.citationMultiplier}`,
|
|
75
|
+
"",
|
|
76
|
+
"# ── Credits (who gets paid) ──",
|
|
77
|
+
`# The gate reads this local JSON file to map an article slug → its author wallet(s).`,
|
|
78
|
+
`# Swap to a live API instead by setting CREDITS_API_URL.`,
|
|
79
|
+
`CREDITS_FIXTURES=${a.creditsPath}`,
|
|
80
|
+
"",
|
|
81
|
+
"# ── Settlement ──",
|
|
82
|
+
];
|
|
83
|
+
if (gateway) {
|
|
84
|
+
lines.push(`SETTLEMENT_NETWORK=${a.settlementNetwork}`, `# gateway mode settles real USDC. Fill these before it can settle — see .env.example:`, `# RELAYER_PRIVATE_KEY= (Arc memo self-relay; pays gas, never touches transferred funds)`, `# CIRCLE_API_KEY= (Circle Gateway; testnet facilitator needs no key)`, `# CREDITS_SETTLEMENT_SECRET is optional — unset keeps the earnings emit dark (still tolls).`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
lines.push(`# mock mode signs payments offline — the whole loop runs with no wallet or API keys.`, `# Set PAYMENT_MODE=gateway (and re-run init) when you're ready to settle real USDC.`);
|
|
88
|
+
}
|
|
89
|
+
return lines.join("\n") + "\n";
|
|
90
|
+
}
|
|
91
|
+
function buildStarterCredits(a) {
|
|
92
|
+
const usedPlaceholder = a.defaultWallet === undefined;
|
|
93
|
+
const entry = {
|
|
94
|
+
slug: a.starterSlug,
|
|
95
|
+
title: a.starterTitle,
|
|
96
|
+
contributors: [{ authorId: a.starterAuthorId, wallet: walletAddress(a.defaultWallet ?? PLACEHOLDER_WALLET) }],
|
|
97
|
+
};
|
|
98
|
+
// Never emit a file the gate would reject — validate through the real contract.
|
|
99
|
+
parseCredits(entry, `starter credits for "${a.starterSlug}"`);
|
|
100
|
+
return { map: { [a.starterSlug]: entry }, usedPlaceholder };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Turn validated answers into the two files the gate boots from. Pure: no I/O, no prompts.
|
|
104
|
+
* Throws only if a generated credits entry fails the contract (a guard, not an expected path).
|
|
105
|
+
*/
|
|
106
|
+
export function buildInitPlan(answers) {
|
|
107
|
+
const a = initAnswersSchema.parse(answers);
|
|
108
|
+
const { map, usedPlaceholder } = buildStarterCredits(a);
|
|
109
|
+
const warnings = [];
|
|
110
|
+
if (a.paymentMode === "mock") {
|
|
111
|
+
warnings.push("PAYMENT_MODE=mock — payments are signed offline; no real USDC moves. Fine for local dev.");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
warnings.push(`PAYMENT_MODE=gateway on ${a.settlementNetwork} — set RELAYER_PRIVATE_KEY / CIRCLE_API_KEY in .env before a real settle.`);
|
|
115
|
+
}
|
|
116
|
+
if (usedPlaceholder) {
|
|
117
|
+
warnings.push(`${a.creditsPath} uses a placeholder wallet (0x00…00). Replace it with a real address, or agents pay into the void.`);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
env: { path: ".env", contents: renderEnv(a) },
|
|
121
|
+
credits: { path: a.creditsPath, contents: JSON.stringify(map, null, 2) + "\n" },
|
|
122
|
+
warnings,
|
|
123
|
+
nextSteps: [
|
|
124
|
+
"Review .env and " + a.creditsPath + " — add your real article slugs + author wallets.",
|
|
125
|
+
`Draft entries from your live site: npx naulon crawl ${a.originUrl} --prefixes ${a.articlePrefixes}`,
|
|
126
|
+
"Start the gate: make dev",
|
|
127
|
+
`Watch the toll: curl -A python-requests localhost:${a.tollgatePort}/${a.articlePrefixes.split(",")[0]}/${a.starterSlug}`,
|
|
128
|
+
"Health-check anytime: npx naulon doctor",
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/init/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The credits-route adapter — deliberately minimal (~20 lines of HTTP framing
|
|
3
|
+
* around the `CreditsResolver` seam). It earns its place by pinning the one thing
|
|
4
|
+
* publishers get wrong: `404 = free read` (the deliberate don't-gate signal),
|
|
5
|
+
* distinct from a `200` with credits. The docs also show the hand-rolled
|
|
6
|
+
* equivalent for publishers who'd rather not take the dependency.
|
|
7
|
+
*
|
|
8
|
+
* Returns a Next App Router GET handler shape — `(req, ctx)` where `ctx.params`
|
|
9
|
+
* is the async params object Next 15+ passes. Wire it as:
|
|
10
|
+
* export const GET = createCreditsRoute(resolver);
|
|
11
|
+
*/
|
|
12
|
+
import type { CreditsResolver } from "../resolver/types.ts";
|
|
13
|
+
export declare function createCreditsRoute(resolver: CreditsResolver, opts?: {
|
|
14
|
+
token?: string;
|
|
15
|
+
}): (req: Request, ctx: {
|
|
16
|
+
params: Promise<{
|
|
17
|
+
slug: string;
|
|
18
|
+
}>;
|
|
19
|
+
}) => Promise<Response>;
|
|
20
|
+
//# sourceMappingURL=credits-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credits-route.d.ts","sourceRoot":"","sources":["../../src/next/credits-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,eAAe,EACzB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACxB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAgBjF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function createCreditsRoute(resolver, opts) {
|
|
2
|
+
return async (req, ctx) => {
|
|
3
|
+
// Optional bearer gate — set a token when the endpoint is public-internet
|
|
4
|
+
// reachable (the cloud-tenant case; the fleet sends `Authorization: Bearer`).
|
|
5
|
+
if (opts?.token) {
|
|
6
|
+
if (req.headers.get("authorization") !== `Bearer ${opts.token}`) {
|
|
7
|
+
return Response.json({ error: "unauthorized" }, { status: 401 });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const { slug } = await ctx.params;
|
|
11
|
+
const credits = await resolver.resolve(slug);
|
|
12
|
+
if (credits === undefined) {
|
|
13
|
+
return Response.json({ error: "not_found" }, { status: 404 }); // 404 = free read
|
|
14
|
+
}
|
|
15
|
+
return Response.json(credits);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=credits-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credits-route.js","sourceRoot":"","sources":["../../src/next/credits-route.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,kBAAkB,CAChC,QAAyB,EACzB,IAAyB;IAEzB,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,0EAA0E;QAC1E,8EAA8E;QAC9E,IAAI,IAAI,EAAE,KAAK,EAAE,CAAC;YAChB,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;gBAChE,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;QACD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;QAClC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,kBAAkB;QACnF,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @naulon/sdk/next — drop-in Next.js App Router adapters over the contract.
|
|
3
|
+
*
|
|
4
|
+
* Both are framework-agnostic web-standard Request→Response handlers (they import
|
|
5
|
+
* nothing from `next`); the subpath name signals the intended consumer. `next` is
|
|
6
|
+
* an OPTIONAL peer dependency.
|
|
7
|
+
*/
|
|
8
|
+
export * from "./settlement-receiver.ts";
|
|
9
|
+
export * from "./credits-route.ts";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @naulon/sdk/next — drop-in Next.js App Router adapters over the contract.
|
|
3
|
+
*
|
|
4
|
+
* Both are framework-agnostic web-standard Request→Response handlers (they import
|
|
5
|
+
* nothing from `next`); the subpath name signals the intended consumer. `next` is
|
|
6
|
+
* an OPTIONAL peer dependency.
|
|
7
|
+
*/
|
|
8
|
+
export * from "./settlement-receiver.js";
|
|
9
|
+
export * from "./credits-route.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,0BAA0B,CAAC;AACzC,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SettlementBody } from "../contract/settlement.ts";
|
|
2
|
+
import type { IdempotencyStore } from "../idempotency.ts";
|
|
3
|
+
export declare function createSettlementReceiver(opts: {
|
|
4
|
+
/** 1..n shared HMAC secrets; >1 only during a rotation overlap. */
|
|
5
|
+
secrets: string[];
|
|
6
|
+
/** Persist the payout here. Runs at most once per eventId (idempotency-gated). */
|
|
7
|
+
onEvent: (event: SettlementBody) => Promise<void>;
|
|
8
|
+
/** REQUIRED. memoryIdempotencyStore() satisfies the type for dev, but is NOT
|
|
9
|
+
* durable — back this with a DB unique-constraint on eventId in production. */
|
|
10
|
+
idempotency: IdempotencyStore;
|
|
11
|
+
}): (req: Request) => Promise<Response>;
|
|
12
|
+
//# sourceMappingURL=settlement-receiver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settlement-receiver.d.ts","sourceRoot":"","sources":["../../src/next/settlement-receiver.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kFAAkF;IAClF,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD;oFACgF;IAChF,WAAW,EAAE,gBAAgB,CAAC;CAC/B,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAyBtC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settlement receiver adapter — the half a publisher most wants handed to
|
|
3
|
+
* them, because HMAC verification + replay defense are easy to get subtly wrong.
|
|
4
|
+
*
|
|
5
|
+
* It wraps `verifySettlement` (authenticity) with the mandatory exactly-once
|
|
6
|
+
* persistence (`IdempotencyStore`): the 300s skew window means an authentic POST
|
|
7
|
+
* is replayable for five minutes, so a money receiver with no dedupe is a
|
|
8
|
+
* double-payout defect. There is deliberately NO dry-run mode — a money receiver
|
|
9
|
+
* gets no public "pretend" path; offline conformance uses `makeSignedSettlementFixture`.
|
|
10
|
+
*
|
|
11
|
+
* Returns a plain `(req: Request) => Promise<Response>` handler — drop it straight
|
|
12
|
+
* into a Next.js App Router route (`export const POST = createSettlementReceiver(...)`),
|
|
13
|
+
* or any framework that speaks web-standard Request/Response.
|
|
14
|
+
*/
|
|
15
|
+
import { verifySettlement } from "../crypto/verify.js";
|
|
16
|
+
export function createSettlementReceiver(opts) {
|
|
17
|
+
if (opts.secrets.length === 0) {
|
|
18
|
+
throw new Error("createSettlementReceiver: at least one secret is required");
|
|
19
|
+
}
|
|
20
|
+
return async (req) => {
|
|
21
|
+
// The signature is over the EXACT bytes — read the raw text, never re-serialize.
|
|
22
|
+
const rawBody = await req.text();
|
|
23
|
+
const result = verifySettlement({
|
|
24
|
+
rawBody,
|
|
25
|
+
timestampHeader: req.headers.get("x-naulon-timestamp"),
|
|
26
|
+
signatureHeader: req.headers.get("x-naulon-signature"),
|
|
27
|
+
secrets: opts.secrets,
|
|
28
|
+
});
|
|
29
|
+
if (!result.ok) {
|
|
30
|
+
// 401 = transient (the gate retries); 400 = permanent (it gives up).
|
|
31
|
+
return Response.json({ error: result.reason }, { status: result.status });
|
|
32
|
+
}
|
|
33
|
+
// Exactly-once: a replay inside the skew window is acknowledged but NOT re-paid.
|
|
34
|
+
const fresh = await opts.idempotency.claim(result.event.eventId);
|
|
35
|
+
if (!fresh) {
|
|
36
|
+
return Response.json({ ok: true, deduped: true });
|
|
37
|
+
}
|
|
38
|
+
await opts.onEvent(result.event);
|
|
39
|
+
return Response.json({ ok: true, deduped: false });
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=settlement-receiver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settlement-receiver.js","sourceRoot":"","sources":["../../src/next/settlement-receiver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAIvD,MAAM,UAAU,wBAAwB,CAAC,IAQxC;IACC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,iFAAiF;QACjF,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,gBAAgB,CAAC;YAC9B,OAAO;YACP,eAAe,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;YACtD,eAAe,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;YACtD,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,qEAAqE;YACrE,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5E,CAAC;QACD,iFAAiF;QACjF,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjE,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjC,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACrD,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ArticleCredits } from "../contract/credits.ts";
|
|
2
|
+
import type { CreditsResolver } from "./types.ts";
|
|
3
|
+
export declare function fixtureResolver(fixtures: Record<string, ArticleCredits>): CreditsResolver;
|
|
4
|
+
/**
|
|
5
|
+
* Load a fixture file lazily; missing file => empty map (gate stays open). Every
|
|
6
|
+
* entry is validated at load, so a malformed fixture fails fast at boot rather
|
|
7
|
+
* than mid-payment.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fixtureResolverFromFile(path: string): Promise<CreditsResolver>;
|
|
10
|
+
//# sourceMappingURL=fixture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../src/resolver/fixture.ts"],"names":[],"mappings":"AAMA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,eAAe,CAMzF;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAYpF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static credits resolvers — a JSON map (great for local dev + demos) or a file.
|
|
3
|
+
* Every entry is validated at load, so a malformed fixture fails fast rather than
|
|
4
|
+
* mid-payment.
|
|
5
|
+
*/
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { parseCredits } from "../contract/credits.js";
|
|
8
|
+
export function fixtureResolver(fixtures) {
|
|
9
|
+
return {
|
|
10
|
+
async resolve(slug) {
|
|
11
|
+
return fixtures[slug];
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Load a fixture file lazily; missing file => empty map (gate stays open). Every
|
|
17
|
+
* entry is validated at load, so a malformed fixture fails fast at boot rather
|
|
18
|
+
* than mid-payment.
|
|
19
|
+
*/
|
|
20
|
+
export async function fixtureResolverFromFile(path) {
|
|
21
|
+
let fixtures = {};
|
|
22
|
+
try {
|
|
23
|
+
const raw = JSON.parse(await readFile(path, "utf8"));
|
|
24
|
+
fixtures = Object.fromEntries(Object.entries(raw).map(([slug, credits]) => [slug, parseCredits(credits, `fixture "${slug}"`)]));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
if (err.code !== "ENOENT")
|
|
28
|
+
throw err;
|
|
29
|
+
console.warn(`[sdk] no credits fixtures at ${path} — articles will pass through ungated.`);
|
|
30
|
+
}
|
|
31
|
+
return fixtureResolver(fixtures);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=fixture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixture.js","sourceRoot":"","sources":["../../src/resolver/fixture.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAuB,MAAM,wBAAwB,CAAC;AAG3E,MAAM,UAAU,eAAe,CAAC,QAAwC;IACtE,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,IAAY;IACxD,IAAI,QAAQ,GAAmC,EAAE,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAA4B,CAAC;QAChF,QAAQ,GAAG,MAAM,CAAC,WAAW,CAC3B,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,YAAY,CAAC,OAAO,EAAE,YAAY,IAAI,GAAG,CAAC,CAAC,CAAC,CACjG,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,gCAAgC,IAAI,wCAAwC,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,eAAe,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/resolver/http.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,eAAe,CAa5E"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP credits resolver — fetch `${apiUrl}/credits/:slug` from your CMS/API.
|
|
3
|
+
* This is the canonical implementation the multi-tenant control plane previously
|
|
4
|
+
* duplicated verbatim; it now lives here once.
|
|
5
|
+
*/
|
|
6
|
+
import { parseCredits } from "../contract/credits.js";
|
|
7
|
+
export function httpResolver(apiUrl, token) {
|
|
8
|
+
const base = apiUrl.replace(/\/$/, "");
|
|
9
|
+
return {
|
|
10
|
+
async resolve(slug) {
|
|
11
|
+
const res = await fetch(`${base}/credits/${encodeURIComponent(slug)}`, {
|
|
12
|
+
headers: token ? { authorization: `Bearer ${token}` } : {},
|
|
13
|
+
});
|
|
14
|
+
if (res.status === 404)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (!res.ok)
|
|
17
|
+
throw new Error(`credits lookup failed: ${res.status} ${res.statusText}`);
|
|
18
|
+
// Validate the upstream response before any wallet here becomes a payTo.
|
|
19
|
+
return parseCredits(await res.json(), `credits for "${slug}" from ${base}`);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/resolver/http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAGtD,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,KAAc;IACzD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACvC,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,YAAY,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE;gBACrE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE;aAC3D,CAAC,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAAE,OAAO,SAAS,CAAC;YACzC,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;YACvF,yEAAyE;YACzE,OAAO,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,gBAAgB,IAAI,UAAU,IAAI,EAAE,CAAC,CAAC;QAC9E,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ArticleCredits } from "../contract/credits.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves an article slug to its credits graph. This is the seam that makes the
|
|
4
|
+
* toll publisher-agnostic: any site implements one of these (HTTP API, static
|
|
5
|
+
* file, database) and the gate works unchanged. `undefined` = no credits for this
|
|
6
|
+
* slug, which the gate reads as the deliberate "free read" signal.
|
|
7
|
+
*/
|
|
8
|
+
export interface CreditsResolver {
|
|
9
|
+
resolve(slug: string): Promise<ArticleCredits | undefined>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/resolver/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;CAC5D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/resolver/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@naulon/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/naulonapp/naulon.git",
|
|
7
|
+
"directory": "packages/sdk"
|
|
8
|
+
},
|
|
9
|
+
"description": "The naulon publisher contract: credits + settlement wire types, validators, HMAC sign/verify, and credits resolvers. One source of truth for the money-routing trust boundary.",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./next": {
|
|
20
|
+
"types": "./dist/next/index.d.ts",
|
|
21
|
+
"import": "./dist/next/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./express": {
|
|
24
|
+
"types": "./dist/express/index.d.ts",
|
|
25
|
+
"import": "./dist/express/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./cli": {
|
|
28
|
+
"types": "./dist/cli/check.d.ts",
|
|
29
|
+
"import": "./dist/cli/check.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"bin": {
|
|
33
|
+
"naulon": "./dist/cli/naulon.js",
|
|
34
|
+
"naulon-kit": "./dist/cli/naulon.js"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -p tsconfig.json",
|
|
41
|
+
"prepublishOnly": "npm run build",
|
|
42
|
+
"test": "node --import tsx --test \"src/**/*.test.ts\""
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"fast-xml-parser": "^4.5.6",
|
|
46
|
+
"zod": "^4.4.3"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"express": ">=4",
|
|
50
|
+
"next": ">=15"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"next": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"express": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=22"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/express": "^5.0.0",
|
|
65
|
+
"express": "^5.2.1"
|
|
66
|
+
}
|
|
67
|
+
}
|