commit 30b2545f4c7a97a60ffb512c08a4d3bb9ffd5e75
Author: 4831c0 <4831c0@proton.me>
Date: Wed Mar 19 19:40:26 2025 +0100
3.1.0+1
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..c88e534
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,431 @@
+## 3.1.0+1
+
+### Fixes
+
+- Fixed error building MacOS library
+
+## 3.1.0
+
+### Breaking
+
+Sorry for this breaking change. Unfortunately, it was necessary to fix stability issues on Android.
+
+- `directory` is now required for `Isar.open()` and `Isar.openSync()`
+
+### Fixes
+
+- Fixed a crash that occasionally occurred when opening Isar
+- Fixed a schema migration issue
+- Fixed an issue where embedded class renaming didn't work correctly
+
+### Enhancements
+
+- Many internal improvements
+- Performance improvements
+
+## 3.0.6
+
+### Fixes
+
+- Add check to verify transactions are used for correct instance
+- Add check to verify that async transactions are still active
+- Fix upstream issue with opening databases
+
+## 3.0.5
+
+### Enhancements
+
+- Improved performance for all operations
+- Added `maxSizeMiB` option to `Isar.open()` to specify the maximum size of the database file
+- Significantly reduced native library size
+- With the help of the community, the docs have been translated into a range of languages
+- Improved API docs
+- Added integration tests for more platforms to ensure high-quality releases
+- Support for unicode paths on Windows
+
+### Fixes
+
+- Fixed crash while opening Isar
+- Fixed crash on older Android devices
+- Fixed a native port that was not closed correctly in some cases
+- Added swift version to podspec
+- Fixed crash on Windows
+- Fixed "IndexNotFound" error
+
+## 3.0.4
+
+REDACTED.
+
+## 3.0.3
+
+REDACTED.
+
+## 3.0.2
+
+### Enhancements
+
+- The Inspector now supports creating objects and importing JSON
+- Added Inspector check to make sure Chrome is used
+
+### Fixes
+
+- Added support for the latest analyzer
+- Fixed native ports that were not closed correctly in some cases
+- Added support for Ubuntu 18.04 and older
+- Fixed issue with aborting transactions
+- Fixed crash when invalid JSON was provided to `importJsonRaw()`
+- Added missing `exportJsonSync()` and `exportJsonRawSync()`
+- Fixed issue where secondary instance could not be selected in the Inspector
+
+## 3.0.1
+
+### Enhancements
+
+- Support for arm64 iOS Simulators
+
+### Fixes
+
+- Fixed issue where `.anyOf()`, `.allOf()`, and `.oneOf()` could not be negated
+- Fixed too low min-iOS version. The minimum supported is 11.0
+- Fixed error during macOS App Store build
+
+## 3.0.0
+
+This release has been a lot of work! Thanks to everyone who contributed and joined the countless discussions. You are really awesome!
+
+Special thanks to [@Jtplouffe](https://github.com/Jtplouffe) and [@Peyman](https://github.com/Viper-Bit) for their incredible work.
+
+### Web support
+
+This version does not support the web target yet. It will be back in the next version. Please continue using 2.5.0 if you need web support.
+
+### Enhancements
+
+- Completely new Isar inspector that does not need to be installed anymore
+- Extreme performance improvements for almost all operations (up to 50%)
+- Support for embedded objects using `@embedded`
+- Support for enums using `@enumerated`
+- Vastly improved Isar binary format space efficiency resulting in about 20% smaller databases
+- Added `id`, `byte`, `short` and `float` typedefs
+- `IsarLinks` now support all `Set` methods based on the Isar `Id` of objects
+- Added `download` option to `Isar.initializeIsarCore()` to download binaries automatically
+- Added `replace` option for indexes
+- Added verification for correct Isar binary version
+- Added `collection.getSize()` and `collection.getSizeSync()`
+- Added `query.anyOf()` and `query.allOf()` query modifiers
+- Support for much more complex composite index queries
+- Support for logical XOR and the `.oneOf()` query modifier
+- Made providing a path optional
+- The default Isar name is now `default` and stored in `dir/name.isar` and `dir/name.isar.lock`
+- On non-web platforms, `IsarLink` and `IsarLinks` will load automatically
+- `.putSync()`, `.putAllSync()` etc. will now save links recursively by default
+- Added `isar.getSize()` and `isar.getSizeSync()`
+- Added `linksLengthEqualTo()`, `linksIsEmpty()`, `linksIsNotEmpty()`, `linksLengthGreaterThan()`, `linksLengthLessThan()`, `linksLengthBetween()` and `linkIsNull()` filters
+- Added `listLengthEqualTo()`, `listIsEmpty()`, `listIsNotEmpty()`, `listLengthGreaterThan()`, `listLengthLessThan()`, `listLengthBetween()` filters
+- Added `isNotNull()` filters
+- Added `compactOnLaunch` conditions to `Isar.open()` for automatic database compaction
+- Added `isar.copyToFile()` which copies a compacted version of the database to a path
+- Added check to verify that linked collections schemas are provided for opening an instance
+- Apply default values from constructor during deserialization
+- Added `isar.verify()` and `col.verify()` methods for checking database integrity in unit tests
+- Added missing float and double queries and an `epsilon` parameter
+
+### Breaking changes
+
+- Removed `TypeConverter` support in favor of `@embedded` and `@enumerated`
+- Removed `@Id()` and `@Size32()` annotations in favor of the `Id` and `short` types
+- Changed the `schemas` parameter from named to positional
+- The maximum size of objects is now 16MB
+- Removed `replaceOnConflict` and `saveLinks` parameter from `collection.put()` and `collection.putAll()`
+- Removed `isar` parameter from `Isar.txn()`, `Isar.writeTxn()`, `Isar.txnSync()` and `Isar.writeTxnSync()`
+- Removed `query.repeat()`
+- Removed `query.sortById()` and `query.distinctById()`
+- Fixed `.or()` instead of `.and()` being used implicitly when combining filters
+- Renamed multi-entry where clauses from `.yourListAnyEqualTo()` to `.yourListElementEqualTo()` to avoid confusion
+- Isar will no longer create the provided directory. Make sure it exists before opening an Isar Instance.
+- Changed the default index type for all `List`s to `IndexType.hash`
+- Renamed `isar.getCollection()` to `isar.collection()`
+- It is no longer allowed to extend or implement another collection
+- Unsupported properties will no longer be ignored by default
+- Renamed the `initialReturn` parameter to `fireImmediately`
+- Renamed `Isar.initializeLibraries()` to `Isar.initializeIsarCore()`
+
+### Fixes
+
+There are too many fixes to list them all.
+
+- A lot of link fixes and a slight behavior change to make them super reliable
+- Fixed missing symbols on older Android phones
+- Fixed composite queries
+- Fixed various generator issues
+- Fixed error retrieving the id property in a query
+- Fixed missing symbols on 32-bit Android 5 & 6 devices
+- Fixed inconsistent `null` handling in json export
+- Fixed default directory issue on Android
+- Fixed different where clauses returning duplicate results
+- Fixed hash index issue where multiple list values resulted in the same hash
+- Fixed edge case where creating a new index failed
+
+## 2.5.0
+
+### Enhancements
+
+- Support for Android x86 (32 bit emulator) and macOS arm64 (Apple Silicon)
+- Greatly improved test coverage for sync methods
+- `col.clear()` now resets the auto increment counter to `0`
+- Significantly reduced Isar Core binary size (about 1.4MB -> 800KB)
+
+### Minor Breaking
+
+- Changed `initializeLibraries(Map libraries)` to `initializeLibraries(Map libraries)`
+- Changed min Dart SDK to `2.16.0`
+
+### Fixes
+
+- Fixed issue with `IsarLink.saveSync()`
+- Fixed `id` queries
+- Fixed error thrown by `BroadcastChannel` in Firefox
+- Fixed Isar Inspector connection issue
+
+## 2.4.0
+
+### Enhancements
+
+- Support for querying links
+- Support for filtering and sorting links
+- Added methods to update and count links without loading them
+- Added `isLoaded` property to links
+- Added methods to count the number of objects in a collection
+- Big internal improvements
+
+### Minor Breaking
+
+- There are now different kinds of where clauses for dynamic queries
+- `isar.getCollection()` no longer requires the name of the collection
+- `Isar.instanceNames` now returns a `Set` instead of a `List`
+
+### Fixes
+
+- Fixed iOS crash that frequently happened on older devices
+- Fixed 32bit issue on Android
+- Fixed link issues
+- Fixed missing `BroadcastChannel` API for older Safari versions
+
+## 2.2.1
+
+### Enhancements
+
+- Reduced Isar web code size by 50%
+- Made `directory` parameter of `Isar.open()` optional for web
+- Made `name` parameter of `Isar.getInstance()` optional
+- Added `Isar.defaultName` constant
+- Enabled `TypeConverter`s with supertypes
+- Added message if `TypeConverter` nullability doesn't match
+- Added more tests
+
+### Fixes
+
+- Fixed issue with date queries
+- Fixed `FilterGroup.not` constructor (thanks for the PR @jtzell)
+
+## 2.2.0
+
+Isar now has full web support ๐. No changes to your code required, just run it.
+
+_Web passes all unit tests but is still considered beta for now._
+
+### Minor Breaking
+
+- Added `saveLinks` parameter to `.put()` and `.putAll()` which defaults to `false`
+- Changed default `overrideChanges` parameter of `links.load()` to `true` to avoid unintended behavior
+
+### Enhancements
+
+- Full web support!
+- Improved write performance
+- Added `deleteFromDisk` option to `isar.close()`
+- Added `.reset()` and `.resetSync()` methods to `IsarLink` and `IsarLinks`
+- Improved `links.save()` performance
+- Added many tests
+
+### Fixed
+
+- Fixed value of `null` dates to be `DateTime.fromMillisecondsSinceEpoch(0)`
+- Fixed problem with migration
+- Fixed incorrect list values for new properties (`[]` instead of `null`)
+- Improved handling of link edge-cases
+
+## 2.1.4
+
+- Removed `path` dependency
+- Fixed incorrect return value of `deleteByIndex()`
+- Fixed wrong auto increment ids in some cases (thanks @robban112)
+- Fixed an issue with `Isar.close()` (thanks @msxenon)
+- Fixed `$` escaping in generated code (thanks @jtzell)
+- Fixed broken link in pub.dev example page
+
+## 2.1.0
+
+`isar_connect` is now integrated into `isar`
+
+### Enhancements
+
+- Added check for outdated generated files
+- Added check for changed schema across isolates
+- Added `Isar.openSync()`
+- Added `col.importJsonRawSync()`, `col.importJsonSync()`, `query.exportJsonRawSync()`, `query.exportJsonSync()`
+- Improved performance for queries
+- Improved handling of ffi memory
+- More tests
+
+### Fixed
+
+- Fixed issue where imported json required existing ids
+- Fixed issue with transaction handling (thanks @Peng-Qian for the awesome help)
+- Fixed issue with `@Ignore` annotation not always working
+- Fixed issue with `getByIndex()` not returning correct object id (thanks @jtzell)
+
+## 2.0.0
+
+### Breaking
+
+- The id for non-final objects is now assigned automatically after `.put()` and `.putSync()`
+- `double` and `List` indexes can no longer be at the beginning of a composite index
+- `List` indexes can no longer be hashed
+- `.greaterThan()`, `.lessThan()` and `.between()` filters and are now excluding for `double` values (`>=` -> `>`)
+- Changed the default index type for lists to `IndexType.value`
+- `IsarLink` and `IsarLinks` will no longer be initialized by Isar and must not be `nullable` or `late`.
+- Dart `2.14` or higher is required
+
+### Enhancements
+
+- Added API docs for all public methods
+- Added `isar.clear()`, `isar.clearSync()`, `col.clear()` and `col.clearSync()`
+- Added `col.filter()` as shortcut for `col.where().filter()`
+- Added `include` parameter to `.greaterThan()` and `.lessThan()` filters and where clauses
+- Added `includeLower` and `includeUpper` parameters to `.between()` filters and where clauses
+- Added `Isar.autoIncrement` to allow non-nullable auto-incrementing ids
+- `Isar.close()` now returns whether the last instance was closed
+- List values in composite indexes are now of type `IndexType.hash` automatically
+- Allowed multiple indexes on the same property
+- Removed exported packages from API docs
+- Improved generated code
+- Improved Isar Core error messages
+- Minor performance improvements
+- Automatic XCode configuration
+- Updated analyzer to `3.0.0`
+- More tests
+
+### Fixed
+
+- `IsarLink` and `IsarLinks` can now be final
+- Fixed multi-entry index queries returning items multiple times in some cases
+- Fixed `.anyLessThan()` and `.anyGreaterThan()` issues
+- Fixed issues with backlinks
+- Fixed issue where query only returned the first `99999` results
+- Fixed issue with id where clauses
+- Fixed default index type for lists and bytes
+- Fixed issue where renaming indexes was not possible
+- Fixed issue where wrong index name was used for `.getByX()` and `.deleteByX()`
+- Fixed issue where composite indexes did not allow non-hashed Strings as last value
+- Fixed issue where `@Ignore()` fields were not ignored
+
+## 1.0.5
+
+### Enhancements
+
+- Updated dependencies
+
+### Fixes:
+
+- Included desktop binaries
+- Fixed "Cannot allocate memory" error on older iOS devices
+- Fixed stripped binaries for iOS release builds
+- Fixed IsarInspector issues (thanks to [RubenBez](https://github.com/RubenBez) and [rizzi37](https://github.com/rizzi37))
+
+## 1.0.0+1
+
+Added missing binaries
+
+## 1.0.0
+
+Switched from liblmdb to libmdbx for better performance, more stability and many internal improvements.
+
+### Breaking
+
+The internal database format has been changed to improve performance. Old databases do not work anymore!
+
+### Fixes
+
+- Fix issue with links being removed after object update
+- Fix String index problems
+
+### Enhancements
+
+- Support `greaterThan`, `lessThan` and `between` queries for String values
+- Support for inheritance (enabled by default)
+- Support for `final` properties and getters
+- Support for `freezed` and other code generators
+- Support getting / deleting objects by a key `col.deleteByName('Anne')`
+- Support for list indexes (hash an element based)
+- Generator now creates individual files instead of one big file
+- Allow specifying the collection accessor name
+- Unsupported properties are now ignored automatically
+- Returns the assigned ids after `.put()` operations (objects are no longer mutated)
+- Introduces `replaceOnConflict` option for `.put()` (instead of specifying it for index)
+- many more...
+
+### Internal
+
+- Improve generated code
+- Many new unit tests
+
+## 0.4.0
+
+### Breaking
+
+- Remove `.where...In()` and `...In()` extension methods
+- Split `.watch(lazy: bool)` into `.watch()` and `.watchLazy()`
+- Remove `include` option for filters
+
+### Fixes
+
+- Generate id for JSON imports that don't have an id
+- Enable `sortBy` and `thenBy` generation
+
+### Enhancements
+
+- Add `.optional()` and `.repeat()` query modifiers
+- Support property queries
+- Support query aggregation
+- Support dynamic queries (for custom query languages)
+- Support multi package configuration with `@ExternalCollection()`
+- Add `caseSensitive` option to `.distinctBy()`
+
+### Internal
+
+- Change iOS linking
+- Improve generated code
+- Set up integration tests and improve unit tests
+- Use CORE/0.4.0
+
+## 0.2.0
+
+- Link support
+- Many improvements and fixes
+
+## 0.1.0
+
+- Support for links and backlinks
+
+## 0.0.4
+
+- Bugfixes and many improvements
+
+## 0.0.2
+
+Fix dependency issue
+
+## 0.0.1
+
+Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f0d4b83
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2022 Simon Leier
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2466b44
--- /dev/null
+++ b/README.md
@@ -0,0 +1,267 @@
+
+
+> #### Isar [ee-zahr]:
+>
+> 1. River in Bavaria, Germany.
+> 2. [Crazy fast](#benchmarks) NoSQL database that is a joy to use.
+
+## Features
+
+- ๐ **Made for Flutter**. Easy to use, no config, no boilerplate
+- ๐ **Highly scalable** The sky is the limit (pun intended)
+- ๐ญ **Feature rich**. Composite & multi-entry indexes, query modifiers, JSON support etc.
+- โฑ **Asynchronous**. Parallel query operations & multi-isolate support by default
+- ๐ฆ **Open source**. Everything is open source and free forever!
+
+Isar database can do much more (and we are just getting started)
+
+- ๐ต๏ธ **Full-text search**. Make searching fast and fun
+- ๐ฑ **Multiplatform**. iOS, Android, Desktop
+- ๐งช **ACID semantics**. Rely on database consistency
+- ๐ **Static typing**. Compile-time checked and autocompleted queries
+- โจ **Beautiful documentation**. Readable, easy to understand and ever-improving
+
+Join the [Telegram group](https://t.me/isardb) for discussion and sneak peeks of new versions of the DB.
+
+If you want to say thank you, star us on GitHub and like us on pub.dev ๐๐
+
+## Quickstart
+
+Holy smokes you're here! Let's get started on using the coolest Flutter database out there...
+
+### 1. Add to pubspec.yaml
+
+```yaml
+isar_version: &isar_version 3.1.0 # define the version to be used
+
+dependencies:
+ isar: *isar_version
+ isar_flutter_libs: *isar_version # contains Isar Core
+
+dev_dependencies:
+ isar_generator: *isar_version
+ build_runner: any
+```
+
+### 2. Annotate a Collection
+
+```dart
+part 'email.g.dart';
+
+@collection
+class Email {
+ Id id = Isar.autoIncrement; // you can also use id = null to auto increment
+
+ @Index(type: IndexType.value)
+ String? title;
+
+ List? recipients;
+
+ @enumerated
+ Status status = Status.pending;
+}
+
+@embedded
+class Recipient {
+ String? name;
+
+ String? address;
+}
+
+enum Status {
+ draft,
+ pending,
+ sent,
+}
+```
+
+### 3. Open a database instance
+
+```dart
+final dir = await getApplicationDocumentsDirectory();
+final isar = await Isar.open(
+ [EmailSchema],
+ directory: dir.path,
+);
+```
+
+### 4. Query the database
+
+```dart
+final emails = await isar.emails.filter()
+ .titleContains('awesome', caseSensitive: false)
+ .sortByStatusDesc()
+ .limit(10)
+ .findAll();
+```
+
+## Isar Database Inspector
+
+The Isar Inspector allows you to inspect the Isar instances & collections of your app in real-time. You can execute queries, edit properties, switch between instances and sort the data.
+
+
+
+To launch the inspector, just run your Isar app in debug mode and open the Inspector link in the logs.
+
+## CRUD operations
+
+All basic crud operations are available via the `IsarCollection`.
+
+```dart
+final newEmail = Email()..title = 'Amazing new database';
+
+await isar.writeTxn(() {
+ await isar.emails.put(newEmail); // insert & update
+});
+
+final existingEmail = await isar.emails.get(newEmail.id!); // get
+
+await isar.writeTxn(() {
+ await isar.emails.delete(existingEmail.id!); // delete
+});
+```
+
+## Database Queries
+
+Isar database has a powerful query language that allows you to make use of your indexes, filter distinct objects, use complex `and()`, `or()` and `.xor()` groups, query links and sort the results.
+
+```dart
+final importantEmails = isar.emails
+ .where()
+ .titleStartsWith('Important') // use index
+ .limit(10)
+ .findAll()
+
+final specificEmails = isar.emails
+ .filter()
+ .recipient((q) => q.nameEqualTo('David')) // query embedded objects
+ .or()
+ .titleMatches('*university*', caseSensitive: false) // title containing 'university' (case insensitive)
+ .findAll()
+```
+
+## Database Watchers
+
+With Isar database, you can watch collections, objects, or queries. A watcher is notified after a transaction commits successfully and the target actually changes.
+Watchers can be lazy and not reload the data or they can be non-lazy and fetch new results in the background.
+
+```dart
+Stream collectionStream = isar.emails.watchLazy();
+
+Stream> queryStream = importantEmails.watch();
+
+queryStream.listen((newResult) {
+ // do UI updates
+})
+```
+
+## Benchmarks
+
+Benchmarks only give a rough idea of the performance of a database but as you can see, Isar NoSQL database is quite fast ๐
+
+| | |
+| ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
+| | |
+
+If you are interested in more benchmarks or want to check how Isar performs on your device you can run the [benchmarks](https://github.com/isar/isar_benchmark) yourself.
+
+## Unit tests
+
+If you want to use Isar database in unit tests or Dart code, call `await Isar.initializeIsarCore(download: true)` before using Isar in your tests.
+
+Isar NoSQL database will automatically download the correct binary for your platform. You can also pass a `libraries` map to adjust the download location for each platform.
+
+Make sure to use `flutter test -j 1` to avoid tests running in parallel. This would break the automatic download.
+
+## Contributors โจ
+
+Big thanks go to these wonderful people:
+
+
+
+
+
+
+
+
+
+
+
+### License
+
+```
+Copyright 2022 Simon Leier
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+```
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..ee7facb
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,11 @@
+include: package:very_good_analysis/analysis_options.yaml
+
+analyzer:
+ exclude:
+ - "lib/src/native/bindings.dart"
+
+ errors:
+ cascade_invocations: ignore
+ avoid_positional_boolean_parameters: ignore
+ parameter_assignments: ignore
+ prefer_asserts_with_message: ignore
\ No newline at end of file
diff --git a/example/README.md b/example/README.md
new file mode 100644
index 0000000..e43ff5c
--- /dev/null
+++ b/example/README.md
@@ -0,0 +1,3 @@
+## The fastest way to get started is by following the [Quickstart Guide](https://isar.dev/tutorials/quickstart.html)!
+
+Have fun using Isar!
\ No newline at end of file
diff --git a/lib/isar.dart b/lib/isar.dart
new file mode 100644
index 0000000..429086e
--- /dev/null
+++ b/lib/isar.dart
@@ -0,0 +1,49 @@
+library isar;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:developer';
+import 'dart:typed_data';
+
+import 'package:isar/src/isar_connect_api.dart';
+import 'package:isar/src/native/isar_core.dart'
+ if (dart.library.html) 'package:isar/src/web/isar_web.dart';
+import 'package:isar/src/native/isar_link_impl.dart'
+ if (dart.library.html) 'package:isar/src/web/isar_link_impl.dart';
+import 'package:isar/src/native/open.dart'
+ if (dart.library.html) 'package:isar/src/web/open.dart';
+import 'package:isar/src/native/split_words.dart'
+ if (dart.library.html) 'package:isar/src/web/split_words.dart';
+import 'package:meta/meta.dart';
+import 'package:meta/meta_meta.dart';
+
+part 'src/annotations/backlink.dart';
+part 'src/annotations/collection.dart';
+part 'src/annotations/embedded.dart';
+part 'src/annotations/enumerated.dart';
+part 'src/annotations/ignore.dart';
+part 'src/annotations/index.dart';
+part 'src/annotations/name.dart';
+part 'src/annotations/type.dart';
+part 'src/isar.dart';
+part 'src/isar_collection.dart';
+part 'src/isar_connect.dart';
+part 'src/isar_error.dart';
+part 'src/isar_link.dart';
+part 'src/isar_reader.dart';
+part 'src/isar_writer.dart';
+part 'src/query.dart';
+part 'src/query_builder.dart';
+part 'src/query_builder_extensions.dart';
+part 'src/query_components.dart';
+part 'src/schema/collection_schema.dart';
+part 'src/schema/index_schema.dart';
+part 'src/schema/link_schema.dart';
+part 'src/schema/property_schema.dart';
+part 'src/schema/schema.dart';
+
+/// @nodoc
+@protected
+typedef IsarUint8List = Uint8List;
+
+const bool _kIsWeb = identical(0, 0.0);
diff --git a/lib/src/annotations/backlink.dart b/lib/src/annotations/backlink.dart
new file mode 100644
index 0000000..54b0ab2
--- /dev/null
+++ b/lib/src/annotations/backlink.dart
@@ -0,0 +1,11 @@
+part of isar;
+
+/// Annotation to create a backlink to an existing link.
+@Target({TargetKind.field})
+class Backlink {
+ /// Annotation to create a backlink to an existing link.
+ const Backlink({required this.to});
+
+ /// The Dart name of the target link.
+ final String to;
+}
diff --git a/lib/src/annotations/collection.dart b/lib/src/annotations/collection.dart
new file mode 100644
index 0000000..979efb0
--- /dev/null
+++ b/lib/src/annotations/collection.dart
@@ -0,0 +1,34 @@
+part of isar;
+
+/// Annotation to create an Isar collection.
+const collection = Collection();
+
+/// Annotation to create an Isar collection.
+@Target({TargetKind.classType})
+class Collection {
+ /// Annotation to create an Isar collection.
+ const Collection({
+ this.inheritance = true,
+ this.accessor,
+ this.ignore = const {},
+ });
+
+ /// Should properties and accessors of parent classes and mixins be included?
+ final bool inheritance;
+
+ /// Allows you to override the default collection accessor.
+ ///
+ /// Example:
+ /// ```dart
+ /// @Collection(accessor: 'col')
+ /// class MyCol {
+ /// Id? id;
+ /// }
+ ///
+ /// // access collection using: isar.col
+ /// ```
+ final String? accessor;
+
+ /// A list of properties or getter names that Isar should ignore.
+ final Set ignore;
+}
diff --git a/lib/src/annotations/embedded.dart b/lib/src/annotations/embedded.dart
new file mode 100644
index 0000000..d8f5ce9
--- /dev/null
+++ b/lib/src/annotations/embedded.dart
@@ -0,0 +1,17 @@
+part of isar;
+
+/// Annotation to nest objects of this type in collections.
+const embedded = Embedded();
+
+/// Annotation to nest objects of this type in collections.
+@Target({TargetKind.classType})
+class Embedded {
+ /// Annotation to nest objects of this type in collections.
+ const Embedded({this.inheritance = true, this.ignore = const {}});
+
+ /// Should properties and accessors of parent classes and mixins be included?
+ final bool inheritance;
+
+ /// A list of properties or getter names that Isar should ignore.
+ final Set ignore;
+}
diff --git a/lib/src/annotations/enumerated.dart b/lib/src/annotations/enumerated.dart
new file mode 100644
index 0000000..ade7e5f
--- /dev/null
+++ b/lib/src/annotations/enumerated.dart
@@ -0,0 +1,33 @@
+part of isar;
+
+/// Annotation to specify how an enum property should be serialized.
+const enumerated = Enumerated(EnumType.ordinal);
+
+/// Annotation to specify how an enum property should be serialized.
+@Target({TargetKind.field, TargetKind.getter})
+class Enumerated {
+ /// Annotation to specify how an enum property should be serialized.
+ const Enumerated(this.type, [this.property]);
+
+ /// How the enum property should be serialized.
+ final EnumType type;
+
+ /// The property to use for the enum values.
+ final String? property;
+}
+
+/// Enum type for enum values.
+enum EnumType {
+ /// Stores the index of the enum as a byte value.
+ ordinal,
+
+ /// Stores the index of the enum as a 4-byte value. Use this type if your enum
+ /// has more than 256 values or needs to be nullable.
+ ordinal32,
+
+ /// Uses the name of the enum value.
+ name,
+
+ /// Uses a custom enum value.
+ value
+}
diff --git a/lib/src/annotations/ignore.dart b/lib/src/annotations/ignore.dart
new file mode 100644
index 0000000..181224e
--- /dev/null
+++ b/lib/src/annotations/ignore.dart
@@ -0,0 +1,11 @@
+part of isar;
+
+/// Annotate a property or accessor in an Isar collection to ignore it.
+const ignore = Ignore();
+
+/// Annotate a property or accessor in an Isar collection to ignore it.
+@Target({TargetKind.field, TargetKind.getter})
+class Ignore {
+ /// Annotate a property or accessor in an Isar collection to ignore it.
+ const Ignore();
+}
diff --git a/lib/src/annotations/index.dart b/lib/src/annotations/index.dart
new file mode 100644
index 0000000..62546a9
--- /dev/null
+++ b/lib/src/annotations/index.dart
@@ -0,0 +1,76 @@
+part of isar;
+
+/// Specifies how an index is stored in Isar.
+enum IndexType {
+ /// Stores the value as-is in the index.
+ value,
+
+ /// Strings or Lists can be hashed to reduce the storage required by the
+ /// index. The disadvantage of hash indexes is that they can't be used for
+ /// prefix scans (`startsWith()` where clauses). String and list indexes are
+ /// hashed by default.
+ hash,
+
+ /// `List` can hash its elements.
+ hashElements,
+}
+
+/// Annotate properties to build an index.
+@Target({TargetKind.field, TargetKind.getter})
+class Index {
+ /// Annotate properties to build an index.
+ const Index({
+ this.name,
+ this.composite = const [],
+ this.unique = false,
+ this.replace = false,
+ this.type,
+ this.caseSensitive,
+ });
+
+ /// Name of the index. By default, the names of the properties are
+ /// concatenated using "_"
+ final String? name;
+
+ /// Specify up to two other properties to build a composite index.
+ final List composite;
+
+ /// A unique index ensures the index does not contain any duplicate values.
+ /// Any attempt to insert or update data into the unique index that causes a
+ /// duplicate will result in an error.
+ final bool unique;
+
+ /// If set to `true`, inserting a duplicate unique value will replace the
+ /// existing object instead of throwing an error.
+ final bool replace;
+
+ /// Specifies how an index is stored in Isar.
+ ///
+ /// Defaults to:
+ /// - `IndexType.hash` for `String`s and `List`s
+ /// - `IndexType.value` for all other types
+ final IndexType? type;
+
+ /// String or `List` indexes can be case sensitive (default) or case
+ /// insensitive.
+ final bool? caseSensitive;
+}
+
+/// Another property that is part of the composite index.
+class CompositeIndex {
+ /// Another property that is part of the composite index.
+ const CompositeIndex(
+ this.property, {
+ this.type,
+ this.caseSensitive,
+ });
+
+ /// Dart name of the property.
+ final String property;
+
+ /// See [Index.type].
+ final IndexType? type;
+
+ /// See [Index.caseSensitive].
+ final bool? caseSensitive;
+}
diff --git a/lib/src/annotations/name.dart b/lib/src/annotations/name.dart
new file mode 100644
index 0000000..a950ac4
--- /dev/null
+++ b/lib/src/annotations/name.dart
@@ -0,0 +1,13 @@
+part of isar;
+
+/// Annotate Isar collections or properties to change their name.
+///
+/// Can be used to change the name in Dart independently of Isar.
+@Target({TargetKind.classType, TargetKind.field, TargetKind.getter})
+class Name {
+ /// Annotate Isar collections or properties to change their name.
+ const Name(this.name);
+
+ /// The name this entity should have in the database.
+ final String name;
+}
diff --git a/lib/src/annotations/type.dart b/lib/src/annotations/type.dart
new file mode 100644
index 0000000..8732265
--- /dev/null
+++ b/lib/src/annotations/type.dart
@@ -0,0 +1,20 @@
+// ignore_for_file: camel_case_types
+
+part of isar;
+
+/// Type to specify the id property of a collection.
+typedef Id = int;
+
+/// Type to mark an [int] property or List as 8-bit sized.
+///
+/// You may only store values between 0 and 255 in such a property.
+typedef byte = int;
+
+/// Type to mark an [int] property or List as 32-bit sized.
+///
+/// You may only store values between -2147483648 and 2147483647 in such a
+/// property.
+typedef short = int;
+
+/// Type to mark a [double] property or List to have 32-bit precision.
+typedef float = double;
diff --git a/lib/src/common/isar_common.dart b/lib/src/common/isar_common.dart
new file mode 100644
index 0000000..ea0873e
--- /dev/null
+++ b/lib/src/common/isar_common.dart
@@ -0,0 +1,222 @@
+// ignore_for_file: invalid_use_of_protected_member
+
+import 'dart:async';
+
+import 'package:isar/isar.dart';
+
+const Symbol _zoneTxn = #zoneTxn;
+
+/// @nodoc
+abstract class IsarCommon extends Isar {
+ /// @nodoc
+ IsarCommon(super.name);
+
+ final List> _activeAsyncTxns = [];
+ var _asyncWriteTxnsActive = 0;
+
+ Transaction? _currentTxnSync;
+
+ void _requireNotInTxn() {
+ if (_currentTxnSync != null || Zone.current[_zoneTxn] != null) {
+ throw IsarError(
+ 'Cannot perform this operation from within an active transaction. '
+ 'Isar does not support nesting transactions.',
+ );
+ }
+ }
+
+ /// @nodoc
+ Future beginTxn(bool write, bool silent);
+
+ Future _beginTxn(
+ bool write,
+ bool silent,
+ Future Function() callback,
+ ) async {
+ requireOpen();
+ _requireNotInTxn();
+
+ final completer = Completer();
+ _activeAsyncTxns.add(completer.future);
+
+ try {
+ if (write) {
+ _asyncWriteTxnsActive++;
+ }
+
+ final txn = await beginTxn(write, silent);
+
+ final zone = Zone.current.fork(
+ zoneValues: {_zoneTxn: txn},
+ );
+
+ T result;
+ try {
+ result = await zone.run(callback);
+ await txn.commit();
+ } catch (e) {
+ await txn.abort();
+ rethrow;
+ } finally {
+ txn.free();
+ }
+ return result;
+ } finally {
+ completer.complete();
+ _activeAsyncTxns.remove(completer.future);
+ if (write) {
+ _asyncWriteTxnsActive--;
+ }
+ }
+ }
+
+ @override
+ Future txn(Future Function() callback) {
+ return _beginTxn(false, false, callback);
+ }
+
+ @override
+ Future writeTxn(Future Function() callback, {bool silent = false}) {
+ return _beginTxn(true, silent, callback);
+ }
+
+ /// @nodoc
+ Future getTxn(
+ bool write,
+ Future Function(T txn) callback,
+ ) {
+ final currentTxn = Zone.current[_zoneTxn] as T?;
+ if (currentTxn != null) {
+ if (!currentTxn.active) {
+ throw IsarError('Transaction is not active anymore. Make sure to await '
+ 'all your asynchronous code within transactions to prevent it from '
+ 'being closed prematurely.');
+ } else if (write && !currentTxn.write) {
+ throw IsarError('Operation cannot be performed within a read '
+ 'transaction. Use isar.writeTxn() instead.');
+ } else if (currentTxn.isar != this) {
+ throw IsarError('Transaction does not match Isar instance. '
+ 'Make sure to use transactions from the same Isar instance.');
+ }
+ return callback(currentTxn);
+ } else if (!write) {
+ return _beginTxn(false, false, () {
+ return callback(Zone.current[_zoneTxn] as T);
+ });
+ } else {
+ throw IsarError('Write operations require an explicit transaction. '
+ 'Wrap your code in isar.writeTxn()');
+ }
+ }
+
+ /// @nodoc
+ Transaction beginTxnSync(bool write, bool silent);
+
+ T _beginTxnSync(bool write, bool silent, T Function() callback) {
+ requireOpen();
+ _requireNotInTxn();
+
+ if (write && _asyncWriteTxnsActive > 0) {
+ throw IsarError(
+ 'An async write transaction is already in progress in this isolate. '
+ 'You cannot begin a sync write transaction until it is finished. '
+ 'Use asynchroneous transactions if you want to queue multiple write '
+ 'transactions.',
+ );
+ }
+
+ final txn = beginTxnSync(write, silent);
+ _currentTxnSync = txn;
+
+ T result;
+ try {
+ result = callback();
+ txn.commitSync();
+ } catch (e) {
+ txn.abortSync();
+ rethrow;
+ } finally {
+ _currentTxnSync = null;
+ txn.free();
+ }
+
+ return result;
+ }
+
+ @override
+ T txnSync(T Function() callback) {
+ return _beginTxnSync(false, false, callback);
+ }
+
+ @override
+ T writeTxnSync(T Function() callback, {bool silent = false}) {
+ return _beginTxnSync(true, silent, callback);
+ }
+
+ /// @nodoc
+ R getTxnSync(
+ bool write,
+ R Function(T txn) callback,
+ ) {
+ if (_currentTxnSync != null) {
+ if (write && !_currentTxnSync!.write) {
+ throw IsarError(
+ 'Operation cannot be performed within a read transaction. '
+ 'Use isar.writeTxnSync() instead.',
+ );
+ }
+ return callback(_currentTxnSync! as T);
+ } else if (!write) {
+ return _beginTxnSync(false, false, () => callback(_currentTxnSync! as T));
+ } else {
+ throw IsarError('Write operations require an explicit transaction. '
+ 'Wrap your code in isar.writeTxnSync()');
+ }
+ }
+
+ @override
+ Future close({bool deleteFromDisk = false}) async {
+ requireOpen();
+ _requireNotInTxn();
+ await Future.wait(_activeAsyncTxns);
+ await super.close();
+
+ return performClose(deleteFromDisk);
+ }
+
+ /// @nodoc
+ bool performClose(bool deleteFromDisk);
+}
+
+/// @nodoc
+abstract class Transaction {
+ /// @nodoc
+ Transaction(this.isar, this.sync, this.write);
+
+ /// @nodoc
+ final Isar isar;
+
+ /// @nodoc
+ final bool sync;
+
+ /// @nodoc
+ final bool write;
+
+ /// @nodoc
+ bool get active;
+
+ /// @nodoc
+ Future commit();
+
+ /// @nodoc
+ void commitSync();
+
+ /// @nodoc
+ Future abort();
+
+ /// @nodoc
+ void abortSync();
+
+ /// @nodoc
+ void free() {}
+}
diff --git a/lib/src/common/isar_link_base_impl.dart b/lib/src/common/isar_link_base_impl.dart
new file mode 100644
index 0000000..1a19192
--- /dev/null
+++ b/lib/src/common/isar_link_base_impl.dart
@@ -0,0 +1,108 @@
+import 'package:isar/isar.dart';
+
+/// @nodoc
+abstract class IsarLinkBaseImpl implements IsarLinkBase {
+ var _initialized = false;
+
+ Id? _objectId;
+
+ /// The isar name of the link
+ late final String linkName;
+
+ /// The origin collection of the link. For backlinks it is actually the target
+ /// collection.
+ late final IsarCollection sourceCollection;
+
+ /// The target collection of the link. For backlinks it is actually the origin
+ /// collection.
+ late final IsarCollection targetCollection;
+
+ @override
+ bool get isAttached => _objectId != null;
+
+ @override
+ void attach(
+ IsarCollection sourceCollection,
+ IsarCollection targetCollection,
+ String linkName,
+ Id? objectId,
+ ) {
+ if (_initialized) {
+ if (linkName != this.linkName ||
+ !identical(sourceCollection, this.sourceCollection) ||
+ !identical(targetCollection, this.targetCollection)) {
+ throw IsarError(
+ 'Link has been moved! It is not allowed to move '
+ 'a link to a different collection.',
+ );
+ }
+ } else {
+ _initialized = true;
+ this.sourceCollection = sourceCollection;
+ this.targetCollection = targetCollection;
+ this.linkName = linkName;
+ }
+
+ _objectId = objectId;
+ }
+
+ /// Returns the containing object's id or throws an exception if this link has
+ /// not been attached to an object yet.
+ Id requireAttached() {
+ if (_objectId == null) {
+ throw IsarError(
+ 'Containing object needs to be managed by Isar to use this method. '
+ 'Use collection.put(yourObject) to add it to the database.',
+ );
+ } else {
+ return _objectId!;
+ }
+ }
+
+ /// Returns the id of a linked object.
+ Id Function(OBJ obj) get getId;
+
+ /// Returns the id of a linked object or throws an exception if the id is
+ /// `null` or set to `Isar.autoIncrement`.
+ Id requireGetId(OBJ object) {
+ final id = getId(object);
+ if (id != Isar.autoIncrement) {
+ return id;
+ } else {
+ throw IsarError(
+ 'Object "$object" has no id and can therefore not be linked. '
+ 'Make sure to .put() objects before you use them in links.',
+ );
+ }
+ }
+
+ /// See [IsarLinks.filter].
+ QueryBuilder filter() {
+ final containingId = requireAttached();
+ final qb = QueryBuilderInternal(
+ collection: targetCollection,
+ whereClauses: [
+ LinkWhereClause(
+ linkCollection: sourceCollection.name,
+ linkName: linkName,
+ id: containingId,
+ ),
+ ],
+ );
+ return QueryBuilder(qb);
+ }
+
+ /// See [IsarLinks.update].
+ Future update({
+ Iterable link = const [],
+ Iterable unlink = const [],
+ bool reset = false,
+ });
+
+ /// See [IsarLinks.updateSync].
+ void updateSync({
+ Iterable link = const [],
+ Iterable unlink = const [],
+ bool reset = false,
+ });
+}
diff --git a/lib/src/common/isar_link_common.dart b/lib/src/common/isar_link_common.dart
new file mode 100644
index 0000000..410ccd3
--- /dev/null
+++ b/lib/src/common/isar_link_common.dart
@@ -0,0 +1,92 @@
+import 'package:isar/isar.dart';
+import 'package:isar/src/common/isar_link_base_impl.dart';
+
+const bool _kIsWeb = identical(0, 0.0);
+
+/// @nodoc
+abstract class IsarLinkCommon extends IsarLinkBaseImpl
+ with IsarLink {
+ OBJ? _value;
+
+ @override
+ bool isChanged = false;
+
+ @override
+ bool isLoaded = false;
+
+ @override
+ OBJ? get value {
+ if (isAttached && !isLoaded && !isChanged && !_kIsWeb) {
+ loadSync();
+ }
+ return _value;
+ }
+
+ @override
+ set value(OBJ? value) {
+ isChanged |= !identical(_value, value);
+ _value = value;
+ isLoaded = true;
+ }
+
+ @override
+ Future load() async {
+ _value = await filter().findFirst();
+ isChanged = false;
+ isLoaded = true;
+ }
+
+ @override
+ void loadSync() {
+ _value = filter().findFirstSync();
+ isChanged = false;
+ isLoaded = true;
+ }
+
+ @override
+ Future save() async {
+ if (!isChanged) {
+ return;
+ }
+
+ final object = value;
+
+ await update(link: [if (object != null) object], reset: true);
+ isChanged = false;
+ isLoaded = true;
+ }
+
+ @override
+ void saveSync() {
+ if (!isChanged) {
+ return;
+ }
+
+ final object = _value;
+ updateSync(link: [if (object != null) object], reset: true);
+
+ isChanged = false;
+ isLoaded = true;
+ }
+
+ @override
+ Future reset() async {
+ await update(reset: true);
+ _value = null;
+ isChanged = false;
+ isLoaded = true;
+ }
+
+ @override
+ void resetSync() {
+ updateSync(reset: true);
+ _value = null;
+ isChanged = false;
+ isLoaded = true;
+ }
+
+ @override
+ String toString() {
+ return 'IsarLink($_value)';
+ }
+}
diff --git a/lib/src/common/isar_links_common.dart b/lib/src/common/isar_links_common.dart
new file mode 100644
index 0000000..98975b7
--- /dev/null
+++ b/lib/src/common/isar_links_common.dart
@@ -0,0 +1,223 @@
+import 'dart:collection';
+
+import 'package:isar/isar.dart';
+import 'package:isar/src/common/isar_link_base_impl.dart';
+
+const bool _kIsWeb = identical(0, 0.0);
+
+/// @nodoc
+abstract class IsarLinksCommon extends IsarLinkBaseImpl
+ with IsarLinks, SetMixin {
+ final _objects = {};
+
+ /// @nodoc
+ final addedObjects = HashSet.identity();
+
+ /// @nodoc
+ final removedObjects = HashSet.identity();
+
+ @override
+ bool isLoaded = false;
+
+ @override
+ bool get isChanged => addedObjects.isNotEmpty || removedObjects.isNotEmpty;
+
+ Map get _loadedObjects {
+ if (isAttached && !isLoaded && !_kIsWeb) {
+ loadSync();
+ }
+ return _objects;
+ }
+
+ @override
+ void attach(
+ IsarCollection sourceCollection,
+ IsarCollection targetCollection,
+ String linkName,
+ Id? objectId,
+ ) {
+ super.attach(sourceCollection, targetCollection, linkName, objectId);
+
+ _applyAddedRemoved();
+ }
+
+ @override
+ Future load({bool overrideChanges = false}) async {
+ final objects = await filter().findAll();
+ _applyLoaded(objects, overrideChanges);
+ }
+
+ @override
+ void loadSync({bool overrideChanges = false}) {
+ final objects = filter().findAllSync();
+ _applyLoaded(objects, overrideChanges);
+ }
+
+ void _applyLoaded(List objects, bool overrideChanges) {
+ _objects.clear();
+ for (final object in objects) {
+ final id = getId(object);
+ if (id != Isar.autoIncrement) {
+ _objects[id] = object;
+ }
+ }
+
+ if (overrideChanges) {
+ addedObjects.clear();
+ removedObjects.clear();
+ } else {
+ _applyAddedRemoved();
+ }
+
+ isLoaded = true;
+ }
+
+ void _applyAddedRemoved() {
+ for (final object in addedObjects) {
+ final id = getId(object);
+ if (id != Isar.autoIncrement) {
+ _objects[id] = object;
+ }
+ }
+
+ for (final object in removedObjects) {
+ final id = getId(object);
+ if (id != Isar.autoIncrement) {
+ _objects.remove(id);
+ }
+ }
+ }
+
+ @override
+ Future save() async {
+ if (!isChanged) {
+ return;
+ }
+
+ await update(link: addedObjects, unlink: removedObjects);
+
+ addedObjects.clear();
+ removedObjects.clear();
+ isLoaded = true;
+ }
+
+ @override
+ void saveSync() {
+ if (!isChanged) {
+ return;
+ }
+
+ updateSync(link: addedObjects, unlink: removedObjects);
+
+ addedObjects.clear();
+ removedObjects.clear();
+ isLoaded = true;
+ }
+
+ @override
+ Future reset() async {
+ await update(reset: true);
+ clear();
+ isLoaded = true;
+ }
+
+ @override
+ void resetSync() {
+ updateSync(reset: true);
+ clear();
+ isLoaded = true;
+ }
+
+ @override
+ bool add(OBJ value) {
+ if (isAttached) {
+ final id = getId(value);
+ if (id != Isar.autoIncrement) {
+ if (_objects.containsKey(id)) {
+ return false;
+ }
+ _objects[id] = value;
+ }
+ }
+
+ removedObjects.remove(value);
+ return addedObjects.add(value);
+ }
+
+ @override
+ bool contains(Object? element) {
+ requireAttached();
+
+ if (element is OBJ) {
+ final id = getId(element);
+ if (id != Isar.autoIncrement) {
+ return _loadedObjects.containsKey(id);
+ }
+ }
+ return false;
+ }
+
+ @override
+ Iterator get iterator => _loadedObjects.values.iterator;
+
+ @override
+ int get length => _loadedObjects.length;
+
+ @override
+ OBJ? lookup(Object? element) {
+ requireAttached();
+
+ if (element is OBJ) {
+ final id = getId(element);
+ if (id != Isar.autoIncrement) {
+ return _loadedObjects[id];
+ }
+ }
+ return null;
+ }
+
+ @override
+ bool remove(Object? value) {
+ if (value is! OBJ) {
+ return false;
+ }
+
+ if (isAttached) {
+ final id = getId(value);
+ if (id != Isar.autoIncrement) {
+ if (isLoaded && !_objects.containsKey(id)) {
+ return false;
+ }
+ _objects.remove(id);
+ }
+ }
+
+ addedObjects.remove(value);
+ return removedObjects.add(value);
+ }
+
+ @override
+ Set toSet() {
+ requireAttached();
+ return HashSet(
+ equals: (o1, o2) => getId(o1) == getId(o2),
+ // ignore: noop_primitive_operations
+ hashCode: (o) => getId(o).toInt(),
+ isValidKey: (o) => o is OBJ && getId(o) != Isar.autoIncrement,
+ )..addAll(_loadedObjects.values);
+ }
+
+ @override
+ void clear() {
+ _objects.clear();
+ addedObjects.clear();
+ removedObjects.clear();
+ }
+
+ @override
+ String toString() {
+ final content =
+ IterableBase.iterableToFullString(_objects.values, '{', '}');
+ return 'IsarLinks($content)';
+ }
+}
diff --git a/lib/src/common/schemas.dart b/lib/src/common/schemas.dart
new file mode 100644
index 0000000..06085d8
--- /dev/null
+++ b/lib/src/common/schemas.dart
@@ -0,0 +1,13 @@
+import 'package:isar/isar.dart';
+
+/// @nodoc
+List> getSchemas(
+ List> collectionSchemas,
+) {
+ final schemas = >{};
+ for (final collectionSchema in collectionSchemas) {
+ schemas.add(collectionSchema);
+ schemas.addAll(collectionSchema.embeddedSchemas.values);
+ }
+ return schemas.toList();
+}
diff --git a/lib/src/isar.dart b/lib/src/isar.dart
new file mode 100644
index 0000000..a67ed71
--- /dev/null
+++ b/lib/src/isar.dart
@@ -0,0 +1,347 @@
+part of isar;
+
+/// Callback for a newly opened Isar instance.
+typedef IsarOpenCallback = void Function(Isar isar);
+
+/// Callback for a release Isar instance.
+typedef IsarCloseCallback = void Function(String isarName);
+
+/// An instance of the Isar Database.
+abstract class Isar {
+ /// @nodoc
+ @protected
+ Isar(this.name) {
+ _instances[name] = this;
+ for (final callback in _openCallbacks) {
+ callback(this);
+ }
+ }
+
+ /// The version of the Isar library.
+ static const version = '3.1.0+1';
+
+ /// Smallest valid id.
+ static const Id minId = isarMinId;
+
+ /// Largest valid id.
+ static const Id maxId = isarMaxId;
+
+ /// The default Isar instance name.
+ static const String defaultName = 'default';
+
+ /// The default max Isar size.
+ static const int defaultMaxSizeMiB = 1024;
+
+ /// Placeholder for an auto-increment id.
+ static const Id autoIncrement = isarAutoIncrementId;
+
+ static final Map _instances = {};
+ static final Set _openCallbacks = {};
+ static final Set _closeCallbacks = {};
+
+ /// Name of the instance.
+ final String name;
+
+ /// The directory containing the database file or `null` on the web.
+ String? get directory;
+
+ /// The full path of the database file is `directory/name.isar` and the lock
+ /// file `directory/name.isar.lock`.
+ String? get path => directory != null ? '$directory/$name.isar' : null;
+
+ late final Map> _collections;
+ late final Map> _collectionsByName;
+
+ bool _isOpen = true;
+
+ static void _checkOpen(String name, List> schemas) {
+ if (name.isEmpty || name.startsWith('_')) {
+ throw IsarError('Instance names must not be empty or start with "_".');
+ }
+ if (_instances.containsKey(name)) {
+ throw IsarError('Instance has already been opened.');
+ }
+ if (schemas.isEmpty) {
+ throw IsarError('At least one collection needs to be opened.');
+ }
+
+ final schemaNames = {};
+ for (final schema in schemas) {
+ if (!schemaNames.add(schema.name)) {
+ throw IsarError('Duplicate collection ${schema.name}.');
+ }
+ }
+ for (final schema in schemas) {
+ final dependencies = schema.links.values.map((e) => e.target);
+ for (final dependency in dependencies) {
+ if (!schemaNames.contains(dependency)) {
+ throw IsarError(
+ "Collection ${schema.name} depends on $dependency but it's schema "
+ 'was not provided.',
+ );
+ }
+ }
+ }
+ }
+
+ /// Open a new Isar instance.
+ static Future open(
+ List> schemas, {
+ required String directory,
+ String name = defaultName,
+ int maxSizeMiB = Isar.defaultMaxSizeMiB,
+ bool relaxedDurability = true,
+ CompactCondition? compactOnLaunch,
+ bool inspector = true,
+ }) {
+ _checkOpen(name, schemas);
+
+ /// Tree shake the inspector for profile and release builds.
+ assert(() {
+ if (!_kIsWeb && inspector) {
+ _IsarConnect.initialize(schemas);
+ }
+ return true;
+ }());
+
+ return openIsar(
+ schemas: schemas,
+ directory: directory,
+ name: name,
+ maxSizeMiB: maxSizeMiB,
+ relaxedDurability: relaxedDurability,
+ compactOnLaunch: compactOnLaunch,
+ );
+ }
+
+ /// Open a new Isar instance.
+ static Isar openSync(
+ List> schemas, {
+ required String directory,
+ String name = defaultName,
+ int maxSizeMiB = Isar.defaultMaxSizeMiB,
+ bool relaxedDurability = true,
+ CompactCondition? compactOnLaunch,
+ bool inspector = true,
+ }) {
+ _checkOpen(name, schemas);
+
+ /// Tree shake the inspector for profile and release builds.
+ assert(() {
+ if (!_kIsWeb && inspector) {
+ _IsarConnect.initialize(schemas);
+ }
+ return true;
+ }());
+
+ return openIsarSync(
+ schemas: schemas,
+ directory: directory,
+ name: name,
+ maxSizeMiB: maxSizeMiB,
+ relaxedDurability: relaxedDurability,
+ compactOnLaunch: compactOnLaunch,
+ );
+ }
+
+ /// Is the instance open?
+ bool get isOpen => _isOpen;
+
+ /// @nodoc
+ @protected
+ void requireOpen() {
+ if (!isOpen) {
+ throw IsarError('Isar instance has already been closed');
+ }
+ }
+
+ /// Executes an asynchronous read-only transaction.
+ Future txn(Future Function() callback);
+
+ /// Executes an asynchronous read-write transaction.
+ ///
+ /// If [silent] is `true`, watchers are not notified about changes in this
+ /// transaction.
+ Future writeTxn(Future Function() callback, {bool silent = false});
+
+ /// Executes a synchronous read-only transaction.
+ T txnSync(T Function() callback);
+
+ /// Executes a synchronous read-write transaction.
+ ///
+ /// If [silent] is `true`, watchers are not notified about changes in this
+ /// transaction.
+ T writeTxnSync(T Function() callback, {bool silent = false});
+
+ /// @nodoc
+ @protected
+ void attachCollections(Map> collections) {
+ _collections = collections;
+ _collectionsByName = {
+ for (IsarCollection col in collections.values) col.name: col,
+ };
+ }
+
+ /// Get a collection by its type.
+ ///
+ /// You should use the generated extension methods instead.
+ IsarCollection collection() {
+ requireOpen();
+ final collection = _collections[T];
+ if (collection == null) {
+ throw IsarError('Missing ${T.runtimeType}Schema in Isar.open');
+ }
+ return collection as IsarCollection;
+ }
+
+ /// @nodoc
+ @protected
+ IsarCollection? getCollectionByNameInternal(String name) {
+ return _collectionsByName[name];
+ }
+
+ /// Remove all data in this instance and reset the auto increment values.
+ Future clear() async {
+ for (final col in _collections.values) {
+ await col.clear();
+ }
+ }
+
+ /// Remove all data in this instance and reset the auto increment values.
+ void clearSync() {
+ for (final col in _collections.values) {
+ col.clearSync();
+ }
+ }
+
+ /// Returns the size of all the collections in bytes. Not supported on web.
+ ///
+ /// This method is extremely fast and independent of the number of objects in
+ /// the instance.
+ Future getSize({bool includeIndexes = false, bool includeLinks = false});
+
+ /// Returns the size of all collections in bytes. Not supported on web.
+ ///
+ /// This method is extremely fast and independent of the number of objects in
+ /// the instance.
+ int getSizeSync({bool includeIndexes = false, bool includeLinks = false});
+
+ /// Copy a compacted version of the database to the specified file.
+ ///
+ /// If you want to backup your database, you should always use a compacted
+ /// version. Compacted does not mean compressed.
+ ///
+ /// Do not run this method while other transactions are active to avoid
+ /// unnecessary growth of the database.
+ Future copyToFile(String targetPath);
+
+ /// Releases an Isar instance.
+ ///
+ /// If this is the only isolate that holds a reference to this instance, the
+ /// Isar instance will be closed. [deleteFromDisk] additionally removes all
+ /// database files if enabled.
+ ///
+ /// Returns whether the instance was actually closed.
+ Future close({bool deleteFromDisk = false}) {
+ requireOpen();
+ _isOpen = false;
+ if (identical(_instances[name], this)) {
+ _instances.remove(name);
+ }
+ for (final callback in _closeCallbacks) {
+ callback(name);
+ }
+ return Future.value(false);
+ }
+
+ /// Verifies the integrity of the database file.
+ ///
+ /// Do not use this method in production apps.
+ @visibleForTesting
+ @experimental
+ Future verify();
+
+ /// A list of all Isar instances opened in the current isolate.
+ static Set get instanceNames => _instances.keys.toSet();
+
+ /// Returns an Isar instance opened in the current isolate by its name. If
+ /// no name is provided, the default instance is returned.
+ static Isar? getInstance([String name = defaultName]) {
+ return _instances[name];
+ }
+
+ /// Registers a listener that is called whenever an Isar instance is opened.
+ static void addOpenListener(IsarOpenCallback callback) {
+ _openCallbacks.add(callback);
+ }
+
+ /// Removes a previously registered `IsarOpenCallback`.
+ static void removeOpenListener(IsarOpenCallback callback) {
+ _openCallbacks.remove(callback);
+ }
+
+ /// Registers a listener that is called whenever an Isar instance is
+ /// released.
+ static void addCloseListener(IsarCloseCallback callback) {
+ _closeCallbacks.add(callback);
+ }
+
+ /// Removes a previously registered `IsarOpenCallback`.
+ static void removeCloseListener(IsarCloseCallback callback) {
+ _closeCallbacks.remove(callback);
+ }
+
+ /// Initialize Isar Core manually. You need to provide Isar Core libraries
+ /// for every platform your app will run on.
+ ///
+ /// If [download] is `true`, Isar will attempt to download the correct
+ /// library and place it in the specified path or the script directory.
+ ///
+ /// Be careful if multiple unit tests try to download the library at the
+ /// same time. Always use `flutter test -j 1` when you rely on auto
+ /// downloading to ensure that only one test is running at a time.
+ ///
+ /// Only use this method for non-Flutter code or unit tests.
+ static Future initializeIsarCore({
+ Map libraries = const {},
+ bool download = false,
+ }) async {
+ await initializeCoreBinary(
+ libraries: libraries,
+ download: download,
+ );
+ }
+
+ /// Split a String into words according to Unicode Annex #29. Only words
+ /// containing at least one alphanumeric character will be included.
+ static List splitWords(String input) => isarSplitWords(input);
+}
+
+/// Isar databases can contain unused space that will be reused for later
+/// operations. You can specify conditions to trigger manual compaction where
+/// the entire database is copied and unused space freed.
+///
+/// This operation can only be performed while a database is being opened and
+/// should only be used if absolutely necessary.
+class CompactCondition {
+ /// Compaction will happen if all of the specified conditions are true.
+ const CompactCondition({
+ this.minFileSize,
+ this.minBytes,
+ this.minRatio,
+ }) : assert(
+ minFileSize != null || minBytes != null || minRatio != null,
+ 'At least one condition needs to be specified.',
+ );
+
+ /// The minimum size in bytes of the database file to trigger compaction. It
+ /// is highly discouraged to trigger compaction solely on this condition.
+ final int? minFileSize;
+
+ /// The minimum number of bytes that can be freed with compaction.
+ final int? minBytes;
+
+ /// The minimum compaction ration. For example `2.0` would trigger compaction
+ /// as soon as the file size can be halved.
+ final double? minRatio;
+}
diff --git a/lib/src/isar_collection.dart b/lib/src/isar_collection.dart
new file mode 100644
index 0000000..2d2b814
--- /dev/null
+++ b/lib/src/isar_collection.dart
@@ -0,0 +1,342 @@
+part of isar;
+
+/// Normal keys consist of a single object, composite keys multiple.
+typedef IndexKey = List