{"_id":"@asaidimu/indexed","_rev":"12-41ce445cd44754e64a967d70ee4a9fcb","name":"@asaidimu/indexed","dist-tags":{"latest":"4.0.1"},"versions":{"1.0.0":{"name":"@asaidimu/indexed","version":"1.0.0","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@1.0.0","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"90d1b2923739def10bed718f8284e5e170e6f889","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-1.0.0.tgz","fileCount":7,"integrity":"sha512-ttpHuFsX5vyy+0N3So65jtRaodEJM/A/Ca5t7kPGcYfCg/GSKJJ3/5fKzIBZYcrJ6MsTSt+N10xM8KiE6S9ySA==","signatures":[{"sig":"MEUCIHV2uPopzqxq3xB9yXXd8DtpshiFXUzt8Czs82N/CRLGAiEAjEHL2WsAw4i+ilhDWZtyqxF+KKpmG8fhkSpjlaKqdJ8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":22524},"main":"index.js","types":"index.d.ts","gitHead":"4395c9fc793890a3b641be5830e7dfe366df7328","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.18.0","dependencies":{},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_1.0.0_1737533579826_0.29033710673011126","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@asaidimu/indexed","version":"1.0.1","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@1.0.1","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"c6370f9d1d9146ba6d0088231e687d7b4218a8e6","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-1.0.1.tgz","fileCount":7,"integrity":"sha512-LTx5/mXtambEKttLMeYoyjAtsp1ZUMfgao34TW7T6J21j1xFkh1rDv0EzAA2qPuQXRjh3WsALay45n2tCkD2Fg==","signatures":[{"sig":"MEUCIQCLSUthZTVJGAtdBJbH4XjiDNJemNekG8G/soCmX7nmfAIgVaWdoGBOFejKNhAQZWKHATCF3+TDH9D48OwUJ4K9vd4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":22204},"main":"index.js","types":"index.d.ts","gitHead":"bc64921dc95db09672d7bedfadc9dcdbee214731","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.18.0","dependencies":{},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_1.0.1_1737537386762_0.07940673986903146","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@asaidimu/indexed","version":"1.1.0","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@1.1.0","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"a47d5da9e0a30e7baa77be515c03f0f171e77c09","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-1.1.0.tgz","fileCount":7,"integrity":"sha512-AofOIx/FZygl8utkNVhbQr/wILvT9aZ+3P4uKDAoW39sWNpo0+ArRq8bCFxnoGEW+YzwbaGxpQi0zlFgvurDhQ==","signatures":[{"sig":"MEQCIBvJPXuKksII21v/zk4E6wyh+tWzrbVbnUgLaQdpEl7YAiACATUoCKdiPV08bvqD68scBpjJ4gzatnySRlrD5oEIPQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":68120},"main":"index.js","types":"index.d.ts","gitHead":"2b96a2a2e65e678c263f7a7bf3d03ec846fa5e66","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.18.0","dependencies":{"zod":"^3.24.1","@asaidimu/query":"^1.1.0","@asaidimu/events":"^1.0.0"},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_1.1.0_1738267846211_0.8713807956540514","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@asaidimu/indexed","version":"1.1.1","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@1.1.1","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"9cbdda4dc1a7434cea2f68f5b8c2052efabce9f9","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-1.1.1.tgz","fileCount":7,"integrity":"sha512-bjcQdDTDA6FcDCACXwuvtfhalU+FSUEGL8Vodw0P73GkNG41RZZby8EpcHbmwYkK9/6FpMLrSfqqFH0yn0cniw==","signatures":[{"sig":"MEQCID7S10cZFZ6ONA/lPN7vf0LQwCQFXGRMizqgDu9B9y0AAiAMqY0TQULq7CQKKk+LCFfQxyGWY49HNqa+chq6WIqENw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":68120},"main":"index.js","types":"index.d.ts","gitHead":"7875f5c6162689d47a147b14c091e9ae195d3cfc","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.18.0","dependencies":{"zod":"^3.24.1","@asaidimu/query":"^1.1.0","@asaidimu/events":"^1.0.0"},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_1.1.1_1740327833472_0.21716155646649415","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@asaidimu/indexed","version":"1.1.2","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@1.1.2","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"bf6222e9d109a7347c52ad5c07e13420a8bc61a9","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-1.1.2.tgz","fileCount":7,"integrity":"sha512-/Q1wqHtrM01MpuQ0yfty+1ukrdPiRiwoAXiG6Pe75qOa1MY8LXy9VzIDquv6jNpXPNUbwao5dErLMCDzppykLA==","signatures":[{"sig":"MEYCIQCrrtqguZ/xaDWd4QKezbKxgRSXbGqT1QbrzBkN/CaDNAIhAOc2ZX+gtn/F9A6g9XWhG7omv6IDbb7/TaeJvLgVExvW","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":72872},"main":"index.js","types":"index.d.ts","gitHead":"50204dcb12737f7621de9bc36676c536911f51c2","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.18.0","dependencies":{"zod":"^3.24.1","@asaidimu/query":"^1.1.0","@asaidimu/events":"^1.0.0"},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_1.1.2_1740329945904_0.580737517370201","host":"s3://npm-registry-packages-npm-production"}},"1.1.3":{"name":"@asaidimu/indexed","version":"1.1.3","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@1.1.3","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"a97f48f513105bf7c7b25c0c5f0bf1df6b179a1b","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-1.1.3.tgz","fileCount":7,"integrity":"sha512-yLx9PlOh3/OKQy4oqUKnRbOCTIzJXBLx07jl48FAsKOX23dmAd8PD1mAtdPC6ABmJGUGgpdh95voUAflffG5sw==","signatures":[{"sig":"MEUCIQD+D3CjzQe45vwXemwjVJ4iJpMIxeA/Ft8XLqh39WIMAgIgd/zWLAhCBr29L9uaLL9z8yliAnpH4Edb/eD9qP0StuU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":72852},"main":"index.js","types":"index.d.ts","gitHead":"0d610fcee0f2f8697f61f8a4a4aa198f06d3ca72","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.18.0","dependencies":{"zod":"^3.24.1","@asaidimu/query":"^1.1.0","@asaidimu/events":"^1.0.0"},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_1.1.3_1740403232623_0.8919327872123335","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@asaidimu/indexed","version":"2.0.0","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@2.0.0","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"91df54e6291f1fac777301ab95e6e64cf6a8adfe","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-2.0.0.tgz","fileCount":7,"integrity":"sha512-WOqDcOBuhOZFHb6vkLX96Joi1EABXo0+j0iT3hJ82xaOaFMzAHBWareTpNGHL+gHi4ts7O4kQCeWDQ6agxK6bg==","signatures":[{"sig":"MEUCIAp49D82Qx1nDi5/LV1JRMowLgkzZo4nl3y+WK/ADXC5AiEAoGHcPoxea7F0W2R231VzCZIDeIE9RTJu5rsf0akB+VQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":71996},"main":"index.js","types":"index.d.ts","gitHead":"0e067da7834394e4173f546698881e6815e1bb1d","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.19.1","dependencies":{"zod":"^3.24.1","uuid":"^11.1.0","@asaidimu/query":"^1.1.0","@asaidimu/anansi":"^1.6.6","@asaidimu/events":"^1.0.0"},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_2.0.0_1748002718391_0.3437129751843988","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@asaidimu/indexed","version":"2.1.0","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@2.1.0","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"f4dc8126da44f29c6022650136da42915e224f1e","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-2.1.0.tgz","fileCount":7,"integrity":"sha512-GPwItEChdtxnfaURkgDN6lqUTqYSzeF1YbTarS1bdmD/XttaxFT81EkRXTI1yXhpJsuwprGVE6XTAHCSRG1hDQ==","signatures":[{"sig":"MEYCIQCt4SsyaanhpuCoM1ti0rI/DQmFFXXZyO8596qYIKRXSAIhALLlT2VVH2lTHaIHIckEw+B35D3bRsts6f0wcnWkbKxc","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":131333},"main":"index.js","types":"index.d.ts","gitHead":"681e10504ecaa72b7da9596372ac158b6e151d60","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.19.1","dependencies":{"zod":"^3.24.1","uuid":"^11.1.0","@asaidimu/query":"^1.1.0","@asaidimu/anansi":"^1.6.6","@asaidimu/events":"^1.0.0"},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_2.1.0_1748116994425_0.16888748112297258","host":"s3://npm-registry-packages-npm-production"}},"3.0.0":{"name":"@asaidimu/indexed","version":"3.0.0","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@3.0.0","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"f2d05c8aa799b320e8ebb24887193062f93f50c8","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-3.0.0.tgz","fileCount":7,"integrity":"sha512-+2kw12HlsFKMVYWGBYpGLyqiwzNUll9fHrb4AjqLLlcfJyXOw8q+kbyA7ofD1nGmb9JJxcUqOW2UjAL5OG7eow==","signatures":[{"sig":"MEQCID7uJitk9dYgpEFZrsdVfhBWxi6r22b+OVEyO4Wm36HiAiB6GfEQO0Om7xwofJlgLbH5l0vxaKBuTxAQhZZYVIYTZg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":134956},"main":"index.js","types":"index.d.ts","gitHead":"68737ea83c501596a6fb5cf847914b77e2fe5262","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.19.1","dependencies":{"zod":"^3.24.1","uuid":"^11.1.0","@asaidimu/query":"^1.1.0","@asaidimu/anansi":"^1.6.6","@asaidimu/events":"^1.0.0"},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_3.0.0_1748186851527_0.21325954100194155","host":"s3://npm-registry-packages-npm-production"}},"3.0.1":{"name":"@asaidimu/indexed","version":"3.0.1","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@3.0.1","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"7822a7a3d2f36d820a17fa8a87b93fba1650057e","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-3.0.1.tgz","fileCount":7,"integrity":"sha512-FR0RgM3t3c6mcTH7KI6pNQpSWzw+7TMG9L9RKYCiGw/ls/RYB7bwCuINPiRxoXLcjciZaS/SH+xc4VjIwmZcZA==","signatures":[{"sig":"MEUCIH+XKCwxXxsRVjYyOLvBE7/47VrjmxPBP10tdbUcfJVSAiEA+KiWfJIfis81CZ5m5rzQLgRNjv/xOL5Lz2guKP+tPrc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":134958},"main":"index.js","types":"index.d.ts","gitHead":"82dba1a653e46a998bc9ead58c648482b3b81010","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.19.1","dependencies":{"zod":"^3.24.1","uuid":"^11.1.0","@asaidimu/query":"^1.1.0","@asaidimu/anansi":"^1.6.6","@asaidimu/events":"^1.0.0"},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_3.0.1_1749297089760_0.601512129526458","host":"s3://npm-registry-packages-npm-production"}},"4.0.0":{"name":"@asaidimu/indexed","version":"4.0.0","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","_id":"@asaidimu/indexed@4.0.0","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"homepage":"https://github.com/asaidimu/indexed#readme","bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"dist":{"shasum":"6cd793b92b7836ea09b87d5e96c05c7fadc50ace","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-4.0.0.tgz","fileCount":7,"integrity":"sha512-mtL725HwgXxKuNEc5X226rj72vu0qDg/pjg02LklaRyHw0NulEJMBlcCKGk4lBMI6EczR42McTbHT/RG4OACAw==","signatures":[{"sig":"MEYCIQDG0YmM7TQbqApfGI4GVQyj6BVqwG45vtFfUOnjuMJs1AIhAJtr6/M9+ngQPZs8Bq/veVIu/M8OVdP1B903SqvOBp7x","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":159539},"main":"index.js","types":"index.d.ts","gitHead":"849ee5eb2296744bad6db3438dd5a61aa1e45807","_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"repository":{"url":"git+https://github.com/asaidimu/indexed.git","type":"git"},"_npmVersion":"10.8.2","description":"A simple and efficient way to interact with IndexedDB","directories":{},"_nodeVersion":"20.19.1","dependencies":{"uuid":"^11.1.0","@asaidimu/query":"^1.1.0","@asaidimu/anansi":"^4.0.2","@asaidimu/events":"^1.0.0"},"publishConfig":{"tag":"latest","access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/indexed_4.0.0_1749923337641_0.5801572168552835","host":"s3://npm-registry-packages-npm-production"}},"4.0.1":{"name":"@asaidimu/indexed","version":"4.0.1","description":"A simple and efficient way to interact with IndexedDB","main":"index.js","types":"index.d.ts","keywords":["typescript"],"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/asaidimu/indexed.git"},"bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"homepage":"https://github.com/asaidimu/indexed#readme","publishConfig":{"registry":"https://registry.npmjs.org/","tag":"latest","access":"public"},"dependencies":{"@asaidimu/anansi":"^4.0.2","@asaidimu/events":"^1.0.0","@asaidimu/query":"^1.1.0","uuid":"^11.1.0"},"_id":"@asaidimu/indexed@4.0.1","gitHead":"f53f5f232329f4a5ded13a0a81000dff02dec48d","_nodeVersion":"20.19.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-eEG++9K/7u6Syg8/wSG05Gvo2evooIWDKmx8jjuuiezdHVVSWgkeWxDZLjlpuSosckwDOIgHtJRtoTpwMcEeJw==","shasum":"5534ee5e05c9ecaae2578d0d72785f429ef27903","tarball":"https://registry.npmjs.org/@asaidimu/indexed/-/indexed-4.0.1.tgz","fileCount":7,"unpackedSize":166740,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCc/xOGF2mcfSd9X2Esl6POTONX4q+SrwAYKPwEYqsMKwIgNc9+IM36aq8TpZ2LvC71cKce8fFEIl4UhOl3OaepBsw="}]},"_npmUser":{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"},"directories":{},"maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/indexed_4.0.1_1749923631883_0.40098433525756394"},"_hasShrinkwrap":false}},"time":{"created":"2025-01-22T08:12:59.702Z","modified":"2025-06-14T17:53:52.299Z","1.0.0":"2025-01-22T08:13:00.001Z","1.0.1":"2025-01-22T09:16:26.917Z","1.1.0":"2025-01-30T20:10:46.511Z","1.1.1":"2025-02-23T16:23:53.653Z","1.1.2":"2025-02-23T16:59:06.101Z","1.1.3":"2025-02-24T13:20:32.860Z","2.0.0":"2025-05-23T12:18:38.577Z","2.1.0":"2025-05-24T20:03:14.614Z","3.0.0":"2025-05-25T15:27:31.744Z","3.0.1":"2025-06-07T11:51:29.956Z","4.0.0":"2025-06-14T17:48:57.815Z","4.0.1":"2025-06-14T17:53:52.122Z"},"bugs":{"url":"https://github.com/asaidimu/indexed/issues"},"author":{"name":"Saidimu","email":"47994458+asaidimu@users.noreply.github.com"},"license":"MIT","homepage":"https://github.com/asaidimu/indexed#readme","keywords":["typescript"],"repository":{"type":"git","url":"git+https://github.com/asaidimu/indexed.git"},"description":"A simple and efficient way to interact with IndexedDB","maintainers":[{"name":"asaidimu","email":"lolokilesaidimu@gmail.com"}],"readme":"# `@asaidimu/indexed`\n\n[![npm version](https://img.shields.io/npm/v/@asaidimu/indexed.svg)](https://www.npmjs.com/package/@asaidimu/indexed)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/asaidimu/indexed/test.yml?branch=main)](https://github.com/asaidimu/indexed/actions/workflows/test.yml)\n\nA simple and efficient TypeScript library providing a document-oriented database interface for IndexedDB, complete with robust schema management, powerful querying, flexible pagination, an event-driven architecture, and built-in telemetry.\n\n## 🔗 Quick Links\n\n*   [Overview & Features](#-overview--features)\n*   [Installation & Setup](#-installation--setup)\n*   [Usage Documentation](#-usage-documentation)\n    *   [Basic Usage](#basic-usage)\n    *   [Database API](#database-api)\n    *   [Collection API](#collection-api)\n    *   [Document API](#document-api)\n    *   [Schema Definition](#schema-definition)\n    *   [Querying](#querying)\n    *   [Pagination](#pagination)\n    *   [Telemetry](#telemetry)\n    *   [Error Handling](#error-handling)\n    *   [Event System](#event-system)\n*   [Project Architecture](#%EF%B8%8F-project-architecture)\n*   [Development & Contributing](#%EF%B8%8F-development--contributing)\n*   [Additional Information](#-additional-information)\n\n---\n\n## 🚀 Overview & Features\n\n`@asaidimu/indexed` is a modern TypeScript library designed to simplify interactions with the browser's native IndexedDB. It abstracts away the complexities of low-level IndexedDB APIs, offering a high-level, document-oriented interface that feels similar to popular NoSQL databases. This library empowers developers to manage structured data in the browser with ease, providing robust schema enforcement, flexible querying capabilities, and advanced features like migrations and performance monitoring.\n\nIt's ideal for single-page applications, progressive web apps, or any client-side project requiring persistent data storage that goes beyond simple key-value pairs, ensuring data integrity and a streamlined development experience. By providing a familiar API pattern, `@asaidimu/indexed` significantly reduces the learning curve and boilerplate typically associated with IndexedDB, allowing developers to focus on application logic rather than database mechanics.\n\n### ✨ Key Features\n\n*   **Document-Oriented Interface**: Interact with your data using familiar document patterns (create, read, update, delete) and an API that mirrors common NoSQL paradigms.\n*   **Comprehensive Schema Management**: Define detailed data schemas using `@asaidimu/anansi`, including field types, validation constraints (e.g., `minLength`, `min`, `unique`), and indexes for optimized queries. Supports complex nested schemas.\n*   **Flexible Data Access**: Retrieve documents using `find` (single document), `filter` (array of matching documents), and `list` (paginated documents) methods.\n*   **Advanced Querying**: Leverage the powerful Query DSL (from `@asaidimu/query`) for expressive and efficient data retrieval, supporting various operators and logical combinations.\n*   **Pagination Support**: Seamlessly paginate through large datasets using both offset-based and cursor-based strategies, returning asynchronous iterators for efficient batch processing.\n*   **Event-Driven Architecture**: Subscribe to database, collection, and document-level events (`document:create`, `document:write`, `document:update`, `document:delete`, `document:read`, `collection:create`, `collection:delete`, `collection:update`, `collection:read`, `migrate`, `telemetry`) for real-time monitoring and reactive programming patterns.\n*   **Built-in Telemetry**: Gain insights into database operation performance, arguments, and outcomes with an optional, pluggable telemetry system, crucial for debugging and optimization.\n*   **Schema Migrations**: Define and apply schema changes over time using `SchemaChange` objects within `SchemaDefinition`, ensuring data compatibility and evolution across application versions. This leverages `@asaidimu/anansi`'s `MigrationEngine` for data transformation streams.\n*   **Automatic ID Generation**: New documents automatically receive a unique `$id` (UUID v4) if not explicitly provided, along with `$created`, `$updated`, and `$version` metadata.\n*   **TypeScript Support**: Full type definitions ensure type safety and an excellent developer experience, with strong interfaces for all API components.\n\n---\n\n## 📦 Installation & Setup\n\n### Prerequisites\n\n*   **Node.js**: v18 or higher recommended.\n*   **Package Manager**: npm, yarn, or Bun.\n*   **Environment**: A browser environment supporting IndexedDB. For Node.js testing, a compatible shim like `fake-indexeddb` and `jsdom` is used internally.\n\n### Installation Steps\n\nTo add `@asaidimu/indexed` to your project, use your preferred package manager:\n\n```bash\n# Using npm\nnpm install @asaidimu/indexed\n\n# Using yarn\nyarn add @asaidimu/indexed\n\n# Using Bun\nbun add @asaidimu/indexed\n```\n\n### Configuration\n\nThe library requires minimal configuration during the `DatabaseConnection` initialization. You provide a database name, and can optionally enable telemetry.\n\n```typescript\nimport { DatabaseConnection } from '@asaidimu/indexed';\n\n// Basic initialization: Connects to or creates 'myCoolAppDB'\nconst db = await DatabaseConnection({\n    name: 'myCoolAppDB'\n});\n\n// Initialization with telemetry enabled: Useful for performance monitoring\nconst dbWithTelemetry = await DatabaseConnection({\n    name: 'myCoolAppDB',\n    enableTelemetry: true\n});\n\n// Advanced configuration\nconst dbAdvanced = await DatabaseConnection({\n    name: 'myCustomDB',\n    indexSchema: '$my_schema_metadata', // Custom name for the internal schema store\n    keyPath: 'documentId', // Custom key path for documents (defaults to '$id')\n    enableTelemetry: true,\n    // Optional: provide custom predicates for schema validation from @asaidimu/anansi\n    // predicates: {\n    //     isEmail: (value: any) => typeof value === 'string' && value.includes('@')\n    // }\n    // validate: true // enables validation \n});\n```\nThe database connection is internally cached, so subsequent calls to `DatabaseConnection` with the same name will return the existing instance.\n\n### Verification\nAfter installation, you can quickly verify by attempting to import and initialize the database in your environment:\n\n```typescript\nimport { DatabaseConnection } from '@asaidimu/indexed';\n\nasync function verifyInstallation() {\n    let db;\n    try {\n        db = await DatabaseConnection({ name: 'test_db_verification' });\n        console.log('IndexedDB Document Store initialized successfully!');\n    } catch (error) {\n        console.error('Failed to initialize IndexedDB Document Store:', error);\n    } finally {\n        if (db) {\n            db.close(); // Don't forget to close the connection to free resources\n            console.log('Database connection closed.');\n        }\n    }\n}\n\nverifyInstallation();\n```\n\n---\n\n## 📖 Usage Documentation\n\n### Basic Usage\n\nLet's start with a simple example: defining a schema, creating a collection, and performing basic CRUD operations.\n\n```typescript\nimport { DatabaseConnection } from '@asaidimu/indexed';\nimport type { SchemaDefinition } from '@asaidimu/anansi'; // Crucial for robust schema definition\n\n// 1. Define your document interface\n// The '$id' property and other metadata ($created, $updated, $version)\n// will be automatically added by the library to your Document instances.\ninterface Product {\n    name: string;\n    price: number;\n    inStock: boolean;\n    category?: string;\n    tags?: string[];\n}\n\n// 2. Define your schema using SchemaDefinition from @asaidimu/anansi\n// This schema will dictate the structure and validation rules for documents\n// stored in the 'products' collection.\nconst productSchema: SchemaDefinition = {\n    name: 'products',\n    version: '1.0.0',\n    description: 'Schema for product documents',\n    fields: {\n        // '$id' is the internal key path for IndexedDB, which defaults to \"$id\".\n        // It's implicitly handled by the library and should not be explicitly defined here.\n        name: { type: 'string', required: true, constraints: [{ name: 'minLength', parameters: 3 }] },\n        price: { type: 'number', required: true, constraints: [{ name: 'min', parameters: 0 }] },\n        inStock: { type: 'boolean', required: true },\n        category: { type: 'string', required: false },\n        tags: { type: 'array', required: false, itemsType: 'string' }\n    },\n    indexes: [\n        { fields: ['name'], type: 'normal' },\n        { fields: ['category'], type: 'normal' },\n        { fields: ['price'], type: 'btree' },\n        { fields: ['name', 'category'], type: 'composite', unique: true }\n    ],\n    constraints: [], // Schema-level constraints can be defined here\n    migrations: [] // Migration plans for schema evolution\n};\n\nasync function runExample() {\n    let db; // Declare db here to ensure it's accessible in finally block\n    try {\n        // 3. Connect to the database with telemetry enabled\n        db = await DatabaseConnection({ name: 'myCommerceDB', enableTelemetry: true });\n        console.log('Database connected.');\n\n        // Subscribe to database-level telemetry for all operations\n        const unsubscribeDbTelemetry = db.subscribe(\"telemetry\", (event) => {\n            console.log(`[DB Telemetry] ${event.method} (Duration: ${event.metadata.performance.durationMs}ms)`);\n        });\n\n        // 4. Create or get your collection (equivalent to an IndexedDB Object Store)\n        let productsCollection;\n        try {\n            productsCollection = await db.collection<Product>('products');\n            console.log('Collection \"products\" already exists. Accessing it.');\n        } catch (e: any) {\n            // If the collection doesn't exist, create it using the defined schema.\n            if (e.type === 'SCHEMA_NOT_FOUND') {\n                productsCollection = await db.createCollection<Product>(productSchema);\n                console.log('Collection \"products\" created successfully!');\n            } else {\n                throw e; // Re-throw other unexpected errors\n            }\n        }\n        \n        // Subscribe to collection-level events for the 'products' collection\n        const unsubscribeCollectionRead = productsCollection.subscribe(\"collection:read\", (event) => {\n          console.log(`[Collection Event] Products collection read via method: '${event.method}'`);\n        });\n\n        // 5. Create a new document in the collection\n        const newProduct = await productsCollection.create({\n            name: 'Laptop Pro X',\n            price: 1200.00,\n            inStock: true,\n            category: 'Electronics',\n            tags: ['tech', 'gadget']\n        });\n        console.log('Created Product:', newProduct);\n\n        // Documents have special meta-properties and methods.\n        // '$id' is automatically generated (UUID v4 in this version)\n        // AVOID depending on internal variables.\n        console.log('New Product ID:', newProduct.$id);\n        console.log('Product created at:', newProduct.$created);\n        console.log('Product version:', newProduct.$version);\n\n        // Subscribe to document-level events for the new product\n        const unsubscribeProductUpdate = await newProduct.subscribe('document:update', (event) => {\n            console.log(`[Document Event] Product updated: ID ${event.data?.$id}, new data: ${JSON.stringify(event.data)}`);\n        });\n\n        // 6. Find a document by query\n        const foundProduct = await productsCollection.find({\n            field: 'name', // Querying by the name prop\n            operator: 'eq',\n            value: newProduct.name\n        });\n        if (foundProduct) {\n            console.log(`Found Product: ${foundProduct.name} `);\n            console.log('Current price:', foundProduct.price);\n\n            // 7. Update a document\n            const updated = await foundProduct.update({ price: 1150.00, inStock: false });\n            if (updated) {\n                console.log('Product price updated to:', foundProduct.price);\n                // The in-memory document instance is updated immediately,\n                // but 'read()' ensures we have the absolute latest from the DB,\n                // useful if other parts of the app might have modified it.\n                await foundProduct.read();\n                console.log('Product in stock status after read:', foundProduct.inStock);\n            }\n\n            // 8. Filter documents based on criteria\n            await productsCollection.create({ name: 'Mechanical Keyboard', price: 75, inStock: true, category: 'Electronics' });\n            await productsCollection.create({ name: 'Ergonomic Mouse', price: 40, inStock: true, category: 'Accessories' });\n            await productsCollection.create({ name: 'Webcam 1080p', price: 60, inStock: false, category: 'Accessories' });\n\n            const electronics = await productsCollection.filter({\n                field: 'category',\n                operator: 'eq',\n                value: 'Electronics'\n            });\n            console.log('Electronics products:', electronics.map(p => p.name));\n\n            // 9. List documents with pagination (offset-based example)\n            console.log('Listing all products (offset pagination, 2 items per page):');\n            const productIterator = await productsCollection.list({ type: 'offset', offset: 0, limit: 2 });\n            let pageNum = 1;\n            // Use for await...of to iterate over the async iterator\n            for await (const batch of productIterator) {\n                if (batch.length === 0) {\n                  console.log('No more data.');\n                  break;\n                }\n                console.log(`--- Page ${pageNum++} ---`);\n                batch.forEach(p => console.log(`- ${p.name} ($${p.price})`));\n            }\n            \n            // 10. Migrate the collection schema and data\n            console.log('\\n--- Running Collection Migration ---');\n            await db.migrateCollection(productSchema.name, {\n                changes: [{ type: \"addField\", name: \"isMigrated\", definition: { type: \"boolean\", default: true } }],\n                description: \"Add isMigrated field\",\n                transform: {\n                    forward: (doc: any) => ({ ...doc, isMigrated: true }),\n                    backward: (doc: any) => {\n                        const { isMigrated, ...rest } = doc;\n                        return rest;\n                    }\n                }\n            });\n            const migratedProduct = await productsCollection.find({ field: 'name', operator: 'eq', value: newProduct.name });\n            console.log('Migrated Product (should have isMigrated: true):', migratedProduct?.state());\n\n            // 11. Delete a document\n            const deleted = await foundProduct.delete();\n            if (deleted) {\n                console.log(`Product \"${foundProduct.name}\" deleted successfully.`);\n            }\n        } else {\n            console.log('Product not found after creation, which is unexpected.');\n        }\n\n        // Cleanup subscriptions\n        unsubscribeProductUpdate();\n        unsubscribeCollectionRead();\n        unsubscribeDbTelemetry();\n\n        // 12. Delete the entire collection\n        await db.deleteCollection('products');\n        console.log('Collection \"products\" deleted.');\n\n    } catch (error) {\n        console.error('An error occurred during the example run:', error);\n    } finally {\n        // 13. Ensure the database connection is closed\n        if (db) {\n            db.close();\n            console.log('Database connection closed.');\n        }\n    }\n}\n\nrunExample();\n```\n\n### Database API\n\nThe `Database` interface provides methods for managing collections (equivalent to IndexedDB object stores) and subscribing to global database events.\n\n```typescript\nimport { DatabaseConnection } from '@asaidimu/indexed';\nimport type { Database, Collection, DatabaseEvent, DatabaseEventType, TelemetryEvent } from '@asaidimu/indexed';\nimport type { SchemaDefinition, PredicateMap, DataTransform, SchemaChange } from '@asaidimu/anansi';\n\ninterface DatabaseConfig {\n    name: string; // The name of your IndexedDB database\n    indexSchema?: string; // Optional: name for the internal schema index store (default: \"$schema\")\n    keyPath?: string; // Optional: key path for all object stores (default: \"$id\")\n    enableTelemetry?: boolean; // Optional: enables performance telemetry (default: false)\n    predicates?: PredicateMap; // Optional: custom validation predicates for schemas\n    validate?: boolean; // Optional: enables schema validation on data entry (default: false)\n}\n\n/**\n * Creates a new database connection or retrieves an existing one from an in-memory cache.\n * This is the primary entry point for interacting with the IndexedDB.\n * Subsequent calls with the same database name will return the cached instance.\n */\nfunction DatabaseConnection(config: DatabaseConfig): Promise<Database>;\n\ninterface CollectionMigrationOptions {\n  changes: SchemaChange<any>[]; // Array of schema changes to apply\n  description: string; // Description of the migration\n  rollback?: SchemaChange<any>[]; // Optional rollback changes\n  transform?: string | DataTransform<any, any>; // Optional data transform function or string representing it\n}\n\ninterface Database {\n    /**\n     * Accesses an existing collection (schema model) by name.\n     * @param schemaName - The name of the schema/collection to access.\n     * @returns A promise resolving to the schema's Collection instance.\n     * @throws DatabaseError if the schema does not exist.\n     */\n    collection: <T>(schemaName: string) => Promise<Collection<T>>;\n\n    /**\n     * Creates a new collection (schema model) in the database.\n     * This operation increments the database version to allow for object store creation.\n     * @param schema - The schema definition for the new collection.\n     * @returns A promise resolving to the created schema's Collection instance.\n     * @throws DatabaseError if the schema already exists or is invalid.\n     */\n    createCollection: <T>(schema: SchemaDefinition) => Promise<Collection<T>>;\n\n    /**\n     * Deletes an existing collection (schema model) by name from the database.\n     * This operation increments the database version to allow for object store deletion.\n     * @param schemaName - The name of the schema/collection to delete.\n     * @returns A promise resolving to `true` if successful.\n     * @throws DatabaseError if the schema is not found or an internal error occurs.\n     */\n    deleteCollection: (schemaName: string) => Promise<boolean>;\n\n    /**\n     * Updates an existing collection's schema definition in the internal `$schema` metadata store.\n     * This method does *not* modify the IndexedDB object store structure itself.\n     * For actual structural changes (e.g., adding/removing indexes or object stores),\n     * you typically need to manage IndexedDB database version upgrades manually or\n     * by leveraging `@asaidimu/anansi`'s migration features combined with this library's `migrateCollection`.\n     * @param schema - The updated schema definition.\n     * @returns A promise resolving to `true` if successful.\n     * @throws DatabaseError if the schema is not found or an internal error occurs.\n     */\n    updateCollection: (schema: SchemaDefinition) => Promise<boolean>;\n\n    /**\n     * Migrates an existing collection's data and updates its schema definition metadata.\n     * This function processes data in a streaming fashion, using `@asaidimu/anansi`'s `MigrationEngine`\n     * to apply schema changes and data transformations. All data and metadata updates\n     * occur within a single atomic IndexedDB transaction.\n     * Note: This function focuses on *data transformation* and *metadata updates*,\n     * not structural IndexedDB changes which require a database version upgrade handled by `createCollection`/`deleteCollection`.\n     * @param name - The name of the collection (IndexedDB object store) to migrate.\n     * @param opts - Options for the migration, including schema changes, description, and an optional data transform.\n     * @returns A Promise resolving to `true` if the migration completes successfully.\n     * @throws DatabaseError if the collection or its schema metadata is missing, or any operation fails.\n     */\n    migrateCollection: (name: string, opts: CollectionMigrationOptions) => Promise<boolean>;\n\n    /**\n     * Subscribes to database-level events.\n     * @param event - The event type to subscribe to (e.g., \"collection:create\", \"telemetry\").\n     * @param callback - The function to call when the event occurs.\n     * @returns An unsubscribe function.\n     */\n    subscribe: (\n        event: DatabaseEventType | \"telemetry\",\n        callback: (event: DatabaseEvent | TelemetryEvent) => void\n    ) => () => void;\n\n    /**\n     * Closes the connection to the underlying IndexedDB database.\n     * It's good practice to close connections when no longer needed to free up resources.\n     */\n    close: () => void;\n}\n```\n\n### Collection API\n\nA `Collection<T>` provides methods for managing documents within a specific schema (object store). The generic type `T` represents the shape of your application data within this collection.\n\n```typescript\nimport type { Document, CollectionEvent, CollectionEventType, TelemetryEvent } from '@asaidimu/indexed';\nimport type { PaginationOptions, QueryFilter } from '@asaidimu/query';\n\ninterface Collection<T> {\n  /**\n   * Finds a single document matching the specified query.\n   * @param query - The query filter to apply.\n   * @returns A promise resolving to the matching document (as a Document<T> instance) or `null` if not found.\n   */\n  find: (query: QueryFilter<T>) => Promise<Document<T> | null>;\n\n  /**\n   * Lists documents based on the provided pagination options.\n   * Supports both offset-based and cursor-based pagination.\n   * @param query - The pagination options (e.g., limit, offset, cursor, direction).\n   * @returns A promise resolving to an AsyncIterator, which yields arrays of Document<T>.\n   */\n  list: (query: PaginationOptions) => Promise<AsyncIterator<Document<T>[]>>;\n\n  /**\n   * Filters documents based on the provided query and returns all matching documents.\n   * @param query - The query filter to apply.\n   * @returns A promise resolving to an array of matching Document<T> instances.\n   */\n  filter: (query: QueryFilter<T>) => Promise<Document<T>[]>;\n\n  /**\n   * Creates a new document in this collection.\n   * The document is automatically assigned internal metadata like `$id`, `$created`, and `$version` and persisted immediately.\n   * @param initial - The initial data for the document.\n   * @returns A promise resolving to the newly created Document<T> instance.\n   */\n  create: (initial: T) => Promise<Document<T>>;\n\n  /**\n   * Subscribes to collection-level events.\n   * @param event - The event type to subscribe to (e.g., \"collection:read\", \"telemetry\").\n   * @param callback - The function to call when the event occurs.\n   * @returns An unsubscribe function.\n   */\n  subscribe: (\n    event: CollectionEventType | TelemetryEventType,\n    callback: (event: CollectionEvent<T> | TelemetryEvent) => void\n  ) => () => void;\n}\n```\n\n### Document API\n\nA `Document<T>` represents a single record in a collection and provides methods for interacting with that specific document. The generic type `T` represents your custom data shape, and the library automatically adds internal properties like `$id`, `$created`, `$updated`, and `$version`.\n\n```typescript\nimport type { TelemetryEvent, TelemetryEventType } from './telemetry';\n\nexport type Document<T> =\n    {\n        readonly [K in keyof T]: T[K]; // Your defined document properties, made read-only\n    } &\n    {\n        /**\n         * A unique identifier for the document. Automatically generated as a UUID v4\n         * if not provided during creation. This is the IndexedDB key.\n         */\n        $id?: string;\n\n        /**\n         * A timestamp indicating when the document was created (ISO 8601 format).\n         * Automatically set on creation.\n         */\n        $created?: string | Date;\n\n        /**\n         * A timestamp indicating when the document was last updated (ISO 8601 format).\n         * Automatically updated on calls to `update()`.\n         */\n        $updated?: string | Date;\n\n        /**\n         * A number representing how many times the document has changed.\n         * Incremented on calls to `update()`.\n         */\n        $version?: number;\n\n        /**\n         * Fetches the latest data for this document from the database.\n         * Updates the in-memory document instance to reflect any changes.\n         * @returns A promise resolving to `true` if successful and found, or `false` if an error occurs or not found.\n         */\n        read: () => Promise<boolean>;\n\n        /**\n         * Updates the document in the database with the provided partial properties.\n         * Also updates the in-memory document instance and increments `$version` and `$updated`.\n         * @param props - Partial object containing the fields to update.\n         * @returns A promise resolving to `true` if successful, or `false` if an error occurs.\n         */\n        update: (props: Partial<T>) => Promise<boolean>;\n\n        /**\n         * Deletes the document from its collection in the database.\n         * @returns A promise resolving to `true` if successful, or `false` if an error occurs.\n         */\n        delete: () => Promise<boolean>;\n\n        /**\n         * Subscribes to document-level events.\n         * @param event - The event type to subscribe to (e.g., \"document:update\", \"document:delete\", \"telemetry\").\n         * @param callback - The function to call when the event occurs.\n         * @returns A promise resolving to an unsubscribe function.\n         */\n        subscribe: (\n            event: DocumentEventType | TelemetryEventType,\n            callback: (event: DocumentEvent<T> | TelemetryEvent) => void\n        ) => Promise<() => void>;\n\n        /**\n         * Returns a structured clone of the current in-memory state of the document.\n         * This provides a plain object representation without the document's methods.\n         * @returns A deep copy of the document's data.\n         */\n        state(): T;\n    }\n\n/**\n * Event payload for DocumentModel events.\n */\nexport type DocumentEventType = \"document:create\" | \"document:write\" | \"document:update\" | \"document:delete\" | \"document:read\"; // The type of event.\nexport type DocumentEvent<T> = {\n    type: DocumentEventType\n    data?: Partial<T>; // The data associated with the event (e.g., updated fields).\n    timestamp: number; // The time the event occurred.\n};\n```\n\n### Schema Definition\n\n`@asaidimu/indexed` utilizes the `SchemaDefinition` from the external library `@asaidimu/anansi` to enforce data integrity and structure. This allows for rich schema definitions, including explicit field types, built-in and custom constraints, indexes for optimized queries, and a mechanism for defining migration plans to evolve your data over time.\n\nFor a detailed understanding of `SchemaDefinition` and its capabilities, please refer to the documentation for [`@asaidimu/anansi`](https://github.com/asaidimu/anansi).\n\nKey aspects of `SchemaDefinition` as used by `@asaidimu/indexed` include:\n*   `name`: Unique identifier for the collection/schema (corresponds to an IndexedDB object store name).\n*   `version`: Version string for the schema, important for tracking changes.\n*   `fields`: A record defining each field's `type` (`string`, `number`, `boolean`, `array`, `object`, `dynamic`), `required` status, `constraints`, `default` values, and more.\n*   `indexes`: Definitions for IndexedDB indexes, used for optimized queries on specific fields.\n*   `constraints`: Schema-wide validation rules applied when documents are created or updated.\n*   `migrations`: An array of `Migration` objects, each detailing atomic `SchemaChange` operations (e.g., `addField`, `removeField`, `modifyField`, `addIndex`, `removeIndex`, `addConstraint`). These are primarily used by `@asaidimu/anansi`'s `MigrationEngine` which `migrateCollection` integrates with.\n\n### Querying\n\nThe `find` and `filter` methods of a `Collection` utilize the `QueryFilter` DSL from `@asaidimu/query` for expressive and flexible data retrieval. This powerful query language allows you to specify conditions, apply logical operators, and target specific fields.\n\n```typescript\nimport type { QueryFilter } from '@asaidimu/query';\n\n// QueryFilter structure (simplified):\ntype QueryFilter<T> = {\n    field: keyof T | string; // The field to query on \n    operator: \"eq\" | \"ne\" | \"gt\" | \"gte\" | \"lt\" | \"lte\" | \"in\" | \"nin\" | \"contains\" | \"startsWith\" | \"endsWith\" | \"exists\" | \"notExists\";\n    value?: any; // The value to compare against\n} | {\n    operator: \"and\" | \"or\" | \"not\" | \"nor\" | \"xor\"; // Logical operators for combining conditions\n    conditions: QueryFilter<T>[]; // Array of nested query filters\n};\n\n// Example usage:\n// Find a user by email address\nconst userByEmail = await usersCollection.find({\n    field: 'email',\n    operator: 'eq',\n    value: 'john@example.com'\n});\n\n// Filter products that are in stock AND cost less than 100\nconst affordableInStock = await productsCollection.filter({\n    operator: 'and',\n    conditions: [\n        { field: 'inStock', operator: 'eq', value: true },\n        { field: 'price', operator: 'lt', value: 100 }\n    ]\n});\n\n// Find products with 'laptop' in their name OR are in the 'Electronics' category\nconst relevantProducts = await productsCollection.filter({\n    operator: 'or',\n    conditions: [\n        { field: 'name', operator: 'contains', value: 'laptop' },\n        { field: 'category', operator: 'eq', value: 'Electronics' }\n    ]\n});\n```\n\n### Pagination\n\nThe `list` method of a `Collection` provides robust pagination capabilities, allowing you to efficiently retrieve documents in batches. It supports both traditional offset-based pagination and more efficient cursor-based pagination, returning an `AsyncIterator` for seamless integration into `for await...of` loops.\n\n```typescript\nimport type { PaginationOptions } from '@asaidimu/query';\nimport type { Collection, Document } from '@asaidimu/indexed';\n\ninterface OffsetPaginationOptions {\n    type: \"offset\"; // Specifies offset-based pagination\n    offset: number; // The number of documents to skip from the beginning\n    limit: number;  // The maximum number of documents to return in a batch\n}\n\ninterface CursorPaginationOptions {\n    type: \"cursor\";    // Specifies cursor-based pagination\n    cursor?: string;   // Optional: The $id of the document to start (or continue) from\n    direction: \"forward\" | \"backward\"; // The direction of iteration from the cursor\n    limit: number;     // The maximum number of documents to return in a batch\n}\n\ntype PaginationOptions = OffsetPaginationOptions | CursorPaginationOptions;\n\n// Example: Offset-based pagination\nasync function fetchProductsOffset(productsCollection: Collection<Product>) {\n    console.log('\\n--- Fetching Products (Offset Pagination) ---');\n    let currentPage = 0;\n    const pageSize = 2; // Number of items per page\n\n    while (true) {\n        // The list method returns an AsyncIterator.\n        // Calling .next() on it fetches the next batch based on the pagination options.\n        const iterator = await productsCollection.list({\n            type: \"offset\",\n            offset: currentPage * pageSize,\n            limit: pageSize\n        });\n\n        const { value: batch, done } = await iterator.next();\n\n        if (batch.length > 0) {\n            console.log(`Page ${currentPage + 1}:`);\n            batch.forEach(product => console.log(`- ${product.name} (ID: ${product.$id})`));\n            currentPage++;\n        }\n\n        // If the batch is empty or we've reached the end, stop.\n        if (done || batch.length < pageSize) {\n            console.log('--- End of Offset Pagination ---');\n            break;\n        }\n    }\n}\n\n// Example: Cursor-based pagination (simple forward iteration)\nasync function fetchProductsCursor(productsCollection: Collection<Product>) {\n    console.log('\\n--- Fetching Products (Cursor Pagination) ---');\n    let lastProductId: string | undefined = undefined; // Used as the cursor for the next batch\n    const pageSize = 2;\n\n    while (true) {\n        const iterator = await productsCollection.list({\n            type: \"cursor\",\n            cursor: lastProductId,\n            direction: \"forward\", // \"forward\" for 'next' cursor, \"backward\" for 'prev'\n            limit: pageSize\n        });\n\n        const { value: batch, done } = await iterator.next();\n\n        if (batch.length > 0) {\n            console.log('Next Batch:');\n            batch.forEach(product => console.log(`- ${product.name} (ID: ${product.$id})`));\n            // Update the cursor to the ID of the last document fetched in this batch\n            lastProductId = batch[batch.length - 1].$id;\n        }\n\n        if (done || batch.length < pageSize) { // If done or last batch is smaller than limit\n            console.log('--- End of Cursor Pagination ---');\n            break;\n        }\n    }\n}\n\n// To run these examples, ensure you have documents in your 'products' collection.\n// e.g., await productsCollection.create({ name: 'Product A', price: 10, inStock: true });\n// ... and so on for several products.\n```\n\n### Telemetry\n\n`@asaidimu/indexed` includes a built-in telemetry system that can be enabled during database initialization. This feature provides detailed performance metrics and contextual information for database operations, proving highly useful for debugging, performance monitoring, and analytics.\n\nTo enable telemetry when connecting to your database:\n\n```typescript\nimport { DatabaseConnection } from '@asaidimu/indexed';\n\nconst db = await DatabaseConnection({\n    name: 'myAppDB',\n    enableTelemetry: true\n});\n```\n\nOnce enabled, you can subscribe to `telemetry` events at the `Database`, `Collection`, or `Document` level to capture granular insights:\n\n```typescript\nimport type { TelemetryEvent } from '@asaidimu/indexed';\n\n// Subscribe to database-level telemetry: captures all operations at the DB level\nconst unsubscribeDbTelemetry = db.subscribe(\"telemetry\", (event: TelemetryEvent) => {\n    console.log(`[DB Telemetry] Method: ${event.method}`);\n    console.log(`Duration: ${event.metadata.performance.durationMs}ms`);\n    if (event.metadata.error) {\n        console.error(`Error: ${event.metadata.error.message}, Stack: ${event.metadata.error.stack}`);\n    }\n    console.log('Arguments:', event.metadata.args);\n    console.log('Result:', event.metadata.result);\n    console.log('Context:', event.metadata.context);\n    console.log('Source:', event.metadata.source); // Indicates where the telemetry event originated (db, collection, document)\n    console.log('---');\n});\n\n// Example usage to trigger DB telemetry\nawait db.createCollection({ name: 'users', version: '1.0.0', fields: { /* ... */ } });\nunsubscribeDbTelemetry(); // Clean up\n\n// Subscribe to collection-level telemetry: specific to operations on a collection\nconst productsCollection = await db.collection<Product>('products');\nconst unsubscribeCollectionTelemetry = productsCollection.subscribe(\"telemetry\", (event: TelemetryEvent) => {\n    console.log(`[Collection Telemetry - Products] Method: ${event.method}`);\n    console.log(`Duration: ${event.metadata.performance.durationMs}ms`);\n    console.log('---');\n});\n\n// Example usage to trigger Collection telemetry\nawait productsCollection.create({ name: 'New Gadget', price: 99, inStock: true });\nawait productsCollection.find({ field: 'name', operator: 'eq', value: 'New Gadget' });\nunsubscribeCollectionTelemetry(); // Clean up\n\n// Subscribe to document-level telemetry: for operations on a specific document\nconst myProduct = await productsCollection.find({ field: 'name', operator: 'eq', value: 'Laptop Pro X' });\nif (myProduct) {\n    const unsubscribeDocumentTelemetry = await myProduct.subscribe(\"telemetry\", (event: TelemetryEvent) => {\n        console.log(`[Document Telemetry - ${myProduct.name}] Method: ${event.method}`);\n        console.log(`Duration: ${event.metadata.performance.durationMs}ms`);\n        console.log('---');\n    });\n    await myProduct.update({ price: 1099.99 }); // This will trigger the document-level telemetry event\n    unsubscribeDocumentTelemetry(); // Clean up\n}\n```\n\nThe `TelemetryEvent` structure provides comprehensive details about each captured operation:\n\n```typescript\ntype TelemetryEvent = {\n    type: \"telemetry\";\n    method: string; // The name of the method called (e.g., \"create\", \"find\", \"updateCollection\", \"update\")\n    timestamp: number; // Unix timestamp (milliseconds) when the operation completed\n    source:  any; // Internal source of the event, can be database, collection, or document level\n    metadata: {\n        args: any[]; // Arguments passed to the method\n        performance: {\n            durationMs: number; // Execution duration of the operation in milliseconds\n        };\n        source:  { // Specifies the level and associated entities (collection, document)\n            level: \"database\" | \"collection\" | \"document\"\n            collection?: string,\n            document?: string\n        },\n        context: {\n            userAgent: string | undefined; // Browser user agent string (from globalThis.navigator?.userAgent)\n        };\n        result?: {\n            type: 'array' | string; // Type of the operation's result (e.g., 'array', 'object', 'number', 'boolean')\n            size?: number; // Size if the result is an array (e.g., for list/filter operations)\n        };\n        error: {\n            message: string;\n            name: string;\n            stack?: string;\n        } | null; // Error details (message, name, stack trace) if the operation failed, null otherwise\n    };\n}\n```\n\n### Error Handling\n\nThe library provides specific error types to help you handle different failure scenarios gracefully. All custom errors extend from `DatabaseError`, making it easy to catch and distinguish them from generic JavaScript errors.\n\n```typescript\nimport { DatabaseError, DatabaseErrorType } from '@asaidimu/indexed';\nimport type { SchemaDefinition } from '@asaidimu/anansi'; // For SchemaDefinition type\n\nexport enum DatabaseErrorType {\n    /** The schema (collection) does not exist when trying to access or modify it. */\n    SCHEMA_NOT_FOUND = \"SCHEMA_NOT_FOUND\",\n    /** The schema (collection) already exists when trying to create a new one with the same name. */\n    SCHEMA_ALREADY_EXISTS = \"SCHEMA_ALREADY_EXISTS\",\n    /** The provided schema name is invalid (e.g., empty or reserved). */\n    INVALID_SCHEMA_NAME = \"INVALID_SCHEMA_NAME\",\n    /** The schema definition itself is malformed or violates validation rules. */\n    INVALID_SCHEMA_DEFINITION = \"INVALID_SCHEMA_DEFINITION\",\n    /** An attempt to subscribe to a database event failed. */\n    SUBSCRIPTION_FAILED = \"SUBSCRIPTION_FAILED\",\n    /** A generic internal error occurred during a database operation. */\n    INTERNAL_ERROR = \"INTERNAL_ERROR\",\n    /** Data being entered into a collection does not satisfy its schema definition. */\n    INVALID_DATA = \"INVALID_DATA\",\n}\n\nexport interface DatabaseError { // This is an interface, the class definition is below\n    type: DatabaseErrorType; // The specific type of database error\n    message: string; // A human-readable message describing the error.\n    schema?: SchemaDefinition; // Associated schema if the error relates to a schema operation\n}\n\nexport class DatabaseError extends Error {\n    public type: DatabaseErrorType; // The specific type of database error\n    public schema?: SchemaDefinition; // Associated schema if the error relates to a schema operation\n\n    /**\n     * Constructs a new DatabaseError instance.\n     * @param type - The specific DatabaseErrorType.\n     * @param message - A human-readable message describing the error.\n     * @param schema - Optional: The SchemaDefinition related to the error, if applicable.\n     */\n    constructor(type: DatabaseErrorType, message: string, schema?: SchemaDefinition) {\n        super(message);\n        this.name = type; // Set the error name to the error type for easier identification\n        this.type = type;\n        this.schema = schema;\n    }\n}\n\n// Example usage of error handling:\nasync function safeCreateCollection(db: Database, schema: SchemaDefinition) {\n    try {\n        await db.createCollection(schema);\n        console.log(`Collection \"${schema.name}\" created successfully.`);\n    } catch (error) {\n        if (error instanceof DatabaseError) {\n            // Handle specific database errors\n            switch (error.type) {\n                case DatabaseErrorType.SCHEMA_ALREADY_EXISTS:\n                    console.warn(`Collection \"${schema.name}\" already exists. Skipping creation.`);\n                    break;\n                case DatabaseErrorType.INVALID_SCHEMA_DEFINITION:\n                    console.error(`Invalid schema definition for \"${schema.name}\": ${error.message}`);\n                    break;\n                case DatabaseErrorType.INVALID_DATA:\n                    console.error(`Invalid data provided for \"${schema.name}\": ${error.message}`);\n                    break;\n                case DatabaseErrorType.INTERNAL_ERROR:\n                    console.error(`An internal database error occurred: ${error.message}`);\n                    break;\n                case DatabaseErrorType.SCHEMA_NOT_FOUND:\n                    console.error(`Schema \"${schema.name}\" not found: ${error.message}`);\n                    break;\n                case DatabaseErrorType.SUBSCRIPTION_FAILED:\n                    console.error(`Subscription failed: ${error.message}`);\n                    break;\n                default:\n                    console.error(`Unhandled Database Error (${error.type}): ${error.message}`);\n            }\n        } else {\n            // Handle unexpected non-DatabaseError errors\n            console.error('An unexpected error occurred:', error);\n        }\n    }\n}\n```\n\n### Event System\n\nThe library leverages a lightweight event-driven design, allowing you to subscribe to various lifecycle and data-related events across the database, collections, and individual documents. This facilitates reactive programming, real-time updates, and integration with other parts of your application.\n\n#### Database Events\n\nEmitted from the `Database` instance. These events provide insights into schema management and database-wide activities.\n\n*   `collection:create`: Triggered when a new collection has been successfully created.\n*   `collection:update`: Triggered when an existing collection's schema metadata has been modified (e.g., via `updateCollection`).\n*   `collection:delete`: Triggered when a collection has been successfully removed from the database.\n*   `collection:read`: Triggered when a collection has been accessed via `db.collection()`.\n*   `migrate`: Triggered when a collection migration starts or ends.\n*   `telemetry`: (If `enableTelemetry` is true) Provides performance and context data for database-level operations.\n\n```typescript\nimport { DatabaseConnection } from '@asaidimu/indexed';\nimport type { DatabaseEvent, DatabaseEventType } from '@asaidimu/indexed';\n\nconst db = await DatabaseConnection({ name: 'myAppDB' });\n\n// Example: Log when a new collection is created\ndb.subscribe(\"collection:create\", (event: DatabaseEvent) => {\n    console.log(`[DB Event] New collection created: ${event.schema?.name} at ${new Date(event.timestamp).toLocaleString()}`);\n});\n\n// Example: Log when a collection is deleted\ndb.subscribe(\"collection:delete\", (event: DatabaseEvent) => {\n    console.log(`[DB Event] Collection deleted: ${event.schema?.name} at ${new Date(event.timestamp).toLocaleString()}`);\n});\n\n// Example: Log when a migration occurs\ndb.subscribe(\"migrate\", (event: DatabaseEvent) => {\n    console.log(`[DB Event] Migration event for schema: ${event.schema?.name}, Type: ${event.type} at ${new Date(event.timestamp).toLocaleString()}`);\n});\n\n// To trigger:\n// await db.createCollection({ name: 'users', version: '1.0.0', fields: { /* ... */ } });\n// await db.deleteCollection('users');\n// await db.migrateCollection('myCollection', { changes: [], description: 'test' });\n```\n\n#### Collection Events\n\nEmitted from a `Collection` instance. These events provide insights into document lifecycle actions within a specific collection.\n\n*   `document:create`: Triggered when a new document is successfully created in this collection (often from `collection.create()`).\n*   `collection:read`: Triggered when documents within this collection have been accessed via `find`, `list`, `filter` methods.\n*   `migration:start`: Triggered when a migration process starts for this specific collection.\n*   `migration:end`: Triggered when a migration process completes for this specific collection.\n*   `telemetry`: (If `enableTelemetry` is true) Provides performance and context data for collection-level operations (e.g., `find`, `list`, `filter`, `create`).\n\n```typescript\nimport type { Collection, CollectionEvent, CollectionEventType } from '@asaidimu/indexed';\nimport type { Product } from './your-types-file'; // Assuming Product is defined\n\nconst productsCollection: Collection<Product> = await db.collection<Product>('products');\n\n// Example: Log when a document is created in the products collection\nproductsCollection.subscribe(\"document:create\", (event: CollectionEvent<Product>) => {\n    console.log(`[Collection Event] Document created in '${event.model}' collection at ${new Date(event.timestamp).toLocaleString()}. Doc ID: ${event.document?.$id}`);\n});\n\n// Example: Log when documents are accessed (find, list, filter)\nproductsCollection.subscribe(\"collection:read\", (event: CollectionEvent<Product>) => {\n    console.log(`[Collection Event] Documents accessed in '${event.model}' collection using method: '${event.method}' at ${new Date(event.timestamp).toLocaleString()}`);\n});\n\n// To trigger:\n// await productsCollection.create({ name: 'Test Product', price: 10, inStock: true });\n// await productsCollection.find({ field: 'name', operator: 'eq', value: 'Test Product' });\n```\n\n#### Document Events\n\nEmitted from a `Document` instance. These events provide granular details about changes and access to a specific document.\n\n*   `document:create`: Triggered just after a new document instance is created and persisted, often from `collection.create()`.\n*   `document:write`: Triggered after a document is initially written to the store (e.g., by `collection.create()`).\n*   `document:update`: Triggered when the document's properties have been successfully updated. The event payload includes the updated data.\n*   `document:delete`: Triggered when the document has been successfully deleted from the database.\n*   `document:read`: Triggered when the document's data has been read or accessed (e.g., via `document.read()` or during its initial retrieval/creation by a collection method).\n*   `telemetry`: (If `enableTelemetry` is true) Provides performance and context data for document-level operations (e.g., `read`, `update`, `delete`).\n\n```typescript\nimport type { Document, DocumentEvent, DocumentEventType } from '@asaidimu/indexed';\nimport type { Product } from './your-types-file'; // Assuming Product is defined\n\nconst myProduct: Document<Product> = await productsCollection.create({\n    name: 'Book', price: 25, inStock: true\n});\n\n// Example: Log when the specific document is updated\nconst unsubscribeUpdate = await myProduct.subscribe(\"document:update\", (event: DocumentEvent<Product>) => {\n    console.log(`[Document Event] Product (ID: ${event.data?.$id}) updated at ${new Date(event.timestamp).toLocaleString()}. New data:`, event.data);\n});\n\n// Example: Log when the specific document is deleted\nconst unsubscribeDelete = await myProduct.subscribe(\"document:delete\", (event: DocumentEvent<Product>) => {\n    console.log(`[Document Event] Product (ID: ${event.data?.$id}) deleted at ${new Date(event.timestamp).toLocaleString()}`);\n});\n\n// To trigger:\n// await myProduct.update({ price: 30 }); // Triggers 'document:update'\n// await myProduct.delete(); // Triggers 'document:delete'\n// Remember to call unsubscribeUpdate() and unsubscribeDelete() when done.\n```\n\n---\n\n## 🏗️ Project Architecture\n\n`@asaidimu/indexed` is structured to provide a clear separation of concerns, from low-level IndexedDB interactions to high-level document management and event handling.\n\n### Core Components\n\n*   **`DatabaseConnection` (`src/database.ts`)**:\n    *   The primary entry point for the library, managing the lifecycle of the IndexedDB connection.\n    *   Handles IndexedDB versioning for object store creation/deletion.\n    *   Maintains an internal `$schema` object store to persist schema definitions, enabling robust schema management and migration.\n    *   Provides access to `Collection` instances.\n*   **`Collection<T>` (`src/document.ts` via `createDocumentCursor`)**:\n    *   Represents an abstraction over an IndexedDB object store (a collection of documents).\n    *   Provides high-level methods (`create`, `find`, `filter`, `list`) for interacting with documents within that store.\n    *   Integrates with `@asaidimu/query` for powerful filtering capabilities and `src/paginate.ts` for efficient list operations.\n    *   Manages collection-level events.\n*   **`Document<T>` (`src/document.ts` via `createDocument`)**:\n    *   Represents a single document (record) within a `Collection`.\n    *   Automatically injects internal metadata like `$id` (UUID v4), `$created`, `$updated`, and `$version`.\n    *   Exposes methods (`read`, `update`, `delete`, `state`) for manipulating the specific document.\n    *   Manages document-level events.\n*   **`Store` (`src/store.ts`)**:\n    *   A low-level wrapper providing direct, simplified access to IndexedDB's `IDBObjectStore` operations.\n    *   Handles IndexedDB transactions (`executeTransaction`, `executeDatabaseTransaction`), requests, and cursor management.\n    *   Used internally by `createDocument` and `createDocumentCursor` to perform core database operations.\n*   **Event Bus (`@asaidimu/events`)**:\n    *   A lightweight, integrated event system used across `Database`, `Collection`, and `Document` instances.\n    *   Facilitates internal communication and enables external subscriptions for reactive programming and monitoring.\n*   **Telemetry Proxy (`src/utils.ts`)**:\n    *   A Proxy-based decorator that wraps public API methods (on `Database`, `Collection`, and `Document` instances) if `enableTelemetry` is true.\n    *   Transparently captures method calls, execution time, arguments, results, and errors.\n    *   Emits structured `telemetry` events to the respective event buses for consumption.\n\n### Data Flow\n\n1.  **Connection Initialization**: `DatabaseConnection` opens or re-uses an IndexedDB connection. This also ensures the internal `$schema` object store is created if it doesn't exist, which stores metadata about all user-defined collections.\n2.  **Schema & Collection Management**:\n    *   `db.createCollection(schema)`: First, validates the provided `schema` definition using `@asaidimu/anansi`. If valid, it triggers an IndexedDB version change by reopening the database with an incremented version, allowing a new object store to be created. The `schema` definition is then saved as a document in the internal `$schema` store.\n    *   `db.collection(name)`: Retrieves the schema definition for the requested collection from the `$schema` store. It then returns a `Collection` instance, optionally configured with a schema validator if `validate` is enabled.\n3.  **Collection Operations**:\n    *   `Collection` methods (`find`, `list`, `filter`, `create`) delegate to the low-level `Store` component specific to their object store.\n    *   For `create`, initial data is validated (if validation is enabled) and then passed to `createDocument`, which uses `Store.put` to persist the new document.\n    *   For query operations (`find`, `list`, `filter`), the `Store`'s `cursor` method iterates records, and the `@asaidimu/query`'s `match` function applies the filtering logic.\n    *   All data retrieved via `find`, `list`, `filter` is wrapped into interactive `Document` instances.\n4.  **Document Operations**:\n    *   `Document` methods (`read`, `update`, `delete`) directly call `Store` methods (e.g., `getById`, `put`, `delete`) using the document's internal `$id` as the key.\n    *   `document.update()` performs schema validation on the updated data if enabled.\n5.  **Migrations**:\n    *   `db.migrateCollection()`: Retrieves the current schema for the target collection from `$schema` store. It then uses `@asaidimu/anansi`'s `MigrationEngine` to apply defined `SchemaChange` operations and optional `DataTransform` functions. Data is streamed out of the target object store, transformed, and streamed back in within a single atomic `executeDatabaseTransaction`. Finally, the updated schema definition is persisted back to the `$schema` store.\n6.  **Event Emission & Telemetry**:\n    *   Throughout these operations, `Database`, `Collection`, and `Document` instances emit relevant lifecycle events (e.g., `document:create`, `document:update`, `collection:read`) via their internal event buses.\n    *   If `enableTelemetry` is active, the `Telemetry Proxy` intercepts public API calls, records performance metrics and context, and emits structured `telemetry` events before forwarding the original call.\n\n### Extension Points\n\n*   **Custom Schema Validation Predicates**: Provide a `predicates` map to `DatabaseConnection` to extend the validation capabilities of `@asaidimu/anansi` for your schemas.\n*   **Telemetry**: The pluggable telemetry system allows you to capture and process detailed operation insights for monitoring, debugging, or analytics.\n*   **Event System**: Subscribe to a wide range of database, collection, and document events to implement reactive patterns, integrate with UI updates, or log application activity.\n\n---\n\n## ⚙️ Development & Contributing\n\nWe welcome contributions! Please read through these guidelines to get started.\n\n### Development Setup\n\n1.  **Clone the repository**:\n    ```bash\n    git clone https://github.com/asaidimu/indexed.git\n    cd indexed\n    ```\n2.  **Install dependencies**:\n    ```bash\n    bun install # or npm install or yarn install\n    ```\n3.  **Build the project**:\n    ```bash\n    bun run build # Compiles TypeScript source to dist/ for CJS and ESM formats.\n    ```\n    The `postbuild` script also copies `README.md`, `LICENSE.md`, and `dist.package.json` into the `dist/` folder, preparing the package for npm publication.\n\n### Scripts\n\nThe `package.json` defines several useful scripts for development, building, and testing:\n\n*   `bun ci`: Installs project dependencies.\n*   `bun clean`: Removes the `dist/` directory, cleaning up build artifacts.\n*   `bun prebuild`: Executes `bun clean` and `bun run .sync-package.ts` (a utility to sync version and other details from `package.json` to `dist.package.json`).\n*   `bun build`: Compiles TypeScript source files (`index.ts`) into `dist/` for CommonJS (`cjs`) and ES Module (`esm`) formats, along with generating TypeScript declaration files (`.d.ts`).\n*   `bun postbuild`: Copies essential files (`README.md`, `LICENSE.md`, `dist.package.json`) into the `dist/` directory, which are included in the published npm package.\n*   `bun test`: Runs unit and integration tests using [Vitest](https://vitest.dev/) in watch mode.\n*   `bun test:run`: Executes all tests once and exits. Suitable for CI/CD pipelines.\n*   `bun test:debug`: Runs tests in debug mode, useful for stepping through code.\n*   `bun test:ci`: An alias for `bun test:run`, designed for continuous integration environments.\n\n### Testing\n\nTests are written with [Vitest](https://vitest.dev/) and provide comprehensive coverage of the library's functionality.\nTo run the tests:\n\n```bash\nbun test\n```\nThis will start Vitest in watch mode, automatically re-running tests on file changes. To run tests once (e.g., for CI or a quick check):\n```bash\nbun test:run\n```\nThe tests are executed in a Node.js environment, simulating a browser using `fake-indexeddb` and `jsdom`. This ensures consistent and fast test execution without requiring a real browser.\n\n### Contributing Guidelines\n\nPlease review our [CONTRIBUTING.md](https://github.com/asaidimu/indexed/blob/main/.github/CONTRIBUTING.md) (placeholder link) for detailed information on:\n\n*   Reporting bugs effectively.\n*   Suggesting and discussing new features.\n*   The process for making pull requests.\n*   Our coding standards and commit message conventions (which follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)).\n\n### Issue Reporting\n\nIf you encounter any bugs, have feature requests, or questions, please open an issue on our [GitHub Issues page](https://github.com/asaidimu/indexed/issues). Provide as much detail as possible to help us understand and address your concerns.\n\n---\n\n## 📚 Additional Information\n\n### Troubleshooting\n\n*   **Database not opening/upgrading**:\n    *   Ensure your browser supports IndexedDB.\n    *   When working directly with IndexedDB's `indexedDB.open()`, ensure you are providing a version number greater than the current version if you intend to create or modify object stores. `@asaidimu/indexed` handles this internally for `createCollection` and `deleteCollection`.\n*   **\"SCHEMA_NOT_FOUND\" error**:\n    *   Verify that the collection name you are trying to access with `db.collection()` or `db.deleteCollection()` was previously created using `db.createCollection()`.\n    *   Check for typos in the collection name.\n*   **\"INVALID_DATA\" error**:\n    *   This error occurs when data being inserted or updated does not conform to the `SchemaDefinition` you provided for the collection, and `validate: true` is set in `DatabaseConnection` config. Review your data and schema constraints.\n*   **Data not persisting or updating**:\n    *   Remember that all database operations are asynchronous and return Promises. Always use `await` or `.then()` to ensure operations complete and their results are handled.\n    *   For new documents, `collection.create()` automatically persists them. For existing `Document` instances, you *must* call `document.update(props)` to persist changes to the database.\n*   **Asynchronous operations**:\n    *   It's crucial to handle the asynchronous nature of all API calls. Incorrect handling (e.g., missing `await`) can lead to unexpected behavior or errors.\n*   **Closing connections**:\n    *   While IndexedDB connections are generally managed by the browser, explicitly calling `db.close()` when your application no longer needs the database connection is a good practice to free up resources and prevent resource leaks, especially in long-running applications or during testing.\n\n### FAQ\n\n**Q: Is this library a full-fledged database replacement?**\nA: `IndexedDB Document Store` provides a robust client-side persistence layer for structured data, making it suitable for many web application needs (e.g., offline capabilities, caching, local data synchronization). It is *not* a replacement for server-side databases (like MongoDB, PostgreSQL) but aims to bring a similar document-oriented development experience to the browser's local storage.\n\n**Q: How does `@asaidimu/indexed` handle schema migrations?**\nA: The `SchemaDefinition` (from `@asaidimu/anansi`) includes a `migrations` array where you can define a series of `SchemaChange` objects. The `db.migrateCollection` method uses `@asaidimu/anansi`'s `MigrationEngine` to apply these changes and transform existing data. This process happens atomically within a single IndexedDB transaction, ensuring data integrity during schema evolution.\n\n**Q: Can I use this in a Node.js environment?**\nA: IndexedDB is fundamentally a browser API. While this library is written in TypeScript and can be built for Node.js, using it directly in a Node.js server environment requires a polyfill like `fake-indexeddb` (which is used for testing) to simulate the browser's IndexedDB API. For server-side Node.js applications, a dedicated server-side database solution is generally more appropriate and performant.\n\n**Q: How do I handle large datasets with this library?**\nA: IndexedDB itself is designed for significant client-side data storage, capable of holding gigabytes of data. `@asaidimu/indexed` enhances this with efficient `cursor`-based iteration and advanced `pagination` options (`list` method), making it suitable for managing large datasets by processing them in manageable batches rather than loading everything into memory at once.\n\n**Q: How are `$id` values generated?**\nA: As of version 2.0.0, `$id` values for new documents are generated using UUID v4. This provides strong uniqueness guarantees without depending on content hashing, simplifying document creation.\n\n### Changelog / Roadmap\n\n*   For a detailed history of changes, features, and bug fixes, please refer to the [CHANGELOG.md](CHANGELOG.md) file.\n*   A formal roadmap is currently TBD, but common future considerations include more advanced query features, deeper integration with `@asaidimu/anansi` for complex schema validation and migrations, and potential performance optimizations through advanced IndexedDB features.\n\n### License\n\nThis project is licensed under the MIT License. See the [LICENSE.md](LICENSE.md) file for full details.\n\n### Acknowledgments\n\n*   Built on the power of [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API).\n*   Utilizes [@asaidimu/anansi](https://github.com/asaidimu/anansi) for robust schema definition, validation, and migration.\n*   Leverages [@asaidimu/query](https://github.com/asaidimu/query) for its powerful and declarative querying DSL.\n*   Employs [@asaidimu/events](https://github.com/asaidimu/events) for its internal event system.\n*   Tested thoroughly with [Vitest](https://vitest.dev/).\n*   Built efficiently using [tsup](https://tsup.sh/) for bundling.\n","readmeFilename":"README.md"}