@trestleinc/replicate 1.1.1 → 1.1.2-preview.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 (91) hide show
  1. package/README.md +395 -146
  2. package/dist/client/index.d.ts +311 -19
  3. package/dist/client/index.js +4027 -0
  4. package/dist/component/_generated/api.d.ts +13 -17
  5. package/dist/component/_generated/api.js +24 -4
  6. package/dist/component/_generated/component.d.ts +79 -77
  7. package/dist/component/_generated/component.js +1 -0
  8. package/dist/component/_generated/dataModel.d.ts +12 -15
  9. package/dist/component/_generated/dataModel.js +1 -0
  10. package/dist/component/_generated/server.d.ts +19 -22
  11. package/dist/component/_generated/server.js +65 -1
  12. package/dist/component/_virtual/rolldown_runtime.js +18 -0
  13. package/dist/component/convex.config.d.ts +6 -2
  14. package/dist/component/convex.config.js +7 -3
  15. package/dist/component/logger.d.ts +10 -6
  16. package/dist/component/logger.js +25 -28
  17. package/dist/component/public.d.ts +70 -61
  18. package/dist/component/public.js +311 -295
  19. package/dist/component/schema.d.ts +53 -45
  20. package/dist/component/schema.js +26 -32
  21. package/dist/component/shared/types.d.ts +9 -0
  22. package/dist/component/shared/types.js +15 -0
  23. package/dist/server/index.d.ts +134 -13
  24. package/dist/server/index.js +368 -0
  25. package/dist/shared/index.d.ts +27 -3
  26. package/dist/shared/index.js +1 -2
  27. package/package.json +34 -29
  28. package/src/client/collection.ts +339 -306
  29. package/src/client/errors.ts +9 -9
  30. package/src/client/index.ts +13 -32
  31. package/src/client/logger.ts +2 -2
  32. package/src/client/merge.ts +37 -34
  33. package/src/client/persistence/custom.ts +84 -0
  34. package/src/client/persistence/index.ts +9 -46
  35. package/src/client/persistence/indexeddb.ts +111 -84
  36. package/src/client/persistence/memory.ts +3 -3
  37. package/src/client/persistence/sqlite/browser.ts +168 -0
  38. package/src/client/persistence/sqlite/native.ts +29 -0
  39. package/src/client/persistence/sqlite/schema.ts +124 -0
  40. package/src/client/persistence/types.ts +32 -28
  41. package/src/client/prose-schema.ts +55 -0
  42. package/src/client/prose.ts +28 -25
  43. package/src/client/replicate.ts +5 -5
  44. package/src/client/services/cursor.ts +109 -0
  45. package/src/component/_generated/component.ts +31 -29
  46. package/src/component/convex.config.ts +2 -2
  47. package/src/component/logger.ts +7 -7
  48. package/src/component/public.ts +225 -237
  49. package/src/component/schema.ts +18 -15
  50. package/src/server/builder.ts +20 -7
  51. package/src/server/index.ts +3 -5
  52. package/src/server/schema.ts +5 -5
  53. package/src/server/storage.ts +113 -59
  54. package/src/shared/index.ts +5 -5
  55. package/src/shared/types.ts +51 -14
  56. package/dist/client/collection.d.ts +0 -96
  57. package/dist/client/errors.d.ts +0 -59
  58. package/dist/client/logger.d.ts +0 -2
  59. package/dist/client/merge.d.ts +0 -77
  60. package/dist/client/persistence/adapters/index.d.ts +0 -8
  61. package/dist/client/persistence/adapters/opsqlite.d.ts +0 -46
  62. package/dist/client/persistence/adapters/sqljs.d.ts +0 -83
  63. package/dist/client/persistence/index.d.ts +0 -49
  64. package/dist/client/persistence/indexeddb.d.ts +0 -17
  65. package/dist/client/persistence/memory.d.ts +0 -16
  66. package/dist/client/persistence/sqlite-browser.d.ts +0 -51
  67. package/dist/client/persistence/sqlite-level.d.ts +0 -63
  68. package/dist/client/persistence/sqlite-rn.d.ts +0 -36
  69. package/dist/client/persistence/sqlite.d.ts +0 -47
  70. package/dist/client/persistence/types.d.ts +0 -42
  71. package/dist/client/prose.d.ts +0 -56
  72. package/dist/client/replicate.d.ts +0 -40
  73. package/dist/client/services/checkpoint.d.ts +0 -18
  74. package/dist/client/services/reconciliation.d.ts +0 -24
  75. package/dist/index.js +0 -1618
  76. package/dist/server/builder.d.ts +0 -94
  77. package/dist/server/schema.d.ts +0 -27
  78. package/dist/server/storage.d.ts +0 -80
  79. package/dist/server.js +0 -281
  80. package/dist/shared/types.d.ts +0 -50
  81. package/dist/shared/types.js +0 -6
  82. package/dist/shared.js +0 -6
  83. package/src/client/persistence/adapters/index.ts +0 -8
  84. package/src/client/persistence/adapters/opsqlite.ts +0 -54
  85. package/src/client/persistence/adapters/sqljs.ts +0 -128
  86. package/src/client/persistence/sqlite-browser.ts +0 -107
  87. package/src/client/persistence/sqlite-level.ts +0 -407
  88. package/src/client/persistence/sqlite-rn.ts +0 -44
  89. package/src/client/persistence/sqlite.ts +0 -160
  90. package/src/client/services/checkpoint.ts +0 -86
  91. package/src/client/services/reconciliation.ts +0 -108
@@ -1,160 +0,0 @@
1
- /**
2
- * Universal SQLite persistence using a user-provided adapter.
3
- *
4
- * The consuming app is responsible for:
5
- * 1. Installing the SQLite package (sql.js, op-sqlite, etc.)
6
- * 2. Creating and initializing the database
7
- * 3. Wrapping it with the appropriate adapter
8
- * 4. Passing the adapter to sqlitePersistence()
9
- *
10
- * @example Browser (sql.js)
11
- * ```typescript
12
- * import initSqlJs from 'sql.js';
13
- * import { sqlitePersistence, SqlJsAdapter } from '@trestleinc/replicate/client';
14
- *
15
- * const SQL = await initSqlJs({ locateFile: file => `/sql-wasm/${file}` });
16
- * const db = new SQL.Database();
17
- * const adapter = new SqlJsAdapter(db, {
18
- * onPersist: async (data) => {
19
- * // Persist to OPFS, localStorage, etc.
20
- * }
21
- * });
22
- * const persistence = await sqlitePersistence({ adapter });
23
- * ```
24
- *
25
- * @example React Native (op-sqlite)
26
- * ```typescript
27
- * import { open } from '@op-engineering/op-sqlite';
28
- * import { sqlitePersistence, OPSqliteAdapter } from '@trestleinc/replicate/client';
29
- *
30
- * const db = open({ name: 'myapp.db' });
31
- * const adapter = new OPSqliteAdapter(db);
32
- * const persistence = await sqlitePersistence({ adapter });
33
- * ```
34
- */
35
- import type * as Y from 'yjs';
36
- import { LeveldbPersistence } from 'y-leveldb';
37
- import { SqliteLevel, type SqliteAdapter } from './sqlite-level.js';
38
- import type { Persistence, PersistenceProvider, KeyValueStore } from './types.js';
39
-
40
- /**
41
- * SQLite-backed key-value store using sqlite-level.
42
- */
43
- class SqliteKeyValueStore implements KeyValueStore {
44
- private db: SqliteLevel<string, string>;
45
- private prefix = 'kv:';
46
-
47
- constructor(db: SqliteLevel<string, string>) {
48
- this.db = db;
49
- }
50
-
51
- async get<T>(key: string): Promise<T | undefined> {
52
- try {
53
- const value = await this.db.get(this.prefix + key);
54
- if (value === undefined) {
55
- return undefined;
56
- }
57
- return JSON.parse(value) as T;
58
- } catch {
59
- return undefined;
60
- }
61
- }
62
-
63
- async set<T>(key: string, value: T): Promise<void> {
64
- await this.db.put(this.prefix + key, JSON.stringify(value));
65
- }
66
-
67
- async del(key: string): Promise<void> {
68
- await this.db.del(this.prefix + key);
69
- }
70
- }
71
-
72
- /**
73
- * SQLite persistence provider using y-leveldb.
74
- */
75
- class SqlitePersistenceProvider implements PersistenceProvider {
76
- private persistence: LeveldbPersistence;
77
- readonly whenSynced: Promise<void>;
78
-
79
- constructor(collection: string, _ydoc: Y.Doc, leveldb: LeveldbPersistence) {
80
- this.persistence = leveldb;
81
- // Load existing document state (may be null for new collections)
82
- this.whenSynced = this.persistence.getYDoc(collection).then((storedDoc: Y.Doc | null) => {
83
- if (storedDoc) {
84
- // Apply stored state to provided ydoc
85
- // The stored doc and ydoc are merged via y-leveldb's internal mechanisms
86
- }
87
- });
88
- }
89
-
90
- destroy(): void {
91
- this.persistence.destroy();
92
- }
93
- }
94
-
95
- /**
96
- * Options for SQLite persistence.
97
- */
98
- export interface SqlitePersistenceOptions {
99
- /**
100
- * Pre-created SQLite adapter (required).
101
- * Use SqlJsAdapter for browser or OPSqliteAdapter for React Native.
102
- */
103
- adapter: SqliteAdapter;
104
-
105
- /**
106
- * Database name for internal y-leveldb usage.
107
- * @default 'replicate'
108
- */
109
- dbName?: string;
110
- }
111
-
112
- /**
113
- * Create a universal SQLite persistence factory.
114
- *
115
- * Requires a pre-created SqliteAdapter - the replicate package does not
116
- * import any SQLite packages directly, making it environment-agnostic.
117
- *
118
- * @param options - Configuration with required adapter
119
- *
120
- * @example Browser (sql.js)
121
- * ```typescript
122
- * import initSqlJs from 'sql.js';
123
- * import { sqlitePersistence, SqlJsAdapter } from '@trestleinc/replicate/client';
124
- *
125
- * const SQL = await initSqlJs();
126
- * const db = new SQL.Database();
127
- * const adapter = new SqlJsAdapter(db);
128
- * const persistence = await sqlitePersistence({ adapter });
129
- * ```
130
- *
131
- * @example React Native (op-sqlite)
132
- * ```typescript
133
- * import { open } from '@op-engineering/op-sqlite';
134
- * import { sqlitePersistence, OPSqliteAdapter } from '@trestleinc/replicate/client';
135
- *
136
- * const db = open({ name: 'myapp.db' });
137
- * const adapter = new OPSqliteAdapter(db);
138
- * const persistence = await sqlitePersistence({ adapter });
139
- * ```
140
- */
141
- export async function sqlitePersistence(options: SqlitePersistenceOptions): Promise<Persistence> {
142
- const { adapter, dbName = 'replicate' } = options;
143
-
144
- // Create sqlite-level database with the provided adapter
145
- const db = new SqliteLevel<string, string>(dbName);
146
- db.setAdapterFactory(() => Promise.resolve(adapter));
147
- await db.open();
148
-
149
- // Create y-leveldb persistence (reuses the sqlite-level database)
150
- const leveldb = new LeveldbPersistence(dbName, { level: db as any });
151
-
152
- // Create key-value store
153
- const kv = new SqliteKeyValueStore(db);
154
-
155
- return {
156
- createDocPersistence: (collection: string, ydoc: Y.Doc) =>
157
- new SqlitePersistenceProvider(collection, ydoc, leveldb),
158
- kv,
159
- };
160
- }
@@ -1,86 +0,0 @@
1
- import { Effect, Context, Layer } from 'effect';
2
- import { IDBError, IDBWriteError } from '$/client/errors.js';
3
- import type { KeyValueStore } from '$/client/persistence/types.js';
4
-
5
- export interface CheckpointData {
6
- lastModified: number;
7
- }
8
-
9
- export class Checkpoint extends Context.Tag('Checkpoint')<
10
- Checkpoint,
11
- {
12
- readonly loadCheckpoint: (collection: string) => Effect.Effect<CheckpointData, IDBError>;
13
- readonly saveCheckpoint: (
14
- collection: string,
15
- checkpoint: CheckpointData
16
- ) => Effect.Effect<void, IDBWriteError>;
17
- readonly clearCheckpoint: (collection: string) => Effect.Effect<void, IDBError>;
18
- }
19
- >() {}
20
-
21
- /**
22
- * Create a Checkpoint service layer using the provided KeyValueStore.
23
- */
24
- export function createCheckpointLayer(kv: KeyValueStore) {
25
- return Layer.succeed(
26
- Checkpoint,
27
- Checkpoint.of({
28
- loadCheckpoint: (collection) =>
29
- Effect.gen(function* (_) {
30
- const key = `checkpoint:${collection}`;
31
- const stored = yield* _(
32
- Effect.tryPromise({
33
- try: () => kv.get<CheckpointData>(key),
34
- catch: (cause) => new IDBError({ operation: 'get', key, cause }),
35
- })
36
- );
37
-
38
- if (stored) {
39
- yield* _(
40
- Effect.logDebug('Loaded checkpoint from storage', {
41
- collection,
42
- checkpoint: stored,
43
- })
44
- );
45
- return stored;
46
- }
47
-
48
- yield* _(
49
- Effect.logDebug('No stored checkpoint, using default', {
50
- collection,
51
- })
52
- );
53
- return { lastModified: 0 };
54
- }),
55
-
56
- saveCheckpoint: (collection, checkpoint) =>
57
- Effect.gen(function* (_) {
58
- const key = `checkpoint:${collection}`;
59
- yield* _(
60
- Effect.tryPromise({
61
- try: () => kv.set(key, checkpoint),
62
- catch: (cause) => new IDBWriteError({ key, value: checkpoint, cause }),
63
- })
64
- );
65
- yield* _(
66
- Effect.logDebug('Checkpoint saved', {
67
- collection,
68
- checkpoint,
69
- })
70
- );
71
- }),
72
-
73
- clearCheckpoint: (collection) =>
74
- Effect.gen(function* (_) {
75
- const key = `checkpoint:${collection}`;
76
- yield* _(
77
- Effect.tryPromise({
78
- try: () => kv.del(key),
79
- catch: (cause) => new IDBError({ operation: 'delete', key, cause }),
80
- })
81
- );
82
- yield* _(Effect.logDebug('Checkpoint cleared', { collection }));
83
- }),
84
- })
85
- );
86
- }
@@ -1,108 +0,0 @@
1
- import { Effect, Context, Layer } from 'effect';
2
- import * as Y from 'yjs';
3
- import { yjsTransact, serializeYMap } from '$/client/merge.js';
4
- import { ReconciliationError as ReconciliationErrorImport } from '$/client/errors.js';
5
-
6
- /**
7
- * Reconciliation handles removal of phantom documents -
8
- * documents that exist locally but have been deleted on the server.
9
- */
10
- export class Reconciliation extends Context.Tag('Reconciliation')<
11
- Reconciliation,
12
- {
13
- /**
14
- * Reconciles local Yjs state with server state by removing phantom documents.
15
- * Uses an existing Yjs document and map instead of creating new ones.
16
- *
17
- * @param ydoc - Existing Yjs document
18
- * @param ymap - Existing Yjs map within the document
19
- * @param collection - Collection name for logging
20
- * @param serverDocs - Documents from server
21
- * @param getKey - Function to extract key from document
22
- */
23
- readonly reconcile: <T>(
24
- ydoc: Y.Doc,
25
- ymap: Y.Map<unknown>,
26
- collection: string,
27
- serverDocs: readonly T[],
28
- getKey: (doc: T) => string
29
- ) => Effect.Effect<T[], ReconciliationErrorImport>;
30
- }
31
- >() {}
32
-
33
- export const ReconciliationLive = Layer.succeed(
34
- Reconciliation,
35
- Reconciliation.of({
36
- reconcile: <T>(
37
- ydoc: Y.Doc,
38
- ymap: Y.Map<unknown>,
39
- collection: string,
40
- serverDocs: readonly T[],
41
- getKey: (doc: T) => string
42
- ) =>
43
- Effect.gen(function* (_) {
44
- const serverDocIds = new Set(serverDocs.map(getKey));
45
- const toDelete: string[] = [];
46
-
47
- // Find phantom documents (exist locally but not on server)
48
- ymap.forEach((_, key) => {
49
- if (!serverDocIds.has(key)) {
50
- toDelete.push(key);
51
- }
52
- });
53
-
54
- if (toDelete.length === 0) {
55
- yield* _(Effect.logDebug('No phantom documents found', { collection }));
56
- return [];
57
- }
58
-
59
- yield* _(
60
- Effect.logWarning(`Found ${toDelete.length} phantom documents`, {
61
- collection,
62
- phantomDocs: toDelete.slice(0, 10), // Log first 10
63
- })
64
- );
65
-
66
- // Extract items before deletion for TanStack DB sync
67
- // Use serializeYMap for consistent ProseMirror JSON (not XML string from toJSON)
68
- const removedItems: T[] = [];
69
- for (const key of toDelete) {
70
- const itemYMap = ymap.get(key);
71
- if (itemYMap instanceof Y.Map) {
72
- removedItems.push(serializeYMap(itemYMap) as T);
73
- }
74
- }
75
-
76
- // Remove from Yjs using plain function
77
- yjsTransact(
78
- ydoc,
79
- () => {
80
- for (const key of toDelete) {
81
- ymap.delete(key);
82
- }
83
- },
84
- 'reconciliation'
85
- );
86
-
87
- yield* _(
88
- Effect.logInfo('Reconciliation completed', {
89
- collection,
90
- deletedCount: removedItems.length,
91
- })
92
- );
93
-
94
- // Return removed items for TanStack DB sync
95
- return removedItems;
96
- }).pipe(
97
- Effect.catchAll((cause) =>
98
- Effect.fail(
99
- new ReconciliationErrorImport({
100
- collection,
101
- reason: 'Reconciliation failed',
102
- cause,
103
- })
104
- )
105
- )
106
- ),
107
- })
108
- );