Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a5a6be2
Collection / Queryable framework
dfahlander Nov 9, 2025
8cbc4b1
createMangoFilter() by Claude 4.5
dfahlander Nov 10, 2025
11efe39
next Where-clause
dfahlander Nov 10, 2025
f968b3d
Updated tsconfig's target and lib to es2020 and 2021
dfahlander Nov 10, 2025
b7aaa6a
toMapKey() to be used in the _exec code later
dfahlander Nov 10, 2025
5c11150
Support for IndexedDB3.0 in DBCore
dfahlander Nov 10, 2025
74f1077
Allow comments in schema
dfahlander Nov 10, 2025
7b3433c
Expose obsSetOverlaps, AnyRange and NeverRange in public api
dfahlander Nov 10, 2025
4ecd0db
New query engine dry-coded with help from Claude 4.5.
dfahlander Nov 10, 2025
76627ae
Export new DexieCollection from "dexie/next"
dfahlander Nov 10, 2025
0238b60
Bump to a 4.5 version track in this branch, so it may coexist in alph…
dfahlander Nov 10, 2025
b63e84b
Fixed 2-step compilation of dexie and then dexie/next.
dfahlander Nov 10, 2025
c45999b
Generics of Collection, Queryable etc
dfahlander Nov 10, 2025
f97f00a
Expose a NextDexie that translates an entire db instance to a NextDex…
dfahlander Nov 10, 2025
d6e27aa
Fixes to run ts tests of dexie/next
dfahlander Nov 10, 2025
7f5c01a
Build next before test
dfahlander Nov 10, 2025
789f5de
Bugfix IndexedDB 3.0 support
dfahlander Nov 10, 2025
7b1d9da
Bugfix multiple build
dfahlander Nov 10, 2025
863c74a
Bugfix: selected compound index when should not
dfahlander Nov 10, 2025
1e13def
enable watching next changes
dfahlander Nov 10, 2025
b886c3e
Added another test
dfahlander Nov 10, 2025
4d140cf
d
dfahlander Nov 10, 2025
d30a008
Fixed a lot of unit tests. Fixed explain and index selections.
dfahlander Nov 11, 2025
2270d63
Fix bug in virtual-index-middleware: keyPath should be the virtual ke…
dfahlander Nov 11, 2025
4e2b554
Fix compound index validation for range queries
dfahlander Nov 11, 2025
7aa55e8
Add test for virtual index fallback behavior
dfahlander Nov 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,4 @@ libs/dexie-cloud-common/tsconfig.tsbuildinfo
#lambdatest tunnel binary
.lambdatest
tunnel.pid
dist/next/*.d.mts
4 changes: 2 additions & 2 deletions libs/dexie-react-hooks/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"compilerOptions": {
"lib": ["es2021", "dom"],
"lib": ["es2022", "dom"],
"moduleResolution": "node",
"importHelpers": false,
"jsx": "react",
"target": "es5",
"target": "es2022",
"module": "es6",
"noImplicitAny": false,
"strictNullChecks": true,
Expand Down
21 changes: 17 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dexie",
"version": "4.2.1",
"version": "4.5.0-alpha.1",
"description": "A Minimalistic Wrapper for IndexedDB",
"main": "dist/dexie.js",
"module": "dist/dexie.mjs",
Expand All @@ -27,7 +27,17 @@
}
},
"./package.json": "./package.json",
"./dist/*": "./dist/*"
"./dist/*": "./dist/*",
"./next": {
"types": "./dist/next/index.d.mts",
"import": "./dist/next/index.mjs",
"default": "./dist/next/index.mjs"
},
"./next/*": {
"types": "./dist/next/*.d.mts",
"import": "./dist/next/*.mjs",
"default": "./dist/next/*.mjs"
}
},
"typings": "dist/dexie.d.ts",
"jspm": {
Expand Down Expand Up @@ -71,7 +81,8 @@
"just-build": {
"default": [
"# Build all targets (es5, es6 and test) and minify the default es5 UMD module",
"just-build release test"
"just-build release",
"just-build test"
],
"dexie": [
"# Build dist/dexie.js, dist/dexie.mjs and dist/dexie.d.ts",
Expand All @@ -86,7 +97,9 @@
"node ../tools/replaceVersionAndDate.js ../dist/modern/dexie.mjs",
"dts-bundle-generator --inline-declare-global --inline-declare-externals -o ../dist/dexie.d.ts public/index.d.ts",
"node ../tools/prepend.js ../dist/dexie.d.ts ../tools/build-configs/banner.txt",
"node ../tools/replaceVersionAndDate.js ../dist/dexie.d.ts"
"node ../tools/replaceVersionAndDate.js ../dist/dexie.d.ts",
"tsc -p next/tsconfig.json [--watch 'Watching for file changes']",
"node ../tools/build-next-modules.js"
],
"release": [
"# Build ES5 umd module as well as the es6 module.",
Expand Down
5 changes: 5 additions & 0 deletions src/classes/dexie/dexie-static-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { extendObservabilitySet } from '../../live-query/extend-observability-se
import { domDeps } from './dexie-dom-dependencies';
import { cmp } from '../../functions/cmp';
import { cache } from '../../live-query/cache/cache';
import { obsSetsOverlap } from '../../live-query/obs-sets-overlap';
import { AnyRange, NeverRange } from '../../dbcore/keyrange';

/* (Dexie) is an instance of DexieConstructor, as defined in public/types/dexie-constructor.d.ts
* (new Dexie()) is an instance of Dexie, as defined in public/types/dexie.d.ts
Expand Down Expand Up @@ -181,6 +183,7 @@ props(Dexie, {
on: globalEvents,
liveQuery,
extendObservabilitySet,
obsSetsOverlap,
// Utilities
getByKeyPath: getByKeyPath,
setByKeyPath: setByKeyPath,
Expand All @@ -192,6 +195,8 @@ props(Dexie, {
asap: asap,
//maxKey: new Dexie('',{addons:[]})._maxKey,
minKey: minKey,
AnyRange,
NeverRange,
// Addon registry
addons: [],
// Global DB connection list
Expand Down
8 changes: 7 additions & 1 deletion src/classes/version/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ export class Version implements IVersion {
): any {
keys(stores).forEach((tableName) => {
if (stores[tableName] !== null) {
let indexes = this._parseIndexSyntax(stores[tableName]);
// Allow comments in index syntax after a '#' char:
const indexSyntax = stores[tableName]
.split('\n')
.map(
line => line.split('#')[0].trim()
).filter(line => line.length > 0).join(',') || '';
let indexes = this._parseIndexSyntax(indexSyntax);

const primKey = indexes.shift();
if (!primKey) {
Expand Down
35 changes: 24 additions & 11 deletions src/dbcore/dbcore-indexeddb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function createDBCore (
IdbKeyRange: typeof IDBKeyRange,
tmpTrans: IDBTransaction) : DBCore
{
function extractSchema(db: IDBDatabase, trans: IDBTransaction) : {schema: DBCoreSchema, hasGetAll: boolean} {
function extractSchema(db: IDBDatabase, trans: IDBTransaction) : {schema: DBCoreSchema, hasGetAll: boolean, hasGetAllRecords?: boolean} {
const tables = arrayify(db.objectStoreNames);
return {
schema: {
Expand Down Expand Up @@ -91,7 +91,8 @@ export function createDBCore (
hasGetAll: tables.length > 0 && ('getAll' in trans.objectStore(tables[0])) &&
!(typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) &&
!/(Chrome\/|Edge\/)/.test(navigator.userAgent) &&
[].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604) // Bug with getAll() on Safari ver<604. See discussion following PR #579
[].concat(navigator.userAgent.match(/Safari\/(\d*)/))[1] < 604), // Bug with getAll() on Safari ver<604. See discussion following PR #579
hasGetAllRecords: 'getAllRecords' in IDBObjectStore.prototype && 'getAllRecords' in IDBIndex.prototype
};
}

Expand Down Expand Up @@ -284,33 +285,45 @@ export function createDBCore (
});
}

function query (hasGetAll: boolean) {
function query (hasGetAll: boolean, hasGetAllRecords: boolean = false) {
return (request: DBCoreQueryRequest) => {
return new Promise<DBCoreQueryResponse>((resolve, reject) => {
resolve = wrap(resolve);
const {trans, values, limit, query} = request;
const {trans, values, limit, query, direction, records} = request;
const nonInfinitLimit = limit === Infinity ? undefined : limit;
const {index, range} = query;
const store = (trans as IDBTransaction).objectStore(tableName);
const source = index.isPrimaryKey ? store : store.index(index.name);
const idbKeyRange = makeIDBKeyRange(range);
if (limit === 0) return resolve({result: []});
if (hasGetAll) {
if (hasGetAllRecords) {
const req = records
? (source as any).getAllRecords({query: idbKeyRange, direction, ...{count: nonInfinitLimit}})
: values
? (source as any).getAll({query: idbKeyRange, direction, ...{count: nonInfinitLimit}})
: (source as any).getAllKeys({query: idbKeyRange, direction, ...{count: nonInfinitLimit}});
req.onsuccess = event => resolve({result: event.target.result});
req.onerror = eventRejectHandler(reject);
} else if (hasGetAll && !records && (!direction || direction === "next")) {
const req = values ?
(source as any).getAll(idbKeyRange, nonInfinitLimit) :
(source as any).getAllKeys(idbKeyRange, nonInfinitLimit);
req.onsuccess = event => resolve({result: event.target.result});
req.onerror = eventRejectHandler(reject);
} else {
let count = 0;
const req = values || !('openKeyCursor' in source) ?
source.openCursor(idbKeyRange) :
source.openKeyCursor(idbKeyRange)
const req = values || records || !('openKeyCursor' in source) ?
source.openCursor(idbKeyRange, direction || "next") :
source.openKeyCursor(idbKeyRange, direction || "next");
const result = [];
req.onsuccess = event => {
const cursor = req.result as IDBCursorWithValue;
if (!cursor) return resolve({result});
result.push(values ? cursor.value : cursor.primaryKey);
result.push(records ? {
key: cursor.key,
primaryKey: cursor.primaryKey,
value: cursor.value
} : values ? cursor.value : cursor.primaryKey);
if (++count === limit) return resolve({result});
cursor.continue();
};
Expand Down Expand Up @@ -368,7 +381,7 @@ export function createDBCore (
});
},

query: query(hasGetAll),
query: query(hasGetAll, hasGetAllRecords),

openCursor,

Expand All @@ -386,7 +399,7 @@ export function createDBCore (
};
}

const {schema, hasGetAll} = extractSchema(db, tmpTrans);
const {schema, hasGetAll, hasGetAllRecords} = extractSchema(db, tmpTrans);
const tables = schema.tables.map(tableSchema => createDbCoreTable(tableSchema));
const tableMap: {[name: string]: DBCoreTable} = {};
tables.forEach(table => tableMap[table.name] = table);
Expand Down
1 change: 1 addition & 0 deletions src/dbcore/virtual-index-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function createVirtualIndexMiddleware (down: DBCore) : DBCore {
name: isVirtual
? `${keyPathAlias}(virtual-from:${lowLevelIndex.name})`
: lowLevelIndex.name,
keyPath, // Override keyPath from lowLevelIndex with the virtual one
lowLevelIndex,
isVirtual,
keyTail,
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { Entity } from './classes/entity/Entity';
import { cmp } from './functions/cmp';
import { PropModification } from './helpers/prop-modification';
import { replacePrefix, add, remove } from './functions/propmods';
import { AnyRange, NeverRange } from './dbcore/keyrange';
import { obsSetsOverlap } from './live-query/obs-sets-overlap';


// Set rejectionMapper of DexiePromise so that it generally tries to map
Expand All @@ -29,6 +31,6 @@ Debug.setDebug(Debug.debug, dexieStackFrameFilter);
export { RangeSet, mergeRanges, rangesOverlap } from "./helpers/rangeset";
export { Dexie, liveQuery }; // Comply with public/index.d.ts.
export { Entity };
export { cmp };
export { cmp, AnyRange, NeverRange, obsSetsOverlap };
export { PropModification, replacePrefix, add, remove };
export default Dexie;
4 changes: 4 additions & 0 deletions src/live-query/cache/find-compatible-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export function findCompatibleQuery(
(entry) =>
(entry.req as DBCoreQueryRequest).limit === req.limit &&
(entry.req as DBCoreQueryRequest).values === req.values &&
(entry.req as DBCoreQueryRequest).direction === req.direction &&
(entry.req as DBCoreQueryRequest).records === req.records &&
areRangesEqual(entry.req.query.range, req.query.range)
);
if (equalEntry)
Expand All @@ -53,6 +55,8 @@ export function findCompatibleQuery(
return (
limit >= req.limit &&
(req.values ? (entry.req as DBCoreQueryRequest).values : true) &&
(entry.req as DBCoreQueryRequest).direction === req.direction &&
(entry.req as DBCoreQueryRequest).records === req.records &&
isSuperRange(entry.req.query.range, req.query.range)
);
});
Expand Down
50 changes: 50 additions & 0 deletions src/next/Collection.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { WhereClause } from './WhereClause.mjs';
import { Queryable } from './Queryable.mjs';
import { canonicalizeMango, MangoExpression, MangoExpressionWithAliases } from './MangoExpression.mjs';
import { OrderByQueryable } from './OrderedQueryable.mjs';
import { Query } from './Query.mjs';

export abstract class Collection<T = any, TKey = any, TInsertType = T> extends Queryable<T, TKey, TInsertType> {
where(prop: string): WhereClause<T, TKey, TInsertType>;
where(expr: MangoExpressionWithAliases): Collection<T, TKey, TInsertType>;
where(arg: string | MangoExpressionWithAliases): WhereClause<T, TKey, TInsertType> | Collection<T, TKey, TInsertType> {
if (typeof arg === 'object') {
return new FilteredCollection(this, canonicalizeMango(arg), 'and');
} else {
return new WhereClause(this, arg, 'and');
}
}
orderBy(...props: string[]): OrderByQueryable<T, TKey, TInsertType> {
return new OrderByQueryable(this, props);
}
}

export class FilteredCollection<T = any, TKey = any, TInsertType = T> extends Collection<T, TKey, TInsertType> {
_op: 'or' | 'and';
_expr: MangoExpression;
constructor(parent: Queryable<T, TKey, TInsertType>, expr: MangoExpression, op: 'or' | 'and') {
super(parent);
this._expr = expr;
this._op = op;
}
or(prop: string): WhereClause<T, TKey, TInsertType>;
or(expr: MangoExpressionWithAliases): Collection<T, TKey, TInsertType>;
or(arg: string | MangoExpressionWithAliases): WhereClause<T, TKey, TInsertType> | Collection<T, TKey, TInsertType> {
if (typeof arg === 'object') {
return new FilteredCollection(this, canonicalizeMango(arg), 'or');
} else {
return new WhereClause(this, arg, 'or');
}
}
protected _build(query: Query): void {
if (query.where === null) {
query.where = this._expr;
} else if (this._op === 'and') {
query.where = {$and: [query.where, this._expr]};
} else if (this._op === 'or') {
query.where = {$or: [query.where, this._expr]};
}
}
}


44 changes: 44 additions & 0 deletions src/next/DexieCollection.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Table } from '../..';
import { Collection } from './Collection.mjs';
import { Query, QueryPlan } from './Query.mjs';
import { executeQuery, executeCount } from './executeMangoQuery.mjs';

export class DexieCollection<T = any, TKey = any, TInsertType = T> extends Collection<T, TKey, TInsertType> {
_table: Table<T, TKey, TInsertType>;
constructor(table: Table<T, TKey, TInsertType>) {
super(null);
this._table = table;
}

protected _build(query: Query): void {
// No additional building needed at the collection level
}

protected _exec(query: Query): Promise<ReadonlyArray<T> | QueryPlan> {
// @ts-ignore
return this._table._trans('readonly', (idbtrans) => {
return executeQuery({
core: this._table.core,
trans: idbtrans as any,
schema: this._table.core.schema,
query,
tableName: this._table.name,
});
});
}

protected _count(query: Query): Promise<number> {
// @ts-ignore
return this._table._trans('readonly', (idbtrans) => {
return executeCount({
core: this._table.core,
trans: idbtrans as any,
schema: this._table.core.schema,
query,
tableName: this._table.name,
});
});
}
}


6 changes: 6 additions & 0 deletions src/next/LIMITATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@


1. Once a query has an offset or limit, where(), desc() and orderBy() is disabled on child querys.
2. If neither a where or orderBy is given, the default order is the primary key.
3. If no orderBy is given and the where-criteria can be resolved with an index, the default order is the order of any index that can resolve the criteria. If no such index is found, the default order is the primary key.
4. orderby will ignore unset entries on the index being ordered
Loading
Loading