js-bao 0.2.9 → 0.2.11

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/dist/browser.cjs CHANGED
@@ -1310,7 +1310,7 @@ var init_BaseModel = __esm({
1310
1310
  Object.setPrototypeOf(this, _RecordNotFoundError.prototype);
1311
1311
  }
1312
1312
  };
1313
- SCHEMA_ACCESSORS_KEY = Symbol("jsBaoSchemaAccessors");
1313
+ SCHEMA_ACCESSORS_KEY = /* @__PURE__ */ Symbol("jsBaoSchemaAccessors");
1314
1314
  BaseModel = class _BaseModel {
1315
1315
  static modelName;
1316
1316
  static listenersMap = /* @__PURE__ */ new Map();
@@ -2093,6 +2093,114 @@ var init_BaseModel = __esm({
2093
2093
  Logger.verbose(
2094
2094
  `[${this.name}] Setting up YMap observer for document ${docId}/${modelName}...`
2095
2095
  );
2096
+ const buildUniqueKey = (recordData, fields) => {
2097
+ const keyParts = [];
2098
+ for (const field of fields) {
2099
+ const value = recordData instanceof Y.Map ? recordData.get(field) : recordData[field];
2100
+ if (value === null || value === void 0) {
2101
+ return null;
2102
+ }
2103
+ keyParts.push(String(value));
2104
+ }
2105
+ return keyParts.join("|");
2106
+ };
2107
+ const extractItemData = (key, recordData) => {
2108
+ let itemData;
2109
+ if (recordData instanceof Y.Map) {
2110
+ itemData = {};
2111
+ const unknownFields = [];
2112
+ for (const [fieldKey, value] of recordData.entries()) {
2113
+ const fieldOptions = schema.fields.get(fieldKey);
2114
+ if (!fieldOptions) {
2115
+ unknownFields.push(fieldKey);
2116
+ continue;
2117
+ }
2118
+ if (fieldOptions.type === "stringset") {
2119
+ continue;
2120
+ }
2121
+ if (value !== void 0) {
2122
+ itemData[fieldKey] = value;
2123
+ }
2124
+ }
2125
+ if (unknownFields.length > 0) {
2126
+ Logger.warn(
2127
+ `[${this.name}] Ignoring unknown fields [${unknownFields.join(
2128
+ ", "
2129
+ )}] when syncing record ${key} from document ${docId}`
2130
+ );
2131
+ }
2132
+ } else {
2133
+ const unknownFields = [];
2134
+ const filteredData = {};
2135
+ for (const [fieldKey, value] of Object.entries(recordData)) {
2136
+ const fieldOptions = schema.fields.get(fieldKey);
2137
+ if (!fieldOptions) {
2138
+ unknownFields.push(fieldKey);
2139
+ continue;
2140
+ }
2141
+ if (fieldOptions.type === "stringset") {
2142
+ continue;
2143
+ }
2144
+ if (value !== void 0) {
2145
+ filteredData[fieldKey] = value;
2146
+ }
2147
+ }
2148
+ if (unknownFields.length > 0) {
2149
+ Logger.warn(
2150
+ `[${this.name}] Ignoring unknown fields [${unknownFields.join(
2151
+ ", "
2152
+ )}] when syncing legacy record ${key} from document ${docId}`
2153
+ );
2154
+ }
2155
+ itemData = filteredData;
2156
+ }
2157
+ if (!itemData.id) return null;
2158
+ return itemData;
2159
+ };
2160
+ const resolveConflictsForBatch = (candidateRecords) => {
2161
+ if (schema.resolvedUniqueConstraints.length === 0) {
2162
+ return /* @__PURE__ */ new Set();
2163
+ }
2164
+ const recordIdsToDiscard = /* @__PURE__ */ new Set();
2165
+ const recordIdsToKeep = /* @__PURE__ */ new Set();
2166
+ const allRecords = /* @__PURE__ */ new Map();
2167
+ for (const [recordId, recordData] of documentYMap.entries()) {
2168
+ if (recordData && !candidateRecords.has(recordId)) {
2169
+ allRecords.set(recordId, recordData);
2170
+ }
2171
+ }
2172
+ for (const [recordId, recordData] of candidateRecords.entries()) {
2173
+ allRecords.set(recordId, recordData);
2174
+ }
2175
+ for (const constraint of schema.resolvedUniqueConstraints) {
2176
+ const recordsByUniqueKey = /* @__PURE__ */ new Map();
2177
+ for (const [recordId, recordData] of allRecords.entries()) {
2178
+ if (recordIdsToDiscard.has(recordId)) continue;
2179
+ const uniqueKey = buildUniqueKey(recordData, constraint.fields);
2180
+ if (uniqueKey === null) continue;
2181
+ if (!recordsByUniqueKey.has(uniqueKey)) {
2182
+ recordsByUniqueKey.set(uniqueKey, []);
2183
+ }
2184
+ recordsByUniqueKey.get(uniqueKey).push(recordId);
2185
+ }
2186
+ for (const [uniqueKey, recordIds] of recordsByUniqueKey.entries()) {
2187
+ if (recordIds.length <= 1) continue;
2188
+ Logger.warn(
2189
+ `[${this.name}] CRDT conflict detected for unique constraint '${constraint.name}' on key '${uniqueKey.substring(0, 50)}${uniqueKey.length > 50 ? "..." : ""}': ${recordIds.length} records found.`
2190
+ );
2191
+ recordIds.sort();
2192
+ const idToKeep = recordIds[recordIds.length - 1];
2193
+ recordIdsToKeep.add(idToKeep);
2194
+ for (let i = 0; i < recordIds.length - 1; i++) {
2195
+ recordIdsToDiscard.add(recordIds[i]);
2196
+ }
2197
+ }
2198
+ }
2199
+ for (const idToKeep of recordIdsToKeep) {
2200
+ recordIdsToDiscard.delete(idToKeep);
2201
+ }
2202
+ return recordIdsToDiscard;
2203
+ };
2096
2204
  documentYMap.observe(async (event) => {
2097
2205
  Logger.verbose(
2098
2206
  `[${this.name}] Document YMap change detected for ${modelName}/${docId}:`,
@@ -2105,89 +2213,31 @@ var init_BaseModel = __esm({
2105
2213
  );
2106
2214
  return;
2107
2215
  }
2216
+ const isRemoteChange = !event.transaction.local;
2217
+ const remoteAdds = /* @__PURE__ */ new Map();
2218
+ const localAddsAndUpdates = [];
2108
2219
  for (const [key, change] of event.changes.keys.entries()) {
2109
2220
  const recordData = documentYMap.get(key);
2110
2221
  if (change.action === "add" || change.action === "update") {
2111
2222
  if (!recordData || !key) continue;
2112
- let itemData;
2113
- if (recordData instanceof Y.Map) {
2114
- itemData = {};
2115
- const unknownFields = [];
2116
- for (const [fieldKey, value] of recordData.entries()) {
2117
- const fieldOptions = schema.fields.get(fieldKey);
2118
- if (!fieldOptions) {
2119
- unknownFields.push(fieldKey);
2120
- continue;
2121
- }
2122
- if (fieldOptions.type === "stringset") {
2123
- continue;
2124
- }
2125
- if (value !== void 0) {
2126
- itemData[fieldKey] = value;
2127
- }
2128
- }
2129
- if (unknownFields.length > 0) {
2130
- Logger.warn(
2131
- `[${this.name}] Ignoring unknown fields [${unknownFields.join(
2132
- ", "
2133
- )}] when syncing record ${key} from document ${docId}`
2134
- );
2135
- }
2136
- if (change.action === "add") {
2137
- Logger.verbose(
2138
- `[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
2139
- );
2140
- this.setupNestedYMapObserverForDocument(
2141
- key,
2142
- recordData,
2143
- docId,
2144
- permissionHint
2145
- );
2146
- }
2147
- } else {
2148
- const unknownFields = [];
2149
- const filteredData = {};
2150
- for (const [fieldKey, value] of Object.entries(recordData)) {
2151
- const fieldOptions = schema.fields.get(fieldKey);
2152
- if (!fieldOptions) {
2153
- unknownFields.push(fieldKey);
2154
- continue;
2155
- }
2156
- if (fieldOptions.type === "stringset") {
2157
- continue;
2158
- }
2159
- if (value !== void 0) {
2160
- filteredData[fieldKey] = value;
2161
- }
2162
- }
2163
- if (unknownFields.length > 0) {
2164
- Logger.warn(
2165
- `[${this.name}] Ignoring unknown fields [${unknownFields.join(
2166
- ", "
2167
- )}] when syncing legacy record ${key} from document ${docId}`
2168
- );
2169
- }
2170
- itemData = filteredData;
2171
- }
2172
- if (!itemData.id) continue;
2173
- try {
2223
+ const itemData = extractItemData(key, recordData);
2224
+ if (!itemData) continue;
2225
+ if (change.action === "add" && recordData instanceof Y.Map) {
2174
2226
  Logger.verbose(
2175
- `[${this.name}] Syncing item to db from document ${docId} (${modelName}):`,
2176
- itemData
2227
+ `[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
2177
2228
  );
2178
- await currentDbInstance.insert(modelName, {
2179
- ...itemData,
2180
- type: modelName,
2181
- _meta_doc_id: docId,
2182
- _meta_permission_hint: permissionHint
2183
- });
2184
- } catch (error) {
2185
- Logger.error(
2186
- `[${this.name}] Error syncing item to db from document ${docId} (${modelName}):`,
2187
- error,
2188
- itemData
2229
+ this.setupNestedYMapObserverForDocument(
2230
+ key,
2231
+ recordData,
2232
+ docId,
2233
+ permissionHint
2189
2234
  );
2190
2235
  }
2236
+ if (isRemoteChange && change.action === "add") {
2237
+ remoteAdds.set(key, { recordData, itemData });
2238
+ } else {
2239
+ localAddsAndUpdates.push({ key, action: change.action, recordData, itemData });
2240
+ }
2191
2241
  } else if (change.action === "delete") {
2192
2242
  Logger.verbose(
2193
2243
  `[${this.name}] Deleting item from db (${modelName}/${docId}):`,
@@ -2204,6 +2254,110 @@ var init_BaseModel = __esm({
2204
2254
  }
2205
2255
  }
2206
2256
  }
2257
+ for (const { itemData } of localAddsAndUpdates) {
2258
+ try {
2259
+ Logger.verbose(
2260
+ `[${this.name}] Syncing local item to db from document ${docId} (${modelName}):`,
2261
+ itemData
2262
+ );
2263
+ await currentDbInstance.insert(modelName, {
2264
+ ...itemData,
2265
+ type: modelName,
2266
+ _meta_doc_id: docId,
2267
+ _meta_permission_hint: permissionHint
2268
+ });
2269
+ } catch (error) {
2270
+ Logger.error(
2271
+ `[${this.name}] Error syncing local item to db from document ${docId} (${modelName}):`,
2272
+ error,
2273
+ itemData
2274
+ );
2275
+ }
2276
+ }
2277
+ if (remoteAdds.size > 0) {
2278
+ Logger.verbose(
2279
+ `[${this.name}] Processing ${remoteAdds.size} remote add(s) with conflict resolution for ${modelName}/${docId}`
2280
+ );
2281
+ const candidateRecords = /* @__PURE__ */ new Map();
2282
+ for (const [key, { recordData }] of remoteAdds.entries()) {
2283
+ candidateRecords.set(key, recordData);
2284
+ }
2285
+ const idsToDiscard = resolveConflictsForBatch(candidateRecords);
2286
+ if (idsToDiscard.size > 0) {
2287
+ Logger.info(
2288
+ `[${this.name}] Discarding ${idsToDiscard.size} duplicate record(s) from remote sync for ${modelName}/${docId}: ${Array.from(idsToDiscard).join(", ")}`
2289
+ );
2290
+ }
2291
+ for (const [key, { itemData }] of remoteAdds.entries()) {
2292
+ if (idsToDiscard.has(key)) {
2293
+ Logger.verbose(
2294
+ `[${this.name}] Skipping SQLite insert for discarded duplicate: ${key}`
2295
+ );
2296
+ continue;
2297
+ }
2298
+ try {
2299
+ Logger.verbose(
2300
+ `[${this.name}] Syncing remote item to db from document ${docId} (${modelName}):`,
2301
+ itemData
2302
+ );
2303
+ await currentDbInstance.insert(modelName, {
2304
+ ...itemData,
2305
+ type: modelName,
2306
+ _meta_doc_id: docId,
2307
+ _meta_permission_hint: permissionHint
2308
+ });
2309
+ } catch (error) {
2310
+ Logger.error(
2311
+ `[${this.name}] Error syncing remote item to db from document ${docId} (${modelName}):`,
2312
+ error,
2313
+ itemData
2314
+ );
2315
+ }
2316
+ }
2317
+ if (idsToDiscard.size > 0) {
2318
+ yDoc.transact(() => {
2319
+ for (const idToDiscard of idsToDiscard) {
2320
+ const recordData = documentYMap.get(idToDiscard);
2321
+ if (!recordData) continue;
2322
+ for (const constraint of schema.resolvedUniqueConstraints) {
2323
+ const uniqueKey = buildUniqueKey(recordData, constraint.fields);
2324
+ if (uniqueKey === null) continue;
2325
+ const constraintMapName = `_uniqueIdx_${modelName}_${constraint.name}`;
2326
+ const constraintMap = yDoc.getMap(constraintMapName);
2327
+ const currentIndexValue = constraintMap.get(uniqueKey);
2328
+ if (currentIndexValue === idToDiscard) {
2329
+ constraintMap.delete(uniqueKey);
2330
+ Logger.verbose(
2331
+ `[${this.name}] Removed discarded record ${idToDiscard} from unique index ${constraintMapName}`
2332
+ );
2333
+ }
2334
+ }
2335
+ documentYMap.delete(idToDiscard);
2336
+ Logger.verbose(
2337
+ `[${this.name}] Removed discarded record ${idToDiscard} from Y.Doc`
2338
+ );
2339
+ }
2340
+ }, `conflict-resolution-${modelName}-${docId}`);
2341
+ yDoc.transact(() => {
2342
+ for (const constraint of schema.resolvedUniqueConstraints) {
2343
+ const constraintMapName = `_uniqueIdx_${modelName}_${constraint.name}`;
2344
+ const constraintMap = yDoc.getMap(constraintMapName);
2345
+ for (const [recordId, recordData] of documentYMap.entries()) {
2346
+ if (!recordData) continue;
2347
+ const uniqueKey = buildUniqueKey(recordData, constraint.fields);
2348
+ if (uniqueKey === null) continue;
2349
+ const currentIndexValue = constraintMap.get(uniqueKey);
2350
+ if (currentIndexValue !== recordId) {
2351
+ constraintMap.set(uniqueKey, recordId);
2352
+ Logger.verbose(
2353
+ `[${this.name}] Updated unique index ${constraintMapName}['${uniqueKey.substring(0, 30)}...'] to point to ${recordId}`
2354
+ );
2355
+ }
2356
+ }
2357
+ }
2358
+ }, `update-indexes-${modelName}-${docId}`);
2359
+ }
2360
+ }
2207
2361
  Logger.verbose(
2208
2362
  `[${this.name}] Document YMap observation transaction for ${modelName}/${docId} completed.`
2209
2363
  );
package/dist/browser.js CHANGED
@@ -1288,7 +1288,7 @@ var init_BaseModel = __esm({
1288
1288
  Object.setPrototypeOf(this, _RecordNotFoundError.prototype);
1289
1289
  }
1290
1290
  };
1291
- SCHEMA_ACCESSORS_KEY = Symbol("jsBaoSchemaAccessors");
1291
+ SCHEMA_ACCESSORS_KEY = /* @__PURE__ */ Symbol("jsBaoSchemaAccessors");
1292
1292
  BaseModel = class _BaseModel {
1293
1293
  static modelName;
1294
1294
  static listenersMap = /* @__PURE__ */ new Map();
@@ -2071,6 +2071,114 @@ var init_BaseModel = __esm({
2071
2071
  Logger.verbose(
2072
2072
  `[${this.name}] Setting up YMap observer for document ${docId}/${modelName}...`
2073
2073
  );
2074
+ const buildUniqueKey = (recordData, fields) => {
2075
+ const keyParts = [];
2076
+ for (const field of fields) {
2077
+ const value = recordData instanceof Y.Map ? recordData.get(field) : recordData[field];
2078
+ if (value === null || value === void 0) {
2079
+ return null;
2080
+ }
2081
+ keyParts.push(String(value));
2082
+ }
2083
+ return keyParts.join("|");
2084
+ };
2085
+ const extractItemData = (key, recordData) => {
2086
+ let itemData;
2087
+ if (recordData instanceof Y.Map) {
2088
+ itemData = {};
2089
+ const unknownFields = [];
2090
+ for (const [fieldKey, value] of recordData.entries()) {
2091
+ const fieldOptions = schema.fields.get(fieldKey);
2092
+ if (!fieldOptions) {
2093
+ unknownFields.push(fieldKey);
2094
+ continue;
2095
+ }
2096
+ if (fieldOptions.type === "stringset") {
2097
+ continue;
2098
+ }
2099
+ if (value !== void 0) {
2100
+ itemData[fieldKey] = value;
2101
+ }
2102
+ }
2103
+ if (unknownFields.length > 0) {
2104
+ Logger.warn(
2105
+ `[${this.name}] Ignoring unknown fields [${unknownFields.join(
2106
+ ", "
2107
+ )}] when syncing record ${key} from document ${docId}`
2108
+ );
2109
+ }
2110
+ } else {
2111
+ const unknownFields = [];
2112
+ const filteredData = {};
2113
+ for (const [fieldKey, value] of Object.entries(recordData)) {
2114
+ const fieldOptions = schema.fields.get(fieldKey);
2115
+ if (!fieldOptions) {
2116
+ unknownFields.push(fieldKey);
2117
+ continue;
2118
+ }
2119
+ if (fieldOptions.type === "stringset") {
2120
+ continue;
2121
+ }
2122
+ if (value !== void 0) {
2123
+ filteredData[fieldKey] = value;
2124
+ }
2125
+ }
2126
+ if (unknownFields.length > 0) {
2127
+ Logger.warn(
2128
+ `[${this.name}] Ignoring unknown fields [${unknownFields.join(
2129
+ ", "
2130
+ )}] when syncing legacy record ${key} from document ${docId}`
2131
+ );
2132
+ }
2133
+ itemData = filteredData;
2134
+ }
2135
+ if (!itemData.id) return null;
2136
+ return itemData;
2137
+ };
2138
+ const resolveConflictsForBatch = (candidateRecords) => {
2139
+ if (schema.resolvedUniqueConstraints.length === 0) {
2140
+ return /* @__PURE__ */ new Set();
2141
+ }
2142
+ const recordIdsToDiscard = /* @__PURE__ */ new Set();
2143
+ const recordIdsToKeep = /* @__PURE__ */ new Set();
2144
+ const allRecords = /* @__PURE__ */ new Map();
2145
+ for (const [recordId, recordData] of documentYMap.entries()) {
2146
+ if (recordData && !candidateRecords.has(recordId)) {
2147
+ allRecords.set(recordId, recordData);
2148
+ }
2149
+ }
2150
+ for (const [recordId, recordData] of candidateRecords.entries()) {
2151
+ allRecords.set(recordId, recordData);
2152
+ }
2153
+ for (const constraint of schema.resolvedUniqueConstraints) {
2154
+ const recordsByUniqueKey = /* @__PURE__ */ new Map();
2155
+ for (const [recordId, recordData] of allRecords.entries()) {
2156
+ if (recordIdsToDiscard.has(recordId)) continue;
2157
+ const uniqueKey = buildUniqueKey(recordData, constraint.fields);
2158
+ if (uniqueKey === null) continue;
2159
+ if (!recordsByUniqueKey.has(uniqueKey)) {
2160
+ recordsByUniqueKey.set(uniqueKey, []);
2161
+ }
2162
+ recordsByUniqueKey.get(uniqueKey).push(recordId);
2163
+ }
2164
+ for (const [uniqueKey, recordIds] of recordsByUniqueKey.entries()) {
2165
+ if (recordIds.length <= 1) continue;
2166
+ Logger.warn(
2167
+ `[${this.name}] CRDT conflict detected for unique constraint '${constraint.name}' on key '${uniqueKey.substring(0, 50)}${uniqueKey.length > 50 ? "..." : ""}': ${recordIds.length} records found.`
2168
+ );
2169
+ recordIds.sort();
2170
+ const idToKeep = recordIds[recordIds.length - 1];
2171
+ recordIdsToKeep.add(idToKeep);
2172
+ for (let i = 0; i < recordIds.length - 1; i++) {
2173
+ recordIdsToDiscard.add(recordIds[i]);
2174
+ }
2175
+ }
2176
+ }
2177
+ for (const idToKeep of recordIdsToKeep) {
2178
+ recordIdsToDiscard.delete(idToKeep);
2179
+ }
2180
+ return recordIdsToDiscard;
2181
+ };
2074
2182
  documentYMap.observe(async (event) => {
2075
2183
  Logger.verbose(
2076
2184
  `[${this.name}] Document YMap change detected for ${modelName}/${docId}:`,
@@ -2083,89 +2191,31 @@ var init_BaseModel = __esm({
2083
2191
  );
2084
2192
  return;
2085
2193
  }
2194
+ const isRemoteChange = !event.transaction.local;
2195
+ const remoteAdds = /* @__PURE__ */ new Map();
2196
+ const localAddsAndUpdates = [];
2086
2197
  for (const [key, change] of event.changes.keys.entries()) {
2087
2198
  const recordData = documentYMap.get(key);
2088
2199
  if (change.action === "add" || change.action === "update") {
2089
2200
  if (!recordData || !key) continue;
2090
- let itemData;
2091
- if (recordData instanceof Y.Map) {
2092
- itemData = {};
2093
- const unknownFields = [];
2094
- for (const [fieldKey, value] of recordData.entries()) {
2095
- const fieldOptions = schema.fields.get(fieldKey);
2096
- if (!fieldOptions) {
2097
- unknownFields.push(fieldKey);
2098
- continue;
2099
- }
2100
- if (fieldOptions.type === "stringset") {
2101
- continue;
2102
- }
2103
- if (value !== void 0) {
2104
- itemData[fieldKey] = value;
2105
- }
2106
- }
2107
- if (unknownFields.length > 0) {
2108
- Logger.warn(
2109
- `[${this.name}] Ignoring unknown fields [${unknownFields.join(
2110
- ", "
2111
- )}] when syncing record ${key} from document ${docId}`
2112
- );
2113
- }
2114
- if (change.action === "add") {
2115
- Logger.verbose(
2116
- `[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
2117
- );
2118
- this.setupNestedYMapObserverForDocument(
2119
- key,
2120
- recordData,
2121
- docId,
2122
- permissionHint
2123
- );
2124
- }
2125
- } else {
2126
- const unknownFields = [];
2127
- const filteredData = {};
2128
- for (const [fieldKey, value] of Object.entries(recordData)) {
2129
- const fieldOptions = schema.fields.get(fieldKey);
2130
- if (!fieldOptions) {
2131
- unknownFields.push(fieldKey);
2132
- continue;
2133
- }
2134
- if (fieldOptions.type === "stringset") {
2135
- continue;
2136
- }
2137
- if (value !== void 0) {
2138
- filteredData[fieldKey] = value;
2139
- }
2140
- }
2141
- if (unknownFields.length > 0) {
2142
- Logger.warn(
2143
- `[${this.name}] Ignoring unknown fields [${unknownFields.join(
2144
- ", "
2145
- )}] when syncing legacy record ${key} from document ${docId}`
2146
- );
2147
- }
2148
- itemData = filteredData;
2149
- }
2150
- if (!itemData.id) continue;
2151
- try {
2201
+ const itemData = extractItemData(key, recordData);
2202
+ if (!itemData) continue;
2203
+ if (change.action === "add" && recordData instanceof Y.Map) {
2152
2204
  Logger.verbose(
2153
- `[${this.name}] Syncing item to db from document ${docId} (${modelName}):`,
2154
- itemData
2205
+ `[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
2155
2206
  );
2156
- await currentDbInstance.insert(modelName, {
2157
- ...itemData,
2158
- type: modelName,
2159
- _meta_doc_id: docId,
2160
- _meta_permission_hint: permissionHint
2161
- });
2162
- } catch (error) {
2163
- Logger.error(
2164
- `[${this.name}] Error syncing item to db from document ${docId} (${modelName}):`,
2165
- error,
2166
- itemData
2207
+ this.setupNestedYMapObserverForDocument(
2208
+ key,
2209
+ recordData,
2210
+ docId,
2211
+ permissionHint
2167
2212
  );
2168
2213
  }
2214
+ if (isRemoteChange && change.action === "add") {
2215
+ remoteAdds.set(key, { recordData, itemData });
2216
+ } else {
2217
+ localAddsAndUpdates.push({ key, action: change.action, recordData, itemData });
2218
+ }
2169
2219
  } else if (change.action === "delete") {
2170
2220
  Logger.verbose(
2171
2221
  `[${this.name}] Deleting item from db (${modelName}/${docId}):`,
@@ -2182,6 +2232,110 @@ var init_BaseModel = __esm({
2182
2232
  }
2183
2233
  }
2184
2234
  }
2235
+ for (const { itemData } of localAddsAndUpdates) {
2236
+ try {
2237
+ Logger.verbose(
2238
+ `[${this.name}] Syncing local item to db from document ${docId} (${modelName}):`,
2239
+ itemData
2240
+ );
2241
+ await currentDbInstance.insert(modelName, {
2242
+ ...itemData,
2243
+ type: modelName,
2244
+ _meta_doc_id: docId,
2245
+ _meta_permission_hint: permissionHint
2246
+ });
2247
+ } catch (error) {
2248
+ Logger.error(
2249
+ `[${this.name}] Error syncing local item to db from document ${docId} (${modelName}):`,
2250
+ error,
2251
+ itemData
2252
+ );
2253
+ }
2254
+ }
2255
+ if (remoteAdds.size > 0) {
2256
+ Logger.verbose(
2257
+ `[${this.name}] Processing ${remoteAdds.size} remote add(s) with conflict resolution for ${modelName}/${docId}`
2258
+ );
2259
+ const candidateRecords = /* @__PURE__ */ new Map();
2260
+ for (const [key, { recordData }] of remoteAdds.entries()) {
2261
+ candidateRecords.set(key, recordData);
2262
+ }
2263
+ const idsToDiscard = resolveConflictsForBatch(candidateRecords);
2264
+ if (idsToDiscard.size > 0) {
2265
+ Logger.info(
2266
+ `[${this.name}] Discarding ${idsToDiscard.size} duplicate record(s) from remote sync for ${modelName}/${docId}: ${Array.from(idsToDiscard).join(", ")}`
2267
+ );
2268
+ }
2269
+ for (const [key, { itemData }] of remoteAdds.entries()) {
2270
+ if (idsToDiscard.has(key)) {
2271
+ Logger.verbose(
2272
+ `[${this.name}] Skipping SQLite insert for discarded duplicate: ${key}`
2273
+ );
2274
+ continue;
2275
+ }
2276
+ try {
2277
+ Logger.verbose(
2278
+ `[${this.name}] Syncing remote item to db from document ${docId} (${modelName}):`,
2279
+ itemData
2280
+ );
2281
+ await currentDbInstance.insert(modelName, {
2282
+ ...itemData,
2283
+ type: modelName,
2284
+ _meta_doc_id: docId,
2285
+ _meta_permission_hint: permissionHint
2286
+ });
2287
+ } catch (error) {
2288
+ Logger.error(
2289
+ `[${this.name}] Error syncing remote item to db from document ${docId} (${modelName}):`,
2290
+ error,
2291
+ itemData
2292
+ );
2293
+ }
2294
+ }
2295
+ if (idsToDiscard.size > 0) {
2296
+ yDoc.transact(() => {
2297
+ for (const idToDiscard of idsToDiscard) {
2298
+ const recordData = documentYMap.get(idToDiscard);
2299
+ if (!recordData) continue;
2300
+ for (const constraint of schema.resolvedUniqueConstraints) {
2301
+ const uniqueKey = buildUniqueKey(recordData, constraint.fields);
2302
+ if (uniqueKey === null) continue;
2303
+ const constraintMapName = `_uniqueIdx_${modelName}_${constraint.name}`;
2304
+ const constraintMap = yDoc.getMap(constraintMapName);
2305
+ const currentIndexValue = constraintMap.get(uniqueKey);
2306
+ if (currentIndexValue === idToDiscard) {
2307
+ constraintMap.delete(uniqueKey);
2308
+ Logger.verbose(
2309
+ `[${this.name}] Removed discarded record ${idToDiscard} from unique index ${constraintMapName}`
2310
+ );
2311
+ }
2312
+ }
2313
+ documentYMap.delete(idToDiscard);
2314
+ Logger.verbose(
2315
+ `[${this.name}] Removed discarded record ${idToDiscard} from Y.Doc`
2316
+ );
2317
+ }
2318
+ }, `conflict-resolution-${modelName}-${docId}`);
2319
+ yDoc.transact(() => {
2320
+ for (const constraint of schema.resolvedUniqueConstraints) {
2321
+ const constraintMapName = `_uniqueIdx_${modelName}_${constraint.name}`;
2322
+ const constraintMap = yDoc.getMap(constraintMapName);
2323
+ for (const [recordId, recordData] of documentYMap.entries()) {
2324
+ if (!recordData) continue;
2325
+ const uniqueKey = buildUniqueKey(recordData, constraint.fields);
2326
+ if (uniqueKey === null) continue;
2327
+ const currentIndexValue = constraintMap.get(uniqueKey);
2328
+ if (currentIndexValue !== recordId) {
2329
+ constraintMap.set(uniqueKey, recordId);
2330
+ Logger.verbose(
2331
+ `[${this.name}] Updated unique index ${constraintMapName}['${uniqueKey.substring(0, 30)}...'] to point to ${recordId}`
2332
+ );
2333
+ }
2334
+ }
2335
+ }
2336
+ }, `update-indexes-${modelName}-${docId}`);
2337
+ }
2338
+ }
2185
2339
  Logger.verbose(
2186
2340
  `[${this.name}] Document YMap observation transaction for ${modelName}/${docId} completed.`
2187
2341
  );