@nebutra/collab 0.2.2 → 0.2.3

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.
@@ -1,240 +0,0 @@
1
- import { _resetTenantLocks } from "@nebutra/tenant-store";
2
- import { beforeEach, describe, expect, it } from "vitest";
3
- import * as Y from "yjs";
4
- import { CollabError, createCollab } from "../index";
5
- import { InMemorySnapshotStore } from "../store/memory";
6
- import { LoopbackTransport } from "../transport/loopback";
7
-
8
- describe("CRDT convergence", () => {
9
- beforeEach(() => _resetTenantLocks());
10
-
11
- it("two clients editing the SAME room converge to identical state", async () => {
12
- const hub = createCollab();
13
- const a = hub.room("t1", "r1");
14
-
15
- // A separate client doc, simulating a remote peer on the same logical room.
16
- const peer = new Y.Doc();
17
-
18
- // Edit on hub side.
19
- a.doc.getMap("m").set("from", "a");
20
- // Ship A's update to the peer.
21
- peer.transact(() => Y.applyUpdate(peer, a.encodeState()));
22
-
23
- // Peer edits concurrently.
24
- peer.getMap("m").set("from", "b-peer");
25
- peer.getArray("list").push([1, 2, 3]);
26
-
27
- // Exchange peer -> A.
28
- a.applyUpdate(Y.encodeStateAsUpdate(peer));
29
- // Exchange A -> peer (A may have other state).
30
- Y.applyUpdate(peer, a.encodeState());
31
-
32
- // Both sides must converge to identical serialized state.
33
- const aState = JSON.stringify(a.doc.getMap("m").toJSON());
34
- const peerState = JSON.stringify(peer.getMap("m").toJSON());
35
- expect(aState).toBe(peerState);
36
- expect(a.doc.getArray("list").toJSON()).toEqual([1, 2, 3]);
37
- });
38
- });
39
-
40
- describe("tenant isolation (security-critical)", () => {
41
- beforeEach(() => _resetTenantLocks());
42
-
43
- it("room(tenantA) and room(tenantB) with same roomId are independent docs", () => {
44
- const hub = createCollab();
45
- const a = hub.room("tenantA", "r1");
46
- const b = hub.room("tenantB", "r1");
47
-
48
- expect(a.doc).not.toBe(b.doc);
49
-
50
- a.doc.getMap("m").set("secret", "A-only");
51
- // B's room must NEVER observe A's update.
52
- expect(b.doc.getMap("m").get("secret")).toBeUndefined();
53
- });
54
-
55
- it("an update applied to A's room never appears in B's room", () => {
56
- const hub = createCollab();
57
- const a = hub.room("tenantA", "r1");
58
- const b = hub.room("tenantB", "r1");
59
-
60
- const seenByB: Uint8Array[] = [];
61
- b.onUpdate((u) => seenByB.push(u));
62
-
63
- a.doc.getMap("m").set("x", 1);
64
- a.applyUpdate(Y.encodeStateAsUpdate(a.doc));
65
-
66
- expect(seenByB).toHaveLength(0);
67
- expect(b.doc.getMap("m").get("x")).toBeUndefined();
68
- });
69
-
70
- it("snapshot store for tenant A never returns tenant B state", async () => {
71
- const store = new InMemorySnapshotStore();
72
- const hub = createCollab({ store });
73
-
74
- const a = hub.room("tenantA", "shared-room-id");
75
- a.doc.getMap("m").set("owner", "A");
76
- await a.snapshot();
77
-
78
- // B asks for the SAME roomId — must get nothing from A.
79
- const bState = await store.load("tenantB", "shared-room-id");
80
- expect(bState).toBeNull();
81
-
82
- const aState = await store.load("tenantA", "shared-room-id");
83
- expect(aState).not.toBeNull();
84
- });
85
-
86
- it("returns the SAME room instance for repeated (tenant, room) calls", () => {
87
- const hub = createCollab();
88
- expect(hub.room("t1", "r1")).toBe(hub.room("t1", "r1"));
89
- expect(hub.room("t1", "r1")).not.toBe(hub.room("t2", "r1"));
90
- });
91
- });
92
-
93
- describe("transport-driven convergence", () => {
94
- beforeEach(() => _resetTenantLocks());
95
-
96
- it("separate hubs sharing a transport converge for the same tenant+room", () => {
97
- const transport = new LoopbackTransport();
98
- const hubA = createCollab({ transport });
99
- const hubB = createCollab({ transport });
100
- const ra = hubA.room("t1", "r1");
101
- const rb = hubB.room("t1", "r1");
102
-
103
- ra.doc.getMap("m").set("k", "v");
104
- expect(rb.doc.getMap("m").get("k")).toBe("v");
105
-
106
- // Reverse direction also converges (origin guard prevents a loop).
107
- rb.doc.getArray("l").push([42]);
108
- expect(ra.doc.getArray("l").toJSON()).toEqual([42]);
109
-
110
- hubA.destroy();
111
- hubB.destroy();
112
- });
113
-
114
- it("a destroyed room throws CollabError COLLAB_DESTROYED", () => {
115
- const hub = createCollab();
116
- const r = hub.room("t1", "r1");
117
- r.destroy();
118
- try {
119
- r.encodeState();
120
- throw new Error("should have thrown");
121
- } catch (e) {
122
- expect(e).toBeInstanceOf(CollabError);
123
- expect((e as CollabError).code).toBe("COLLAB_DESTROYED");
124
- expect((e as CollabError).suggestion.length).toBeGreaterThan(0);
125
- }
126
- });
127
- });
128
-
129
- describe("restore / snapshot failure surfaces CollabError", () => {
130
- beforeEach(() => _resetTenantLocks());
131
-
132
- it("roomRestored wraps a throwing store load", async () => {
133
- const store = {
134
- load: async () => {
135
- throw new Error("db down");
136
- },
137
- save: async () => {},
138
- };
139
- const hub = createCollab({ store });
140
- try {
141
- await hub.roomRestored("t1", "r1");
142
- throw new Error("should have thrown");
143
- } catch (e) {
144
- expect(e).toBeInstanceOf(CollabError);
145
- expect((e as CollabError).code).toBe("COLLAB_RESTORE_FAILED");
146
- expect((e as CollabError).suggestion.length).toBeGreaterThan(0);
147
- }
148
- });
149
-
150
- it("snapshot wraps a throwing store save", async () => {
151
- const store = {
152
- load: async () => null,
153
- save: async () => {
154
- throw new Error("disk full");
155
- },
156
- };
157
- const hub = createCollab({ store });
158
- const r = hub.room("t1", "r1");
159
- r.doc.getMap("m").set("a", 1);
160
- try {
161
- await r.snapshot();
162
- throw new Error("should have thrown");
163
- } catch (e) {
164
- expect(e).toBeInstanceOf(CollabError);
165
- expect((e as CollabError).code).toBe("COLLAB_SNAPSHOT_FAILED");
166
- expect((e as CollabError).suggestion.length).toBeGreaterThan(0);
167
- }
168
- });
169
- });
170
-
171
- describe("snapshot + restore", () => {
172
- beforeEach(() => _resetTenantLocks());
173
-
174
- it("snapshot() then reload restores doc state", async () => {
175
- const store = new InMemorySnapshotStore();
176
- const hub1 = createCollab({ store });
177
- const r1 = hub1.room("t1", "doc1");
178
- r1.doc.getText("body").insert(0, "hello world");
179
- await r1.snapshot();
180
-
181
- // Fresh hub, same store — must hydrate from snapshot.
182
- const hub2 = createCollab({ store });
183
- const r2 = await hub2.roomRestored("t1", "doc1");
184
- expect(r2.doc.getText("body").toString()).toBe("hello world");
185
- });
186
- });
187
-
188
- describe("onUpdate subscription", () => {
189
- beforeEach(() => _resetTenantLocks());
190
-
191
- it("unsubscribe stops further callbacks", () => {
192
- const hub = createCollab();
193
- const r = hub.room("t1", "r1");
194
- const seen: number[] = [];
195
- const off = r.onUpdate(() => seen.push(1));
196
-
197
- r.doc.getMap("m").set("a", 1);
198
- expect(seen).toHaveLength(1);
199
-
200
- off();
201
- r.doc.getMap("m").set("b", 2);
202
- expect(seen).toHaveLength(1);
203
- });
204
- });
205
-
206
- describe("CollabError", () => {
207
- it("carries a non-empty suggestion and a code", () => {
208
- const err = new CollabError("something failed", {
209
- code: "COLLAB_TEST",
210
- suggestion: "do the thing differently",
211
- });
212
- expect(err).toBeInstanceOf(Error);
213
- expect(err.code).toBe("COLLAB_TEST");
214
- expect(err.suggestion.length).toBeGreaterThan(0);
215
- });
216
-
217
- it("throws CollabError with suggestion on empty tenantId", () => {
218
- const hub = createCollab();
219
- try {
220
- hub.room("", "r1");
221
- throw new Error("should have thrown");
222
- } catch (e) {
223
- expect(e).toBeInstanceOf(CollabError);
224
- expect((e as CollabError).suggestion.length).toBeGreaterThan(0);
225
- }
226
- });
227
- });
228
-
229
- describe("doctor()", () => {
230
- it("returns a structured health report in <3s", async () => {
231
- const hub = createCollab();
232
- const start = Date.now();
233
- const report = await hub.doctor();
234
- expect(Date.now() - start).toBeLessThan(3000);
235
- expect(report.ok).toBe(true);
236
- expect(report.checks.yjs.ok).toBe(true);
237
- expect(report.checks.store.ok).toBe(true);
238
- expect(report.checks.transport.ok).toBe(true);
239
- });
240
- });
package/src/cli.ts DELETED
@@ -1,27 +0,0 @@
1
- /**
2
- * collab CLI — `doctor` (Yjs/store/transport health) and `debug <roomId>`
3
- * (open a real zero-config room, apply an update, report convergence state).
4
- * The doctor/debug argv switch is the shared `@nebutra/capability-kit`
5
- * runner; only the collab-specific probes live here.
6
- */
7
-
8
- import { runCapabilityCli } from "@nebutra/capability-kit";
9
- import { getCollab } from "./index";
10
-
11
- const hub = await getCollab();
12
-
13
- await runCapabilityCli({
14
- capability: "collab",
15
- doctor: () => hub.doctor(),
16
- debug: (roomId?: string) => {
17
- const tenantId = "debug-tenant";
18
- const id = roomId ?? "debug-room";
19
- const room = hub.room(tenantId, id);
20
- const before = room.encodeState().length;
21
- room.doc.getText("debug").insert(0, "hello canvas");
22
- const after = room.encodeState().length;
23
- const text = room.doc.getText("debug").toString();
24
- room.destroy();
25
- return { tenantId, roomId: id, encodedBytes: { before, after }, text };
26
- },
27
- });
package/src/errors.ts DELETED
@@ -1,42 +0,0 @@
1
- /**
2
- * Every failure surfaced by this package is a `CollabError`. The contract is
3
- * deliberately strict: a machine-stable `code` and a human-actionable
4
- * `suggestion` are MANDATORY, so no code path can throw a bare `Error` that
5
- * leaves a caller without a remediation hint.
6
- *
7
- * Mechanics (code/suggestion/toJSON/empty-suggestion fallback) are inherited
8
- * from the shared `@nebutra/capability-kit` `CapabilityError`; this subclass
9
- * only pins collab's error name + its package-specific fallback wording, so
10
- * the observable contract is unchanged.
11
- */
12
-
13
- import { CapabilityError } from "@nebutra/capability-kit";
14
-
15
- export type CollabErrorCode =
16
- | "COLLAB_INVALID_TENANT"
17
- | "COLLAB_INVALID_ROOM"
18
- | "COLLAB_SNAPSHOT_FAILED"
19
- | "COLLAB_RESTORE_FAILED"
20
- | "COLLAB_DESTROYED"
21
- | "COLLAB_TEST"
22
- | (string & {});
23
-
24
- export interface CollabErrorInit {
25
- readonly code: CollabErrorCode;
26
- /** A non-empty, actionable remediation hint. */
27
- readonly suggestion: string;
28
- readonly cause?: unknown;
29
- }
30
-
31
- export class CollabError extends CapabilityError {
32
- declare readonly code: CollabErrorCode;
33
-
34
- constructor(message: string, init: CollabErrorInit) {
35
- super(message, init, {
36
- name: "CollabError",
37
- emptySuggestionFallback:
38
- "No suggestion was provided. This is a bug in @nebutra/collab — " +
39
- "report it with the failing operation.",
40
- });
41
- }
42
- }
package/src/hub.ts DELETED
Binary file
package/src/index.ts DELETED
@@ -1,42 +0,0 @@
1
- /**
2
- * @nebutra/collab — multi-tenant, transport-agnostic real-time collaborative
3
- * sync layer.
4
- *
5
- * Sailor already had Pusher pub/sub (fire-and-forget broadcast) but no
6
- * conflict-free concurrent editing. This package fills that gap with
7
- * tenant-partitioned CRDT rooms built on Yjs (MIT). It is generic: a
8
- * node-graph canvas, a rich-text document, or any shared structure binds to
9
- * a `CollabRoom` and gets convergence for free.
10
- *
11
- * Multi-tenancy is NON-NEGOTIABLE. Rooms are hard-partitioned by `tenantId`:
12
- * a room handle is only ever produced by passing an explicit tenant, the
13
- * snapshot store and transport are addressed by (tenantId, roomId), and the
14
- * composite key uses a NUL separator so partitions cannot collide. See
15
- * `hub.ts` for the structural argument and `__tests__` for the proof.
16
- *
17
- * Zero-config: `getCollab()` with no args yields REAL (non-mock) CRDT
18
- * behaviour via an in-memory snapshot store (composed from
19
- * `@nebutra/tenant-store`) and an in-process loopback transport. Production
20
- * swaps a Prisma/Redis `SnapshotStore` and a Pusher/WebSocket
21
- * `CollabTransport` through the same interfaces (see README).
22
- *
23
- * This package keeps itself in the `active` tier of the three-tier module
24
- * lifecycle by shipping a real in-package caller of the exported factory —
25
- * see `examples/zero-config-convergence.ts`.
26
- */
27
-
28
- export type { CollabErrorCode, CollabErrorInit } from "./errors";
29
- export { CollabError } from "./errors";
30
- export { createCollab, getCollab } from "./hub";
31
- export { InMemorySnapshotStore } from "./store/memory";
32
- export { LoopbackTransport } from "./transport/loopback";
33
- export type {
34
- CollabConfig,
35
- CollabHub,
36
- CollabRoom,
37
- CollabTransport,
38
- DoctorCheck,
39
- DoctorReport,
40
- SnapshotStore,
41
- UpdateListener,
42
- } from "./types";
package/src/room.ts DELETED
@@ -1,130 +0,0 @@
1
- /**
2
- * A single tenant-scoped CRDT room. One `Y.Doc` per (tenant, room). The hub
3
- * owns the partitioning; this class assumes its `tenantId`/`roomId` are
4
- * already the partition it belongs to and never reaches outside them.
5
- *
6
- * Snapshot persistence is serialized through `withTenantLock(tenantId,
7
- * roomId, ...)` borrowed from `@nebutra/tenant-store` rather than a
8
- * hand-rolled mutex — same primitive used by canvas/reel, so a future swap
9
- * to a distributed lock changes one place.
10
- */
11
-
12
- import { withTenantLock } from "@nebutra/tenant-store";
13
- import * as Y from "yjs";
14
- import { CollabError } from "./errors";
15
- import type { CollabRoom, CollabTransport, SnapshotStore, UpdateListener } from "./types";
16
-
17
- /** Origin tag used when applying remote updates so we don't echo them back. */
18
- const REMOTE_ORIGIN = Symbol("collab.remote");
19
-
20
- export class Room implements CollabRoom {
21
- readonly doc: Y.Doc;
22
- private readonly listeners = new Set<UpdateListener>();
23
- private readonly unsubTransport: () => void;
24
- private destroyed = false;
25
-
26
- constructor(
27
- readonly tenantId: string,
28
- readonly roomId: string,
29
- private readonly store: SnapshotStore,
30
- private readonly transport: CollabTransport,
31
- ) {
32
- this.doc = new Y.Doc();
33
-
34
- // Fan local updates out to: registered listeners + the transport. The
35
- // transport echo is guarded by origin so a remote-applied update is not
36
- // re-broadcast into a loop.
37
- this.doc.on("update", (update: Uint8Array, origin: unknown) => {
38
- for (const cb of [...this.listeners]) cb(update, origin);
39
- if (origin !== REMOTE_ORIGIN) {
40
- void Promise.resolve(this.transport.broadcast(this.tenantId, this.roomId, update)).catch(
41
- () => {
42
- // Transport delivery is best-effort; CRDT state stays correct and
43
- // converges on the next exchanged update. Swallowing here avoids
44
- // an unhandled rejection from a flaky network adapter.
45
- },
46
- );
47
- }
48
- });
49
-
50
- // Remote updates for THIS tenant-scoped channel only.
51
- this.unsubTransport = this.transport.subscribe(this.tenantId, this.roomId, (update) => {
52
- if (this.destroyed) return;
53
- Y.applyUpdate(this.doc, update, REMOTE_ORIGIN);
54
- });
55
- }
56
-
57
- applyUpdate(update: Uint8Array, origin?: unknown): void {
58
- this.assertLive();
59
- Y.applyUpdate(this.doc, update, origin);
60
- }
61
-
62
- encodeState(): Uint8Array {
63
- this.assertLive();
64
- return Y.encodeStateAsUpdate(this.doc);
65
- }
66
-
67
- onUpdate(cb: UpdateListener): () => void {
68
- this.assertLive();
69
- this.listeners.add(cb);
70
- return () => {
71
- this.listeners.delete(cb);
72
- };
73
- }
74
-
75
- async snapshot(): Promise<void> {
76
- this.assertLive();
77
- const state = this.encodeState();
78
- try {
79
- // Serialize concurrent snapshots of the SAME room; different rooms (or
80
- // the same room under another tenant) persist in parallel.
81
- await withTenantLock(this.tenantId, this.roomId, () =>
82
- this.store.save(this.tenantId, this.roomId, state),
83
- );
84
- } catch (cause) {
85
- throw new CollabError(`Failed to persist snapshot for room "${this.roomId}".`, {
86
- code: "COLLAB_SNAPSHOT_FAILED",
87
- suggestion:
88
- "Verify the configured SnapshotStore is reachable (DB/Redis up, " +
89
- "credentials valid). The in-memory default never fails; a custom " +
90
- "adapter likely threw.",
91
- cause,
92
- });
93
- }
94
- }
95
-
96
- /** Hydrate this doc from persisted state, if any. Internal to the hub. */
97
- async _restore(): Promise<void> {
98
- try {
99
- const persisted = await this.store.load(this.tenantId, this.roomId);
100
- if (persisted) Y.applyUpdate(this.doc, persisted, REMOTE_ORIGIN);
101
- } catch (cause) {
102
- throw new CollabError(`Failed to restore room "${this.roomId}" from snapshot store.`, {
103
- code: "COLLAB_RESTORE_FAILED",
104
- suggestion:
105
- "Check the SnapshotStore adapter's load() — it should resolve " +
106
- "null (not throw) when no snapshot exists for the tenant+room.",
107
- cause,
108
- });
109
- }
110
- }
111
-
112
- destroy(): void {
113
- if (this.destroyed) return;
114
- this.destroyed = true;
115
- this.unsubTransport();
116
- this.listeners.clear();
117
- this.doc.destroy();
118
- }
119
-
120
- private assertLive(): void {
121
- if (this.destroyed) {
122
- throw new CollabError(`Room "${this.roomId}" was destroyed and can no longer be used.`, {
123
- code: "COLLAB_DESTROYED",
124
- suggestion:
125
- "Acquire a fresh room via hub.room(tenantId, roomId) instead of " +
126
- "reusing a destroyed instance.",
127
- });
128
- }
129
- }
130
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * Zero-config default `SnapshotStore`. The storage MECHANICS (composite-key
3
- * map, never returning rows across tenants) are NOT re-implemented here —
4
- * they are composed from `@nebutra/tenant-store`'s `InMemoryTenantStore`,
5
- * which already enforces tenant isolation structurally via its
6
- * `tenantId:id` key plus a defense-in-depth tenantId equality check.
7
- *
8
- * A production deployment swaps this for a Prisma/Redis adapter that
9
- * implements the same `SnapshotStore` interface; the README documents that
10
- * shape. The isolation property a Prisma adapter would get from RLS is here
11
- * provided by the borrowed composite key.
12
- */
13
-
14
- import { InMemoryTenantStore, type TenantOwned } from "@nebutra/tenant-store";
15
- import type { SnapshotStore } from "../types";
16
-
17
- interface SnapshotRow extends TenantOwned {
18
- readonly tenantId: string;
19
- readonly state: Uint8Array;
20
- }
21
-
22
- export class InMemorySnapshotStore implements SnapshotStore {
23
- private readonly inner = new InMemoryTenantStore<SnapshotRow>();
24
-
25
- async load(tenantId: string, roomId: string): Promise<Uint8Array | null> {
26
- const row = await this.inner.read(tenantId, roomId);
27
- return row ? row.state : null;
28
- }
29
-
30
- async save(tenantId: string, roomId: string, state: Uint8Array): Promise<void> {
31
- // Copy so a later in-place mutation of the caller's buffer can't
32
- // retroactively corrupt persisted state.
33
- await this.inner.write(tenantId, roomId, {
34
- tenantId,
35
- state: Uint8Array.from(state),
36
- });
37
- }
38
-
39
- /** Test helper. */
40
- clear(): void {
41
- this.inner.clear();
42
- }
43
- }
Binary file
package/src/types.ts DELETED
@@ -1,64 +0,0 @@
1
- import type { DoctorCheck, DoctorReportBase } from "@nebutra/capability-kit";
2
- import type * as Y from "yjs";
3
-
4
- /**
5
- * Pluggable persistence for encoded room state. State is an opaque Yjs
6
- * update (`Uint8Array`); this layer never interprets it. The tenant+room
7
- * pair is the partition key — adapters MUST scope reads/writes by it and
8
- * MUST NOT return one tenant's bytes for another's key.
9
- */
10
- export interface SnapshotStore {
11
- load(tenantId: string, roomId: string): Promise<Uint8Array | null>;
12
- save(tenantId: string, roomId: string, state: Uint8Array): Promise<void>;
13
- }
14
-
15
- /**
16
- * Transport-agnostic fan-out seam. The default is an in-memory loopback;
17
- * a Pusher / WebSocket adapter implements the same two methods and plugs in
18
- * via `createCollab({ transport })`. Updates are opaque Yjs update bytes.
19
- */
20
- export interface CollabTransport {
21
- broadcast(tenantId: string, roomId: string, update: Uint8Array): void | Promise<void>;
22
- /** Subscribe to remote updates for one tenant-scoped room; returns unsub. */
23
- subscribe(tenantId: string, roomId: string, cb: (update: Uint8Array) => void): () => void;
24
- }
25
-
26
- export type UpdateListener = (update: Uint8Array, origin: unknown) => void;
27
-
28
- export interface CollabConfig {
29
- store?: SnapshotStore;
30
- transport?: CollabTransport;
31
- }
32
-
33
- export interface CollabRoom {
34
- readonly tenantId: string;
35
- readonly roomId: string;
36
- readonly doc: Y.Doc;
37
- applyUpdate(update: Uint8Array, origin?: unknown): void;
38
- encodeState(): Uint8Array;
39
- onUpdate(cb: UpdateListener): () => void;
40
- snapshot(): Promise<void>;
41
- destroy(): void;
42
- }
43
-
44
- // DoctorCheck + the {ok,durationMs} base come from the shared
45
- // @nebutra/capability-kit contract; collab only adds its specific probe map.
46
- export type { DoctorCheck };
47
-
48
- export interface DoctorReport extends DoctorReportBase {
49
- readonly checks: {
50
- readonly yjs: DoctorCheck;
51
- readonly store: DoctorCheck;
52
- readonly transport: DoctorCheck;
53
- };
54
- }
55
-
56
- export interface CollabHub {
57
- /** Returns/creates a tenant-scoped CRDT room. Hard-partitioned by tenant. */
58
- room(tenantId: string, roomId: string): CollabRoom;
59
- /** Like `room`, but first hydrates from the snapshot store if present. */
60
- roomRestored(tenantId: string, roomId: string): Promise<CollabRoom>;
61
- doctor(): Promise<DoctorReport>;
62
- /** Destroy every live room and release resources. */
63
- destroy(): void;
64
- }
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "target": "esnext",
7
- "types": ["node"],
8
- "incremental": false
9
- },
10
- "include": ["src", "examples"],
11
- "exclude": ["node_modules", "dist"]
12
- }
package/tsup.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import { defineConfig } from "tsup";
2
-
3
- export default defineConfig({
4
- entry: ["src/index.ts", "src/store/memory.ts", "src/transport/loopback.ts"],
5
- format: ["esm"],
6
- dts: true,
7
- sourcemap: true,
8
- clean: true,
9
- target: "es2022",
10
- external: ["@nebutra/tenant-store"],
11
- });