Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Break out liveQuery() #1244

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions libs/dexie-live-query/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**/bundle.js
**/bundle.js.map
dist
114 changes: 114 additions & 0 deletions libs/dexie-live-query/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions libs/dexie-live-query/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "dexie-live-query",
"version": "1.0.0",
"description": "Observe IndexedDB by turning Promise-returning functions into Observables.",
"main": "dist/dexie-live-query.js",
"module": "dist/dexie-live-query.mjs",
"scripts": {
"build": "just-build",
"test": "echo This package is tested in Dexie.js test suite"
},
"repository": {
"type": "git",
"url": "git+https://github.com/dfahlander/Dexie.js.git"
},
"keywords": [
"observables",
"reactive",
"indexeddb"
],
"author": "david@dexie.org",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/dfahlander/Dexie.js/issues"
},
"homepage": "https://github.com/dfahlander/Dexie.js#readme",
"dependencies": {
"dexie": "file:../..",
"rxjs": "^6.0.0"
},
"just-build": {
"default": [
"just-build dexie",
"rollup -c rollup.config.js",
"tsc --emitDeclarationOnly"
],
"run-tests": [
"karma start test/karma.conf.js --single-run"
],
"dexie": [
"cd ../..",
"npm run build"
]
}
}
40 changes: 40 additions & 0 deletions libs/dexie-live-query/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import commonjs from '@rollup/plugin-commonjs';
import nodeResolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';

const ERRORS_TO_IGNORE = [
"THIS_IS_UNDEFINED",
];

export default {
input: './src/index.ts',
output: [{
file: 'dist/dexie-live-query.js',
format: 'umd',
globals: {dexie: "Dexie", rxjs: "Rx"},
name: 'liveQuery',
sourcemap: true
},{
file: 'dist/dexie-live-query.mjs',
format: 'es',
sourcemap: true
}],
external: ['dexie', 'rxjs'],
plugins: [
typescript(),
nodeResolve({
browser: true,
preferBuiltins: false
}),
commonjs(),
],
onwarn ({loc, frame, code, message}) {
if (ERRORS_TO_IGNORE.includes(code)) return;
if ( loc ) {
console.warn( `${loc.file} (${loc.line}:${loc.column}) ${message}` );
if ( frame ) console.warn( frame );
} else {
console.warn(`${code} ${message}`);
}
}
};
16 changes: 16 additions & 0 deletions libs/dexie-live-query/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ObservabilitySet, IntervalTree, mergeRanges } from "dexie";

export const isAsyncFunction = typeof Symbol !== 'undefined'
? (fn: Function) => fn[Symbol.toStringTag] === 'AsyncFunction'
: ()=>false;

export function extendObservabilitySet(
target: ObservabilitySet,
newSet: ObservabilitySet
): ObservabilitySet {
Object.keys(newSet).forEach(part => {
const rangeSet: IntervalTree = target[part] || (target[part] = {d: 0});
mergeRanges(rangeSet, newSet[part]);
});
return target;
}
1 change: 1 addition & 0 deletions libs/dexie-live-query/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import { liveQuery } from "./liveQuery.js";
95 changes: 95 additions & 0 deletions libs/dexie-live-query/src/liveQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { isAsyncFunction, extendObservabilitySet } from "./helpers";
import Dexie, { ObservabilitySet, rangesOverlap, DexiePromiseConstructor } from "dexie";
import {
Observable
} from "rxjs";

const DexiePromise = Dexie.Promise as DexiePromiseConstructor; // Reveals private properties of Dexie.Promise.
const {iea, dea, newPSD, usePSD} = DexiePromise;

export function liveQuery<T>(querier: () => T | Promise<T>): Observable<T> {
return new Observable<T>((observer) => {
const scopeFuncIsAsync = isAsyncFunction(querier);
function execute(subscr: ObservabilitySet) {
if (scopeFuncIsAsync) {
iea();
}
const exec = () => newPSD(querier, { subscr, trans: null });
const rv = DexiePromise.PSD.trans
? // Ignore current transaction if active when calling subscribe().
usePSD(DexiePromise.PSD.transless, exec)
: exec();
if (scopeFuncIsAsync) {
(rv as Promise<any>).then(dea, dea);
}
return rv;
}

let closed = false;

let accumMuts: ObservabilitySet = {};
let currentObs: ObservabilitySet = {};

const subscription = {
get closed() {
return closed;
},
unsubscribe: () => {
closed = true;
Dexie.on.txcommitted.unsubscribe(mutationListener);
},
};

let querying = false,
startedListening = false;

function shouldNotify() {
return Object.keys(currentObs).some(
(key) =>
accumMuts[key] && rangesOverlap(accumMuts[key], currentObs[key])
);
}

const mutationListener = (parts: ObservabilitySet) => {
extendObservabilitySet(accumMuts, parts);
if (shouldNotify()) {
doQuery();
}
};

const doQuery = () => {
if (querying || closed) return;
accumMuts = {};
const subscr: ObservabilitySet = {};
const ret = execute(subscr);
if (!startedListening) {
Dexie.on("txcommitted", mutationListener);
startedListening = true;
}
querying = true;
Promise.resolve(ret).then(
(result) => {
querying = false;
if (closed) return;
if (shouldNotify()) {
// Mutations has happened while we were querying. Redo query.
doQuery();
} else {
accumMuts = {};
// Update what we are subscribing for based on this last run:
currentObs = subscr;
observer.next && observer.next(result);
}
},
(err) => {
querying = false;
observer.error && observer.error(err);
subscription.unsubscribe();
}
);
};

doQuery();
return subscription;
});
}
18 changes: 18 additions & 0 deletions libs/dexie-live-query/test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>live-query unit tests</title>
<link rel="stylesheet" href="../../../node_modules/qunitjs/qunit/qunit.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="../../../test/babel-polyfill/polyfill.min.js"></script>
<script src="../../../node_modules/qunitjs/qunit/qunit.js"></script>
<script src="../../../node_modules/rxjs/bundles/rxjs.umd.js"></script>
<script src="../../../dist/dexie.js"></script>
<script src="../dist/dexie-live-query.js"></script>
<script src="bundle.js"></script>
</body>
</html>
1 change: 1 addition & 0 deletions libs/dexie-live-query/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./test-live-query.js";
31 changes: 31 additions & 0 deletions libs/dexie-live-query/test/karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Include common configuration
const {karmaCommon, getKarmaConfig, defaultBrowserMatrix} = require('../../../test/karma.common');

module.exports = function (config) {
const cfg = getKarmaConfig({
// Be fine with testing on local travis firefox
ci: [
"Firefox",
"bs_chrome_latest_supported",
"bs_safari_latest_supported"
],
pre_npm_publish: [
"Firefox",
"bs_chrome_latest_supported",
"bs_safari_latest_supported"
]
}, {
// Base path should point at the root
basePath: '../../../',
files: karmaCommon.files.concat([
'node_modules/rxjs/bundles/rxjs.umd.js',
'dist/dexie.js',
'libs/dexie-live-query/dist/dexie-live-query.js',
'libs/dexie-live-query/test/bundle.js',
{ pattern: 'libs/dexie-live-query/test/*.map', watched: false, included: false },
{ pattern: 'libs/dexie-live-query/dist/*.map', watched: false, included: false }
])
});

config.set(cfg);
}