tally-sdk 0.1.0 → 0.2.1

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 (46) hide show
  1. package/.github/workflows/publish.yml +38 -0
  2. package/dist/src/hook.d.ts +6 -0
  3. package/dist/src/hook.d.ts.map +1 -0
  4. package/dist/src/hook.js +37 -0
  5. package/dist/src/hook.js.map +1 -0
  6. package/dist/src/index.d.ts +6 -0
  7. package/dist/src/index.d.ts.map +1 -0
  8. package/dist/src/index.js +22 -0
  9. package/dist/src/index.js.map +1 -0
  10. package/dist/src/middleware/core.d.ts +15 -0
  11. package/dist/src/middleware/core.d.ts.map +1 -0
  12. package/dist/src/middleware/core.js +20 -0
  13. package/dist/src/middleware/core.js.map +1 -0
  14. package/dist/src/middleware/express.d.ts +4 -0
  15. package/dist/src/middleware/express.d.ts.map +1 -0
  16. package/dist/src/middleware/express.js +26 -0
  17. package/dist/src/middleware/express.js.map +1 -0
  18. package/dist/src/store.d.ts +14 -0
  19. package/dist/src/store.d.ts.map +1 -0
  20. package/dist/src/store.js +84 -0
  21. package/dist/src/store.js.map +1 -0
  22. package/dist/src/types.d.ts +36 -0
  23. package/dist/src/types.d.ts.map +1 -0
  24. package/dist/src/types.js +2 -0
  25. package/dist/src/types.js.map +1 -0
  26. package/package.json +19 -5
  27. package/src/index.ts +2 -0
  28. package/src/middleware/core.ts +38 -0
  29. package/src/middleware/express.ts +32 -0
  30. package/.opencode/commands/opsx-apply.md +0 -152
  31. package/.opencode/commands/opsx-archive.md +0 -157
  32. package/.opencode/commands/opsx-explore.md +0 -169
  33. package/.opencode/commands/opsx-propose.md +0 -104
  34. package/.opencode/commands/opsx-sync.md +0 -140
  35. package/.opencode/skills/openspec-apply-change/SKILL.md +0 -159
  36. package/.opencode/skills/openspec-archive-change/SKILL.md +0 -117
  37. package/.opencode/skills/openspec-explore/SKILL.md +0 -287
  38. package/.opencode/skills/openspec-propose/SKILL.md +0 -111
  39. package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -147
  40. package/docs/design-hook-store.md +0 -122
  41. package/docs/plan-hook-store.md +0 -752
  42. package/test/hook.test.ts +0 -87
  43. package/test/index.test.ts +0 -36
  44. package/test/integration.test.ts +0 -44
  45. package/test/store.test.ts +0 -88
  46. package/test/types.test.ts +0 -30
package/test/hook.test.ts DELETED
@@ -1,87 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { createTallyFetch } from "../src/hook.js";
3
- import type { Store, PaymentPayload } from "../src/types.js";
4
-
5
- function createMockStore(): Store {
6
- const inserts: PaymentPayload[] = [];
7
- return {
8
- insert: vi.fn(async (p: PaymentPayload) => { inserts.push(p); }),
9
- list: vi.fn(async () => []),
10
- get: vi.fn(async () => null),
11
- close: vi.fn(async () => {}),
12
- };
13
- }
14
-
15
- describe("createTallyFetch", () => {
16
- let store: Store;
17
-
18
- beforeEach(() => {
19
- store = createMockStore();
20
- });
21
-
22
- it("passes through non-payment responses", async () => {
23
- const mockFetch = vi.fn(async () => new Response("ok", { status: 200 }));
24
- const tallyFetch = createTallyFetch(mockFetch, store);
25
-
26
- const res = await tallyFetch("https://api.example.com/data");
27
- expect(res.status).toBe(200);
28
- expect(store.insert).not.toHaveBeenCalled();
29
- });
30
-
31
- it("extracts PaymentPayload from response with x-facilitator header", async () => {
32
- const paymentPayload = {
33
- requestId: "req-1",
34
- fromAddress: "0xsender",
35
- toAddress: "0xreceiver",
36
- amount: "1.50",
37
- asset: "USDC",
38
- chainId: "eip155:8453",
39
- txHash: "0xtx",
40
- timestamp: Date.now(),
41
- };
42
-
43
- const mockFetch = vi.fn(async () =>
44
- Response.json(paymentPayload, {
45
- status: 200,
46
- headers: { "x-facilitator": "dexter", "x-request-id": "req-1" },
47
- })
48
- );
49
-
50
- const tallyFetch = createTallyFetch(mockFetch, store, { facilitator: "dexter" });
51
- await tallyFetch("https://api.example.com/pay");
52
-
53
- expect(store.insert).toHaveBeenCalledTimes(1);
54
- const inserted = (store.insert as ReturnType<typeof vi.fn>).mock.calls[0][0] as PaymentPayload;
55
- expect(inserted.facilitator).toBe("dexter");
56
- expect(inserted.amount).toBe("1.50");
57
- expect(inserted.chainId).toBe("eip155:8453");
58
- });
59
-
60
- it("generates an id when none is in the response", async () => {
61
- const mockFetch = vi.fn(async () =>
62
- Response.json({ amount: "5.00" }, {
63
- status: 200,
64
- headers: { "x-facilitator": "dexter", "x-request-id": "req-2" },
65
- })
66
- );
67
-
68
- const tallyFetch = createTallyFetch(mockFetch, store, { facilitator: "dexter" });
69
- await tallyFetch("https://api.example.com/pay");
70
-
71
- expect(store.insert).toHaveBeenCalledTimes(1);
72
- const inserted = (store.insert as ReturnType<typeof vi.fn>).mock.calls[0][0] as PaymentPayload;
73
- expect(inserted.id).toBeDefined();
74
- expect(inserted.id.length).toBeGreaterThan(0);
75
- });
76
-
77
- it("does not store on error responses", async () => {
78
- const mockFetch = vi.fn(async () =>
79
- Response.json({ error: "not found" }, { status: 404 })
80
- );
81
-
82
- const tallyFetch = createTallyFetch(mockFetch, store);
83
- const res = await tallyFetch("https://api.example.com/nope");
84
- expect(res.status).toBe(404);
85
- expect(store.insert).not.toHaveBeenCalled();
86
- });
87
- });
@@ -1,36 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { createTally } from "../src/index.js";
3
-
4
- describe("createTally", () => {
5
- it("returns a TallyClient with a store", () => {
6
- const tally = createTally({ facilitator: "dexter" });
7
- expect(tally.fetch).toBeDefined();
8
- expect(tally.wrap).toBeDefined();
9
- expect(tally.unwrap).toBeDefined();
10
- expect(tally.store).toBeDefined();
11
- expect(tally.close).toBeDefined();
12
- tally.close();
13
- });
14
-
15
- it("accepts a custom store", () => {
16
- const customStore = {
17
- insert: async () => {},
18
- list: async () => [],
19
- get: async () => null,
20
- close: async () => {},
21
- };
22
- const tally = createTally({ facilitator: "dexter", store: customStore });
23
- expect(tally.store).toBe(customStore);
24
- tally.close();
25
- });
26
-
27
- it("wrap and unwrap monkey-patches and restores globalThis.fetch", () => {
28
- const originalFetch = globalThis.fetch;
29
- const tally = createTally({ facilitator: "dexter" });
30
- tally.wrap();
31
- expect(globalThis.fetch).not.toBe(originalFetch);
32
- tally.unwrap();
33
- expect(globalThis.fetch).toBe(originalFetch);
34
- tally.close();
35
- });
36
- });
@@ -1,44 +0,0 @@
1
- import { describe, it, expect, afterEach } from "vitest";
2
- import { createTally } from "../src/index.js";
3
- import { SqliteStore } from "../src/store.js";
4
- import type { Store } from "../src/types.js";
5
-
6
- describe("integration", () => {
7
- let store: Store;
8
-
9
- afterEach(async () => {
10
- await store.close();
11
- });
12
-
13
- it("captures a payment end-to-end", async () => {
14
- const tally = createTally({ facilitator: "dexter" });
15
- store = tally.store;
16
-
17
- // Verify store is ready
18
- const list = await tally.store.list();
19
- expect(list).toEqual([]);
20
-
21
- tally.close();
22
- });
23
-
24
- it("manual pipeline: hook captures, store persists", async () => {
25
- const customStore = new SqliteStore(":memory:");
26
- store = customStore;
27
-
28
- const fetchSpy = async () =>
29
- Response.json(
30
- { requestId: "req-int", amount: "3.00", asset: "USDC", chainId: "eip155:1", txHash: "0xint" },
31
- { status: 200, headers: { "x-facilitator": "dexter", "x-request-id": "req-int" } }
32
- );
33
-
34
- const { createTallyFetch } = await import("../src/hook.js");
35
- const tallyFetch = createTallyFetch(fetchSpy, customStore, { facilitator: "dexter" });
36
-
37
- await tallyFetch("https://api.example.com/pay");
38
-
39
- const all = await customStore.list();
40
- expect(all).toHaveLength(1);
41
- expect(all[0].amount).toBe("3.00");
42
- expect(all[0].facilitator).toBe("dexter");
43
- });
44
- });
@@ -1,88 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { SqliteStore } from "../src/store.js";
3
- import type { PaymentPayload } from "../src/types.js";
4
-
5
- function makePayload(overrides?: Partial<PaymentPayload>): PaymentPayload {
6
- return {
7
- id: overrides?.id ?? "test-1",
8
- facilitator: overrides?.facilitator ?? "dexter",
9
- requestId: "req-1",
10
- fromAddress: "0xsender",
11
- toAddress: "0xreceiver",
12
- amount: "10.00",
13
- asset: "USDC",
14
- chainId: "eip155:1",
15
- txHash: "0xtx",
16
- timestamp: Date.now(),
17
- ...overrides,
18
- };
19
- }
20
-
21
- describe("SqliteStore", () => {
22
- let store: SqliteStore;
23
-
24
- beforeEach(() => {
25
- store = new SqliteStore(":memory:");
26
- });
27
-
28
- afterEach(() => {
29
- store.close();
30
- });
31
-
32
- it("inserts and retrieves a payment", async () => {
33
- const payload = makePayload();
34
- await store.insert(payload);
35
- const result = await store.get(payload.id);
36
- expect(result).not.toBeNull();
37
- expect(result!.id).toBe(payload.id);
38
- expect(result!.amount).toBe("10.00");
39
- });
40
-
41
- it("returns null for unknown id", async () => {
42
- const result = await store.get("nonexistent");
43
- expect(result).toBeNull();
44
- });
45
-
46
- it("lists payments ordered by created_at desc", async () => {
47
- const p1 = makePayload({ id: "1", timestamp: 1000 });
48
- const p2 = makePayload({ id: "2", timestamp: 2000 });
49
- await store.insert(p1);
50
- await store.insert(p2);
51
- const all = await store.list();
52
- expect(all).toHaveLength(2);
53
- expect(all[0].id).toBe("2"); // newest first
54
- });
55
-
56
- it("filters by facilitator", async () => {
57
- await store.insert(makePayload({ id: "1", facilitator: "dexter" }));
58
- await store.insert(makePayload({ id: "2", facilitator: "coinbase-cdp" }));
59
- const filtered = await store.list({ facilitator: "dexter" });
60
- expect(filtered).toHaveLength(1);
61
- expect(filtered[0].facilitator).toBe("dexter");
62
- });
63
-
64
- it("paginates with limit and offset", async () => {
65
- for (let i = 0; i < 10; i++) {
66
- await store.insert(makePayload({ id: `p${i}`, timestamp: i }));
67
- }
68
- const page = await store.list({ limit: 3, offset: 0 });
69
- expect(page).toHaveLength(3);
70
- });
71
-
72
- it("handles metadata serialization", async () => {
73
- const payload = makePayload({ metadata: { foo: "bar", num: 42 } });
74
- await store.insert(payload);
75
- const result = await store.get(payload.id);
76
- expect(result!.metadata).toEqual({ foo: "bar", num: 42 });
77
- });
78
-
79
- it("ignores duplicate inserts (idempotency)", async () => {
80
- const payload = makePayload({ id: "dup-1" });
81
- await store.insert(payload);
82
- await store.insert(payload);
83
- const result = await store.get("dup-1");
84
- expect(result).not.toBeNull();
85
- const all = await store.list();
86
- expect(all).toHaveLength(1);
87
- });
88
- });
@@ -1,30 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import type { PaymentPayload, Store, TallyConfig, TallyClient } from "../src/types.js";
3
-
4
- describe("types", () => {
5
- it("PaymentPayload can be constructed", () => {
6
- const payload: PaymentPayload = {
7
- id: "abc-123",
8
- facilitator: "dexter",
9
- requestId: "req-1",
10
- fromAddress: "0xsender",
11
- toAddress: "0xreceiver",
12
- amount: "10.00",
13
- asset: "USDC",
14
- chainId: "eip155:1",
15
- txHash: "0xtx",
16
- timestamp: Date.now(),
17
- };
18
- expect(payload.amount).toBe("10.00");
19
- });
20
-
21
- it("Store interface is structurally typed", () => {
22
- const store: Store = {
23
- insert: async () => {},
24
- list: async () => [],
25
- get: async () => null,
26
- close: async () => {},
27
- };
28
- expect(store).toBeDefined();
29
- });
30
- });