@dvina/sdk 4.0.142 → 4.0.151

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.
@@ -8672,6 +8672,28 @@ function flattenEntity(data) {
8672
8672
  }
8673
8673
  return result;
8674
8674
  }
8675
+ function isPlainObject(value) {
8676
+ return Object.prototype.toString.call(value) === "[object Object]";
8677
+ }
8678
+ function areRecordValuesEqual(left, right) {
8679
+ if (Object.is(left, right)) {
8680
+ return true;
8681
+ }
8682
+ if (left instanceof Date && right instanceof Date) {
8683
+ return left.getTime() === right.getTime();
8684
+ }
8685
+ if (Array.isArray(left) && Array.isArray(right)) {
8686
+ return left.length === right.length && left.every((value, index) => areRecordValuesEqual(value, right[index]));
8687
+ }
8688
+ if (isPlainObject(left) && isPlainObject(right)) {
8689
+ const leftKeys = Object.keys(left);
8690
+ const rightKeys = Object.keys(right);
8691
+ return leftKeys.length === rightKeys.length && leftKeys.every(
8692
+ (key) => Object.prototype.hasOwnProperty.call(right, key) && areRecordValuesEqual(left[key], right[key])
8693
+ );
8694
+ }
8695
+ return false;
8696
+ }
8675
8697
  function normalize(data, rootField) {
8676
8698
  const entities = /* @__PURE__ */ new Map();
8677
8699
  const nestedConnections = /* @__PURE__ */ new Map();
@@ -8782,33 +8804,73 @@ function extractEntity(data, entities, nestedConnections) {
8782
8804
  }
8783
8805
  }
8784
8806
  function mergeEntityRecord(existing, incoming) {
8785
- const result = { ...existing };
8807
+ let result;
8786
8808
  for (const [key, value] of Object.entries(incoming)) {
8787
- if (value !== void 0) {
8788
- result[key] = value;
8809
+ if (value === void 0) {
8810
+ continue;
8789
8811
  }
8812
+ if (Object.prototype.hasOwnProperty.call(existing, key) && areRecordValuesEqual(existing[key], value)) {
8813
+ continue;
8814
+ }
8815
+ result ??= { ...existing };
8816
+ result[key] = value;
8790
8817
  }
8791
- return result;
8818
+ return result ?? existing;
8792
8819
  }
8793
8820
 
8794
8821
  // src/store/cache-manager.ts
8822
+ var QUERY_FRESHNESS_KEY_PREFIX = "queryFreshness:";
8823
+ function getQueryFreshnessKey(queryKey) {
8824
+ return `${QUERY_FRESHNESS_KEY_PREFIX}${queryKey}`;
8825
+ }
8826
+ function areArraysEqual(left, right) {
8827
+ return left.length === right.length && left.every((value, index) => value === right[index]);
8828
+ }
8829
+ function arePageInfosEqual(left, right) {
8830
+ if (left === right) {
8831
+ return true;
8832
+ }
8833
+ if (!left || !right) {
8834
+ return left === right;
8835
+ }
8836
+ return left.hasNextPage === right.hasNextPage && left.hasPreviousPage === right.hasPreviousPage && left.startCursor === right.startCursor && left.endCursor === right.endCursor;
8837
+ }
8838
+ function hasSameQueryResultContents(existing, incoming) {
8839
+ return existing.entityType === incoming.entityType && areArraysEqual(existing.entityIds, incoming.entityIds) && arePageInfosEqual(existing.pageInfo, incoming.pageInfo) && existing.totalCount === incoming.totalCount;
8840
+ }
8795
8841
  var CacheManager = class {
8796
8842
  constructor(_db) {
8797
8843
  this._db = _db;
8798
8844
  }
8845
+ async _touchQueryFreshness(queryKey, fetchedAt = Date.now()) {
8846
+ await this._db._sync.put({ key: getQueryFreshnessKey(queryKey), value: fetchedAt });
8847
+ }
8848
+ async getQueryFetchedAt(queryKey, fallback) {
8849
+ const freshness = await this._db._sync.get(getQueryFreshnessKey(queryKey));
8850
+ return typeof freshness?.value === "number" ? freshness.value : fallback?.fetchedAt;
8851
+ }
8799
8852
  /**
8800
8853
  * Write connection metadata to the `_queryResults` table.
8801
8854
  */
8802
8855
  async writeQueryResult(queryKey, connection) {
8856
+ const fetchedAt = Date.now();
8857
+ const existing = await this._db._queryResults.get(queryKey);
8858
+ if (existing && hasSameQueryResultContents(existing, connection)) {
8859
+ await this._touchQueryFreshness(queryKey, fetchedAt);
8860
+ return;
8861
+ }
8803
8862
  const entry = {
8804
8863
  key: queryKey,
8805
8864
  entityType: connection.entityType,
8806
8865
  entityIds: connection.entityIds,
8807
8866
  pageInfo: connection.pageInfo,
8808
8867
  totalCount: connection.totalCount,
8809
- fetchedAt: Date.now()
8868
+ fetchedAt
8810
8869
  };
8811
- await this._db._queryResults.put(entry);
8870
+ await this._db.transaction("rw", [this._db._queryResults, this._db._sync], async () => {
8871
+ await this._db._queryResults.put(entry);
8872
+ await this._touchQueryFreshness(queryKey, fetchedAt);
8873
+ });
8812
8874
  }
8813
8875
  /**
8814
8876
  * Append new page results to an existing query result entry.
@@ -8817,13 +8879,24 @@ var CacheManager = class {
8817
8879
  async appendQueryResult(queryKey, connection) {
8818
8880
  const existing = await this._db._queryResults.get(queryKey);
8819
8881
  if (existing) {
8882
+ const fetchedAt = Date.now();
8820
8883
  const existingIds = new Set(existing.entityIds);
8821
8884
  const newIds = connection.entityIds.filter((id) => !existingIds.has(id));
8822
- existing.entityIds = [...existing.entityIds, ...newIds];
8823
- existing.pageInfo = connection.pageInfo ?? existing.pageInfo;
8824
- existing.totalCount = connection.totalCount ?? existing.totalCount;
8825
- existing.fetchedAt = Date.now();
8826
- await this._db._queryResults.put(existing);
8885
+ const nextEntityIds = newIds.length > 0 ? [...existing.entityIds, ...newIds] : existing.entityIds;
8886
+ const nextPageInfo = connection.pageInfo ?? existing.pageInfo;
8887
+ const nextTotalCount = connection.totalCount ?? existing.totalCount;
8888
+ if (areArraysEqual(existing.entityIds, nextEntityIds) && arePageInfosEqual(existing.pageInfo, nextPageInfo) && existing.totalCount === nextTotalCount) {
8889
+ await this._touchQueryFreshness(queryKey, fetchedAt);
8890
+ return;
8891
+ }
8892
+ existing.entityIds = nextEntityIds;
8893
+ existing.pageInfo = nextPageInfo;
8894
+ existing.totalCount = nextTotalCount;
8895
+ existing.fetchedAt = fetchedAt;
8896
+ await this._db.transaction("rw", [this._db._queryResults, this._db._sync], async () => {
8897
+ await this._db._queryResults.put(existing);
8898
+ await this._touchQueryFreshness(queryKey, fetchedAt);
8899
+ });
8827
8900
  } else {
8828
8901
  await this.writeQueryResult(queryKey, connection);
8829
8902
  }
@@ -8840,7 +8913,10 @@ var CacheManager = class {
8840
8913
  */
8841
8914
  async invalidateQuery(operationName, variables) {
8842
8915
  const key = buildQueryKey(operationName, variables);
8843
- await this._db._queryResults.delete(key);
8916
+ await this._db.transaction("rw", [this._db._queryResults, this._db._sync], async () => {
8917
+ await this._db._queryResults.delete(key);
8918
+ await this._db._sync.delete(getQueryFreshnessKey(key));
8919
+ });
8844
8920
  }
8845
8921
  /**
8846
8922
  * Remove an entity ID from all query results for a given entity type.
@@ -9126,7 +9202,8 @@ var QueryExecutor = class {
9126
9202
  try {
9127
9203
  const result = await buildResult(this._db);
9128
9204
  ref._markInitialized(result);
9129
- if (Date.now() - cached.fetchedAt > CACHE_TTL_MS) {
9205
+ const fetchedAt = await this._cache.getQueryFetchedAt(queryKey, cached);
9206
+ if (!fetchedAt || Date.now() - fetchedAt > CACHE_TTL_MS) {
9130
9207
  this.query(document2, variables, operationName).catch(() => {
9131
9208
  });
9132
9209
  }
@@ -9175,10 +9252,17 @@ var QueryExecutor = class {
9175
9252
  return record[pkField];
9176
9253
  });
9177
9254
  const existingRecords = await table.bulkGet(pks);
9178
- const toPut = records.map((record, i) => {
9255
+ const toPut = records.flatMap((record, i) => {
9179
9256
  const existing = existingRecords[i];
9180
- return existing ? mergeEntityRecord(existing, record) : record;
9257
+ if (!existing) {
9258
+ return [record];
9259
+ }
9260
+ const merged = mergeEntityRecord(existing, record);
9261
+ return merged === existing ? [] : [merged];
9181
9262
  });
9263
+ if (toPut.length === 0) {
9264
+ continue;
9265
+ }
9182
9266
  await table.bulkPut(toPut);
9183
9267
  }
9184
9268
  });
@@ -9298,7 +9382,9 @@ var OptimisticManager = class {
9298
9382
  const snapshot = existing ? structuredClone(existing) : void 0;
9299
9383
  if (existing) {
9300
9384
  const merged = mergeEntityRecord(existing, data);
9301
- await table.put(merged);
9385
+ if (merged !== existing) {
9386
+ await table.put(merged);
9387
+ }
9302
9388
  }
9303
9389
  return async () => {
9304
9390
  if (snapshot) {
@@ -9765,7 +9851,9 @@ var SyncEngine = class {
9765
9851
  await this._queryExecutor.writeEntities(entities);
9766
9852
  } else if (existing) {
9767
9853
  const merged = mergeEntityRecord(existing, record);
9768
- await table.put(merged);
9854
+ if (merged !== existing) {
9855
+ await table.put(merged);
9856
+ }
9769
9857
  } else {
9770
9858
  await table.put(record);
9771
9859
  }
@@ -9809,7 +9897,8 @@ var SyncEngine = class {
9809
9897
  if (!touchedAt) return;
9810
9898
  const cached = await this._db._queryResults.get(sub.queryKey);
9811
9899
  if (!cached) return;
9812
- if (cached.fetchedAt >= touchedAt) return;
9900
+ const fetchedAt = await this._cache.getQueryFetchedAt(sub.queryKey, cached);
9901
+ if ((fetchedAt ?? cached.fetchedAt) >= touchedAt) return;
9813
9902
  await this.query(sub.document, sub.variables, sub.operationName).catch(() => void 0);
9814
9903
  }
9815
9904
  async _revalidateAllActiveSubscriptions() {
@@ -10500,6 +10589,124 @@ function inferTargetTables(fieldName) {
10500
10589
  return normalizedTypeName === normalizedFieldName || normalizedTypeName.endsWith(normalizedFieldName) || normalizedTableName === normalizedFieldName || normalizedTableName === `${normalizedFieldName}s`;
10501
10590
  }).map(([tableName]) => tableName);
10502
10591
  }
10592
+ function getIncludeQueryKey(parentTable, parentId, fieldName) {
10593
+ return `${parentTable}.${parentId}.${fieldName}`;
10594
+ }
10595
+ function hasNestedIncludes(include) {
10596
+ return Boolean(include.children?.length);
10597
+ }
10598
+ function canUseFlatIncludeBatch(includes) {
10599
+ return includes.length > 0 && includes.every((include) => !hasNestedIncludes(include));
10600
+ }
10601
+ function cloneEntity(entity) {
10602
+ return { ...entity };
10603
+ }
10604
+ function defaultIncludeValue(inc) {
10605
+ if (inc.isConnection) {
10606
+ return {
10607
+ __typename: "Unknown",
10608
+ nodes: [],
10609
+ pageInfo: { hasNextPage: false, hasPreviousPage: false },
10610
+ totalCount: 0
10611
+ };
10612
+ }
10613
+ if (inc.isList) {
10614
+ return [];
10615
+ }
10616
+ return null;
10617
+ }
10618
+ async function preloadFlatIncludeContext(db, parentTable, parentIds, includes) {
10619
+ const queryKeys = [];
10620
+ for (const parentId of parentIds) {
10621
+ for (const include of includes) {
10622
+ queryKeys.push(getIncludeQueryKey(parentTable, parentId, include.fieldName));
10623
+ }
10624
+ }
10625
+ const queryResults = queryKeys.length > 0 ? await db._queryResults.bulkGet(queryKeys) : [];
10626
+ const queryResultsByKey = /* @__PURE__ */ new Map();
10627
+ const childIdsByType = /* @__PURE__ */ new Map();
10628
+ for (let i = 0; i < queryKeys.length; i++) {
10629
+ const queryResult = queryResults[i];
10630
+ if (!queryResult) {
10631
+ continue;
10632
+ }
10633
+ queryResultsByKey.set(queryKeys[i], queryResult);
10634
+ let entityIds = childIdsByType.get(queryResult.entityType);
10635
+ if (!entityIds) {
10636
+ entityIds = /* @__PURE__ */ new Set();
10637
+ childIdsByType.set(queryResult.entityType, entityIds);
10638
+ }
10639
+ for (const entityId of queryResult.entityIds) {
10640
+ entityIds.add(entityId);
10641
+ }
10642
+ }
10643
+ const childEntitiesByType = /* @__PURE__ */ new Map();
10644
+ await Promise.all(
10645
+ Array.from(childIdsByType.entries()).map(async ([entityType, entityIds]) => {
10646
+ const idList = Array.from(entityIds);
10647
+ const childRecords = await db.table(entityType).bulkGet(idList.map((id) => parsePrimaryKeyString(entityType, id)));
10648
+ const childMap = /* @__PURE__ */ new Map();
10649
+ for (let i = 0; i < idList.length; i++) {
10650
+ const childRecord = childRecords[i];
10651
+ if (!childRecord || typeof childRecord !== "object") {
10652
+ continue;
10653
+ }
10654
+ childMap.set(idList[i], childRecord);
10655
+ }
10656
+ childEntitiesByType.set(entityType, childMap);
10657
+ })
10658
+ );
10659
+ return { childEntitiesByType, queryResultsByKey };
10660
+ }
10661
+ function resolveFlatChildEntities(context, queryResult) {
10662
+ const childMap = context.childEntitiesByType.get(queryResult.entityType);
10663
+ if (!childMap) {
10664
+ return [];
10665
+ }
10666
+ const children = [];
10667
+ for (const entityId of queryResult.entityIds) {
10668
+ const child = childMap.get(entityId);
10669
+ if (child) {
10670
+ children.push(child);
10671
+ }
10672
+ }
10673
+ return children;
10674
+ }
10675
+ async function reconstructFlatEntity(db, parentTable, parentEntity, parentId, includes, context) {
10676
+ const result = cloneEntity(parentEntity);
10677
+ for (const inc of includes) {
10678
+ const queryResult = context.queryResultsByKey.get(getIncludeQueryKey(parentTable, parentId, inc.fieldName));
10679
+ if (!queryResult) {
10680
+ const fallback = inc.isConnection ? void 0 : await resolveSingleRelationFallback(db, parentTable, result, inc);
10681
+ if (fallback) {
10682
+ result[inc.fieldName] = fallback.entity;
10683
+ continue;
10684
+ }
10685
+ result[inc.fieldName] = defaultIncludeValue(inc);
10686
+ continue;
10687
+ }
10688
+ const childEntities = resolveFlatChildEntities(context, queryResult);
10689
+ if (inc.isConnection) {
10690
+ result[inc.fieldName] = {
10691
+ __typename: (TABLE_TO_TYPENAME[queryResult.entityType] ?? queryResult.entityType) + "Connection",
10692
+ nodes: childEntities.map(cloneEntity),
10693
+ pageInfo: queryResult.pageInfo ?? {
10694
+ hasNextPage: false,
10695
+ hasPreviousPage: false
10696
+ },
10697
+ totalCount: queryResult.totalCount ?? 0
10698
+ };
10699
+ continue;
10700
+ }
10701
+ if (inc.isList) {
10702
+ result[inc.fieldName] = childEntities.map(cloneEntity);
10703
+ continue;
10704
+ }
10705
+ const child = childEntities[0];
10706
+ result[inc.fieldName] = child ? cloneEntity(child) : null;
10707
+ }
10708
+ return result;
10709
+ }
10503
10710
  async function resolveSingleRelationFallback(db, parentTable, parentEntity, inc) {
10504
10711
  const targetTables = inc.syncMetadata?.entityType ? [inc.syncMetadata.entityType] : inferTargetTables(inc.fieldName);
10505
10712
  if (targetTables.length === 0) {
@@ -10524,9 +10731,13 @@ async function reconstructEntity(db, tableName, entityId, includes) {
10524
10731
  const dexieKey = parsePrimaryKeyString(tableName, entityId);
10525
10732
  const entity = await db.table(tableName).get(dexieKey);
10526
10733
  if (!entity) return {};
10734
+ if (canUseFlatIncludeBatch(includes)) {
10735
+ const context = await preloadFlatIncludeContext(db, tableName, [entityId], includes);
10736
+ return reconstructFlatEntity(db, tableName, entity, entityId, includes, context);
10737
+ }
10527
10738
  const result = { ...entity };
10528
10739
  for (const inc of includes) {
10529
- const connKey = `${tableName}.${entityId}.${inc.fieldName}`;
10740
+ const connKey = getIncludeQueryKey(tableName, entityId, inc.fieldName);
10530
10741
  const qr = await db._queryResults.get(connKey);
10531
10742
  if (!qr) {
10532
10743
  const fallback = inc.isConnection ? void 0 : await resolveSingleRelationFallback(db, tableName, result, inc);
@@ -10539,12 +10750,7 @@ async function reconstructEntity(db, tableName, entityId, includes) {
10539
10750
  result[inc.fieldName] = fallback.entity;
10540
10751
  continue;
10541
10752
  }
10542
- result[inc.fieldName] = inc.isConnection ? {
10543
- __typename: "Unknown",
10544
- nodes: [],
10545
- pageInfo: { hasNextPage: false, hasPreviousPage: false },
10546
- totalCount: 0
10547
- } : inc.isList ? [] : null;
10753
+ result[inc.fieldName] = defaultIncludeValue(inc);
10548
10754
  continue;
10549
10755
  }
10550
10756
  if (inc.isConnection) {
@@ -10593,6 +10799,27 @@ async function reconstructEntity(db, tableName, entityId, includes) {
10593
10799
  }
10594
10800
  async function reconstructConnectionNodes(db, entityTable, nodes, includes) {
10595
10801
  if (includes.length === 0) return nodes;
10802
+ if (canUseFlatIncludeBatch(includes)) {
10803
+ const nodeIds = nodes.map((node) => primaryKeyToString(entityTable, node));
10804
+ const parentEntities = await db.table(entityTable).bulkGet(nodeIds.map((id) => parsePrimaryKeyString(entityTable, id)));
10805
+ const context = await preloadFlatIncludeContext(db, entityTable, nodeIds, includes);
10806
+ return Promise.all(
10807
+ nodeIds.map(async (nodeId, index) => {
10808
+ const parentEntity = parentEntities[index];
10809
+ if (!parentEntity || typeof parentEntity !== "object") {
10810
+ return {};
10811
+ }
10812
+ return reconstructFlatEntity(
10813
+ db,
10814
+ entityTable,
10815
+ parentEntity,
10816
+ nodeId,
10817
+ includes,
10818
+ context
10819
+ );
10820
+ })
10821
+ );
10822
+ }
10596
10823
  return Promise.all(
10597
10824
  nodes.map(async (node) => {
10598
10825
  const nodeId = primaryKeyToString(entityTable, node);
@@ -20734,5 +20961,5 @@ exports.getOrCreateDatabase = getOrCreateDatabase;
20734
20961
  exports.reconstructConnectionNodes = reconstructConnectionNodes;
20735
20962
  exports.reconstructEntity = reconstructEntity;
20736
20963
  exports.uploadFile = uploadFile;
20737
- //# sourceMappingURL=chunk-UDXG6COO.cjs.map
20738
- //# sourceMappingURL=chunk-UDXG6COO.cjs.map
20964
+ //# sourceMappingURL=chunk-RSSJCKVR.cjs.map
20965
+ //# sourceMappingURL=chunk-RSSJCKVR.cjs.map