web-a2e 1.0.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 (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. package/wiki/Video-Rendering.md +658 -0
@@ -0,0 +1,117 @@
1
+ /*
2
+ * basic-tokens.js - Applesoft BASIC token definitions
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ // Applesoft BASIC tokens - array index = token value - 0x80
9
+ export const APPLESOFT_TOKENS = [
10
+ "END", // $80
11
+ "FOR", // $81
12
+ "NEXT", // $82
13
+ "DATA", // $83
14
+ "INPUT", // $84
15
+ "DEL", // $85
16
+ "DIM", // $86
17
+ "READ", // $87
18
+ "GR", // $88
19
+ "TEXT", // $89
20
+ "PR#", // $8A
21
+ "IN#", // $8B
22
+ "CALL", // $8C
23
+ "PLOT", // $8D
24
+ "HLIN", // $8E
25
+ "VLIN", // $8F
26
+ "HGR2", // $90
27
+ "HGR", // $91
28
+ "HCOLOR=", // $92
29
+ "HPLOT", // $93
30
+ "DRAW", // $94
31
+ "XDRAW", // $95
32
+ "HTAB", // $96
33
+ "HOME", // $97
34
+ "ROT=", // $98
35
+ "SCALE=", // $99
36
+ "SHLOAD", // $9A
37
+ "TRACE", // $9B
38
+ "NOTRACE", // $9C
39
+ "NORMAL", // $9D
40
+ "INVERSE", // $9E
41
+ "FLASH", // $9F
42
+ "COLOR=", // $A0
43
+ "POP", // $A1
44
+ "VTAB", // $A2
45
+ "HIMEM:", // $A3
46
+ "LOMEM:", // $A4
47
+ "ONERR", // $A5
48
+ "RESUME", // $A6
49
+ "RECALL", // $A7
50
+ "STORE", // $A8
51
+ "SPEED=", // $A9
52
+ "LET", // $AA
53
+ "GOTO", // $AB
54
+ "RUN", // $AC
55
+ "IF", // $AD
56
+ "RESTORE", // $AE
57
+ "&", // $AF - Ampersand (machine language hook)
58
+ "GOSUB", // $B0
59
+ "RETURN", // $B1
60
+ "REM", // $B2
61
+ "STOP", // $B3
62
+ "ON", // $B4
63
+ "WAIT", // $B5
64
+ "LOAD", // $B6
65
+ "SAVE", // $B7
66
+ "DEF", // $B8
67
+ "POKE", // $B9
68
+ "PRINT", // $BA
69
+ "CONT", // $BB
70
+ "LIST", // $BC
71
+ "CLEAR", // $BD
72
+ "GET", // $BE
73
+ "NEW", // $BF
74
+ "TAB(", // $C0
75
+ "TO", // $C1
76
+ "FN", // $C2
77
+ "SPC(", // $C3
78
+ "THEN", // $C4
79
+ "AT", // $C5
80
+ "NOT", // $C6
81
+ "STEP", // $C7
82
+ "+", // $C8
83
+ "-", // $C9
84
+ "*", // $CA
85
+ "/", // $CB
86
+ "^", // $CC
87
+ "AND", // $CD
88
+ "OR", // $CE
89
+ ">", // $CF
90
+ "=", // $D0
91
+ "<", // $D1
92
+ "SGN", // $D2
93
+ "INT", // $D3
94
+ "ABS", // $D4
95
+ "USR", // $D5
96
+ "FRE", // $D6
97
+ "SCRN(", // $D7
98
+ "PDL", // $D8
99
+ "POS", // $D9
100
+ "SQR", // $DA
101
+ "RND", // $DB
102
+ "LOG", // $DC
103
+ "EXP", // $DD
104
+ "COS", // $DE
105
+ "SIN", // $DF
106
+ "TAN", // $E0
107
+ "ATN", // $E1
108
+ "PEEK", // $E2
109
+ "LEN", // $E3
110
+ "STR$", // $E4
111
+ "VAL", // $E5
112
+ "ASC", // $E6
113
+ "CHR$", // $E7
114
+ "LEFT$", // $E8
115
+ "RIGHT$", // $E9
116
+ "MID$", // $EA
117
+ ];
@@ -0,0 +1,28 @@
1
+ /*
2
+ * constants.js - Shared constants
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ // localStorage key constants
9
+ export const STORAGE_KEYS = {
10
+ VOLUME: "a2e-volume",
11
+ DISPLAY_SETTINGS: "a2e-display-settings",
12
+ SCREEN_WIDTH: "a2e-screen-width",
13
+ SHOW_DRIVES: "a2e-show-drives",
14
+ DRIVE_SOUNDS: "a2e-drive-sounds",
15
+ CHARSET: "a2e-charset",
16
+ DEBUG_WINDOWS: "a2e-debug-windows",
17
+ ZP_CUSTOM_WATCHES: "zp-custom-watches",
18
+ THEME: "a2e-theme",
19
+ };
20
+
21
+ // Canvas/display dimension constants
22
+ export const DIMENSIONS = {
23
+ MIN_CANVAS_WIDTH: 280,
24
+ MIN_CANVAS_HEIGHT: 200,
25
+ BEZEL_PADDING_X: 88,
26
+ BEZEL_PADDING_Y: 104,
27
+ DEFAULT_PADDING: 32,
28
+ };
@@ -0,0 +1,225 @@
1
+ /*
2
+ * indexeddb-helper.js - IndexedDB helper utilities
3
+ *
4
+ * Written by
5
+ * Mike Daley <michael_daley@icloud.com>
6
+ */
7
+
8
+ /**
9
+ * Creates a database manager for a specific IndexedDB database.
10
+ * Handles connection caching, database opening, and provides
11
+ * Promise-based wrappers for common operations.
12
+ *
13
+ * @param {Object} config - Database configuration
14
+ * @param {string} config.dbName - The database name
15
+ * @param {number} config.version - The database version
16
+ * @param {function} config.onUpgrade - Upgrade handler (event) => void
17
+ * @returns {Object} Database manager with helper methods
18
+ */
19
+ export function createDatabaseManager({ dbName, version, onUpgrade }) {
20
+ let cachedDb = null;
21
+
22
+ /**
23
+ * Open the database, using cached connection if available
24
+ * @returns {Promise<IDBDatabase>}
25
+ */
26
+ async function open() {
27
+ if (cachedDb) {
28
+ return cachedDb;
29
+ }
30
+
31
+ return new Promise((resolve, reject) => {
32
+ const request = indexedDB.open(dbName, version);
33
+
34
+ request.onerror = () => {
35
+ console.error(`Failed to open database ${dbName}:`, request.error);
36
+ reject(request.error);
37
+ };
38
+
39
+ request.onsuccess = () => {
40
+ cachedDb = request.result;
41
+ resolve(cachedDb);
42
+ };
43
+
44
+ request.onupgradeneeded = onUpgrade;
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Get a record by key from a store
50
+ * @param {string} storeName - The object store name
51
+ * @param {*} key - The key to look up
52
+ * @returns {Promise<*>} The record or undefined
53
+ */
54
+ async function get(storeName, key) {
55
+ const db = await open();
56
+ const transaction = db.transaction(storeName, "readonly");
57
+ const store = transaction.objectStore(storeName);
58
+
59
+ return new Promise((resolve, reject) => {
60
+ const request = store.get(key);
61
+ request.onsuccess = () => resolve(request.result);
62
+ request.onerror = () => reject(request.error);
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Put (insert or update) a record in a store
68
+ * @param {string} storeName - The object store name
69
+ * @param {*} record - The record to store
70
+ * @returns {Promise<*>} The key of the stored record
71
+ */
72
+ async function put(storeName, record) {
73
+ const db = await open();
74
+ const transaction = db.transaction(storeName, "readwrite");
75
+ const store = transaction.objectStore(storeName);
76
+
77
+ return new Promise((resolve, reject) => {
78
+ const request = store.put(record);
79
+ request.onsuccess = () => resolve(request.result);
80
+ request.onerror = () => reject(request.error);
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Add a new record to a store (fails if key exists)
86
+ * @param {string} storeName - The object store name
87
+ * @param {*} record - The record to add
88
+ * @returns {Promise<*>} The key of the added record
89
+ */
90
+ async function add(storeName, record) {
91
+ const db = await open();
92
+ const transaction = db.transaction(storeName, "readwrite");
93
+ const store = transaction.objectStore(storeName);
94
+
95
+ return new Promise((resolve, reject) => {
96
+ const request = store.add(record);
97
+ request.onsuccess = () => resolve(request.result);
98
+ request.onerror = () => reject(request.error);
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Delete a record by key from a store
104
+ * @param {string} storeName - The object store name
105
+ * @param {*} key - The key to delete
106
+ * @returns {Promise<void>}
107
+ */
108
+ async function remove(storeName, key) {
109
+ const db = await open();
110
+ const transaction = db.transaction(storeName, "readwrite");
111
+ const store = transaction.objectStore(storeName);
112
+
113
+ return new Promise((resolve, reject) => {
114
+ const request = store.delete(key);
115
+ request.onsuccess = () => resolve();
116
+ request.onerror = () => reject(request.error);
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Count records in a store
122
+ * @param {string} storeName - The object store name
123
+ * @returns {Promise<number>}
124
+ */
125
+ async function count(storeName) {
126
+ const db = await open();
127
+ const transaction = db.transaction(storeName, "readonly");
128
+ const store = transaction.objectStore(storeName);
129
+
130
+ return new Promise((resolve, reject) => {
131
+ const request = store.count();
132
+ request.onsuccess = () => resolve(request.result);
133
+ request.onerror = () => reject(request.error);
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Iterate over records using a cursor
139
+ * @param {string} storeName - The object store name
140
+ * @param {Object} options - Cursor options
141
+ * @param {string} [options.indexName] - Optional index name to use
142
+ * @param {IDBKeyRange} [options.range] - Optional key range
143
+ * @param {string} [options.direction] - Cursor direction ('next', 'prev', etc.)
144
+ * @param {function} callback - Called for each record: (value, cursor) => void
145
+ * Return false to stop iteration
146
+ * @returns {Promise<void>}
147
+ */
148
+ async function iterate(storeName, options, callback) {
149
+ const db = await open();
150
+ const transaction = db.transaction(storeName, "readonly");
151
+ const store = transaction.objectStore(storeName);
152
+ const source = options.indexName ? store.index(options.indexName) : store;
153
+
154
+ return new Promise((resolve, reject) => {
155
+ const request = source.openCursor(options.range || null, options.direction || "next");
156
+
157
+ request.onsuccess = (event) => {
158
+ const cursor = event.target.result;
159
+ if (cursor) {
160
+ const result = callback(cursor.value, cursor);
161
+ if (result !== false) {
162
+ cursor.continue();
163
+ } else {
164
+ resolve();
165
+ }
166
+ } else {
167
+ resolve();
168
+ }
169
+ };
170
+ request.onerror = () => reject(request.error);
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Iterate over records with write access (for deletion during iteration)
176
+ * @param {string} storeName - The object store name
177
+ * @param {Object} options - Cursor options
178
+ * @param {string} [options.indexName] - Optional index name to use
179
+ * @param {IDBKeyRange} [options.range] - Optional key range
180
+ * @param {string} [options.direction] - Cursor direction ('next', 'prev', etc.)
181
+ * @param {function} callback - Called for each record: (value, cursor, store) => void
182
+ * @returns {Promise<void>}
183
+ */
184
+ async function iterateWithWrite(storeName, options, callback) {
185
+ const db = await open();
186
+ const transaction = db.transaction(storeName, "readwrite");
187
+ const store = transaction.objectStore(storeName);
188
+ const source = options.indexName ? store.index(options.indexName) : store;
189
+
190
+ return new Promise((resolve, reject) => {
191
+ const request = source.openCursor(options.range || null, options.direction || "next");
192
+
193
+ request.onsuccess = (event) => {
194
+ const cursor = event.target.result;
195
+ if (cursor) {
196
+ callback(cursor.value, cursor, store);
197
+ cursor.continue();
198
+ } else {
199
+ resolve();
200
+ }
201
+ };
202
+ request.onerror = () => reject(request.error);
203
+ });
204
+ }
205
+
206
+ /**
207
+ * Get raw database reference for complex operations
208
+ * @returns {Promise<IDBDatabase>}
209
+ */
210
+ async function getDatabase() {
211
+ return open();
212
+ }
213
+
214
+ return {
215
+ open,
216
+ get,
217
+ put,
218
+ add,
219
+ remove,
220
+ count,
221
+ iterate,
222
+ iterateWithWrite,
223
+ getDatabase,
224
+ };
225
+ }