meridian-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 (81) hide show
  1. package/README.md +119 -0
  2. package/dist/auth/token.d.ts +27 -0
  3. package/dist/auth/token.d.ts.map +1 -0
  4. package/dist/auth/token.js +75 -0
  5. package/dist/auth/token.js.map +1 -0
  6. package/dist/client.d.ts +60 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +166 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/codec.d.ts +29 -0
  11. package/dist/codec.d.ts.map +1 -0
  12. package/dist/codec.js +72 -0
  13. package/dist/codec.js.map +1 -0
  14. package/dist/crdt/gcounter.d.ts +36 -0
  15. package/dist/crdt/gcounter.d.ts.map +1 -0
  16. package/dist/crdt/gcounter.js +76 -0
  17. package/dist/crdt/gcounter.js.map +1 -0
  18. package/dist/crdt/lwwregister.d.ts +42 -0
  19. package/dist/crdt/lwwregister.d.ts.map +1 -0
  20. package/dist/crdt/lwwregister.js +93 -0
  21. package/dist/crdt/lwwregister.js.map +1 -0
  22. package/dist/crdt/orset.d.ts +40 -0
  23. package/dist/crdt/orset.d.ts.map +1 -0
  24. package/dist/crdt/orset.js +115 -0
  25. package/dist/crdt/orset.js.map +1 -0
  26. package/dist/crdt/pncounter.d.ts +32 -0
  27. package/dist/crdt/pncounter.d.ts.map +1 -0
  28. package/dist/crdt/pncounter.js +77 -0
  29. package/dist/crdt/pncounter.js.map +1 -0
  30. package/dist/crdt/presence.d.ts +45 -0
  31. package/dist/crdt/presence.d.ts.map +1 -0
  32. package/dist/crdt/presence.js +133 -0
  33. package/dist/crdt/presence.js.map +1 -0
  34. package/dist/errors.d.ts +53 -0
  35. package/dist/errors.d.ts.map +1 -0
  36. package/dist/errors.js +30 -0
  37. package/dist/errors.js.map +1 -0
  38. package/dist/index.d.ts +18 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +20 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/schema.d.ts +118 -0
  43. package/dist/schema.d.ts.map +1 -0
  44. package/dist/schema.js +95 -0
  45. package/dist/schema.js.map +1 -0
  46. package/dist/sync/clock.d.ts +42 -0
  47. package/dist/sync/clock.d.ts.map +1 -0
  48. package/dist/sync/clock.js +95 -0
  49. package/dist/sync/clock.js.map +1 -0
  50. package/dist/sync/delta.d.ts +52 -0
  51. package/dist/sync/delta.d.ts.map +1 -0
  52. package/dist/sync/delta.js +35 -0
  53. package/dist/sync/delta.js.map +1 -0
  54. package/dist/transport/http.d.ts +38 -0
  55. package/dist/transport/http.d.ts.map +1 -0
  56. package/dist/transport/http.js +98 -0
  57. package/dist/transport/http.js.map +1 -0
  58. package/dist/transport/websocket.d.ts +60 -0
  59. package/dist/transport/websocket.d.ts.map +1 -0
  60. package/dist/transport/websocket.js +144 -0
  61. package/dist/transport/websocket.js.map +1 -0
  62. package/package.json +31 -0
  63. package/src/auth/token.ts +96 -0
  64. package/src/client.ts +197 -0
  65. package/src/codec.ts +92 -0
  66. package/src/crdt/gcounter.ts +101 -0
  67. package/src/crdt/lwwregister.ts +113 -0
  68. package/src/crdt/orset.ts +131 -0
  69. package/src/crdt/pncounter.ts +93 -0
  70. package/src/crdt/presence.ts +162 -0
  71. package/src/errors.ts +51 -0
  72. package/src/index.ts +59 -0
  73. package/src/schema.ts +144 -0
  74. package/src/sync/clock.ts +117 -0
  75. package/src/sync/delta.ts +104 -0
  76. package/src/transport/http.ts +150 -0
  77. package/src/transport/websocket.ts +189 -0
  78. package/test/crdt.test.ts +290 -0
  79. package/test/sync.test.ts +178 -0
  80. package/test/tsconfig.json +8 -0
  81. package/tsconfig.json +22 -0
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Unit tests for CRDT handles (no server required).
3
+ *
4
+ * We stub the WsTransport so sends are captured but not actually sent.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach } from "bun:test";
8
+ import { GCounterHandle } from "../src/crdt/gcounter.js";
9
+ import { PNCounterHandle } from "../src/crdt/pncounter.js";
10
+ import { ORSetHandle } from "../src/crdt/orset.js";
11
+ import { LwwRegisterHandle } from "../src/crdt/lwwregister.js";
12
+ import { PresenceHandle } from "../src/crdt/presence.js";
13
+ import type { WsTransport } from "../src/transport/websocket.js";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Stub transport — captures sends, never actually connects
17
+ // ---------------------------------------------------------------------------
18
+
19
+ function stubTransport(): WsTransport & { sent: unknown[] } {
20
+ const sent: unknown[] = [];
21
+ return {
22
+ sent,
23
+ connect: () => {},
24
+ close: () => {},
25
+ subscribe: () => {},
26
+ updateClock: () => {},
27
+ send: (msg: unknown) => { sent.push(msg); },
28
+ get currentState() { return "CONNECTED" as const; },
29
+ } as unknown as WsTransport & { sent: unknown[] };
30
+ }
31
+
32
+ const BASE_OPTS = { ns: "test", clientId: 1 };
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // GCounter
36
+ // ---------------------------------------------------------------------------
37
+
38
+ describe("GCounterHandle", () => {
39
+ it("starts at 0", () => {
40
+ const t = stubTransport();
41
+ const h = new GCounterHandle({ ...BASE_OPTS, crdtId: "c", transport: t });
42
+ expect(h.value()).toBe(0);
43
+ });
44
+
45
+ it("increment updates value optimistically", () => {
46
+ const t = stubTransport();
47
+ const h = new GCounterHandle({ ...BASE_OPTS, crdtId: "c", transport: t });
48
+ h.increment(5);
49
+ expect(h.value()).toBe(5);
50
+ expect(t.sent).toHaveLength(1);
51
+ });
52
+
53
+ it("increment fires onChange", () => {
54
+ const t = stubTransport();
55
+ const h = new GCounterHandle({ ...BASE_OPTS, crdtId: "c", transport: t });
56
+ const values: number[] = [];
57
+ h.onChange(v => values.push(v));
58
+ h.increment(3);
59
+ h.increment(2);
60
+ expect(values).toEqual([3, 5]);
61
+ });
62
+
63
+ it("applyDelta merges remote counts (max-merge)", () => {
64
+ const t = stubTransport();
65
+ const h = new GCounterHandle({ ...BASE_OPTS, crdtId: "c", transport: t });
66
+ h.increment(10); // client 1 = 10
67
+ h.applyDelta({ increments: { "2": 20 } });
68
+ expect(h.value()).toBe(30);
69
+ });
70
+
71
+ it("applyDelta ignores stale counts (no regression)", () => {
72
+ const t = stubTransport();
73
+ const h = new GCounterHandle({ ...BASE_OPTS, crdtId: "c", transport: t });
74
+ h.increment(10);
75
+ h.applyDelta({ increments: { "1": 5 } }); // 5 < 10 — stale
76
+ expect(h.value()).toBe(10);
77
+ });
78
+
79
+ it("unsubscribe stops onChange notifications", () => {
80
+ const t = stubTransport();
81
+ const h = new GCounterHandle({ ...BASE_OPTS, crdtId: "c", transport: t });
82
+ const values: number[] = [];
83
+ const unsub = h.onChange(v => values.push(v));
84
+ h.increment(1);
85
+ unsub();
86
+ h.increment(1);
87
+ expect(values).toEqual([1]); // only the first increment
88
+ });
89
+
90
+ it("increment rejects non-positive amount", () => {
91
+ const t = stubTransport();
92
+ const h = new GCounterHandle({ ...BASE_OPTS, crdtId: "c", transport: t });
93
+ expect(() => h.increment(0)).toThrow(RangeError);
94
+ expect(() => h.increment(-1)).toThrow(RangeError);
95
+ });
96
+ });
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // PNCounter
100
+ // ---------------------------------------------------------------------------
101
+
102
+ describe("PNCounterHandle", () => {
103
+ it("starts at 0", () => {
104
+ const t = stubTransport();
105
+ const h = new PNCounterHandle({ ...BASE_OPTS, crdtId: "pn", transport: t });
106
+ expect(h.value()).toBe(0);
107
+ });
108
+
109
+ it("increment and decrement", () => {
110
+ const t = stubTransport();
111
+ const h = new PNCounterHandle({ ...BASE_OPTS, crdtId: "pn", transport: t });
112
+ h.increment(10);
113
+ h.decrement(3);
114
+ expect(h.value()).toBe(7);
115
+ });
116
+
117
+ it("can go negative", () => {
118
+ const t = stubTransport();
119
+ const h = new PNCounterHandle({ ...BASE_OPTS, crdtId: "pn", transport: t });
120
+ h.decrement(5);
121
+ expect(h.value()).toBe(-5);
122
+ });
123
+
124
+ it("applyDelta merges both GCounters", () => {
125
+ const t = stubTransport();
126
+ const h = new PNCounterHandle({ ...BASE_OPTS, crdtId: "pn", transport: t });
127
+ h.applyDelta({ p: { increments: { "2": 100 } }, n: { increments: { "2": 40 } } });
128
+ expect(h.value()).toBe(60);
129
+ });
130
+ });
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // ORSet
134
+ // ---------------------------------------------------------------------------
135
+
136
+ describe("ORSetHandle", () => {
137
+ it("empty by default", () => {
138
+ const t = stubTransport();
139
+ const h = new ORSetHandle({ ...BASE_OPTS, crdtId: "s", transport: t });
140
+ expect(h.elements()).toEqual([]);
141
+ });
142
+
143
+ it("add makes element visible", () => {
144
+ const t = stubTransport();
145
+ const h = new ORSetHandle<string>({ ...BASE_OPTS, crdtId: "s", transport: t });
146
+ h.add("alice");
147
+ expect(h.has("alice")).toBe(true);
148
+ });
149
+
150
+ it("remove makes element invisible", () => {
151
+ const t = stubTransport();
152
+ const h = new ORSetHandle<string>({ ...BASE_OPTS, crdtId: "s", transport: t });
153
+ h.add("alice");
154
+ h.remove("alice");
155
+ expect(h.has("alice")).toBe(false);
156
+ });
157
+
158
+ it("add-wins: concurrent add+remove → element survives via applyDelta", () => {
159
+ const t = stubTransport();
160
+ const h = new ORSetHandle<string>({ ...BASE_OPTS, crdtId: "s", transport: t });
161
+ // Remote adds "alice" with tag "t1", local remove only removes "t2" (different tag)
162
+ h.applyDelta({ added: { '"alice"': ["t1"] }, removed: {} });
163
+ h.applyDelta({ added: {}, removed: { '"alice"': ["t2"] } }); // unknown tag — no effect
164
+ expect(h.has("alice")).toBe(true);
165
+ });
166
+
167
+ it("fires onChange on add and remove", () => {
168
+ const t = stubTransport();
169
+ const h = new ORSetHandle<string>({ ...BASE_OPTS, crdtId: "s", transport: t });
170
+ const snapshots: string[][] = [];
171
+ h.onChange(elems => snapshots.push([...elems].sort()));
172
+ h.add("a");
173
+ h.add("b");
174
+ h.remove("a");
175
+ expect(snapshots).toEqual([["a"], ["a", "b"], ["b"]]);
176
+ });
177
+ });
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // LWW Register
181
+ // ---------------------------------------------------------------------------
182
+
183
+ describe("LwwRegisterHandle", () => {
184
+ it("starts null", () => {
185
+ const t = stubTransport();
186
+ const h = new LwwRegisterHandle({ ...BASE_OPTS, crdtId: "r", transport: t });
187
+ expect(h.value()).toBeNull();
188
+ });
189
+
190
+ it("set updates value", () => {
191
+ const t = stubTransport();
192
+ const h = new LwwRegisterHandle<string>({ ...BASE_OPTS, crdtId: "r", transport: t });
193
+ h.set("hello");
194
+ expect(h.value()).toBe("hello");
195
+ });
196
+
197
+ it("applyDelta with higher HLC wins", () => {
198
+ const t = stubTransport();
199
+ const h = new LwwRegisterHandle<string>({ ...BASE_OPTS, crdtId: "r", transport: t });
200
+ h.set("first");
201
+ const localMeta = h.meta()!;
202
+ h.applyDelta({
203
+ entry: {
204
+ value: "second",
205
+ hlc: { wall_ms: localMeta.updatedAtMs + 1000, logical: 0, node_id: 2 },
206
+ author: 2,
207
+ },
208
+ });
209
+ expect(h.value()).toBe("second");
210
+ });
211
+
212
+ it("applyDelta with lower HLC is rejected", () => {
213
+ const t = stubTransport();
214
+ const h = new LwwRegisterHandle<string>({ ...BASE_OPTS, crdtId: "r", transport: t });
215
+ // Set local value with current time
216
+ h.set("current");
217
+ h.applyDelta({
218
+ entry: {
219
+ value: "old",
220
+ hlc: { wall_ms: 1, logical: 0, node_id: 99 }, // epoch — always loses
221
+ author: 99,
222
+ },
223
+ });
224
+ expect(h.value()).toBe("current");
225
+ });
226
+ });
227
+
228
+ // ---------------------------------------------------------------------------
229
+ // Presence
230
+ // ---------------------------------------------------------------------------
231
+
232
+ describe("PresenceHandle", () => {
233
+ it("empty by default", () => {
234
+ const t = stubTransport();
235
+ const h = new PresenceHandle({ ...BASE_OPTS, crdtId: "p", transport: t });
236
+ expect(h.online()).toEqual([]);
237
+ });
238
+
239
+ it("heartbeat adds self to online list", () => {
240
+ const t = stubTransport();
241
+ const h = new PresenceHandle<{ name: string }>({ ...BASE_OPTS, crdtId: "p", transport: t });
242
+ h.heartbeat({ name: "alice" }, 30_000);
243
+ expect(h.online()).toHaveLength(1);
244
+ expect(h.online()[0]?.data).toEqual({ name: "alice" });
245
+ });
246
+
247
+ it("leave removes self from online list", () => {
248
+ const t = stubTransport();
249
+ const h = new PresenceHandle<{ name: string }>({ ...BASE_OPTS, crdtId: "p", transport: t });
250
+ h.heartbeat({ name: "alice" }, 30_000);
251
+ h.leave();
252
+ expect(h.online()).toHaveLength(0);
253
+ });
254
+
255
+ it("applyDelta adds remote entries", () => {
256
+ const t = stubTransport();
257
+ const h = new PresenceHandle<{ name: string }>({ ...BASE_OPTS, crdtId: "p", transport: t });
258
+ const now = Date.now();
259
+ h.applyDelta({
260
+ changes: {
261
+ "2": { data: { name: "bob" }, hlc: { wall_ms: now, logical: 0, node_id: 2 }, ttl_ms: 30_000 },
262
+ },
263
+ });
264
+ expect(h.online()).toHaveLength(1);
265
+ expect(h.online()[0]?.data).toEqual({ name: "bob" });
266
+ });
267
+
268
+ it("applyDelta null removes entry", () => {
269
+ const t = stubTransport();
270
+ const h = new PresenceHandle<{ name: string }>({ ...BASE_OPTS, crdtId: "p", transport: t });
271
+ const now = Date.now();
272
+ h.applyDelta({
273
+ changes: {
274
+ "2": { data: { name: "bob" }, hlc: { wall_ms: now, logical: 0, node_id: 2 }, ttl_ms: 30_000 },
275
+ },
276
+ });
277
+ h.applyDelta({ changes: { "2": null } });
278
+ expect(h.online()).toHaveLength(0);
279
+ });
280
+
281
+ it("fires onChange on heartbeat and leave", () => {
282
+ const t = stubTransport();
283
+ const h = new PresenceHandle<string>({ ...BASE_OPTS, crdtId: "p", transport: t });
284
+ const counts: number[] = [];
285
+ h.onChange(entries => counts.push(entries.length));
286
+ h.heartbeat("alice", 30_000);
287
+ h.leave();
288
+ expect(counts).toEqual([1, 0]);
289
+ });
290
+ });
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Unit tests for sync utilities: VectorClockTracker, codec, token parsing.
3
+ */
4
+
5
+ import { describe, it, expect } from "bun:test";
6
+ import { Effect } from "effect";
7
+ import { VectorClockTracker } from "../src/sync/clock.js";
8
+ import { encode, decode, encodeClientMsg, decodeServerMsg } from "../src/codec.js";
9
+ import { parseToken, checkTokenExpiry } from "../src/auth/token.js";
10
+ import { CodecError, TokenParseError, TokenExpiredError } from "../src/errors.js";
11
+ import { pack } from "msgpackr";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // VectorClockTracker
15
+ // ---------------------------------------------------------------------------
16
+
17
+ describe("VectorClockTracker", () => {
18
+ function freshStorage() {
19
+ const store = new Map<string, Record<string, number>>();
20
+ return {
21
+ load: (key: string) => store.get(key) ?? null,
22
+ save: (key: string, vc: Record<string, number>) => { store.set(key, { ...vc }); },
23
+ };
24
+ }
25
+
26
+ function tracker(initial: Record<string, number> = {}) {
27
+ return new VectorClockTracker({ namespace: "ns", crdtId: "c", storage: freshStorage(), initial });
28
+ }
29
+
30
+ it("starts empty", () => {
31
+ expect(tracker().snapshot()).toEqual({});
32
+ });
33
+
34
+ it("observe updates clock", () => {
35
+ const t = tracker();
36
+ t.observe("1", 5);
37
+ expect(t.snapshot()["1"]).toBe(5);
38
+ });
39
+
40
+ it("observe ignores older versions", () => {
41
+ const t = tracker({ "1": 10 });
42
+ t.observe("1", 3);
43
+ expect(t.snapshot()["1"]).toBe(10);
44
+ });
45
+
46
+ it("merge takes max per key", () => {
47
+ const t = tracker({ "1": 5, "2": 10 });
48
+ t.merge({ "1": 3, "2": 20, "3": 1 });
49
+ const snap = t.snapshot();
50
+ expect(snap["1"]).toBe(5);
51
+ expect(snap["2"]).toBe(20);
52
+ expect(snap["3"]).toBe(1);
53
+ });
54
+
55
+ it("reset clears all entries", () => {
56
+ const t = tracker({ "1": 10 });
57
+ t.reset();
58
+ expect(t.snapshot()).toEqual({});
59
+ });
60
+
61
+ it("persists to storage on observe", () => {
62
+ const storage = freshStorage();
63
+ const t1 = new VectorClockTracker({ namespace: "ns", crdtId: "x", storage });
64
+ t1.observe("42", 7);
65
+ const t2 = new VectorClockTracker({ namespace: "ns", crdtId: "x", storage });
66
+ expect(t2.snapshot()["42"]).toBe(7);
67
+ });
68
+ });
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Codec
72
+ // ---------------------------------------------------------------------------
73
+
74
+ describe("codec", () => {
75
+ it("encode/decode roundtrip for plain objects", () => {
76
+ const obj = { foo: "bar", n: 42 };
77
+ expect(decode(encode(obj))).toEqual(obj);
78
+ });
79
+
80
+ it("encodeClientMsg produces bytes", () => {
81
+ const bytes = encodeClientMsg({ Subscribe: { crdt_id: "foo" } });
82
+ expect(bytes).toBeInstanceOf(Uint8Array);
83
+ expect(bytes.length).toBeGreaterThan(0);
84
+ });
85
+
86
+ it("decodeServerMsg succeeds on valid Ack", async () => {
87
+ const msg = { Ack: { seq: 99 } };
88
+ const bytes = new Uint8Array(encode(msg));
89
+ const result = await Effect.runPromise(decodeServerMsg(bytes));
90
+ expect(result).toEqual(msg);
91
+ });
92
+
93
+ it("decodeServerMsg fails with CodecError on unknown variant", async () => {
94
+ const bad = new Uint8Array(encode({ Unknown: {} }));
95
+ const err = await Effect.runPromise(
96
+ decodeServerMsg(bad).pipe(Effect.flip),
97
+ );
98
+ expect(err).toBeInstanceOf(CodecError);
99
+ });
100
+
101
+ it("decodeServerMsg fails with CodecError on non-object", async () => {
102
+ const bad = new Uint8Array(encode("not-an-object"));
103
+ const err = await Effect.runPromise(
104
+ decodeServerMsg(bad).pipe(Effect.flip),
105
+ );
106
+ expect(err).toBeInstanceOf(CodecError);
107
+ });
108
+
109
+ it("decodeServerMsg succeeds on multi-key map (Schema matches first valid variant)", async () => {
110
+ // Effect Schema union picks first matching variant — Ack wins here.
111
+ // The server never sends multi-key maps; this tests Schema's lenient-but-safe behaviour.
112
+ const bytes = new Uint8Array(
113
+ encode({ Ack: { seq: 1 }, Delta: { crdt_id: "x", delta_bytes: new Uint8Array() } }),
114
+ );
115
+ const result = await Effect.runPromise(decodeServerMsg(bytes));
116
+ // Schema picks whichever variant matches first — just assert it decoded without error
117
+ expect("Ack" in result || "Delta" in result).toBe(true);
118
+ });
119
+ });
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Token parsing
123
+ // ---------------------------------------------------------------------------
124
+
125
+ describe("parseToken", () => {
126
+ function makeToken(claims: object): string {
127
+ const payload = pack(claims);
128
+ const b64 = btoa(String.fromCharCode(...payload))
129
+ .replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
130
+ return `${b64}.fakesig`;
131
+ }
132
+
133
+ it("parses valid claims", async () => {
134
+ const token = makeToken({
135
+ namespace: "my-room",
136
+ client_id: 42,
137
+ expires_at: Date.now() + 3_600_000,
138
+ permissions: { read: true, write: true, admin: false },
139
+ });
140
+ const claims = await Effect.runPromise(parseToken(token));
141
+ expect(claims.namespace).toBe("my-room");
142
+ expect(claims.client_id).toBe(42);
143
+ });
144
+
145
+ it("fails with TokenParseError on missing dot", async () => {
146
+ const err = await Effect.runPromise(parseToken("nodothere").pipe(Effect.flip));
147
+ expect(err).toBeInstanceOf(TokenParseError);
148
+ });
149
+
150
+ it("fails with TokenParseError on bad base64", async () => {
151
+ const err = await Effect.runPromise(parseToken("!!!.sig").pipe(Effect.flip));
152
+ expect(err).toBeInstanceOf(TokenParseError);
153
+ });
154
+
155
+ it("checkTokenExpiry succeeds for future expiry", async () => {
156
+ const token = makeToken({
157
+ namespace: "ns",
158
+ client_id: 1,
159
+ expires_at: Date.now() + 3_600_000,
160
+ permissions: { read: true, write: false, admin: false },
161
+ });
162
+ const claims = await Effect.runPromise(parseToken(token));
163
+ const result = await Effect.runPromise(checkTokenExpiry(claims));
164
+ expect(result.client_id).toBe(1);
165
+ });
166
+
167
+ it("checkTokenExpiry fails with TokenExpiredError for past expiry", async () => {
168
+ const token = makeToken({
169
+ namespace: "ns",
170
+ client_id: 1,
171
+ expires_at: 1,
172
+ permissions: { read: true, write: false, admin: false },
173
+ });
174
+ const claims = await Effect.runPromise(parseToken(token));
175
+ const err = await Effect.runPromise(checkTokenExpiry(claims).pipe(Effect.flip));
176
+ expect(err).toBeInstanceOf(TokenExpiredError);
177
+ });
178
+ });
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "..",
5
+ "types": ["bun-types"]
6
+ },
7
+ "include": ["../**/*"]
8
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ESNext"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "sourceMap": true,
12
+ "strict": true,
13
+ "exactOptionalPropertyTypes": true,
14
+ "noUncheckedIndexedAccess": true,
15
+ "noImplicitReturns": true,
16
+ "noFallthroughCasesInSwitch": true,
17
+ "verbatimModuleSyntax": true,
18
+ "skipLibCheck": true
19
+ },
20
+ "include": ["src/**/*"],
21
+ "exclude": ["node_modules", "dist", "test"]
22
+ }