@fieldnotes/sync-redis 0.2.0 → 0.3.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/README.md +5 -1
- package/dist/index.cjs +10 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ pnpm add ioredis
|
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
`@fieldnotes/sync-redis` has **no Redis dependency of its own** — you inject a client that satisfies a
|
|
17
|
-
minimal `RedisHashClient` interface (`hGetAll` / `hSet` / `hDel` / `del`).
|
|
17
|
+
minimal `RedisHashClient` interface (`hGetAll` / `hGet` / `hSet` / `hDel` / `del`).
|
|
18
18
|
|
|
19
19
|
## node-redis v4 (direct)
|
|
20
20
|
|
|
@@ -41,6 +41,7 @@ import { RedisHubBackend } from '@fieldnotes/sync-redis';
|
|
|
41
41
|
const io = new Redis(process.env.REDIS_URL);
|
|
42
42
|
const client = {
|
|
43
43
|
hGetAll: (k: string) => io.hgetall(k),
|
|
44
|
+
hGet: (k: string, f: string) => io.hget(k, f),
|
|
44
45
|
hSet: (k: string, f: string, v: string) => io.hset(k, f, v),
|
|
45
46
|
hDel: (k: string, f: string) => io.hdel(k, f),
|
|
46
47
|
del: (k: string) => io.del(k),
|
|
@@ -55,6 +56,9 @@ Each room is stored as a Redis **HASH** at `{keyPrefix}{room}` (default prefix `
|
|
|
55
56
|
- **field** = element id
|
|
56
57
|
- **value** = `JSON.stringify(element)`
|
|
57
58
|
|
|
59
|
+
node-redis v4 conforms to `RedisHashClient` directly (it has `hGet`); `RedisHubBackend.get(room, id)`
|
|
60
|
+
(via `HGET`) powers the relay's ownership lookups for write authorization (D2).
|
|
61
|
+
|
|
58
62
|
The prefix is configurable:
|
|
59
63
|
|
|
60
64
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,16 @@ var RedisHubBackend = class {
|
|
|
51
51
|
}
|
|
52
52
|
return out;
|
|
53
53
|
}
|
|
54
|
+
async get(room, id) {
|
|
55
|
+
const value = await this.client.hGet(this.key(room), id);
|
|
56
|
+
if (value == null) return void 0;
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(value);
|
|
59
|
+
return (0, import_sync.isValidElement)(parsed) ? parsed : void 0;
|
|
60
|
+
} catch {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
54
64
|
async apply(room, op) {
|
|
55
65
|
const key = this.key(room);
|
|
56
66
|
if (op.kind === "upsert")
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/redis-hub-backend.ts","../src/redis-hub-fanout.ts"],"sourcesContent":["export { RedisHubBackend } from './redis-hub-backend';\nexport type { RedisHubBackendOptions } from './redis-hub-backend';\nexport type { RedisHashClient } from './redis-hash-client';\nexport { RedisHubFanout } from './redis-hub-fanout';\nexport type { RedisHubFanoutOptions } from './redis-hub-fanout';\nexport type { RedisPublisher, RedisSubscriber } from './redis-fanout-client';\n","import { isValidElement, type SyncOp } from '@fieldnotes/sync';\
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/redis-hub-backend.ts","../src/redis-hub-fanout.ts"],"sourcesContent":["export { RedisHubBackend } from './redis-hub-backend';\r\nexport type { RedisHubBackendOptions } from './redis-hub-backend';\r\nexport type { RedisHashClient } from './redis-hash-client';\r\nexport { RedisHubFanout } from './redis-hub-fanout';\r\nexport type { RedisHubFanoutOptions } from './redis-hub-fanout';\r\nexport type { RedisPublisher, RedisSubscriber } from './redis-fanout-client';\r\n","import { isValidElement, type SyncOp } from '@fieldnotes/sync';\nimport type { CanvasElement } from '@fieldnotes/core';\nimport type { HubBackend } from '@fieldnotes/sync-server';\nimport type { RedisHashClient } from './redis-hash-client';\n\nexport interface RedisHubBackendOptions {\n keyPrefix?: string; // default 'fieldnotes:room:'\n}\n\nexport class RedisHubBackend implements HubBackend {\n private readonly client: RedisHashClient;\n private readonly keyPrefix: string;\n\n constructor(client: RedisHashClient, options: RedisHubBackendOptions = {}) {\n this.client = client;\n this.keyPrefix = options.keyPrefix ?? 'fieldnotes:room:';\n }\n\n private key(room: string): string {\n return `${this.keyPrefix}${room}`;\n }\n\n async snapshot(room: string): Promise<CanvasElement[]> {\n const map = await this.client.hGetAll(this.key(room));\n const out: CanvasElement[] = [];\n for (const value of Object.values(map)) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n continue; // skip a corrupt stored value rather than throwing the whole snapshot\n }\n if (isValidElement(parsed)) out.push(parsed);\n }\n return out;\n }\n\n async get(room: string, id: string): Promise<CanvasElement | undefined> {\n const value = await this.client.hGet(this.key(room), id);\n if (value == null) return undefined;\n try {\n const parsed: unknown = JSON.parse(value);\n return isValidElement(parsed) ? parsed : undefined;\n } catch {\n return undefined;\n }\n }\n\n async apply(room: string, op: SyncOp): Promise<void> {\n const key = this.key(room);\n if (op.kind === 'upsert')\n await this.client.hSet(key, op.element.id, JSON.stringify(op.element));\n else if (op.kind === 'remove') await this.client.hDel(key, op.id);\n else if (op.kind === 'clear') await this.client.del(key);\n // request-snapshot/snapshot never reach apply (the hub only applies data ops)\n }\n}\n","import type { HubFanout } from '@fieldnotes/sync-server';\r\nimport type { RedisPublisher, RedisSubscriber } from './redis-fanout-client';\r\n\r\nexport interface RedisHubFanoutOptions {\r\n channel?: string;\r\n onError?: (err: unknown) => void;\r\n}\r\n\r\nexport class RedisHubFanout implements HubFanout {\r\n private readonly publisher: RedisPublisher;\r\n private readonly subscriber: RedisSubscriber;\r\n private readonly channel: string;\r\n private readonly onError: (err: unknown) => void;\r\n private readonly handlers = new Set<(payload: string) => void>();\r\n private subscribed = false;\r\n\r\n constructor(\r\n publisher: RedisPublisher,\r\n subscriber: RedisSubscriber,\r\n options: RedisHubFanoutOptions = {},\r\n ) {\r\n this.publisher = publisher;\r\n this.subscriber = subscriber;\r\n this.channel = options.channel ?? 'fieldnotes:fanout';\r\n this.onError = options.onError ?? (() => undefined);\r\n }\r\n\r\n publish(payload: string): void {\r\n Promise.resolve(this.publisher.publish(this.channel, payload)).catch(this.onError);\r\n }\r\n\r\n subscribe(handler: (payload: string) => void): () => void {\r\n this.handlers.add(handler);\r\n if (!this.subscribed) {\r\n this.subscribed = true;\r\n Promise.resolve(\r\n this.subscriber.subscribe(this.channel, (message) => {\r\n for (const h of this.handlers) {\r\n try {\r\n h(message);\r\n } catch {\r\n /* isolate handlers */\r\n }\r\n }\r\n }),\r\n ).catch(this.onError);\r\n }\r\n return () => this.handlers.delete(handler);\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA4C;AASrC,IAAM,kBAAN,MAA4C;AAAA,EAChC;AAAA,EACA;AAAA,EAEjB,YAAY,QAAyB,UAAkC,CAAC,GAAG;AACzE,SAAK,SAAS;AACd,SAAK,YAAY,QAAQ,aAAa;AAAA,EACxC;AAAA,EAEQ,IAAI,MAAsB;AAChC,WAAO,GAAG,KAAK,SAAS,GAAG,IAAI;AAAA,EACjC;AAAA,EAEA,MAAM,SAAS,MAAwC;AACrD,UAAM,MAAM,MAAM,KAAK,OAAO,QAAQ,KAAK,IAAI,IAAI,CAAC;AACpD,UAAM,MAAuB,CAAC;AAC9B,eAAW,SAAS,OAAO,OAAO,GAAG,GAAG;AACtC,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,MAAM,KAAK;AAAA,MAC3B,QAAQ;AACN;AAAA,MACF;AACA,cAAI,4BAAe,MAAM,EAAG,KAAI,KAAK,MAAM;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,MAAc,IAAgD;AACtE,UAAM,QAAQ,MAAM,KAAK,OAAO,KAAK,KAAK,IAAI,IAAI,GAAG,EAAE;AACvD,QAAI,SAAS,KAAM,QAAO;AAC1B,QAAI;AACF,YAAM,SAAkB,KAAK,MAAM,KAAK;AACxC,iBAAO,4BAAe,MAAM,IAAI,SAAS;AAAA,IAC3C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,MAAc,IAA2B;AACnD,UAAM,MAAM,KAAK,IAAI,IAAI;AACzB,QAAI,GAAG,SAAS;AACd,YAAM,KAAK,OAAO,KAAK,KAAK,GAAG,QAAQ,IAAI,KAAK,UAAU,GAAG,OAAO,CAAC;AAAA,aAC9D,GAAG,SAAS,SAAU,OAAM,KAAK,OAAO,KAAK,KAAK,GAAG,EAAE;AAAA,aACvD,GAAG,SAAS,QAAS,OAAM,KAAK,OAAO,IAAI,GAAG;AAAA,EAEzD;AACF;;;AChDO,IAAM,iBAAN,MAA0C;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW,oBAAI,IAA+B;AAAA,EACvD,aAAa;AAAA,EAErB,YACE,WACA,YACA,UAAiC,CAAC,GAClC;AACA,SAAK,YAAY;AACjB,SAAK,aAAa;AAClB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,UAAU,QAAQ,YAAY,MAAM;AAAA,EAC3C;AAAA,EAEA,QAAQ,SAAuB;AAC7B,YAAQ,QAAQ,KAAK,UAAU,QAAQ,KAAK,SAAS,OAAO,CAAC,EAAE,MAAM,KAAK,OAAO;AAAA,EACnF;AAAA,EAEA,UAAU,SAAgD;AACxD,SAAK,SAAS,IAAI,OAAO;AACzB,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa;AAClB,cAAQ;AAAA,QACN,KAAK,WAAW,UAAU,KAAK,SAAS,CAAC,YAAY;AACnD,qBAAW,KAAK,KAAK,UAAU;AAC7B,gBAAI;AACF,gBAAE,OAAO;AAAA,YACX,QAAQ;AAAA,YAER;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH,EAAE,MAAM,KAAK,OAAO;AAAA,IACtB;AACA,WAAO,MAAM,KAAK,SAAS,OAAO,OAAO;AAAA,EAC3C;AACF;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -4,6 +4,7 @@ import { HubBackend, HubFanout } from '@fieldnotes/sync-server';
|
|
|
4
4
|
|
|
5
5
|
interface RedisHashClient {
|
|
6
6
|
hGetAll(key: string): Promise<Record<string, string>>;
|
|
7
|
+
hGet(key: string, field: string): Promise<string | null>;
|
|
7
8
|
hSet(key: string, field: string, value: string): Promise<unknown>;
|
|
8
9
|
hDel(key: string, field: string): Promise<unknown>;
|
|
9
10
|
del(key: string): Promise<unknown>;
|
|
@@ -18,6 +19,7 @@ declare class RedisHubBackend implements HubBackend {
|
|
|
18
19
|
constructor(client: RedisHashClient, options?: RedisHubBackendOptions);
|
|
19
20
|
private key;
|
|
20
21
|
snapshot(room: string): Promise<CanvasElement[]>;
|
|
22
|
+
get(room: string, id: string): Promise<CanvasElement | undefined>;
|
|
21
23
|
apply(room: string, op: SyncOp): Promise<void>;
|
|
22
24
|
}
|
|
23
25
|
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { HubBackend, HubFanout } from '@fieldnotes/sync-server';
|
|
|
4
4
|
|
|
5
5
|
interface RedisHashClient {
|
|
6
6
|
hGetAll(key: string): Promise<Record<string, string>>;
|
|
7
|
+
hGet(key: string, field: string): Promise<string | null>;
|
|
7
8
|
hSet(key: string, field: string, value: string): Promise<unknown>;
|
|
8
9
|
hDel(key: string, field: string): Promise<unknown>;
|
|
9
10
|
del(key: string): Promise<unknown>;
|
|
@@ -18,6 +19,7 @@ declare class RedisHubBackend implements HubBackend {
|
|
|
18
19
|
constructor(client: RedisHashClient, options?: RedisHubBackendOptions);
|
|
19
20
|
private key;
|
|
20
21
|
snapshot(room: string): Promise<CanvasElement[]>;
|
|
22
|
+
get(room: string, id: string): Promise<CanvasElement | undefined>;
|
|
21
23
|
apply(room: string, op: SyncOp): Promise<void>;
|
|
22
24
|
}
|
|
23
25
|
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,16 @@ var RedisHubBackend = class {
|
|
|
24
24
|
}
|
|
25
25
|
return out;
|
|
26
26
|
}
|
|
27
|
+
async get(room, id) {
|
|
28
|
+
const value = await this.client.hGet(this.key(room), id);
|
|
29
|
+
if (value == null) return void 0;
|
|
30
|
+
try {
|
|
31
|
+
const parsed = JSON.parse(value);
|
|
32
|
+
return isValidElement(parsed) ? parsed : void 0;
|
|
33
|
+
} catch {
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
27
37
|
async apply(room, op) {
|
|
28
38
|
const key = this.key(room);
|
|
29
39
|
if (op.kind === "upsert")
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/redis-hub-backend.ts","../src/redis-hub-fanout.ts"],"sourcesContent":["import { isValidElement, type SyncOp } from '@fieldnotes/sync';\
|
|
1
|
+
{"version":3,"sources":["../src/redis-hub-backend.ts","../src/redis-hub-fanout.ts"],"sourcesContent":["import { isValidElement, type SyncOp } from '@fieldnotes/sync';\nimport type { CanvasElement } from '@fieldnotes/core';\nimport type { HubBackend } from '@fieldnotes/sync-server';\nimport type { RedisHashClient } from './redis-hash-client';\n\nexport interface RedisHubBackendOptions {\n keyPrefix?: string; // default 'fieldnotes:room:'\n}\n\nexport class RedisHubBackend implements HubBackend {\n private readonly client: RedisHashClient;\n private readonly keyPrefix: string;\n\n constructor(client: RedisHashClient, options: RedisHubBackendOptions = {}) {\n this.client = client;\n this.keyPrefix = options.keyPrefix ?? 'fieldnotes:room:';\n }\n\n private key(room: string): string {\n return `${this.keyPrefix}${room}`;\n }\n\n async snapshot(room: string): Promise<CanvasElement[]> {\n const map = await this.client.hGetAll(this.key(room));\n const out: CanvasElement[] = [];\n for (const value of Object.values(map)) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n continue; // skip a corrupt stored value rather than throwing the whole snapshot\n }\n if (isValidElement(parsed)) out.push(parsed);\n }\n return out;\n }\n\n async get(room: string, id: string): Promise<CanvasElement | undefined> {\n const value = await this.client.hGet(this.key(room), id);\n if (value == null) return undefined;\n try {\n const parsed: unknown = JSON.parse(value);\n return isValidElement(parsed) ? parsed : undefined;\n } catch {\n return undefined;\n }\n }\n\n async apply(room: string, op: SyncOp): Promise<void> {\n const key = this.key(room);\n if (op.kind === 'upsert')\n await this.client.hSet(key, op.element.id, JSON.stringify(op.element));\n else if (op.kind === 'remove') await this.client.hDel(key, op.id);\n else if (op.kind === 'clear') await this.client.del(key);\n // request-snapshot/snapshot never reach apply (the hub only applies data ops)\n }\n}\n","import type { HubFanout } from '@fieldnotes/sync-server';\r\nimport type { RedisPublisher, RedisSubscriber } from './redis-fanout-client';\r\n\r\nexport interface RedisHubFanoutOptions {\r\n channel?: string;\r\n onError?: (err: unknown) => void;\r\n}\r\n\r\nexport class RedisHubFanout implements HubFanout {\r\n private readonly publisher: RedisPublisher;\r\n private readonly subscriber: RedisSubscriber;\r\n private readonly channel: string;\r\n private readonly onError: (err: unknown) => void;\r\n private readonly handlers = new Set<(payload: string) => void>();\r\n private subscribed = false;\r\n\r\n constructor(\r\n publisher: RedisPublisher,\r\n subscriber: RedisSubscriber,\r\n options: RedisHubFanoutOptions = {},\r\n ) {\r\n this.publisher = publisher;\r\n this.subscriber = subscriber;\r\n this.channel = options.channel ?? 'fieldnotes:fanout';\r\n this.onError = options.onError ?? (() => undefined);\r\n }\r\n\r\n publish(payload: string): void {\r\n Promise.resolve(this.publisher.publish(this.channel, payload)).catch(this.onError);\r\n }\r\n\r\n subscribe(handler: (payload: string) => void): () => void {\r\n this.handlers.add(handler);\r\n if (!this.subscribed) {\r\n this.subscribed = true;\r\n Promise.resolve(\r\n this.subscriber.subscribe(this.channel, (message) => {\r\n for (const h of this.handlers) {\r\n try {\r\n h(message);\r\n } catch {\r\n /* isolate handlers */\r\n }\r\n }\r\n }),\r\n ).catch(this.onError);\r\n }\r\n return () => this.handlers.delete(handler);\r\n }\r\n}\r\n"],"mappings":";AAAA,SAAS,sBAAmC;AASrC,IAAM,kBAAN,MAA4C;AAAA,EAChC;AAAA,EACA;AAAA,EAEjB,YAAY,QAAyB,UAAkC,CAAC,GAAG;AACzE,SAAK,SAAS;AACd,SAAK,YAAY,QAAQ,aAAa;AAAA,EACxC;AAAA,EAEQ,IAAI,MAAsB;AAChC,WAAO,GAAG,KAAK,SAAS,GAAG,IAAI;AAAA,EACjC;AAAA,EAEA,MAAM,SAAS,MAAwC;AACrD,UAAM,MAAM,MAAM,KAAK,OAAO,QAAQ,KAAK,IAAI,IAAI,CAAC;AACpD,UAAM,MAAuB,CAAC;AAC9B,eAAW,SAAS,OAAO,OAAO,GAAG,GAAG;AACtC,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,MAAM,KAAK;AAAA,MAC3B,QAAQ;AACN;AAAA,MACF;AACA,UAAI,eAAe,MAAM,EAAG,KAAI,KAAK,MAAM;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,MAAc,IAAgD;AACtE,UAAM,QAAQ,MAAM,KAAK,OAAO,KAAK,KAAK,IAAI,IAAI,GAAG,EAAE;AACvD,QAAI,SAAS,KAAM,QAAO;AAC1B,QAAI;AACF,YAAM,SAAkB,KAAK,MAAM,KAAK;AACxC,aAAO,eAAe,MAAM,IAAI,SAAS;AAAA,IAC3C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,MAAc,IAA2B;AACnD,UAAM,MAAM,KAAK,IAAI,IAAI;AACzB,QAAI,GAAG,SAAS;AACd,YAAM,KAAK,OAAO,KAAK,KAAK,GAAG,QAAQ,IAAI,KAAK,UAAU,GAAG,OAAO,CAAC;AAAA,aAC9D,GAAG,SAAS,SAAU,OAAM,KAAK,OAAO,KAAK,KAAK,GAAG,EAAE;AAAA,aACvD,GAAG,SAAS,QAAS,OAAM,KAAK,OAAO,IAAI,GAAG;AAAA,EAEzD;AACF;;;AChDO,IAAM,iBAAN,MAA0C;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW,oBAAI,IAA+B;AAAA,EACvD,aAAa;AAAA,EAErB,YACE,WACA,YACA,UAAiC,CAAC,GAClC;AACA,SAAK,YAAY;AACjB,SAAK,aAAa;AAClB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,UAAU,QAAQ,YAAY,MAAM;AAAA,EAC3C;AAAA,EAEA,QAAQ,SAAuB;AAC7B,YAAQ,QAAQ,KAAK,UAAU,QAAQ,KAAK,SAAS,OAAO,CAAC,EAAE,MAAM,KAAK,OAAO;AAAA,EACnF;AAAA,EAEA,UAAU,SAAgD;AACxD,SAAK,SAAS,IAAI,OAAO;AACzB,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa;AAClB,cAAQ;AAAA,QACN,KAAK,WAAW,UAAU,KAAK,SAAS,CAAC,YAAY;AACnD,qBAAW,KAAK,KAAK,UAAU;AAC7B,gBAAI;AACF,gBAAE,OAAO;AAAA,YACX,QAAQ;AAAA,YAER;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH,EAAE,MAAM,KAAK,OAAO;AAAA,IACtB;AACA,WAAO,MAAM,KAAK,SAAS,OAAO,OAAO;AAAA,EAC3C;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fieldnotes/sync-redis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Redis-backed HubBackend for Field Notes real-time sync relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"fieldnotes"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@fieldnotes/sync": "0.
|
|
39
|
+
"@fieldnotes/sync": "0.5.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@vitest/coverage-v8": "^4.1.0",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
44
|
"vitest": "^4.1.0",
|
|
45
|
-
"@fieldnotes/
|
|
46
|
-
"@fieldnotes/
|
|
45
|
+
"@fieldnotes/core": "0.46.0",
|
|
46
|
+
"@fieldnotes/sync-server": "0.4.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsup",
|