persian-data-grid 0.5.0 → 0.7.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 (282) hide show
  1. package/OFL.txt +93 -0
  2. package/README.md +179 -22
  3. package/dist/ERPTableGroupRow-E5Q6FYDS.js +125 -0
  4. package/dist/ERPTableGroupRow-E5Q6FYDS.js.map +1 -0
  5. package/dist/ERPTableGroupRow-F2IPDYL5.cjs +131 -0
  6. package/dist/ERPTableGroupRow-F2IPDYL5.cjs.map +1 -0
  7. package/dist/GridPlugin-Bewn0X51.d.ts +57 -0
  8. package/dist/GridPlugin-DewWufGZ.d.cts +57 -0
  9. package/dist/GridPresenceService-GEH6535Q.js +3 -0
  10. package/dist/GridPresenceService-GEH6535Q.js.map +1 -0
  11. package/dist/GridPresenceService-RDN5FXHQ.cjs +12 -0
  12. package/dist/GridPresenceService-RDN5FXHQ.cjs.map +1 -0
  13. package/dist/GridSession-CeIfVFck.d.ts +2625 -0
  14. package/dist/GridSession-CmQn2RnL.d.cts +2625 -0
  15. package/dist/GridSyncService-3JQLEY65.cjs +12 -0
  16. package/dist/GridSyncService-3JQLEY65.cjs.map +1 -0
  17. package/dist/GridSyncService-GECXGEKL.js +3 -0
  18. package/dist/GridSyncService-GECXGEKL.js.map +1 -0
  19. package/dist/PdfRenderer-B9FJaCo_.d.cts +22 -0
  20. package/dist/PdfRenderer-bN7ca_Aj.d.ts +22 -0
  21. package/dist/RemoteApi-CCQxiSf1.d.cts +28 -0
  22. package/dist/RemoteApi-CCQxiSf1.d.ts +28 -0
  23. package/dist/SelectImpl-NM4O4IGW.js +447 -0
  24. package/dist/SelectImpl-NM4O4IGW.js.map +1 -0
  25. package/dist/SelectImpl-W2EI4T7O.cjs +453 -0
  26. package/dist/SelectImpl-W2EI4T7O.cjs.map +1 -0
  27. package/dist/UITableController-C71LXBiX.d.ts +678 -0
  28. package/dist/UITableController-CIwnHIw8.d.cts +678 -0
  29. package/dist/VazirmatnPdfRenderer-CpCqA-Wh.d.cts +96 -0
  30. package/dist/VazirmatnPdfRenderer-D46smncn.d.ts +96 -0
  31. package/dist/adapters/arian-erp.cjs +89 -0
  32. package/dist/adapters/arian-erp.cjs.map +1 -0
  33. package/dist/adapters/arian-erp.d.cts +62 -0
  34. package/dist/adapters/arian-erp.d.ts +62 -0
  35. package/dist/adapters/arian-erp.js +79 -0
  36. package/dist/adapters/arian-erp.js.map +1 -0
  37. package/dist/appendAtEnd-RHIQRG2W.cjs +35 -0
  38. package/dist/appendAtEnd-RHIQRG2W.cjs.map +1 -0
  39. package/dist/appendAtEnd-TJEABLGD.js +33 -0
  40. package/dist/appendAtEnd-TJEABLGD.js.map +1 -0
  41. package/dist/applyImportedRows-C2fWYoPy.d.cts +135 -0
  42. package/dist/applyImportedRows-Cqh_N_0k.d.ts +135 -0
  43. package/dist/applyRowVisibilityHandler-Q64ZFC7A.cjs +86 -0
  44. package/dist/applyRowVisibilityHandler-Q64ZFC7A.cjs.map +1 -0
  45. package/dist/applyRowVisibilityHandler-ZLZXQEGY.js +84 -0
  46. package/dist/applyRowVisibilityHandler-ZLZXQEGY.js.map +1 -0
  47. package/dist/broadcastChannelTransport-FHUMV76O.cjs +12 -0
  48. package/dist/broadcastChannelTransport-FHUMV76O.cjs.map +1 -0
  49. package/dist/broadcastChannelTransport-MIH53VNZ.js +3 -0
  50. package/dist/broadcastChannelTransport-MIH53VNZ.js.map +1 -0
  51. package/dist/callbackRemoteApi-BYLjmSI6.d.ts +51 -0
  52. package/dist/callbackRemoteApi-Cv26MmSD.d.cts +51 -0
  53. package/dist/cellRenderContext-BaNh4qn_.d.ts +42 -0
  54. package/dist/cellRenderContext-DvngkptV.d.cts +42 -0
  55. package/dist/chunk-27ALJSZP.js +12187 -0
  56. package/dist/chunk-27ALJSZP.js.map +1 -0
  57. package/dist/chunk-3HP3HQCW.cjs +688 -0
  58. package/dist/chunk-3HP3HQCW.cjs.map +1 -0
  59. package/dist/chunk-3Y3KKGEC.js +3 -0
  60. package/dist/chunk-3Y3KKGEC.js.map +1 -0
  61. package/dist/chunk-52LFOPXD.cjs +588 -0
  62. package/dist/chunk-52LFOPXD.cjs.map +1 -0
  63. package/dist/chunk-5ALMNLUR.cjs +1408 -0
  64. package/dist/chunk-5ALMNLUR.cjs.map +1 -0
  65. package/dist/chunk-5HKVMPBD.js +937 -0
  66. package/dist/chunk-5HKVMPBD.js.map +1 -0
  67. package/dist/chunk-72IVAOTK.cjs +38 -0
  68. package/dist/chunk-72IVAOTK.cjs.map +1 -0
  69. package/dist/chunk-77UXAJII.js +112 -0
  70. package/dist/chunk-77UXAJII.js.map +1 -0
  71. package/dist/chunk-7GWV5HGO.js +36 -0
  72. package/dist/chunk-7GWV5HGO.js.map +1 -0
  73. package/dist/chunk-7T6AWRPU.js +581 -0
  74. package/dist/chunk-7T6AWRPU.js.map +1 -0
  75. package/dist/chunk-AU3RDJYQ.js +243 -0
  76. package/dist/chunk-AU3RDJYQ.js.map +1 -0
  77. package/dist/chunk-CILNIWYW.cjs +100 -0
  78. package/dist/chunk-CILNIWYW.cjs.map +1 -0
  79. package/dist/chunk-CMQ4VLCW.cjs +840 -0
  80. package/dist/chunk-CMQ4VLCW.cjs.map +1 -0
  81. package/dist/chunk-DFFP7SJ6.cjs +257 -0
  82. package/dist/chunk-DFFP7SJ6.cjs.map +1 -0
  83. package/dist/chunk-E2J533UH.cjs +53 -0
  84. package/dist/chunk-E2J533UH.cjs.map +1 -0
  85. package/dist/chunk-EBV6HGZB.cjs +33 -0
  86. package/dist/chunk-EBV6HGZB.cjs.map +1 -0
  87. package/dist/chunk-F4PYOYHR.js +112 -0
  88. package/dist/chunk-F4PYOYHR.js.map +1 -0
  89. package/dist/chunk-F6EMC75A.cjs +34 -0
  90. package/dist/chunk-F6EMC75A.cjs.map +1 -0
  91. package/dist/chunk-GKE33FOJ.js +32 -0
  92. package/dist/chunk-GKE33FOJ.js.map +1 -0
  93. package/dist/chunk-GQEDLUMB.js +168 -0
  94. package/dist/chunk-GQEDLUMB.js.map +1 -0
  95. package/dist/chunk-HLOHWFOA.js +645 -0
  96. package/dist/chunk-HLOHWFOA.js.map +1 -0
  97. package/dist/chunk-IMPCRWYP.cjs +251 -0
  98. package/dist/chunk-IMPCRWYP.cjs.map +1 -0
  99. package/dist/chunk-IPPIXOVQ.js +317 -0
  100. package/dist/chunk-IPPIXOVQ.js.map +1 -0
  101. package/dist/chunk-J4XJ4UBC.js +1404 -0
  102. package/dist/chunk-J4XJ4UBC.js.map +1 -0
  103. package/dist/chunk-J7CPPFXT.cjs +89 -0
  104. package/dist/chunk-J7CPPFXT.cjs.map +1 -0
  105. package/dist/chunk-JOICD47Q.cjs +41 -0
  106. package/dist/chunk-JOICD47Q.cjs.map +1 -0
  107. package/dist/chunk-KCNTKYUY.js +206 -0
  108. package/dist/chunk-KCNTKYUY.js.map +1 -0
  109. package/dist/chunk-KHMG6EI2.js +13 -0
  110. package/dist/chunk-KHMG6EI2.js.map +1 -0
  111. package/dist/chunk-KMRURD5J.cjs +15 -0
  112. package/dist/chunk-KMRURD5J.cjs.map +1 -0
  113. package/dist/chunk-KRJG3KMP.cjs +174 -0
  114. package/dist/chunk-KRJG3KMP.cjs.map +1 -0
  115. package/dist/chunk-LY27YAXE.cjs +208 -0
  116. package/dist/chunk-LY27YAXE.cjs.map +1 -0
  117. package/dist/chunk-MT3KLB2Q.cjs +944 -0
  118. package/dist/chunk-MT3KLB2Q.cjs.map +1 -0
  119. package/dist/chunk-MUQQKPWR.js +84 -0
  120. package/dist/chunk-MUQQKPWR.js.map +1 -0
  121. package/dist/chunk-N7SJTINL.cjs +4 -0
  122. package/dist/chunk-N7SJTINL.cjs.map +1 -0
  123. package/dist/chunk-NB5GCGHL.js +833 -0
  124. package/dist/chunk-NB5GCGHL.js.map +1 -0
  125. package/dist/chunk-NQUVAJQA.js +298 -0
  126. package/dist/chunk-NQUVAJQA.js.map +1 -0
  127. package/dist/chunk-NTV2UUAJ.cjs +149 -0
  128. package/dist/chunk-NTV2UUAJ.cjs.map +1 -0
  129. package/dist/chunk-PJQWHF6T.js +31 -0
  130. package/dist/chunk-PJQWHF6T.js.map +1 -0
  131. package/dist/chunk-PQY54EL5.js +248 -0
  132. package/dist/chunk-PQY54EL5.js.map +1 -0
  133. package/dist/chunk-Q7TBBUTX.cjs +110 -0
  134. package/dist/chunk-Q7TBBUTX.cjs.map +1 -0
  135. package/dist/chunk-QZN7R2WP.cjs +114 -0
  136. package/dist/chunk-QZN7R2WP.cjs.map +1 -0
  137. package/dist/chunk-RKVSJU46.js +35 -0
  138. package/dist/chunk-RKVSJU46.js.map +1 -0
  139. package/dist/chunk-SVO62ZV2.js +50 -0
  140. package/dist/chunk-SVO62ZV2.js.map +1 -0
  141. package/dist/chunk-SX4VQXAA.cjs +114 -0
  142. package/dist/chunk-SX4VQXAA.cjs.map +1 -0
  143. package/dist/chunk-SZ7BLAY3.js +92 -0
  144. package/dist/chunk-SZ7BLAY3.js.map +1 -0
  145. package/dist/chunk-TN4MG6FY.js +112 -0
  146. package/dist/chunk-TN4MG6FY.js.map +1 -0
  147. package/dist/chunk-UC7PHDM6.cjs +331 -0
  148. package/dist/chunk-UC7PHDM6.cjs.map +1 -0
  149. package/dist/chunk-UXVLYUUW.js +108 -0
  150. package/dist/chunk-UXVLYUUW.js.map +1 -0
  151. package/dist/chunk-VBAZ77Q4.js +932 -0
  152. package/dist/chunk-VBAZ77Q4.js.map +1 -0
  153. package/dist/chunk-VSAFQTW5.cjs +319 -0
  154. package/dist/chunk-VSAFQTW5.cjs.map +1 -0
  155. package/dist/chunk-VYJQ2HTA.cjs +936 -0
  156. package/dist/chunk-VYJQ2HTA.cjs.map +1 -0
  157. package/dist/chunk-W5256BJ4.js +56 -0
  158. package/dist/chunk-W5256BJ4.js.map +1 -0
  159. package/dist/chunk-WTHTVKVI.cjs +12299 -0
  160. package/dist/chunk-WTHTVKVI.cjs.map +1 -0
  161. package/dist/chunk-WUMOSOEK.cjs +62 -0
  162. package/dist/chunk-WUMOSOEK.cjs.map +1 -0
  163. package/dist/chunk-XLRE2J25.js +140 -0
  164. package/dist/chunk-XLRE2J25.js.map +1 -0
  165. package/dist/chunk-Z4QPZ3Z3.cjs +114 -0
  166. package/dist/chunk-Z4QPZ3Z3.cjs.map +1 -0
  167. package/dist/createEngineGridQuery-BBkud5oE.d.ts +939 -0
  168. package/dist/createEngineGridQuery-D-fgO_jg.d.cts +939 -0
  169. package/dist/date-picker-jalali.cjs +263 -0
  170. package/dist/date-picker-jalali.cjs.map +1 -0
  171. package/dist/date-picker-jalali.d.cts +62 -0
  172. package/dist/date-picker-jalali.d.ts +62 -0
  173. package/dist/date-picker-jalali.js +256 -0
  174. package/dist/date-picker-jalali.js.map +1 -0
  175. package/dist/finance.cjs +205 -0
  176. package/dist/finance.cjs.map +1 -0
  177. package/dist/finance.d.cts +90 -0
  178. package/dist/finance.d.ts +90 -0
  179. package/dist/finance.js +157 -0
  180. package/dist/finance.js.map +1 -0
  181. package/dist/foldedColumn-IHDT4ARZ.cjs +66 -0
  182. package/dist/foldedColumn-IHDT4ARZ.cjs.map +1 -0
  183. package/dist/foldedColumn-TFURUKFG.js +64 -0
  184. package/dist/foldedColumn-TFURUKFG.js.map +1 -0
  185. package/dist/headless.cjs +729 -0
  186. package/dist/headless.cjs.map +1 -0
  187. package/dist/headless.d.cts +508 -0
  188. package/dist/headless.d.ts +508 -0
  189. package/dist/headless.js +226 -0
  190. package/dist/headless.js.map +1 -0
  191. package/dist/index-DN8M8Jkq.d.cts +14 -0
  192. package/dist/index-DmzL-F_o.d.ts +14 -0
  193. package/dist/index.cjs +8060 -11409
  194. package/dist/index.cjs.map +1 -1
  195. package/dist/index.d.cts +457 -2305
  196. package/dist/index.d.ts +457 -2305
  197. package/dist/index.js +7302 -11084
  198. package/dist/index.js.map +1 -1
  199. package/dist/locales/ar.cjs +12 -0
  200. package/dist/locales/ar.cjs.map +1 -0
  201. package/dist/locales/ar.d.cts +6 -0
  202. package/dist/locales/ar.d.ts +6 -0
  203. package/dist/locales/ar.js +3 -0
  204. package/dist/locales/ar.js.map +1 -0
  205. package/dist/locales/en.cjs +12 -0
  206. package/dist/locales/en.cjs.map +1 -0
  207. package/dist/locales/en.d.cts +6 -0
  208. package/dist/locales/en.d.ts +6 -0
  209. package/dist/locales/en.js +3 -0
  210. package/dist/locales/en.js.map +1 -0
  211. package/dist/locales/fa.cjs +12 -0
  212. package/dist/locales/fa.cjs.map +1 -0
  213. package/dist/locales/fa.d.cts +6 -0
  214. package/dist/locales/fa.d.ts +6 -0
  215. package/dist/locales/fa.js +3 -0
  216. package/dist/locales/fa.js.map +1 -0
  217. package/dist/locales/index.cjs +51 -0
  218. package/dist/locales/index.cjs.map +1 -0
  219. package/dist/locales/index.d.cts +5 -0
  220. package/dist/locales/index.d.ts +5 -0
  221. package/dist/locales/index.js +6 -0
  222. package/dist/locales/index.js.map +1 -0
  223. package/dist/memoryRecords-AWDKUJ2F.cjs +50 -0
  224. package/dist/memoryRecords-AWDKUJ2F.cjs.map +1 -0
  225. package/dist/memoryRecords-OJTGXLRH.js +47 -0
  226. package/dist/memoryRecords-OJTGXLRH.js.map +1 -0
  227. package/dist/pagedFooterTotals-DXCGDO6F.cjs +27 -0
  228. package/dist/pagedFooterTotals-DXCGDO6F.cjs.map +1 -0
  229. package/dist/pagedFooterTotals-ORWKUNE7.js +25 -0
  230. package/dist/pagedFooterTotals-ORWKUNE7.js.map +1 -0
  231. package/dist/pagedKit-MFGAJFSK.cjs +134 -0
  232. package/dist/pagedKit-MFGAJFSK.cjs.map +1 -0
  233. package/dist/pagedKit-WYWY4LRV.js +127 -0
  234. package/dist/pagedKit-WYWY4LRV.js.map +1 -0
  235. package/dist/pagedSearch-HCPJW7VM.js +38 -0
  236. package/dist/pagedSearch-HCPJW7VM.js.map +1 -0
  237. package/dist/pagedSearch-U3WCG4GN.cjs +42 -0
  238. package/dist/pagedSearch-U3WCG4GN.cjs.map +1 -0
  239. package/dist/plugins/csv.cjs +124 -0
  240. package/dist/plugins/csv.cjs.map +1 -0
  241. package/dist/plugins/csv.d.cts +44 -0
  242. package/dist/plugins/csv.d.ts +44 -0
  243. package/dist/plugins/csv.js +120 -0
  244. package/dist/plugins/csv.js.map +1 -0
  245. package/dist/plugins/excel.cjs +52 -0
  246. package/dist/plugins/excel.cjs.map +1 -0
  247. package/dist/plugins/excel.d.cts +14 -0
  248. package/dist/plugins/excel.d.ts +14 -0
  249. package/dist/plugins/excel.js +7 -0
  250. package/dist/plugins/excel.js.map +1 -0
  251. package/dist/plugins/pdf/fonts/vazirmatn.cjs +50 -0
  252. package/dist/plugins/pdf/fonts/vazirmatn.cjs.map +1 -0
  253. package/dist/plugins/pdf/fonts/vazirmatn.d.cts +24 -0
  254. package/dist/plugins/pdf/fonts/vazirmatn.d.ts +24 -0
  255. package/dist/plugins/pdf/fonts/vazirmatn.js +39 -0
  256. package/dist/plugins/pdf/fonts/vazirmatn.js.map +1 -0
  257. package/dist/plugins/pdf.cjs +24 -0
  258. package/dist/plugins/pdf.cjs.map +1 -0
  259. package/dist/plugins/pdf.d.cts +21 -0
  260. package/dist/plugins/pdf.d.ts +21 -0
  261. package/dist/plugins/pdf.js +7 -0
  262. package/dist/plugins/pdf.js.map +1 -0
  263. package/dist/presence.cjs +56 -0
  264. package/dist/presence.cjs.map +1 -0
  265. package/dist/presence.d.cts +17 -0
  266. package/dist/presence.d.ts +17 -0
  267. package/dist/presence.js +54 -0
  268. package/dist/presence.js.map +1 -0
  269. package/dist/rebuildRowVisibility-66EAZ356.cjs +21 -0
  270. package/dist/rebuildRowVisibility-66EAZ356.cjs.map +1 -0
  271. package/dist/rebuildRowVisibility-6TYM5BP2.js +19 -0
  272. package/dist/rebuildRowVisibility-6TYM5BP2.js.map +1 -0
  273. package/dist/styles.css +1440 -0
  274. package/dist/sync.cjs +148 -0
  275. package/dist/sync.cjs.map +1 -0
  276. package/dist/sync.d.cts +17 -0
  277. package/dist/sync.d.ts +17 -0
  278. package/dist/sync.js +146 -0
  279. package/dist/sync.js.map +1 -0
  280. package/dist/types-D__47BKv.d.cts +23 -0
  281. package/dist/types-D__47BKv.d.ts +23 -0
  282. package/package.json +185 -29
@@ -0,0 +1,1404 @@
1
+ import { Cell, Row, Collection, Table } from './chunk-5HKVMPBD.js';
2
+ import { RowId, ColumnId, CellValue, RowState, Pagination } from './chunk-XLRE2J25.js';
3
+
4
+ // infrastructure/Repositories/paged/PagedIndexedDbTableRepository.ts
5
+ var DB_NAME = "ERP_TABLE_DB";
6
+ function rowRecordId(tableId, collection, rowId) {
7
+ return `${tableId}::${collection}::${rowId}`;
8
+ }
9
+ function sortIndexOf(row) {
10
+ const raw = row.getCell("index")?.getValue();
11
+ const n = Number(raw);
12
+ return Number.isFinite(n) ? n : 0;
13
+ }
14
+ function isValidIdbKey(value) {
15
+ if (typeof value === "string") return true;
16
+ if (typeof value === "number") return Number.isFinite(value);
17
+ if (value instanceof Date) return !Number.isNaN(value.getTime());
18
+ if (Array.isArray(value)) return value.length > 0 && value.every(isValidIdbKey);
19
+ if (value instanceof ArrayBuffer) return true;
20
+ if (ArrayBuffer.isView(value)) return true;
21
+ return false;
22
+ }
23
+ function asTableIdKey(tableId) {
24
+ if (typeof tableId === "string" && tableId.length > 0) return tableId;
25
+ if (typeof tableId === "number" && Number.isFinite(tableId)) return String(tableId);
26
+ return null;
27
+ }
28
+ var PagedIndexedDbTableRepository = class {
29
+ constructor() {
30
+ this.dbPromise = this.initDb();
31
+ }
32
+ bindDb(db) {
33
+ db.onversionchange = () => {
34
+ db.close();
35
+ console.warn("DB version changed, connection closed");
36
+ this.dbPromise = this.initDb();
37
+ };
38
+ return db;
39
+ }
40
+ /**
41
+ * Open at the *existing* DB version (no fixed version number).
42
+ * ensureStores() bumps version only when new object stores are needed.
43
+ * A fixed open(version) would throw VersionError after lazy upgrades.
44
+ */
45
+ initDb() {
46
+ return new Promise((resolve, reject) => {
47
+ const request = indexedDB.open(DB_NAME);
48
+ request.onsuccess = () => resolve(this.bindDb(request.result));
49
+ request.onerror = () => reject(request.error);
50
+ });
51
+ }
52
+ async getDb() {
53
+ return this.dbPromise;
54
+ }
55
+ metaStore(storeName) {
56
+ return storeName;
57
+ }
58
+ rowsStore(storeName) {
59
+ return `${storeName}__rows`;
60
+ }
61
+ async ensureStores(db, storeName) {
62
+ const meta = this.metaStore(storeName);
63
+ const rows = this.rowsStore(storeName);
64
+ if (db.objectStoreNames.contains(meta) && db.objectStoreNames.contains(rows)) {
65
+ return db;
66
+ }
67
+ const nextVersion = db.version + 1;
68
+ db.close();
69
+ const newDb = await new Promise((resolve, reject) => {
70
+ const req = indexedDB.open(DB_NAME, nextVersion);
71
+ req.onupgradeneeded = (event) => {
72
+ const upgraded = event.target.result;
73
+ if (!upgraded.objectStoreNames.contains(meta)) {
74
+ upgraded.createObjectStore(meta, { keyPath: "id" });
75
+ }
76
+ if (!upgraded.objectStoreNames.contains(rows)) {
77
+ const rowStore = upgraded.createObjectStore(rows, { keyPath: "id" });
78
+ rowStore.createIndex("byTableCollectionSort", ["tableId", "collection", "sortIndex"], {
79
+ unique: false
80
+ });
81
+ rowStore.createIndex("byTable", "tableId", { unique: false });
82
+ }
83
+ };
84
+ req.onsuccess = () => resolve(this.bindDb(req.result));
85
+ req.onerror = () => reject(req.error);
86
+ });
87
+ this.dbPromise = Promise.resolve(newDb);
88
+ return newDb;
89
+ }
90
+ // ─── Row (de)serialization ──────────────────────────────────────────────────
91
+ serializeRow(tableId, row) {
92
+ return {
93
+ id: rowRecordId(tableId, row.collection, row.id.value),
94
+ tableId,
95
+ rowId: row.id.value,
96
+ collection: row.collection,
97
+ sortIndex: sortIndexOf(row),
98
+ rowState: row.rowState.value,
99
+ visibilityCondition: row.visibilityCondition,
100
+ isSaveDescription: row.isSaveDescription,
101
+ cells: Array.from(row.getCells().entries()).map(([colId, cell]) => ({
102
+ colId,
103
+ value: cell.getValue(),
104
+ originalValue: cell.getOriginalValue().value,
105
+ display: cell.getDisplay()
106
+ }))
107
+ };
108
+ }
109
+ deserializeRow(r) {
110
+ const cells = /* @__PURE__ */ new Map();
111
+ r.cells.forEach((c) => {
112
+ cells.set(
113
+ c.colId,
114
+ new Cell(
115
+ new RowId(r.rowId),
116
+ new ColumnId(c.colId),
117
+ new CellValue(c.value),
118
+ new CellValue(c.originalValue),
119
+ new CellValue(c.display)
120
+ )
121
+ );
122
+ });
123
+ const changeFields = /* @__PURE__ */ new Set();
124
+ if (r.rowState === "Update" /* Modified */) {
125
+ r.cells.forEach((c) => {
126
+ if (c.value !== c.originalValue) changeFields.add(c.colId);
127
+ });
128
+ }
129
+ return new Row(
130
+ new RowId(r.rowId),
131
+ cells,
132
+ new RowState(r.rowState),
133
+ changeFields,
134
+ r.visibilityCondition,
135
+ r.collection,
136
+ r.isSaveDescription
137
+ );
138
+ }
139
+ serializeColumn(col) {
140
+ return {
141
+ fieldName: col.fieldName,
142
+ fieldCaption: col.fieldCaption,
143
+ fieldType: col.fieldType,
144
+ fieldCollection: col.fieldCollection,
145
+ order: col.order,
146
+ editable: col.editable,
147
+ isUnique: col.isUnique,
148
+ noCache: col.noCache,
149
+ expandable: col.expandable,
150
+ enableDeleteButton: col.enableDeleteButton,
151
+ enableExpandButton: col.enableExpandButton,
152
+ enableMoveButton: col.enableMoveButton,
153
+ enableOptionsButton: col.enableOptionsButton,
154
+ disable: col.disable,
155
+ hidden: col.hidden,
156
+ className: col.className,
157
+ visibility: col.visibility,
158
+ selectOptions: col.selectOptions,
159
+ minWidth: col.minWidth,
160
+ style: col.style,
161
+ popUpWidth: col.popUpWidth,
162
+ labelValue: col.labelValue
163
+ };
164
+ }
165
+ buildMetaFromTable(table, totalRowsOverride, existing) {
166
+ const id = asTableIdKey(table.id);
167
+ if (!id) {
168
+ throw new Error(`[PagedIndexedDbTableRepository] Cannot persist meta: invalid table.id (${String(table.id)})`);
169
+ }
170
+ const pagination = table.getPagination();
171
+ const active = table.getActiveCollectionName() || "default";
172
+ const collections = [];
173
+ for (const [name, col] of table.getCollections()) {
174
+ collections.push({
175
+ name,
176
+ columns: Array.from(col.getColumns().values()).map((c) => this.serializeColumn(c))
177
+ });
178
+ }
179
+ const windowCounts = {};
180
+ let counted = 0;
181
+ let otherCollectionsHaveRows = false;
182
+ for (const [name, col] of table.getCollections()) {
183
+ const size = col.getRows().size;
184
+ if (name === active) windowCounts[name] = size;
185
+ else if (size > 0) otherCollectionsHaveRows = true;
186
+ counted += size;
187
+ }
188
+ const isFullMultiCollectionSeed = otherCollectionsHaveRows;
189
+ const totalRows = isFullMultiCollectionSeed ? windowCounts[active] ?? 0 : totalRowsOverride ?? Math.max(pagination.totalRows, counted);
190
+ const totalRowsByCollection = {
191
+ ...existing?.totalRowsByCollection ?? {},
192
+ ...windowCounts,
193
+ [active]: totalRows
194
+ };
195
+ if (isFullMultiCollectionSeed) {
196
+ for (const [name, col] of table.getCollections()) {
197
+ totalRowsByCollection[name] = col.getRows().size;
198
+ }
199
+ }
200
+ return {
201
+ id,
202
+ kind: "meta",
203
+ activeCollection: active,
204
+ page: pagination.page || 1,
205
+ pageSize: pagination.pageSize || 10,
206
+ totalRows,
207
+ totalRowsByCollection,
208
+ collections
209
+ };
210
+ }
211
+ buildTableFromPage(meta, pageRows, page, pageSize) {
212
+ const collectionMap = /* @__PURE__ */ new Map();
213
+ const active = meta.activeCollection || "default";
214
+ for (const colMeta of meta.collections ?? []) {
215
+ const colMap = /* @__PURE__ */ new Map();
216
+ for (const c of colMeta.columns ?? []) {
217
+ colMap.set(c.fieldName, c);
218
+ }
219
+ collectionMap.set(colMeta.name, new Collection(colMeta.name, /* @__PURE__ */ new Map(), colMap));
220
+ }
221
+ if (!collectionMap.has(active)) {
222
+ collectionMap.set(active, new Collection(active, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()));
223
+ }
224
+ for (const row of pageRows) {
225
+ const name = row.collection || active;
226
+ let collection = collectionMap.get(name);
227
+ if (!collection) {
228
+ collection = new Collection(name, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
229
+ collectionMap.set(name, collection);
230
+ }
231
+ collection.getRows().set(row.id.value, row);
232
+ }
233
+ const total = meta.totalRowsByCollection?.[active] ?? meta.totalRows;
234
+ return new Table(
235
+ meta.id,
236
+ collectionMap,
237
+ new Pagination(page, pageSize, total),
238
+ null,
239
+ active
240
+ ).setPageWindow(true);
241
+ }
242
+ async readMeta(db, storeName, tableId) {
243
+ const key = asTableIdKey(tableId);
244
+ if (!key) return null;
245
+ const metaName = this.metaStore(storeName);
246
+ if (!db.objectStoreNames.contains(metaName)) return null;
247
+ return new Promise((resolve, reject) => {
248
+ const tx = db.transaction(metaName, "readonly");
249
+ const req = tx.objectStore(metaName).get(key);
250
+ req.onsuccess = () => resolve(req.result ?? null);
251
+ req.onerror = () => reject(req.error);
252
+ });
253
+ }
254
+ isLegacy(data) {
255
+ return !("kind" in data) || data.kind !== "meta";
256
+ }
257
+ async migrateLegacy(db, storeName, legacy) {
258
+ const active = legacy.activeCollection ?? legacy.activeDetailType ?? "default";
259
+ const allRows = [];
260
+ const collections = [];
261
+ for (const c of legacy.collections ?? []) {
262
+ const name = c.name ?? c.type ?? active;
263
+ collections.push({ name, columns: [] });
264
+ for (const r of c.rows ?? []) {
265
+ allRows.push(
266
+ this.deserializeRow({
267
+ id: rowRecordId(legacy.id, r.collection ?? r.rowType ?? name, r.rowId),
268
+ tableId: legacy.id,
269
+ rowId: r.rowId,
270
+ collection: r.collection ?? r.rowType ?? name,
271
+ sortIndex: 0,
272
+ rowState: r.rowState,
273
+ visibilityCondition: r.visibilityCondition,
274
+ isSaveDescription: r.isSaveDescription,
275
+ cells: r.cells
276
+ })
277
+ );
278
+ }
279
+ }
280
+ for (const row of allRows) {
281
+ }
282
+ const meta = {
283
+ id: legacy.id,
284
+ kind: "meta",
285
+ activeCollection: active,
286
+ page: 1,
287
+ pageSize: 10,
288
+ totalRows: allRows.length,
289
+ collections
290
+ };
291
+ db = await this.ensureStores(db, storeName);
292
+ await this.putMeta(db, storeName, meta);
293
+ await this.putRows(db, storeName, legacy.id, allRows);
294
+ return meta;
295
+ }
296
+ putMeta(db, storeName, meta) {
297
+ const id = asTableIdKey(meta.id);
298
+ if (!id) {
299
+ console.warn("[PagedIndexedDbTableRepository] putMeta skipped: invalid id", meta?.id);
300
+ return Promise.resolve();
301
+ }
302
+ let cloneable;
303
+ try {
304
+ cloneable = JSON.parse(JSON.stringify({ ...meta, id }));
305
+ } catch (e) {
306
+ console.warn("[PagedIndexedDbTableRepository] putMeta skipped: not serializable", e);
307
+ return Promise.resolve();
308
+ }
309
+ cloneable.id = id;
310
+ if (!isValidIdbKey(cloneable.id)) {
311
+ console.warn("[PagedIndexedDbTableRepository] putMeta skipped: id not a valid IDB key", cloneable.id);
312
+ return Promise.resolve();
313
+ }
314
+ return new Promise((resolve, reject) => {
315
+ const tx = db.transaction(this.metaStore(storeName), "readwrite");
316
+ tx.objectStore(this.metaStore(storeName)).put(cloneable);
317
+ tx.oncomplete = () => resolve();
318
+ tx.onerror = () => reject(tx.error);
319
+ });
320
+ }
321
+ putRows(db, storeName, tableId, rows) {
322
+ const key = asTableIdKey(tableId);
323
+ if (!key) {
324
+ console.warn("[PagedIndexedDbTableRepository] putRows skipped: invalid tableId", tableId);
325
+ return Promise.resolve();
326
+ }
327
+ return new Promise((resolve, reject) => {
328
+ const tx = db.transaction(this.rowsStore(storeName), "readwrite");
329
+ const os = tx.objectStore(this.rowsStore(storeName));
330
+ for (const row of rows) {
331
+ const record = this.serializeRow(key, row);
332
+ if (!isValidIdbKey(record.id)) continue;
333
+ const cloneable = JSON.parse(JSON.stringify(record));
334
+ cloneable.id = record.id;
335
+ cloneable.tableId = key;
336
+ os.put(cloneable);
337
+ }
338
+ tx.oncomplete = () => resolve();
339
+ tx.onerror = () => reject(tx.error);
340
+ });
341
+ }
342
+ /** Put already-serialized row records directly (no domain Row round-trip). */
343
+ putRowRecords(db, storeName, records) {
344
+ if (records.length === 0) return Promise.resolve();
345
+ return new Promise((resolve, reject) => {
346
+ const tx = db.transaction(this.rowsStore(storeName), "readwrite");
347
+ const os = tx.objectStore(this.rowsStore(storeName));
348
+ for (const record of records) {
349
+ if (!isValidIdbKey(record.id)) continue;
350
+ os.put(JSON.parse(JSON.stringify(record)));
351
+ }
352
+ tx.oncomplete = () => resolve();
353
+ tx.onerror = () => reject(tx.error);
354
+ });
355
+ }
356
+ async deleteAllRowsForTable(db, storeName, tableId) {
357
+ const key = asTableIdKey(tableId);
358
+ if (!key) {
359
+ console.warn("[PagedIndexedDbTableRepository] deleteAllRows skipped: invalid tableId", tableId);
360
+ return;
361
+ }
362
+ const rowsName = this.rowsStore(storeName);
363
+ if (!db.objectStoreNames.contains(rowsName)) return;
364
+ return new Promise((resolve, reject) => {
365
+ const tx = db.transaction(rowsName, "readwrite");
366
+ const store = tx.objectStore(rowsName);
367
+ const runFullScan = () => {
368
+ const req = store.openCursor();
369
+ req.onsuccess = () => {
370
+ const cursor = req.result;
371
+ if (!cursor) return;
372
+ const row = cursor.value;
373
+ if (String(row.tableId) === key) cursor.delete();
374
+ cursor.continue();
375
+ };
376
+ };
377
+ try {
378
+ if (store.indexNames.contains("byTable") && isValidIdbKey(key)) {
379
+ const index = store.index("byTable");
380
+ const req = index.openCursor(IDBKeyRange.only(key));
381
+ req.onsuccess = () => {
382
+ const cursor = req.result;
383
+ if (!cursor) return;
384
+ cursor.delete();
385
+ cursor.continue();
386
+ };
387
+ req.onerror = () => {
388
+ runFullScan();
389
+ };
390
+ } else {
391
+ runFullScan();
392
+ }
393
+ } catch {
394
+ runFullScan();
395
+ }
396
+ tx.oncomplete = () => resolve();
397
+ tx.onerror = () => reject(tx.error);
398
+ });
399
+ }
400
+ /**
401
+ * Single cursor pass over the byTableCollectionSort index, yielding batches.
402
+ * The index gives collection-sorted order — no in-memory sort needed.
403
+ * Falls back to a full scan when the index is missing (legacy stores).
404
+ * onBatch may return false to stop early.
405
+ */
406
+ async iterateRows(db, storeName, tableId, collection, onBatch, batchSize = 500) {
407
+ const key = asTableIdKey(tableId);
408
+ if (!key) return;
409
+ const rowsName = this.rowsStore(storeName);
410
+ if (!db.objectStoreNames.contains(rowsName)) return;
411
+ const useIndex = collection !== null;
412
+ let pending = [];
413
+ let ordinal = 0;
414
+ let stopped = false;
415
+ const flush = async () => {
416
+ if (pending.length === 0 || stopped) return;
417
+ const batch = pending;
418
+ pending = [];
419
+ const base = ordinal;
420
+ ordinal += batch.length;
421
+ if (await onBatch(batch, base) === false) stopped = true;
422
+ };
423
+ const batchOrDefer = async (record) => {
424
+ if (record) pending.push(record);
425
+ if (pending.length >= batchSize) await flush();
426
+ };
427
+ if (useIndex && collection) {
428
+ const runIndexed = () => new Promise((resolve, reject) => {
429
+ const tx = db.transaction(rowsName, "readonly");
430
+ const index = tx.objectStore(rowsName).index("byTableCollectionSort");
431
+ const range = IDBKeyRange.bound(
432
+ [key, collection, Number.MIN_SAFE_INTEGER],
433
+ [key, collection, Number.MAX_SAFE_INTEGER]
434
+ );
435
+ const req = index.openCursor(range);
436
+ req.onsuccess = async () => {
437
+ const cursor = req.result;
438
+ if (!cursor || stopped) {
439
+ await flush();
440
+ resolve();
441
+ return;
442
+ }
443
+ await batchOrDefer(cursor.value);
444
+ if (stopped) {
445
+ await flush();
446
+ resolve();
447
+ return;
448
+ }
449
+ cursor.continue();
450
+ };
451
+ req.onerror = () => reject(req.error);
452
+ });
453
+ try {
454
+ await runIndexed();
455
+ return;
456
+ } catch {
457
+ pending = [];
458
+ ordinal = 0;
459
+ stopped = false;
460
+ }
461
+ }
462
+ await new Promise((resolve, reject) => {
463
+ const tx = db.transaction(rowsName, "readonly");
464
+ const store = tx.objectStore(rowsName);
465
+ const req = store.openCursor();
466
+ req.onsuccess = async () => {
467
+ const cursor = req.result;
468
+ if (!cursor || stopped) {
469
+ await flush();
470
+ resolve();
471
+ return;
472
+ }
473
+ const record = cursor.value;
474
+ if (String(record.tableId) === key && (!collection || record.collection === collection)) {
475
+ await batchOrDefer(record);
476
+ if (stopped) {
477
+ await flush();
478
+ resolve();
479
+ return;
480
+ }
481
+ }
482
+ cursor.continue();
483
+ };
484
+ req.onerror = () => reject(req.error);
485
+ });
486
+ }
487
+ /**
488
+ * Page-window read via cursor + offset on the sorted index.
489
+ * Only `pageSize` records cross the heap — not the whole collection.
490
+ */
491
+ async queryPageRows(db, storeName, tableId, collection, page, pageSize) {
492
+ const start = Math.max(0, (page - 1) * pageSize);
493
+ const picked = [];
494
+ await this.iterateRows(
495
+ db,
496
+ storeName,
497
+ tableId,
498
+ collection,
499
+ async (records, ordinalBase) => {
500
+ if (ordinalBase + records.length > start && picked.length < pageSize) {
501
+ for (let i = 0; i < records.length; i++) {
502
+ const pos = ordinalBase + i;
503
+ if (pos >= start && pos < start + pageSize) {
504
+ picked.push(records[i]);
505
+ if (picked.length === pageSize) return false;
506
+ }
507
+ }
508
+ }
509
+ if (ordinalBase > start + pageSize) return false;
510
+ },
511
+ Math.max(pageSize, 500)
512
+ );
513
+ return picked.map((r) => this.deserializeRow(r));
514
+ }
515
+ async queryAllRowRecords(db, storeName, tableId) {
516
+ const key = asTableIdKey(tableId);
517
+ if (!key) return [];
518
+ const rowsName = this.rowsStore(storeName);
519
+ if (!db.objectStoreNames.contains(rowsName)) return [];
520
+ return new Promise((resolve, reject) => {
521
+ const tx = db.transaction(rowsName, "readonly");
522
+ const store = tx.objectStore(rowsName);
523
+ if (store.indexNames.contains("byTable") && isValidIdbKey(key)) {
524
+ try {
525
+ const index = store.index("byTable");
526
+ const req2 = index.getAll(IDBKeyRange.only(key));
527
+ req2.onsuccess = () => resolve(req2.result ?? []);
528
+ req2.onerror = () => reject(req2.error);
529
+ return;
530
+ } catch {
531
+ }
532
+ }
533
+ const req = store.getAll();
534
+ req.onsuccess = () => {
535
+ const all = req.result ?? [];
536
+ resolve(all.filter((r) => String(r.tableId) === key));
537
+ };
538
+ req.onerror = () => reject(req.error);
539
+ });
540
+ }
541
+ async ensureMeta(db, storeName, tableId) {
542
+ const key = asTableIdKey(tableId);
543
+ if (!key) {
544
+ throw new Error(`[PagedIndexedDbTableRepository] ensureMeta: invalid tableId (${String(tableId)})`);
545
+ }
546
+ let current = await this.getDb().then((d) => this.ensureStores(d, storeName));
547
+ let raw = await this.readMeta(current, storeName, key);
548
+ if (!raw) {
549
+ throw new Error(`Table ${key} not found`);
550
+ }
551
+ if (this.isLegacy(raw)) {
552
+ const migrated = await this.migrateLegacy(current, storeName, raw);
553
+ current = await this.getDb();
554
+ return { db: current, meta: migrated };
555
+ }
556
+ if (raw.recordKeyVersion !== 2) {
557
+ const migrated = await this.migrateToCollectionScopedKeys(current, storeName, raw);
558
+ current = await this.getDb();
559
+ return { db: current, meta: migrated };
560
+ }
561
+ return { db: current, meta: raw };
562
+ }
563
+ /**
564
+ * One-time re-key: `table::rowId` → `table::collection::rowId`.
565
+ * Old-format records may collide (same PK in different collections overwrote
566
+ * each other); after re-keying, per-collection counts in meta are re-derived
567
+ * from the actual records so pagination stops being scrambled.
568
+ */
569
+ async migrateToCollectionScopedKeys(db, storeName, meta) {
570
+ db = await this.ensureStores(db, storeName);
571
+ const rowsName = this.rowsStore(storeName);
572
+ await new Promise((resolve, reject) => {
573
+ const tx = db.transaction(rowsName, "readwrite");
574
+ const store = tx.objectStore(rowsName);
575
+ const rekey = (src) => {
576
+ const req = src.openCursor();
577
+ req.onsuccess = () => {
578
+ const cursor = req.result;
579
+ if (!cursor) return;
580
+ const rec = cursor.value;
581
+ const segments = typeof rec.id === "string" ? rec.id.split("::").length : 0;
582
+ if (String(rec.tableId) === (meta.id ?? "") && segments === 2) {
583
+ const rec2 = { ...rec, id: rowRecordId(rec.tableId, rec.collection, rec.rowId) };
584
+ cursor.delete();
585
+ store.put(JSON.parse(JSON.stringify(rec2)));
586
+ }
587
+ cursor.continue();
588
+ };
589
+ req.onerror = () => reject(req.error);
590
+ };
591
+ rekey(store);
592
+ tx.oncomplete = () => resolve();
593
+ tx.onerror = () => reject(tx.error);
594
+ });
595
+ const counts = {};
596
+ await this.iterateRows(
597
+ db,
598
+ storeName,
599
+ meta.id ?? "",
600
+ null,
601
+ async (records) => {
602
+ for (const rec of records) {
603
+ counts[rec.collection] = (counts[rec.collection] ?? 0) + 1;
604
+ }
605
+ }
606
+ );
607
+ const next = {
608
+ ...meta,
609
+ recordKeyVersion: 2,
610
+ totalRowsByCollection: counts,
611
+ totalRows: counts[meta.activeCollection || "default"] ?? meta.totalRows ?? 0
612
+ };
613
+ await this.putMeta(db, storeName, next);
614
+ return next;
615
+ }
616
+ // ─── TableRepository ────────────────────────────────────────────────────────
617
+ async getById(storeName, tableId) {
618
+ const key = asTableIdKey(tableId);
619
+ if (!key) {
620
+ throw new Error(`[PagedIndexedDbTableRepository] getById: invalid tableId (${String(tableId)})`);
621
+ }
622
+ let db = await this.getDb();
623
+ db = await this.ensureStores(db, storeName);
624
+ const { db: d2, meta } = await this.ensureMeta(db, storeName, key);
625
+ const page = meta.page || 1;
626
+ const pageSize = meta.pageSize || 10;
627
+ const rows = await this.queryPageRows(
628
+ d2,
629
+ storeName,
630
+ key,
631
+ meta.activeCollection || "default",
632
+ page,
633
+ pageSize
634
+ );
635
+ return this.buildTableFromPage(meta, rows, page, pageSize);
636
+ }
637
+ async save(storeName, table) {
638
+ const key = asTableIdKey(table?.id);
639
+ if (!key) {
640
+ console.warn("[PagedIndexedDbTableRepository] save skipped: invalid table.id", table?.id);
641
+ return;
642
+ }
643
+ let db = await this.getDb();
644
+ db = await this.ensureStores(db, storeName);
645
+ let existingTotal = table.getPagination().totalRows;
646
+ let existingMeta = null;
647
+ try {
648
+ const raw = await this.readMeta(db, storeName, key);
649
+ if (raw && !this.isLegacy(raw)) {
650
+ existingMeta = raw;
651
+ existingTotal = Math.max(existingTotal, raw.totalRows);
652
+ }
653
+ } catch {
654
+ }
655
+ const totalRows = Math.max(existingTotal, table.getPagination().totalRows);
656
+ const meta = this.buildMetaFromTable(table, totalRows, existingMeta);
657
+ await this.putMeta(db, storeName, meta);
658
+ const rows = [];
659
+ for (const col of table.getCollections().values()) {
660
+ rows.push(...col.getRows().values());
661
+ }
662
+ await this.putRows(db, storeName, key, rows);
663
+ }
664
+ async replaceAll(storeName, table) {
665
+ const key = asTableIdKey(table?.id);
666
+ if (!key) {
667
+ console.warn("[PagedIndexedDbTableRepository] replaceAll skipped: invalid table.id", table?.id);
668
+ return;
669
+ }
670
+ let db = await this.getDb();
671
+ db = await this.ensureStores(db, storeName);
672
+ let survivorRecords = [];
673
+ try {
674
+ const raw = await this.readMeta(db, storeName, key);
675
+ if (raw && !this.isLegacy(raw)) {
676
+ const active = raw.activeCollection || "default";
677
+ const incoming = /* @__PURE__ */ new Set();
678
+ for (const col of table.getCollections().values()) {
679
+ if (col.getRows().size > 0) incoming.add(col.getCollectionName());
680
+ }
681
+ const allRecords = await this.queryAllRowRecords(db, storeName, key);
682
+ survivorRecords = allRecords.filter(
683
+ (r) => r.collection !== active && !incoming.has(r.collection)
684
+ );
685
+ }
686
+ } catch {
687
+ }
688
+ const rows = [];
689
+ for (const col of table.getCollections().values()) {
690
+ rows.push(...col.getRows().values());
691
+ }
692
+ await this.deleteAllRowsForTable(db, storeName, key);
693
+ const meta = this.buildMetaFromTable(table, rows.length);
694
+ meta.page = 1;
695
+ meta.recordKeyVersion = 2;
696
+ const survivorCounts = {};
697
+ for (const rec of survivorRecords) {
698
+ survivorCounts[rec.collection] = (survivorCounts[rec.collection] ?? 0) + 1;
699
+ }
700
+ for (const [name, count] of Object.entries(survivorCounts)) {
701
+ meta.totalRowsByCollection = meta.totalRowsByCollection ?? {};
702
+ meta.totalRowsByCollection[name] = count;
703
+ }
704
+ await this.putMeta(db, storeName, meta);
705
+ await this.putRows(db, storeName, key, rows);
706
+ if (survivorRecords.length > 0) {
707
+ await this.putRowRecords(db, storeName, survivorRecords);
708
+ }
709
+ }
710
+ async loadPage(storeName, tableId, page, pageSize) {
711
+ const key = asTableIdKey(tableId);
712
+ if (!key) {
713
+ console.warn("[PagedIndexedDbTableRepository] loadPage skipped: invalid tableId", tableId);
714
+ return null;
715
+ }
716
+ let db = await this.getDb();
717
+ db = await this.ensureStores(db, storeName);
718
+ const { db: d2, meta } = await this.ensureMeta(db, storeName, key);
719
+ const size = pageSize ?? meta.pageSize ?? 10;
720
+ const totalPages = Math.max(1, Math.ceil(Math.max(0, meta.totalRows) / Math.max(1, size)));
721
+ const safePage = Math.max(1, Math.min(page, totalPages));
722
+ const active = meta.activeCollection || "default";
723
+ const rows = await this.queryPageRows(
724
+ d2,
725
+ storeName,
726
+ key,
727
+ active,
728
+ safePage,
729
+ size
730
+ );
731
+ const activeTotal = meta.totalRowsByCollection?.[active] ?? meta.totalRows ?? 0;
732
+ const activeTotalPages = Math.max(1, Math.ceil(Math.max(0, activeTotal) / Math.max(1, size)));
733
+ const collectionSafePage = Math.max(1, Math.min(safePage, activeTotalPages));
734
+ let pageRows = rows;
735
+ if (collectionSafePage !== safePage) {
736
+ pageRows = await this.queryPageRows(d2, storeName, key, active, collectionSafePage, size);
737
+ }
738
+ meta.page = collectionSafePage;
739
+ meta.pageSize = size;
740
+ await this.putMeta(d2, storeName, meta);
741
+ return this.buildTableFromPage(meta, pageRows, collectionSafePage, size);
742
+ }
743
+ /**
744
+ * Persist the new active collection (page back to 1) and return a RAM window
745
+ * holding page 1 of that collection only. Rows of other collections stay
746
+ * untouched in IndexedDB — the returned Table contains no other-collection rows.
747
+ */
748
+ async setActiveCollection(storeName, tableId, collection) {
749
+ const key = asTableIdKey(tableId);
750
+ if (!key) return null;
751
+ let db = await this.getDb();
752
+ db = await this.ensureStores(db, storeName);
753
+ const { db: d2, meta } = await this.ensureMeta(db, storeName, key);
754
+ if (!meta.collections?.some((c) => c.name === collection)) {
755
+ console.warn(`[PagedIndexedDbTableRepository] setActiveCollection: unknown collection "${collection}"`);
756
+ return null;
757
+ }
758
+ const pageSize = meta.pageSize || 10;
759
+ const total = meta.totalRowsByCollection?.[collection] ?? 0;
760
+ const rows = await this.queryPageRows(d2, storeName, key, collection, 1, pageSize);
761
+ meta.activeCollection = collection;
762
+ meta.page = 1;
763
+ meta.totalRows = total;
764
+ await this.putMeta(d2, storeName, meta);
765
+ return this.buildTableFromPage(meta, rows, 1, pageSize);
766
+ }
767
+ async getAllRows(storeName, tableId, collectionScope = "active") {
768
+ const key = asTableIdKey(tableId);
769
+ if (!key) return [];
770
+ let db = await this.getDb();
771
+ db = await this.ensureStores(db, storeName);
772
+ const { db: d2, meta } = await this.ensureMeta(db, storeName, key);
773
+ const records = await this.queryAllRowRecords(d2, storeName, key);
774
+ let rows = records.map((r) => this.deserializeRow(r));
775
+ if (collectionScope === "all") {
776
+ return rows.sort((a, b) => sortIndexOf(a) - sortIndexOf(b));
777
+ }
778
+ if (Array.isArray(collectionScope)) {
779
+ const set = new Set(collectionScope);
780
+ rows = rows.filter((r) => set.has(r.collection));
781
+ } else {
782
+ const active = meta.activeCollection || "default";
783
+ rows = rows.filter((r) => r.collection === active);
784
+ }
785
+ return rows.sort((a, b) => sortIndexOf(a) - sortIndexOf(b));
786
+ }
787
+ async getAllRowIds(storeName, tableId, collectionScope = "active") {
788
+ const key = asTableIdKey(tableId);
789
+ if (!key) return [];
790
+ let db = await this.getDb();
791
+ db = await this.ensureStores(db, storeName);
792
+ const { db: d2, meta } = await this.ensureMeta(db, storeName, key);
793
+ let records = await this.queryAllRowRecords(d2, storeName, key);
794
+ if (collectionScope === "all") ; else if (Array.isArray(collectionScope)) {
795
+ const set = new Set(collectionScope);
796
+ records = records.filter((r) => set.has(r.collection));
797
+ } else {
798
+ const active = meta.activeCollection || "default";
799
+ records = records.filter((r) => r.collection === active);
800
+ }
801
+ records.sort((a, b) => a.sortIndex - b.sortIndex);
802
+ return records.map((r) => r.rowId);
803
+ }
804
+ clearLocal(_store, _tableKey) {
805
+ }
806
+ async flush(_store, _tableKey) {
807
+ }
808
+ async exists(storeName, tableId) {
809
+ const key = asTableIdKey(tableId);
810
+ if (!key) return false;
811
+ const db = await this.getDb();
812
+ const metaName = this.metaStore(storeName);
813
+ if (!db.objectStoreNames.contains(metaName)) return false;
814
+ return new Promise((resolve) => {
815
+ const tx = db.transaction(metaName, "readonly");
816
+ const req = tx.objectStore(metaName).get(key);
817
+ req.onsuccess = () => resolve(Boolean(req.result));
818
+ req.onerror = () => resolve(false);
819
+ });
820
+ }
821
+ async delete(storeName, tableId) {
822
+ const key = asTableIdKey(tableId);
823
+ if (!key) {
824
+ console.warn("[PagedIndexedDbTableRepository] delete skipped: invalid tableId", tableId);
825
+ return;
826
+ }
827
+ let db = await this.getDb();
828
+ if (!db.objectStoreNames.contains(this.metaStore(storeName))) return;
829
+ db = await this.ensureStores(db, storeName);
830
+ await this.deleteAllRowsForTable(db, storeName, key);
831
+ await new Promise((resolve, reject) => {
832
+ const tx = db.transaction(this.metaStore(storeName), "readwrite");
833
+ tx.objectStore(this.metaStore(storeName)).delete(key);
834
+ tx.oncomplete = () => resolve();
835
+ tx.onerror = () => reject(tx.error);
836
+ });
837
+ }
838
+ async resetTable(storeName, tableId) {
839
+ const key = asTableIdKey(tableId);
840
+ if (!key) {
841
+ console.warn("[PagedIndexedDbTableRepository] resetTable skipped: invalid tableId", tableId);
842
+ return;
843
+ }
844
+ let db = await this.getDb();
845
+ if (!db.objectStoreNames.contains(this.metaStore(storeName))) return;
846
+ db = await this.ensureStores(db, storeName);
847
+ const raw = await this.readMeta(db, storeName, key);
848
+ if (!raw) throw new Error(`Table ${key} not found`);
849
+ await this.deleteAllRowsForTable(db, storeName, key);
850
+ const active = this.isLegacy(raw) ? raw.activeCollection ?? raw.activeDetailType ?? "default" : raw.activeCollection;
851
+ const meta = {
852
+ id: key,
853
+ kind: "meta",
854
+ activeCollection: active,
855
+ page: 1,
856
+ pageSize: this.isLegacy(raw) ? 10 : raw.pageSize || 10,
857
+ totalRows: 0,
858
+ collections: this.isLegacy(raw) ? [] : (raw.collections ?? []).map((c) => ({ name: c.name, columns: c.columns ?? [] }))
859
+ };
860
+ await this.putMeta(db, storeName, meta);
861
+ }
862
+ /**
863
+ * Port: stream serialized row records in bounded batches (cursor over the
864
+ * sorted index). 'active' → index cursor on the active collection;
865
+ * 'all'/array → full scan filtered in-memory per batch. Heap = one batch.
866
+ */
867
+ async iterateRowRecords(storeName, tableId, collectionScope = "active", onBatch, batchSize = 500) {
868
+ const key = asTableIdKey(tableId);
869
+ if (!key) return;
870
+ let db = await this.getDb();
871
+ db = await this.ensureStores(db, storeName);
872
+ const { db: d2, meta } = await this.ensureMeta(db, storeName, key);
873
+ const active = meta.activeCollection || "default";
874
+ const collection = collectionScope === "active" ? active : null;
875
+ if (collection !== null) {
876
+ const normalized = async (records, ordinalBase) => onBatch(
877
+ records.map((r) => ({
878
+ rowId: r.rowId,
879
+ collection: r.collection,
880
+ sortIndex: r.sortIndex,
881
+ rowState: r.rowState,
882
+ cells: Object.fromEntries(
883
+ r.cells.map((c) => [c.colId, { value: c.value, originalValue: c.originalValue, display: c.display }])
884
+ )
885
+ })),
886
+ ordinalBase
887
+ );
888
+ await this.iterateRows(d2, storeName, key, collection, normalized, batchSize);
889
+ return;
890
+ }
891
+ const allowed = collectionScope === "all" ? null : new Set(collectionScope);
892
+ await this.iterateRows(
893
+ d2,
894
+ storeName,
895
+ key,
896
+ null,
897
+ async (records, ordinalBase) => {
898
+ const filtered = allowed ? records.filter((r) => allowed.has(r.collection)) : records;
899
+ if (filtered.length === 0) return;
900
+ return onBatch(
901
+ filtered.map((r) => ({
902
+ rowId: r.rowId,
903
+ collection: r.collection,
904
+ sortIndex: r.sortIndex,
905
+ rowState: r.rowState,
906
+ cells: Object.fromEntries(
907
+ r.cells.map((c) => [c.colId, { value: c.value, originalValue: c.originalValue, display: c.display }])
908
+ )
909
+ })),
910
+ ordinalBase
911
+ );
912
+ },
913
+ batchSize
914
+ );
915
+ }
916
+ /**
917
+ * Bulk soft/hard delete directly on persisted records in ONE readwrite
918
+ * transaction — cursor over the `byTable` index (no full hydration, heap
919
+ * stays O(1)). `Added` → hard delete; others → cursor.update to Deleted;
920
+ * already-Deleted → untouched. Never goes through replaceAll.
921
+ */
922
+ async softDeleteRows(storeName, tableId, rowIds) {
923
+ const key = asTableIdKey(tableId);
924
+ if (!key || rowIds.length === 0) {
925
+ return { softDeleted: 0, hardRemoved: 0, remaining: 0 };
926
+ }
927
+ const wanted = new Set(rowIds);
928
+ let softDeleted = 0;
929
+ let hardRemoved = 0;
930
+ let remaining = 0;
931
+ const deleteOne = (record, recordStore) => {
932
+ if (record.rowState === "Add" /* Added */) {
933
+ recordStore.delete(rowRecordId(key, record.collection, record.rowId));
934
+ hardRemoved += 1;
935
+ return;
936
+ }
937
+ if (record.rowState === "Delete" /* Deleted */) {
938
+ return;
939
+ }
940
+ record.rowState = "Delete" /* Deleted */;
941
+ record.cells = record.cells.map((c) => ({ ...c, originalValue: c.value }));
942
+ recordStore.put(JSON.parse(JSON.stringify(record)));
943
+ softDeleted += 1;
944
+ };
945
+ let db = await this.getDb();
946
+ db = await this.ensureStores(db, storeName);
947
+ const rowsName = this.rowsStore(storeName);
948
+ await new Promise((resolve, reject) => {
949
+ const tx = db.transaction([rowsName, this.metaStore(storeName)], "readwrite");
950
+ const recordStore = tx.objectStore(rowsName);
951
+ const finishMeta = () => {
952
+ const metaReq = tx.objectStore(this.metaStore(storeName)).get(key);
953
+ metaReq.onsuccess = () => {
954
+ const meta = metaReq.result;
955
+ if (meta && meta.kind === "meta") {
956
+ const active = meta.activeCollection || "default";
957
+ const activeTotal = meta.totalRowsByCollection?.[active] ?? meta.totalRows ?? 0;
958
+ remaining = Math.max(0, activeTotal - softDeleted - hardRemoved);
959
+ meta.totalRows = remaining;
960
+ if (meta.totalRowsByCollection) {
961
+ meta.totalRowsByCollection[active] = remaining;
962
+ }
963
+ tx.objectStore(this.metaStore(storeName)).put(
964
+ JSON.parse(JSON.stringify(meta))
965
+ );
966
+ }
967
+ };
968
+ metaReq.onerror = () => reject(metaReq.error);
969
+ };
970
+ const runFullScan = () => {
971
+ const req = recordStore.openCursor();
972
+ req.onsuccess = () => {
973
+ const cursor = req.result;
974
+ if (!cursor) {
975
+ finishMeta();
976
+ return;
977
+ }
978
+ const record = cursor.value;
979
+ if (String(record.tableId) === key && wanted.has(record.rowId)) {
980
+ deleteOne(record, recordStore);
981
+ }
982
+ cursor.continue();
983
+ };
984
+ req.onerror = () => reject(req.error);
985
+ };
986
+ const runIndexed = () => {
987
+ const index = recordStore.index("byTable");
988
+ const req = index.openCursor(IDBKeyRange.only(key));
989
+ req.onsuccess = () => {
990
+ const cursor = req.result;
991
+ if (!cursor) {
992
+ finishMeta();
993
+ return;
994
+ }
995
+ const record = cursor.value;
996
+ if (wanted.has(record.rowId)) deleteOne(record, recordStore);
997
+ cursor.continue();
998
+ };
999
+ req.onerror = () => reject(req.error);
1000
+ };
1001
+ try {
1002
+ if (recordStore.indexNames.contains("byTable")) {
1003
+ runIndexed();
1004
+ } else {
1005
+ runFullScan();
1006
+ }
1007
+ } catch {
1008
+ runFullScan();
1009
+ }
1010
+ tx.oncomplete = () => resolve();
1011
+ tx.onerror = () => reject(tx.error);
1012
+ tx.onabort = () => reject(tx.error ?? new Error("softDeleteRows aborted"));
1013
+ });
1014
+ return { softDeleted, hardRemoved, remaining };
1015
+ }
1016
+ async removeRows(storeName, tableId, rowIds) {
1017
+ const key = asTableIdKey(tableId);
1018
+ if (!key || rowIds.length === 0) return;
1019
+ let db = await this.getDb();
1020
+ db = await this.ensureStores(db, storeName);
1021
+ const rowsName = this.rowsStore(storeName);
1022
+ const wanted = new Set(rowIds);
1023
+ await new Promise((resolve, reject) => {
1024
+ const tx = db.transaction([rowsName, this.metaStore(storeName)], "readwrite");
1025
+ const rowStore = tx.objectStore(rowsName);
1026
+ const sweep = (store) => {
1027
+ const runIndexed = () => {
1028
+ const req = store.index("byTable").openCursor(IDBKeyRange.only(key));
1029
+ req.onsuccess = () => {
1030
+ const cursor = req.result;
1031
+ if (!cursor) return;
1032
+ const rec = cursor.value;
1033
+ if (wanted.has(rec.rowId)) cursor.delete();
1034
+ cursor.continue();
1035
+ };
1036
+ };
1037
+ try {
1038
+ if (store.indexNames.contains("byTable")) runIndexed();
1039
+ else runFullScan(store);
1040
+ } catch {
1041
+ runFullScan(store);
1042
+ }
1043
+ };
1044
+ const runFullScan = (store) => {
1045
+ const req = store.openCursor();
1046
+ req.onsuccess = () => {
1047
+ const cursor = req.result;
1048
+ if (!cursor) return;
1049
+ const rec = cursor.value;
1050
+ if (String(rec.tableId) === key && wanted.has(rec.rowId)) cursor.delete();
1051
+ cursor.continue();
1052
+ };
1053
+ };
1054
+ sweep(rowStore);
1055
+ const metaReq = tx.objectStore(this.metaStore(storeName)).get(key);
1056
+ metaReq.onsuccess = () => {
1057
+ const meta = metaReq.result;
1058
+ if (meta && meta.kind === "meta") {
1059
+ meta.totalRows = Math.max(0, (meta.totalRows ?? 0) - rowIds.length);
1060
+ tx.objectStore(this.metaStore(storeName)).put(
1061
+ JSON.parse(JSON.stringify(meta))
1062
+ );
1063
+ }
1064
+ };
1065
+ tx.oncomplete = () => resolve();
1066
+ tx.onerror = () => reject(tx.error);
1067
+ });
1068
+ }
1069
+ };
1070
+
1071
+ // infrastructure/Repositories/paged/PagedLocalTableRepository.ts
1072
+ var PagedLocalTableRepository = class {
1073
+ constructor() {
1074
+ this.tables = /* @__PURE__ */ new Map();
1075
+ }
1076
+ async exists(_store, tableKey) {
1077
+ return this.tables.has(tableKey);
1078
+ }
1079
+ async getById(_store, tableKey) {
1080
+ const table = this.tables.get(tableKey);
1081
+ if (!table) throw new Error(`Table ${tableKey} not found in local window`);
1082
+ return table;
1083
+ }
1084
+ async save(_store, table) {
1085
+ this.tables.set(table.id, table);
1086
+ }
1087
+ async replaceAll(_store, table) {
1088
+ this.tables.set(table.id, table);
1089
+ }
1090
+ async loadPage(_store, tableKey, _page, _pageSize) {
1091
+ const table = this.tables.get(tableKey);
1092
+ return table ?? null;
1093
+ }
1094
+ /**
1095
+ * RAM window: a switch replaces the window with the one returned by the
1096
+ * IndexedDB layer, so nothing to persist here — just keep the new table.
1097
+ */
1098
+ async setActiveCollection(_store, tableKey, collection) {
1099
+ const table = this.tables.get(tableKey);
1100
+ if (!table) return null;
1101
+ table.setActiveCollection(collection);
1102
+ table.changePage(1);
1103
+ return table;
1104
+ }
1105
+ async getAllRows(_store, tableKey, collectionScope = "active") {
1106
+ const table = this.tables.get(tableKey);
1107
+ if (!table) return [];
1108
+ return table.getRows(collectionScope);
1109
+ }
1110
+ async getAllRowIds(_store, tableKey, collectionScope = "active") {
1111
+ const rows = await this.getAllRows(_store, tableKey, collectionScope);
1112
+ return rows.map((r) => r.id.value);
1113
+ }
1114
+ clearLocal(_store, tableKey) {
1115
+ this.tables.delete(tableKey);
1116
+ }
1117
+ async flush() {
1118
+ }
1119
+ async delete(_store, tableKey) {
1120
+ this.tables.delete(tableKey);
1121
+ }
1122
+ async resetTable(_store, tableKey) {
1123
+ this.tables.delete(tableKey);
1124
+ }
1125
+ /**
1126
+ * RAM window only — streams the current page's rows as a single batch.
1127
+ * (The full dataset lives in IndexedDB, not here.)
1128
+ */
1129
+ async iterateRowRecords(_store, tableKey, collectionScope, onBatch) {
1130
+ const table = this.tables.get(tableKey);
1131
+ if (!table) return;
1132
+ const rows = table.getRows(collectionScope);
1133
+ const records = rows.map((r) => {
1134
+ const cells = {};
1135
+ for (const [colId, cell] of r.getCells().entries()) {
1136
+ cells[colId] = { value: cell.getValue(), originalValue: cell.getOriginalValue().value, display: cell.getDisplay() };
1137
+ }
1138
+ return {
1139
+ rowId: r.id.value,
1140
+ collection: r.getCollection(),
1141
+ sortIndex: Number(r.getCell("index")?.getValue() ?? 0),
1142
+ rowState: r.rowState.value,
1143
+ cells
1144
+ };
1145
+ });
1146
+ await onBatch(records, 0);
1147
+ }
1148
+ async removeRows() {
1149
+ }
1150
+ /**
1151
+ * RAM-window bulk delete (Memory Iron Rule compliant): only the current
1152
+ * page's rows are mutated — Added hard-removed, others soft-deleted via
1153
+ * domain Row state; already-Deleted untouched. Remaining counts come from
1154
+ * pagination totals minus this operation's effects (no full-dataset scan).
1155
+ */
1156
+ async softDeleteRows(_store, tableKey, rowIds) {
1157
+ const table = this.tables.get(tableKey);
1158
+ if (!table || rowIds.length === 0) {
1159
+ return { softDeleted: 0, hardRemoved: 0, remaining: 0 };
1160
+ }
1161
+ const wanted = new Set(rowIds);
1162
+ let softDeleted = 0;
1163
+ let hardRemoved = 0;
1164
+ for (const col of table.getCollections().values()) {
1165
+ for (const row of Array.from(col.getRows().values())) {
1166
+ if (!wanted.has(row.id.value)) continue;
1167
+ if (row.rowState.value === "Add" /* Added */) {
1168
+ col.getRows().delete(row.id.value);
1169
+ hardRemoved += 1;
1170
+ continue;
1171
+ }
1172
+ if (row.rowState.value === "Delete" /* Deleted */) continue;
1173
+ row.rowState = new RowState("Delete" /* Deleted */);
1174
+ row.updateChangeFields(new ColumnId("RowState"));
1175
+ softDeleted += 1;
1176
+ }
1177
+ }
1178
+ const p = table.getPagination();
1179
+ const remaining = Math.max(0, p.totalRows - softDeleted - hardRemoved);
1180
+ table.setPagination(new Pagination(p.page, p.pageSize, remaining));
1181
+ return { softDeleted, hardRemoved, remaining };
1182
+ }
1183
+ };
1184
+
1185
+ // infrastructure/Repositories/paged/PagedCachedTableRepository.ts
1186
+ function isValidTableKey(tableKey) {
1187
+ return typeof tableKey === "string" && tableKey.length > 0;
1188
+ }
1189
+ var PagedCachedTableRepository = class {
1190
+ constructor() {
1191
+ this.persistChain = /* @__PURE__ */ new Map();
1192
+ /** Tables closed by dispose — block further IDB rebuild/load until reopened */
1193
+ this.closedKeys = /* @__PURE__ */ new Set();
1194
+ this.localRepo = new PagedLocalTableRepository();
1195
+ this.indexedRepo = new PagedIndexedDbTableRepository();
1196
+ }
1197
+ queueKey(store, tableKey) {
1198
+ return `${store}::${tableKey}`;
1199
+ }
1200
+ isClosed(store, tableKey) {
1201
+ return this.closedKeys.has(this.queueKey(store, tableKey));
1202
+ }
1203
+ reopen(store, tableKey) {
1204
+ this.closedKeys.delete(this.queueKey(store, tableKey));
1205
+ }
1206
+ enqueuePersist(store, tableKey, task) {
1207
+ const key = this.queueKey(store, tableKey);
1208
+ const prev = this.persistChain.get(key) ?? Promise.resolve();
1209
+ const next = prev.catch(() => void 0).then(async () => {
1210
+ if (this.closedKeys.has(key)) return;
1211
+ await task();
1212
+ }).catch((err) => {
1213
+ console.error("[PagedCachedTableRepository] IndexedDB persist failed", err);
1214
+ });
1215
+ this.persistChain.set(key, next);
1216
+ }
1217
+ async flush(store, tableKey) {
1218
+ if (!isValidTableKey(tableKey)) return;
1219
+ const key = this.queueKey(store, tableKey);
1220
+ await (this.persistChain.get(key) ?? Promise.resolve());
1221
+ }
1222
+ async getById(store, tableKey) {
1223
+ if (!isValidTableKey(tableKey)) {
1224
+ throw new Error("[PagedCachedTableRepository] getById: invalid tableKey");
1225
+ }
1226
+ this.reopen(store, tableKey);
1227
+ if (await this.localRepo.exists(store, tableKey)) {
1228
+ return this.localRepo.getById(store, tableKey);
1229
+ }
1230
+ await this.flush(store, tableKey);
1231
+ const table = await this.indexedRepo.getById(store, tableKey);
1232
+ await this.localRepo.save(store, table);
1233
+ return table;
1234
+ }
1235
+ async save(store, table) {
1236
+ if (!isValidTableKey(table?.id)) {
1237
+ console.warn("[PagedCachedTableRepository] save skipped: invalid table.id", table?.id);
1238
+ return;
1239
+ }
1240
+ if (this.isClosed(store, table.id)) {
1241
+ return;
1242
+ }
1243
+ await this.localRepo.save(store, table);
1244
+ await this.indexedRepo.save(store, table);
1245
+ }
1246
+ async replaceAll(store, table) {
1247
+ if (!isValidTableKey(table?.id)) {
1248
+ console.warn("[PagedCachedTableRepository] replaceAll skipped: invalid table.id", table?.id);
1249
+ return;
1250
+ }
1251
+ this.reopen(store, table.id);
1252
+ await this.flush(store, table.id);
1253
+ await this.indexedRepo.replaceAll(store, table);
1254
+ const pageSize = table.getPagination().pageSize;
1255
+ const pageTable = await this.indexedRepo.loadPage(store, table.id, 1, pageSize);
1256
+ if (!pageTable) return;
1257
+ this.restoreLiveColumns(table, pageTable);
1258
+ await this.localRepo.save(store, pageTable);
1259
+ }
1260
+ async loadPage(store, tableKey, page, pageSize) {
1261
+ if (!isValidTableKey(tableKey) || this.isClosed(store, tableKey)) {
1262
+ return null;
1263
+ }
1264
+ await this.flush(store, tableKey);
1265
+ let previous = null;
1266
+ if (await this.localRepo.exists(store, tableKey)) {
1267
+ previous = await this.localRepo.getById(store, tableKey);
1268
+ await this.indexedRepo.save(store, previous);
1269
+ }
1270
+ const table = await this.indexedRepo.loadPage(store, tableKey, page, pageSize);
1271
+ if (!table) {
1272
+ return previous;
1273
+ }
1274
+ if (previous) {
1275
+ this.restoreLiveColumns(previous, table);
1276
+ const prevActive = previous.getActiveCollectionName();
1277
+ const nextActive = table.getActiveCollectionName();
1278
+ if (prevActive === nextActive) {
1279
+ const prevTotal = previous.getPagination().totalRows;
1280
+ const cur = table.getPagination();
1281
+ if (prevTotal > cur.totalRows) {
1282
+ table.setPagination(new Pagination(cur.page, cur.pageSize, prevTotal));
1283
+ await this.indexedRepo.save(store, table);
1284
+ }
1285
+ }
1286
+ }
1287
+ await this.localRepo.save(store, table);
1288
+ return table;
1289
+ }
1290
+ /**
1291
+ * Collection switch: swap the RAM window with page 1 of the NEW collection
1292
+ * (loaded from IndexedDB) and persist the switch in meta. Rows of other
1293
+ * collections are never touched.
1294
+ */
1295
+ async setActiveCollection(store, tableKey, collection) {
1296
+ if (!isValidTableKey(tableKey) || this.isClosed(store, tableKey)) return null;
1297
+ await this.flush(store, tableKey);
1298
+ let previous = null;
1299
+ if (await this.localRepo.exists(store, tableKey)) {
1300
+ previous = await this.localRepo.getById(store, tableKey);
1301
+ await this.indexedRepo.save(store, previous);
1302
+ }
1303
+ const table = await this.indexedRepo.setActiveCollection(store, tableKey, collection);
1304
+ if (!table) {
1305
+ return previous;
1306
+ }
1307
+ if (previous) {
1308
+ this.restoreLiveColumns(previous, table);
1309
+ }
1310
+ await this.localRepo.save(store, table);
1311
+ return table;
1312
+ }
1313
+ /** ColumnSchema holds functions — never come back from IndexedDB; keep RAM handlers. */
1314
+ restoreLiveColumns(from, to) {
1315
+ for (const [name, col] of from.getCollections()) {
1316
+ const target = to.getCollections().get(name);
1317
+ if (!target) continue;
1318
+ const dest = target.getColumns();
1319
+ dest.clear();
1320
+ for (const [field, schema] of col.getColumns()) {
1321
+ dest.set(field, schema);
1322
+ }
1323
+ }
1324
+ }
1325
+ async getAllRows(store, tableKey, collectionScope) {
1326
+ if (!isValidTableKey(tableKey) || this.isClosed(store, tableKey)) return [];
1327
+ await this.flush(store, tableKey);
1328
+ if (await this.localRepo.exists(store, tableKey)) {
1329
+ const current = await this.localRepo.getById(store, tableKey);
1330
+ await this.indexedRepo.save(store, current);
1331
+ }
1332
+ return this.indexedRepo.getAllRows(store, tableKey, collectionScope);
1333
+ }
1334
+ async getAllRowIds(store, tableKey, collectionScope) {
1335
+ if (!isValidTableKey(tableKey) || this.isClosed(store, tableKey)) return [];
1336
+ await this.flush(store, tableKey);
1337
+ if (await this.localRepo.exists(store, tableKey)) {
1338
+ const current = await this.localRepo.getById(store, tableKey);
1339
+ await this.indexedRepo.save(store, current);
1340
+ }
1341
+ return this.indexedRepo.getAllRowIds(store, tableKey, collectionScope);
1342
+ }
1343
+ clearLocal(store, tableKey) {
1344
+ if (!isValidTableKey(tableKey)) return;
1345
+ const key = this.queueKey(store, tableKey);
1346
+ this.closedKeys.add(key);
1347
+ this.persistChain.set(key, Promise.resolve());
1348
+ this.localRepo.clearLocal(store, tableKey);
1349
+ }
1350
+ async delete(store, tableKey) {
1351
+ if (!isValidTableKey(tableKey)) return;
1352
+ await this.flush(store, tableKey);
1353
+ this.localRepo.clearLocal(store, tableKey);
1354
+ this.closedKeys.add(this.queueKey(store, tableKey));
1355
+ await this.indexedRepo.delete(store, tableKey);
1356
+ }
1357
+ async resetTable(store, tableKey) {
1358
+ if (!isValidTableKey(tableKey) || this.isClosed(store, tableKey)) return;
1359
+ await this.flush(store, tableKey);
1360
+ await this.indexedRepo.resetTable(store, tableKey);
1361
+ this.localRepo.clearLocal(store, tableKey);
1362
+ const empty = await this.indexedRepo.loadPage(store, tableKey, 1);
1363
+ if (empty) await this.localRepo.save(store, empty);
1364
+ }
1365
+ async exists(store, tableKey) {
1366
+ if (!isValidTableKey(tableKey)) return false;
1367
+ if (await this.localRepo.exists(store, tableKey)) return true;
1368
+ return this.indexedRepo.exists(store, tableKey);
1369
+ }
1370
+ /** Stream row records in batches (never the whole dataset in heap). */
1371
+ async iterateRowRecords(store, tableKey, collectionScope, onBatch, batchSize) {
1372
+ if (!isValidTableKey(tableKey) || this.isClosed(store, tableKey)) return;
1373
+ await this.flush(store, tableKey);
1374
+ if (await this.localRepo.exists(store, tableKey)) {
1375
+ const current = await this.localRepo.getById(store, tableKey);
1376
+ await this.indexedRepo.save(store, current);
1377
+ }
1378
+ await this.indexedRepo.iterateRowRecords(store, tableKey, collectionScope, onBatch, batchSize);
1379
+ }
1380
+ async removeRows(store, tableKey, rowIds) {
1381
+ if (!isValidTableKey(tableKey) || this.isClosed(store, tableKey)) return;
1382
+ await this.flush(store, tableKey);
1383
+ await this.indexedRepo.removeRows(store, tableKey, rowIds);
1384
+ }
1385
+ /**
1386
+ * Bulk delete straight on IndexedDB records (Memory Iron Rule: no full
1387
+ * hydration, no replaceAll). After the persisted delete, the RAM window is
1388
+ * reloaded from IndexedDB so pagination/rows stay consistent.
1389
+ */
1390
+ async softDeleteRows(store, tableKey, rowIds) {
1391
+ if (!isValidTableKey(tableKey) || this.isClosed(store, tableKey) || rowIds.length === 0) {
1392
+ return { softDeleted: 0, hardRemoved: 0, remaining: 0 };
1393
+ }
1394
+ await this.flush(store, tableKey);
1395
+ const result = await this.indexedRepo.softDeleteRows(store, tableKey, rowIds);
1396
+ const pageTable = await this.indexedRepo.loadPage(store, tableKey, 1);
1397
+ if (pageTable) await this.localRepo.save(store, pageTable);
1398
+ return result;
1399
+ }
1400
+ };
1401
+
1402
+ export { PagedCachedTableRepository, PagedIndexedDbTableRepository, PagedLocalTableRepository };
1403
+ //# sourceMappingURL=chunk-J4XJ4UBC.js.map
1404
+ //# sourceMappingURL=chunk-J4XJ4UBC.js.map