@instantdb/core
Advanced tools
Comparing version 0.5.2 to 0.5.3
@@ -32,4 +32,4 @@ "use strict"; | ||
// ----------------- | ||
function makeVar(x) { | ||
return `?${x}`; | ||
function makeVar(x, level) { | ||
return `?${x}-${level}`; | ||
} | ||
@@ -44,4 +44,4 @@ function ignoredAttr(attrs, id) { | ||
// ----------------- | ||
function defaultWhere(store, ns) { | ||
return [eidWhere(store, ns), attrWhere(ns)]; | ||
function defaultWhere(store, etype, level) { | ||
return [eidWhere(store, etype, level), attrWhere(etype, level)]; | ||
} | ||
@@ -55,7 +55,7 @@ function idAttr(store, ns) { | ||
} | ||
function eidWhere(store, ns) { | ||
return [makeVar(ns), idAttr(store, ns).id, makeVar(ns)]; | ||
function eidWhere(store, etype, level) { | ||
return [makeVar(etype, level), idAttr(store, etype).id, makeVar(etype, level)]; | ||
} | ||
function attrWhere(ns) { | ||
return [makeVar(ns), ATTR_VAR, VAL_VAR]; | ||
function attrWhere(etype, level) { | ||
return [makeVar(etype, level), ATTR_VAR, VAL_VAR]; | ||
} | ||
@@ -65,3 +65,3 @@ function replaceInAttrPat(attrPat, needle, v) { | ||
} | ||
function refAttrPat(store, etype, label) { | ||
function refAttrPat(store, etype, level, label) { | ||
const fwdAttr = getAttrByFwdIdentName(store.attrs, etype, label); | ||
@@ -78,7 +78,10 @@ const revAttr = getAttrByReverseIdentName(store.attrs, etype, label); | ||
const [_r, revEtype] = attr["reverse-identity"]; | ||
const attrPat = [makeVar(fwdEtype), attr.id, makeVar(revEtype)]; | ||
const nextLevel = level + 1; | ||
const attrPat = fwdAttr | ||
? [makeVar(fwdEtype, level), attr.id, makeVar(revEtype, nextLevel)] | ||
: [makeVar(fwdEtype, nextLevel), attr.id, makeVar(revEtype, level)]; | ||
const nextEtype = fwdAttr ? revEtype : fwdEtype; | ||
return [nextEtype, attrPat]; | ||
return [nextEtype, nextLevel, attrPat]; | ||
} | ||
function valueAttrPat(store, valueEtype, valueLabel, v) { | ||
function valueAttrPat(store, valueEtype, valueLevel, valueLabel, v) { | ||
const attr = getAttrByFwdIdentName(store.attrs, valueEtype, valueLabel); | ||
@@ -88,40 +91,40 @@ if (!attr) { | ||
} | ||
return [makeVar(valueEtype), attr.id, v]; | ||
return [makeVar(valueEtype, valueLevel), attr.id, v]; | ||
} | ||
function refAttrPats(store, etype, refsPath) { | ||
const [lastEtype, attrPats] = refsPath.reduce((acc, label) => { | ||
const [etype, attrPats] = acc; | ||
const [nextEtype, attrPat] = refAttrPat(store, etype, label); | ||
return [nextEtype, [...attrPats, attrPat]]; | ||
}, [etype, []]); | ||
return [lastEtype, attrPats]; | ||
function refAttrPats(store, etype, level, refsPath) { | ||
const [lastEtype, lastLevel, attrPats] = refsPath.reduce((acc, label) => { | ||
const [etype, level, attrPats] = acc; | ||
const [nextEtype, nextLevel, attrPat] = refAttrPat(store, etype, level, label); | ||
return [nextEtype, nextLevel, [...attrPats, attrPat]]; | ||
}, [etype, level, []]); | ||
return [lastEtype, lastLevel, attrPats]; | ||
} | ||
function whereCondAttrPats(store, etype, path, v) { | ||
function whereCondAttrPats(store, etype, level, path, v) { | ||
const refsPath = path.slice(0, path.length - 1); | ||
const valueLabel = path[path.length - 1]; | ||
const [lastEtype, refPats] = refAttrPats(store, etype, refsPath); | ||
const valuePat = valueAttrPat(store, lastEtype, valueLabel, v); | ||
const [lastEtype, lastLevel, refPats] = refAttrPats(store, etype, level, refsPath); | ||
const valuePat = valueAttrPat(store, lastEtype, lastLevel, valueLabel, v); | ||
return refPats.concat([valuePat]); | ||
} | ||
function makeWhere(store, ns, where) { | ||
function makeWhere(store, etype, level, where) { | ||
if (!where) { | ||
return defaultWhere(store, ns); | ||
return defaultWhere(store, etype, level); | ||
} | ||
const parsedWhere = Object.entries(where).flatMap(([k, v]) => { | ||
const path = k.split("."); | ||
return whereCondAttrPats(store, ns, path, v); | ||
return whereCondAttrPats(store, etype, level, path, v); | ||
}); | ||
return parsedWhere.concat(defaultWhere(store, ns)); | ||
return parsedWhere.concat(defaultWhere(store, etype, level)); | ||
} | ||
// Find | ||
// ----------------- | ||
function makeFind(ns) { | ||
return [makeVar(ns), ATTR_VAR, VAL_VAR]; | ||
function makeFind(etype, level) { | ||
return [makeVar(etype, level), ATTR_VAR, VAL_VAR]; | ||
} | ||
// Relations | ||
// ----------------- | ||
function makeJoin(store, etype, label, eid) { | ||
const [nextEtype, pat] = refAttrPat(store, etype, label); | ||
const actualized = replaceInAttrPat(pat, makeVar(etype), eid); | ||
return [nextEtype, actualized]; | ||
function makeJoin(store, etype, level, label, eid) { | ||
const [nextEtype, nextLevel, pat] = refAttrPat(store, etype, level, label); | ||
const actualized = replaceInAttrPat(pat, makeVar(etype, level), eid); | ||
return [nextEtype, nextLevel, actualized]; | ||
} | ||
@@ -145,9 +148,9 @@ function withJoin(where, join) { | ||
} | ||
function queryParents(store, { etype, form, join }) { | ||
function queryParents(store, { etype, level, form, join }) { | ||
var _a; | ||
const where = withJoin(makeWhere(store, etype, (_a = form.$) === null || _a === void 0 ? void 0 : _a.where), join); | ||
const find = makeFind(etype); | ||
const where = withJoin(makeWhere(store, etype, level, (_a = form.$) === null || _a === void 0 ? void 0 : _a.where), join); | ||
const find = makeFind(etype, level); | ||
return runDatalog(store, { where, find }); | ||
} | ||
function guaredQueryParents(store, opts) { | ||
function guardedQueryParents(store, opts) { | ||
try { | ||
@@ -163,3 +166,3 @@ return queryParents(store, opts); | ||
} | ||
function extendParents(store, { etype, form }, parents) { | ||
function extendParents(store, { etype, level, form }, parents) { | ||
const children = Object.keys(form).filter((c) => c !== "$"); | ||
@@ -176,4 +179,9 @@ if (!children.length) { | ||
try { | ||
const [nextEtype, join] = makeJoin(store, etype, label, eid); | ||
const child = queryOne(store, { etype: nextEtype, form: form[label], join }); | ||
const [nextEtype, nextLevel, join] = makeJoin(store, etype, level, label, eid); | ||
const child = queryOne(store, { | ||
etype: nextEtype, | ||
level: nextLevel, | ||
form: form[label], | ||
join | ||
}); | ||
return { [label]: child }; | ||
@@ -194,3 +202,3 @@ } | ||
function queryOne(store, opts) { | ||
const parents = guaredQueryParents(store, opts); | ||
const parents = guardedQueryParents(store, opts); | ||
return extendParents(store, opts, parents); | ||
@@ -200,3 +208,3 @@ } | ||
return Object.keys(q).reduce((res, k) => { | ||
res[k] = queryOne(store, { etype: k, form: q[k] }); | ||
res[k] = queryOne(store, { etype: k, form: q[k], level: 0 }); | ||
return res; | ||
@@ -203,0 +211,0 @@ }, {}); |
@@ -30,4 +30,4 @@ import { query as datalogQuery } from "./datalog"; | ||
// ----------------- | ||
function makeVar(x) { | ||
return `?${x}`; | ||
function makeVar(x, level) { | ||
return `?${x}-${level}`; | ||
} | ||
@@ -42,4 +42,4 @@ function ignoredAttr(attrs, id) { | ||
// ----------------- | ||
function defaultWhere(store, ns) { | ||
return [eidWhere(store, ns), attrWhere(ns)]; | ||
function defaultWhere(store, etype, level) { | ||
return [eidWhere(store, etype, level), attrWhere(etype, level)]; | ||
} | ||
@@ -53,7 +53,7 @@ function idAttr(store, ns) { | ||
} | ||
function eidWhere(store, ns) { | ||
return [makeVar(ns), idAttr(store, ns).id, makeVar(ns)]; | ||
function eidWhere(store, etype, level) { | ||
return [makeVar(etype, level), idAttr(store, etype).id, makeVar(etype, level)]; | ||
} | ||
function attrWhere(ns) { | ||
return [makeVar(ns), ATTR_VAR, VAL_VAR]; | ||
function attrWhere(etype, level) { | ||
return [makeVar(etype, level), ATTR_VAR, VAL_VAR]; | ||
} | ||
@@ -63,3 +63,3 @@ function replaceInAttrPat(attrPat, needle, v) { | ||
} | ||
function refAttrPat(store, etype, label) { | ||
function refAttrPat(store, etype, level, label) { | ||
const fwdAttr = getAttrByFwdIdentName(store.attrs, etype, label); | ||
@@ -76,7 +76,10 @@ const revAttr = getAttrByReverseIdentName(store.attrs, etype, label); | ||
const [_r, revEtype] = attr["reverse-identity"]; | ||
const attrPat = [makeVar(fwdEtype), attr.id, makeVar(revEtype)]; | ||
const nextLevel = level + 1; | ||
const attrPat = fwdAttr | ||
? [makeVar(fwdEtype, level), attr.id, makeVar(revEtype, nextLevel)] | ||
: [makeVar(fwdEtype, nextLevel), attr.id, makeVar(revEtype, level)]; | ||
const nextEtype = fwdAttr ? revEtype : fwdEtype; | ||
return [nextEtype, attrPat]; | ||
return [nextEtype, nextLevel, attrPat]; | ||
} | ||
function valueAttrPat(store, valueEtype, valueLabel, v) { | ||
function valueAttrPat(store, valueEtype, valueLevel, valueLabel, v) { | ||
const attr = getAttrByFwdIdentName(store.attrs, valueEtype, valueLabel); | ||
@@ -86,40 +89,40 @@ if (!attr) { | ||
} | ||
return [makeVar(valueEtype), attr.id, v]; | ||
return [makeVar(valueEtype, valueLevel), attr.id, v]; | ||
} | ||
function refAttrPats(store, etype, refsPath) { | ||
const [lastEtype, attrPats] = refsPath.reduce((acc, label) => { | ||
const [etype, attrPats] = acc; | ||
const [nextEtype, attrPat] = refAttrPat(store, etype, label); | ||
return [nextEtype, [...attrPats, attrPat]]; | ||
}, [etype, []]); | ||
return [lastEtype, attrPats]; | ||
function refAttrPats(store, etype, level, refsPath) { | ||
const [lastEtype, lastLevel, attrPats] = refsPath.reduce((acc, label) => { | ||
const [etype, level, attrPats] = acc; | ||
const [nextEtype, nextLevel, attrPat] = refAttrPat(store, etype, level, label); | ||
return [nextEtype, nextLevel, [...attrPats, attrPat]]; | ||
}, [etype, level, []]); | ||
return [lastEtype, lastLevel, attrPats]; | ||
} | ||
function whereCondAttrPats(store, etype, path, v) { | ||
function whereCondAttrPats(store, etype, level, path, v) { | ||
const refsPath = path.slice(0, path.length - 1); | ||
const valueLabel = path[path.length - 1]; | ||
const [lastEtype, refPats] = refAttrPats(store, etype, refsPath); | ||
const valuePat = valueAttrPat(store, lastEtype, valueLabel, v); | ||
const [lastEtype, lastLevel, refPats] = refAttrPats(store, etype, level, refsPath); | ||
const valuePat = valueAttrPat(store, lastEtype, lastLevel, valueLabel, v); | ||
return refPats.concat([valuePat]); | ||
} | ||
function makeWhere(store, ns, where) { | ||
function makeWhere(store, etype, level, where) { | ||
if (!where) { | ||
return defaultWhere(store, ns); | ||
return defaultWhere(store, etype, level); | ||
} | ||
const parsedWhere = Object.entries(where).flatMap(([k, v]) => { | ||
const path = k.split("."); | ||
return whereCondAttrPats(store, ns, path, v); | ||
return whereCondAttrPats(store, etype, level, path, v); | ||
}); | ||
return parsedWhere.concat(defaultWhere(store, ns)); | ||
return parsedWhere.concat(defaultWhere(store, etype, level)); | ||
} | ||
// Find | ||
// ----------------- | ||
function makeFind(ns) { | ||
return [makeVar(ns), ATTR_VAR, VAL_VAR]; | ||
function makeFind(etype, level) { | ||
return [makeVar(etype, level), ATTR_VAR, VAL_VAR]; | ||
} | ||
// Relations | ||
// ----------------- | ||
function makeJoin(store, etype, label, eid) { | ||
const [nextEtype, pat] = refAttrPat(store, etype, label); | ||
const actualized = replaceInAttrPat(pat, makeVar(etype), eid); | ||
return [nextEtype, actualized]; | ||
function makeJoin(store, etype, level, label, eid) { | ||
const [nextEtype, nextLevel, pat] = refAttrPat(store, etype, level, label); | ||
const actualized = replaceInAttrPat(pat, makeVar(etype, level), eid); | ||
return [nextEtype, nextLevel, actualized]; | ||
} | ||
@@ -143,9 +146,9 @@ function withJoin(where, join) { | ||
} | ||
function queryParents(store, { etype, form, join }) { | ||
function queryParents(store, { etype, level, form, join }) { | ||
var _a; | ||
const where = withJoin(makeWhere(store, etype, (_a = form.$) === null || _a === void 0 ? void 0 : _a.where), join); | ||
const find = makeFind(etype); | ||
const where = withJoin(makeWhere(store, etype, level, (_a = form.$) === null || _a === void 0 ? void 0 : _a.where), join); | ||
const find = makeFind(etype, level); | ||
return runDatalog(store, { where, find }); | ||
} | ||
function guaredQueryParents(store, opts) { | ||
function guardedQueryParents(store, opts) { | ||
try { | ||
@@ -161,3 +164,3 @@ return queryParents(store, opts); | ||
} | ||
function extendParents(store, { etype, form }, parents) { | ||
function extendParents(store, { etype, level, form }, parents) { | ||
const children = Object.keys(form).filter((c) => c !== "$"); | ||
@@ -174,4 +177,9 @@ if (!children.length) { | ||
try { | ||
const [nextEtype, join] = makeJoin(store, etype, label, eid); | ||
const child = queryOne(store, { etype: nextEtype, form: form[label], join }); | ||
const [nextEtype, nextLevel, join] = makeJoin(store, etype, level, label, eid); | ||
const child = queryOne(store, { | ||
etype: nextEtype, | ||
level: nextLevel, | ||
form: form[label], | ||
join | ||
}); | ||
return { [label]: child }; | ||
@@ -192,3 +200,3 @@ } | ||
function queryOne(store, opts) { | ||
const parents = guaredQueryParents(store, opts); | ||
const parents = guardedQueryParents(store, opts); | ||
return extendParents(store, opts, parents); | ||
@@ -198,3 +206,3 @@ } | ||
return Object.keys(q).reduce((res, k) => { | ||
res[k] = queryOne(store, { etype: k, form: q[k] }); | ||
res[k] = queryOne(store, { etype: k, form: q[k], level: 0 }); | ||
return res; | ||
@@ -201,0 +209,0 @@ }, {}); |
@@ -19,3 +19,2 @@ export default class ReactiveDB { | ||
* effects: | ||
* - Send add query for querySubs in storage but not in memory | ||
* - We notify all queryCbs because results may been added during merge | ||
@@ -56,2 +55,3 @@ */ | ||
notifyOne: (hash: any) => void; | ||
notifyQueryError: (hash: any, msg: any) => void; | ||
/** Re-compute all subscriptions */ | ||
@@ -58,0 +58,0 @@ notifyAll(): void; |
@@ -56,2 +56,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
} | ||
// PersistedObjects save data outside of memory | ||
// When we load a persisted object, it's possible we call `set` | ||
// before we finish loading. To address we handle set in two ways: | ||
// | ||
// 1. Before load | ||
// We simply update currentValue in memory | ||
// | ||
// 2. After load | ||
// We update currentValue in memory and in storage | ||
// | ||
// Each PersistedObject provides it's own `onMerge` | ||
// function to handle the merge of data from storage and memory | ||
// on load | ||
class PersistedObject { | ||
@@ -105,3 +118,2 @@ constructor(persister, key, defaultValue, onMerge, toJSON = (x) => { | ||
* effects: | ||
* - Send add query for querySubs in storage but not in memory | ||
* - We notify all queryCbs because results may been added during merge | ||
@@ -140,8 +152,2 @@ */ | ||
this.querySubs.set((_) => ret); | ||
// Since these subs have not been subscribed before, | ||
// we nede to send an add-query message to the server | ||
storageKsToAdd.forEach((k) => { | ||
const { q, eventId } = ret[k]; | ||
this._send(eventId, { op: "add-query", q }); | ||
}); | ||
this.notifyAll(); | ||
@@ -172,2 +178,6 @@ }; | ||
}; | ||
this.notifyQueryError = (hash, msg) => { | ||
const cbs = this.queryCbs[hash] || []; | ||
cbs.forEach((cb) => cb({ error: msg })); | ||
}; | ||
this._wsOnOpen = () => { | ||
@@ -254,2 +264,3 @@ log.info("[socket] connected"); | ||
_handleReceive(msg) { | ||
var _a; | ||
switch (msg.op) { | ||
@@ -315,3 +326,4 @@ case "init-ok": | ||
// (XXX): Error handling is spaghetti right now. | ||
const { "client-event-id": errorEventId, q: errorQ } = msg; | ||
const errorQ = msg.q || ((_a = msg["original-event"]) === null || _a === void 0 ? void 0 : _a.q); | ||
const { "client-event-id": errorEventId } = msg; | ||
const errorObj = Object.assign({}, msg); | ||
@@ -332,3 +344,3 @@ delete errorObj.message; | ||
} | ||
if (errorQ && msg.status === 400) { | ||
if (errorQ) { | ||
// This is a hack. | ||
@@ -344,2 +356,3 @@ // Imagine a user writes a malformed query. | ||
}); | ||
this.notifyQueryError(weakHash(errorQ), { message: msg.message || "Uh-oh, something went wrong. Ping Joe & Stopa." }); | ||
} | ||
@@ -375,2 +388,7 @@ break; | ||
this.queryCbs[hash].push(cb); | ||
this.querySubs.set((prev) => { | ||
prev[hash] = prev[hash] || { q, result: null, eventId }; | ||
return prev; | ||
}); | ||
this._send(eventId, { op: "add-query", q }); | ||
const prevResult = (_b = (_a = this.querySubs.currentValue) === null || _a === void 0 ? void 0 : _a[hash]) === null || _b === void 0 ? void 0 : _b.result; | ||
@@ -380,7 +398,2 @@ if (prevResult) { | ||
} | ||
this._send(eventId, { op: "add-query", q }); | ||
this.querySubs.set((prev) => { | ||
prev[hash] = prev[hash] || { q, result: null, eventId }; | ||
return prev; | ||
}); | ||
return () => { | ||
@@ -511,3 +524,9 @@ this.queryCbs[hash] = this.queryCbs[hash].filter((x) => x !== cb); | ||
_flushPendingMessages() { | ||
Object.values(this.querySubs.currentValue).forEach(({ eventId, q }) => { | ||
const subs = Object.keys(this.queryCbs).map((hash) => { | ||
return this.querySubs.currentValue[hash]; | ||
}); | ||
// Note: we should not have any nulls in subs, but we're | ||
// doing this defensively just in case. | ||
const safeSubs = subs.filter(x => x); | ||
safeSubs.forEach(({ eventId, q }) => { | ||
this._send(eventId, { op: "add-query", q }); | ||
@@ -597,2 +616,3 @@ }); | ||
yield this.setCurrentUser(newUser); | ||
this.querySubs.set((_) => ({})); | ||
this._reconnectTimeoutMs = 0; | ||
@@ -599,0 +619,0 @@ this._ws.close(); |
@@ -19,3 +19,2 @@ export default class ReactiveDB { | ||
* effects: | ||
* - Send add query for querySubs in storage but not in memory | ||
* - We notify all queryCbs because results may been added during merge | ||
@@ -56,2 +55,3 @@ */ | ||
notifyOne: (hash: any) => void; | ||
notifyQueryError: (hash: any, msg: any) => void; | ||
/** Re-compute all subscriptions */ | ||
@@ -58,0 +58,0 @@ notifyAll(): void; |
@@ -84,2 +84,15 @@ "use strict"; | ||
} | ||
// PersistedObjects save data outside of memory | ||
// When we load a persisted object, it's possible we call `set` | ||
// before we finish loading. To address we handle set in two ways: | ||
// | ||
// 1. Before load | ||
// We simply update currentValue in memory | ||
// | ||
// 2. After load | ||
// We update currentValue in memory and in storage | ||
// | ||
// Each PersistedObject provides it's own `onMerge` | ||
// function to handle the merge of data from storage and memory | ||
// on load | ||
class PersistedObject { | ||
@@ -133,3 +146,2 @@ constructor(persister, key, defaultValue, onMerge, toJSON = (x) => { | ||
* effects: | ||
* - Send add query for querySubs in storage but not in memory | ||
* - We notify all queryCbs because results may been added during merge | ||
@@ -168,8 +180,2 @@ */ | ||
this.querySubs.set((_) => ret); | ||
// Since these subs have not been subscribed before, | ||
// we nede to send an add-query message to the server | ||
storageKsToAdd.forEach((k) => { | ||
const { q, eventId } = ret[k]; | ||
this._send(eventId, { op: "add-query", q }); | ||
}); | ||
this.notifyAll(); | ||
@@ -200,2 +206,6 @@ }; | ||
}; | ||
this.notifyQueryError = (hash, msg) => { | ||
const cbs = this.queryCbs[hash] || []; | ||
cbs.forEach((cb) => cb({ error: msg })); | ||
}; | ||
this._wsOnOpen = () => { | ||
@@ -282,2 +292,3 @@ log_1.default.info("[socket] connected"); | ||
_handleReceive(msg) { | ||
var _a; | ||
switch (msg.op) { | ||
@@ -343,3 +354,4 @@ case "init-ok": | ||
// (XXX): Error handling is spaghetti right now. | ||
const { "client-event-id": errorEventId, q: errorQ } = msg; | ||
const errorQ = msg.q || ((_a = msg["original-event"]) === null || _a === void 0 ? void 0 : _a.q); | ||
const { "client-event-id": errorEventId } = msg; | ||
const errorObj = Object.assign({}, msg); | ||
@@ -360,3 +372,3 @@ delete errorObj.message; | ||
} | ||
if (errorQ && msg.status === 400) { | ||
if (errorQ) { | ||
// This is a hack. | ||
@@ -372,2 +384,3 @@ // Imagine a user writes a malformed query. | ||
}); | ||
this.notifyQueryError((0, weakHash_1.default)(errorQ), { message: msg.message || "Uh-oh, something went wrong. Ping Joe & Stopa." }); | ||
} | ||
@@ -403,2 +416,7 @@ break; | ||
this.queryCbs[hash].push(cb); | ||
this.querySubs.set((prev) => { | ||
prev[hash] = prev[hash] || { q, result: null, eventId }; | ||
return prev; | ||
}); | ||
this._send(eventId, { op: "add-query", q }); | ||
const prevResult = (_b = (_a = this.querySubs.currentValue) === null || _a === void 0 ? void 0 : _a[hash]) === null || _b === void 0 ? void 0 : _b.result; | ||
@@ -408,7 +426,2 @@ if (prevResult) { | ||
} | ||
this._send(eventId, { op: "add-query", q }); | ||
this.querySubs.set((prev) => { | ||
prev[hash] = prev[hash] || { q, result: null, eventId }; | ||
return prev; | ||
}); | ||
return () => { | ||
@@ -539,3 +552,9 @@ this.queryCbs[hash] = this.queryCbs[hash].filter((x) => x !== cb); | ||
_flushPendingMessages() { | ||
Object.values(this.querySubs.currentValue).forEach(({ eventId, q }) => { | ||
const subs = Object.keys(this.queryCbs).map((hash) => { | ||
return this.querySubs.currentValue[hash]; | ||
}); | ||
// Note: we should not have any nulls in subs, but we're | ||
// doing this defensively just in case. | ||
const safeSubs = subs.filter(x => x); | ||
safeSubs.forEach(({ eventId, q }) => { | ||
this._send(eventId, { op: "add-query", q }); | ||
@@ -625,2 +644,3 @@ }); | ||
yield this.setCurrentUser(newUser); | ||
this.querySubs.set((_) => ({})); | ||
this._reconnectTimeoutMs = 0; | ||
@@ -627,0 +647,0 @@ this._ws.close(); |
{ | ||
"name": "@instantdb/core", | ||
"version": "0.5.2", | ||
"version": "0.5.3", | ||
"description": "Instant's core local abstraction", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -36,4 +36,4 @@ import { query as datalogQuery } from "./datalog"; | ||
function makeVar(x) { | ||
return `?${x}`; | ||
function makeVar(x, level) { | ||
return `?${x}-${level}`; | ||
} | ||
@@ -53,4 +53,4 @@ | ||
function defaultWhere(store, ns) { | ||
return [eidWhere(store, ns), attrWhere(ns)]; | ||
function defaultWhere(store, etype, level) { | ||
return [eidWhere(store, etype, level), attrWhere(etype, level)]; | ||
} | ||
@@ -66,8 +66,8 @@ | ||
function eidWhere(store, ns) { | ||
return [makeVar(ns), idAttr(store, ns).id, makeVar(ns)]; | ||
function eidWhere(store, etype, level) { | ||
return [makeVar(etype, level), idAttr(store, etype).id, makeVar(etype, level)]; | ||
} | ||
function attrWhere(ns) { | ||
return [makeVar(ns), ATTR_VAR, VAL_VAR]; | ||
function attrWhere(etype, level) { | ||
return [makeVar(etype, level), ATTR_VAR, VAL_VAR]; | ||
} | ||
@@ -79,3 +79,3 @@ | ||
function refAttrPat(store, etype, label) { | ||
function refAttrPat(store, etype, level, label) { | ||
const fwdAttr = getAttrByFwdIdentName(store.attrs, etype, label); | ||
@@ -95,9 +95,13 @@ const revAttr = getAttrByReverseIdentName(store.attrs, etype, label); | ||
const [_r, revEtype] = attr["reverse-identity"]; | ||
const attrPat = [makeVar(fwdEtype), attr.id, makeVar(revEtype)]; | ||
const nextLevel = level + 1; | ||
const attrPat = fwdAttr | ||
? [makeVar(fwdEtype, level), attr.id, makeVar(revEtype, nextLevel)] | ||
: [makeVar(fwdEtype, nextLevel), attr.id, makeVar(revEtype, level)]; | ||
const nextEtype = fwdAttr ? revEtype : fwdEtype; | ||
return [nextEtype, attrPat]; | ||
return [nextEtype, nextLevel, attrPat]; | ||
} | ||
function valueAttrPat(store, valueEtype, valueLabel, v) { | ||
function valueAttrPat(store, valueEtype, valueLevel, valueLabel, v) { | ||
const attr = getAttrByFwdIdentName(store.attrs, valueEtype, valueLabel); | ||
@@ -111,23 +115,23 @@ | ||
return [makeVar(valueEtype), attr.id, v]; | ||
return [makeVar(valueEtype, valueLevel), attr.id, v]; | ||
} | ||
function refAttrPats(store, etype, refsPath) { | ||
const [lastEtype, attrPats] = refsPath.reduce( | ||
function refAttrPats(store, etype, level, refsPath) { | ||
const [lastEtype, lastLevel, attrPats] = refsPath.reduce( | ||
(acc, label) => { | ||
const [etype, attrPats] = acc; | ||
const [nextEtype, attrPat] = refAttrPat(store, etype, label); | ||
return [nextEtype, [...attrPats, attrPat]]; | ||
const [etype, level, attrPats] = acc; | ||
const [nextEtype, nextLevel, attrPat] = refAttrPat(store, etype, level, label); | ||
return [nextEtype, nextLevel, [...attrPats, attrPat]]; | ||
}, | ||
[etype, []], | ||
[etype, level, []], | ||
); | ||
return [lastEtype, attrPats]; | ||
return [lastEtype, lastLevel, attrPats]; | ||
} | ||
function whereCondAttrPats(store, etype, path, v) { | ||
function whereCondAttrPats(store, etype, level, path, v) { | ||
const refsPath = path.slice(0, path.length - 1); | ||
const valueLabel = path[path.length - 1]; | ||
const [lastEtype, refPats] = refAttrPats(store, etype, refsPath); | ||
const valuePat = valueAttrPat(store, lastEtype, valueLabel, v); | ||
const [lastEtype, lastLevel, refPats] = refAttrPats(store, etype, level, refsPath); | ||
const valuePat = valueAttrPat(store, lastEtype, lastLevel, valueLabel, v); | ||
@@ -137,11 +141,11 @@ return refPats.concat([valuePat]); | ||
function makeWhere(store, ns, where) { | ||
function makeWhere(store, etype, level, where) { | ||
if (!where) { | ||
return defaultWhere(store, ns); | ||
return defaultWhere(store, etype, level); | ||
} | ||
const parsedWhere = Object.entries(where).flatMap(([k, v]) => { | ||
const path = k.split("."); | ||
return whereCondAttrPats(store, ns, path, v); | ||
return whereCondAttrPats(store, etype, level, path, v); | ||
}); | ||
return parsedWhere.concat(defaultWhere(store, ns)); | ||
return parsedWhere.concat(defaultWhere(store, etype, level)); | ||
} | ||
@@ -152,4 +156,4 @@ | ||
function makeFind(ns) { | ||
return [makeVar(ns), ATTR_VAR, VAL_VAR]; | ||
function makeFind(etype, level) { | ||
return [makeVar(etype, level), ATTR_VAR, VAL_VAR]; | ||
} | ||
@@ -160,6 +164,6 @@ | ||
function makeJoin(store, etype, label, eid) { | ||
const [nextEtype, pat] = refAttrPat(store, etype, label); | ||
const actualized = replaceInAttrPat(pat, makeVar(etype), eid); | ||
return [nextEtype, actualized]; | ||
function makeJoin(store, etype, level, label, eid) { | ||
const [nextEtype, nextLevel, pat] = refAttrPat(store, etype, level, label); | ||
const actualized = replaceInAttrPat(pat, makeVar(etype, level), eid); | ||
return [nextEtype, nextLevel, actualized]; | ||
} | ||
@@ -187,9 +191,9 @@ | ||
function queryParents(store, { etype, form, join }) { | ||
const where = withJoin(makeWhere(store, etype, form.$?.where), join); | ||
const find = makeFind(etype); | ||
function queryParents(store, { etype, level, form, join }) { | ||
const where = withJoin(makeWhere(store, etype, level, form.$?.where), join); | ||
const find = makeFind(etype, level); | ||
return runDatalog(store, { where, find }); | ||
} | ||
function guaredQueryParents(store, opts) { | ||
function guardedQueryParents(store, opts) { | ||
try { | ||
@@ -205,3 +209,3 @@ return queryParents(store, opts); | ||
function extendParents(store, { etype, form }, parents) { | ||
function extendParents(store, { etype, level, form }, parents) { | ||
const children = Object.keys(form).filter((c) => c !== "$"); | ||
@@ -219,4 +223,9 @@ if (!children.length) { | ||
try { | ||
const [nextEtype, join] = makeJoin(store, etype, label, eid); | ||
const child = queryOne(store, { etype: nextEtype, form: form[label], join }); | ||
const [nextEtype, nextLevel, join] = makeJoin(store, etype, level, label, eid); | ||
const child = queryOne(store, { | ||
etype: nextEtype, | ||
level: nextLevel, | ||
form: form[label], | ||
join | ||
}); | ||
return { [label]: child }; | ||
@@ -238,3 +247,3 @@ } catch (e) { | ||
function queryOne(store, opts) { | ||
const parents = guaredQueryParents(store, opts); | ||
const parents = guardedQueryParents(store, opts); | ||
return extendParents(store, opts, parents); | ||
@@ -245,5 +254,5 @@ } | ||
return Object.keys(q).reduce((res, k) => { | ||
res[k] = queryOne(store, { etype: k, form: q[k] }); | ||
res[k] = queryOne(store, { etype: k, form: q[k], level: 0 }); | ||
return res; | ||
}, {}); | ||
} |
@@ -50,2 +50,15 @@ import log from "./utils/log"; | ||
// PersistedObjects save data outside of memory | ||
// When we load a persisted object, it's possible we call `set` | ||
// before we finish loading. To address we handle set in two ways: | ||
// | ||
// 1. Before load | ||
// We simply update currentValue in memory | ||
// | ||
// 2. After load | ||
// We update currentValue in memory and in storage | ||
// | ||
// Each PersistedObject provides it's own `onMerge` | ||
// function to handle the merge of data from storage and memory | ||
// on load | ||
class PersistedObject { | ||
@@ -169,3 +182,2 @@ constructor( | ||
* effects: | ||
* - Send add query for querySubs in storage but not in memory | ||
* - We notify all queryCbs because results may been added during merge | ||
@@ -208,9 +220,2 @@ */ | ||
// Since these subs have not been subscribed before, | ||
// we nede to send an add-query message to the server | ||
storageKsToAdd.forEach((k) => { | ||
const { q, eventId } = ret[k]; | ||
this._send(eventId, { op: "add-query", q }); | ||
}); | ||
this.notifyAll(); | ||
@@ -318,3 +323,4 @@ }; | ||
// (XXX): Error handling is spaghetti right now. | ||
const { "client-event-id": errorEventId, q: errorQ } = msg; | ||
const errorQ = msg.q || msg["original-event"]?.q; | ||
const { "client-event-id": errorEventId } = msg; | ||
const errorObj = { ...msg }; | ||
@@ -339,3 +345,3 @@ delete errorObj.message; | ||
} | ||
if (errorQ && msg.status === 400) { | ||
if (errorQ) { | ||
// This is a hack. | ||
@@ -351,2 +357,3 @@ // Imagine a user writes a malformed query. | ||
}); | ||
this.notifyQueryError(weakHash(errorQ), { message: msg.message || "Uh-oh, something went wrong. Ping Joe & Stopa." }); | ||
} | ||
@@ -382,4 +389,12 @@ break; | ||
const hash = weakHash(q); | ||
this.queryCbs[hash] = this.queryCbs[hash] || []; | ||
this.queryCbs[hash].push(cb); | ||
this.querySubs.set((prev) => { | ||
prev[hash] = prev[hash] || { q, result: null, eventId }; | ||
return prev; | ||
}); | ||
this._send(eventId, { op: "add-query", q }); | ||
const prevResult = this.querySubs.currentValue?.[hash]?.result; | ||
@@ -389,7 +404,3 @@ if (prevResult) { | ||
} | ||
this._send(eventId, { op: "add-query", q }); | ||
this.querySubs.set((prev) => { | ||
prev[hash] = prev[hash] || { q, result: null, eventId }; | ||
return prev; | ||
}); | ||
return () => { | ||
@@ -501,2 +512,8 @@ this.queryCbs[hash] = this.queryCbs[hash].filter((x) => x !== cb); | ||
notifyQueryError = (hash, msg) => { | ||
const cbs = this.queryCbs[hash] || []; | ||
cbs.forEach((cb) => cb({ error: msg })); | ||
}; | ||
/** Re-compute all subscriptions */ | ||
@@ -554,3 +571,9 @@ notifyAll() { | ||
_flushPendingMessages() { | ||
Object.values(this.querySubs.currentValue).forEach(({ eventId, q }) => { | ||
const subs = Object.keys(this.queryCbs).map((hash) => { | ||
return this.querySubs.currentValue[hash]; | ||
}) | ||
// Note: we should not have any nulls in subs, but we're | ||
// doing this defensively just in case. | ||
const safeSubs = subs.filter(x => x); | ||
safeSubs.forEach(({ eventId, q }) => { | ||
this._send(eventId, { op: "add-query", q }); | ||
@@ -678,2 +701,3 @@ }); | ||
await this.setCurrentUser(newUser); | ||
this.querySubs.set((_) => ({})); | ||
this._reconnectTimeoutMs = 0; | ||
@@ -680,0 +704,0 @@ this._ws.close(); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1034202
19834