@instantdb/core
Advanced tools
Comparing version 0.10.5 to 0.10.6
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const datalog_1 = require("./datalog"); | ||
class AttrNotFoundError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = "AttrNotFoundError"; | ||
} | ||
} | ||
// (XXX) copied | ||
function getAttrByFwdIdentName(attrs, inputEtype, inputIdentName) { | ||
@@ -16,2 +11,3 @@ return Object.values(attrs).find((attr) => { | ||
} | ||
// (XXX) copied | ||
function getAttrByReverseIdentName(attrs, inputEtype, inputIdentName) { | ||
@@ -26,3 +22,3 @@ return Object.values(attrs).find((attr) => { | ||
} | ||
// Parts | ||
// Pattern variables | ||
// ----------------- | ||
@@ -40,8 +36,10 @@ // (XXX) | ||
} | ||
function ignoredAttr(attrs, id) { | ||
const attr = attrs[id]; | ||
return attr["value-type"] === "ref" && attr["forward-identity"][2] !== "id"; | ||
} | ||
// Where | ||
// ----------------- | ||
class AttrNotFoundError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = "AttrNotFoundError"; | ||
} | ||
} | ||
function idAttr(store, ns) { | ||
@@ -127,2 +125,5 @@ const attr = getAttrByFwdIdentName(store.attrs, ns, "id"); | ||
} | ||
function withJoin(where, join) { | ||
return join ? [join].concat(where) : where; | ||
} | ||
function makeWhere(store, etype, level, where) { | ||
@@ -148,3 +149,3 @@ if (!where) { | ||
} | ||
// Relations | ||
// extendObjects | ||
// ----------------- | ||
@@ -156,8 +157,38 @@ function makeJoin(store, etype, level, label, eid) { | ||
} | ||
function withJoin(where, join) { | ||
return join ? [join].concat(where) : where; | ||
function extendObjects(store, { etype, level, form }, objects) { | ||
const children = Object.keys(form).filter((c) => c !== "$"); | ||
if (!children.length) { | ||
return Object.values(objects); | ||
} | ||
return Object.entries(objects).map(([eid, parent]) => { | ||
const childResults = children.map((label) => { | ||
try { | ||
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 }; | ||
} | ||
catch (e) { | ||
if (e instanceof AttrNotFoundError) { | ||
return { [label]: [] }; | ||
} | ||
throw e; | ||
} | ||
}); | ||
return childResults.reduce((parent, child) => { | ||
return Object.assign(Object.assign({}, parent), child); | ||
}, parent); | ||
}); | ||
} | ||
// Query | ||
// resolveObjects | ||
// ----------------- | ||
function runDatalog(store, dq) { | ||
function shouldIgnoreAttr(attrs, id) { | ||
const attr = attrs[id]; | ||
return attr["value-type"] === "ref" && attr["forward-identity"][2] !== "id"; | ||
} | ||
function runDataloadAndReturnObjects(store, dq) { | ||
return (0, datalog_1.query)(store, dq) | ||
@@ -170,3 +201,3 @@ .sort((tripleA, tripleB) => { | ||
.reduce((res, [e, a, v]) => { | ||
if (ignoredAttr(store.attrs, a)) { | ||
if (shouldIgnoreAttr(store.attrs, a)) { | ||
return res; | ||
@@ -181,11 +212,33 @@ } | ||
} | ||
function queryParents(store, { etype, level, form, join }) { | ||
/** | ||
* Given a query like: | ||
* | ||
* { | ||
* users: { | ||
* $: { where: { name: "Joe" } }, | ||
* }, | ||
* }; | ||
* | ||
* `resolveObjects`, turns where clause: `{ name: "Joe" }` | ||
* into a datalog query. We then run the datalog query, | ||
* and reduce all the triples into objects. | ||
*/ | ||
function resolveObjects(store, { etype, level, form, join }) { | ||
var _a; | ||
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 }); | ||
return runDataloadAndReturnObjects(store, { where, find }); | ||
} | ||
function guardedQueryParents(store, opts) { | ||
/** | ||
* It's possible that we query | ||
* for an attribute that doesn't exist yet. | ||
* | ||
* { users: { $: { where: { nonExistentProperty: "foo" } } } } | ||
* | ||
* This swallows the missing attr error and returns | ||
* an empty result instead | ||
*/ | ||
function guardedResolveObjects(store, opts) { | ||
try { | ||
return queryParents(store, opts); | ||
return resolveObjects(store, opts); | ||
} | ||
@@ -199,34 +252,18 @@ catch (e) { | ||
} | ||
function extendParents(store, { etype, level, form }, parents) { | ||
const children = Object.keys(form).filter((c) => c !== "$"); | ||
if (!children.length) { | ||
return Object.values(parents); | ||
} | ||
return Object.entries(parents).map(([eid, parent]) => { | ||
const childResults = children.map((label) => { | ||
try { | ||
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 }; | ||
} | ||
catch (e) { | ||
if (e instanceof AttrNotFoundError) { | ||
return { [label]: [] }; | ||
} | ||
throw e; | ||
} | ||
}); | ||
return childResults.reduce((parent, child) => { | ||
return Object.assign(Object.assign({}, parent), child); | ||
}, parent); | ||
}); | ||
} | ||
/** | ||
* Given a query like: | ||
* | ||
* { | ||
* users: { | ||
* $: { where: { name: "Joe" } }, | ||
* posts: {}, | ||
* }, | ||
* }; | ||
* | ||
* `guardResolveObjects` will return the relevant `users` objects | ||
* `extendObjects` will then extend each `user` object with relevant `posts`. | ||
*/ | ||
function queryOne(store, opts) { | ||
const parents = guardedQueryParents(store, opts); | ||
return extendParents(store, opts, parents); | ||
const objects = guardedResolveObjects(store, opts); | ||
return extendObjects(store, opts, objects); | ||
} | ||
@@ -233,0 +270,0 @@ function query(store, q) { |
import { query as datalogQuery } from "./datalog"; | ||
class AttrNotFoundError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = "AttrNotFoundError"; | ||
} | ||
} | ||
// (XXX) copied | ||
function getAttrByFwdIdentName(attrs, inputEtype, inputIdentName) { | ||
@@ -14,2 +9,3 @@ return Object.values(attrs).find((attr) => { | ||
} | ||
// (XXX) copied | ||
function getAttrByReverseIdentName(attrs, inputEtype, inputIdentName) { | ||
@@ -24,3 +20,3 @@ return Object.values(attrs).find((attr) => { | ||
} | ||
// Parts | ||
// Pattern variables | ||
// ----------------- | ||
@@ -38,8 +34,10 @@ // (XXX) | ||
} | ||
function ignoredAttr(attrs, id) { | ||
const attr = attrs[id]; | ||
return attr["value-type"] === "ref" && attr["forward-identity"][2] !== "id"; | ||
} | ||
// Where | ||
// ----------------- | ||
class AttrNotFoundError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = "AttrNotFoundError"; | ||
} | ||
} | ||
function idAttr(store, ns) { | ||
@@ -125,2 +123,5 @@ const attr = getAttrByFwdIdentName(store.attrs, ns, "id"); | ||
} | ||
function withJoin(where, join) { | ||
return join ? [join].concat(where) : where; | ||
} | ||
function makeWhere(store, etype, level, where) { | ||
@@ -146,3 +147,3 @@ if (!where) { | ||
} | ||
// Relations | ||
// extendObjects | ||
// ----------------- | ||
@@ -154,8 +155,38 @@ function makeJoin(store, etype, level, label, eid) { | ||
} | ||
function withJoin(where, join) { | ||
return join ? [join].concat(where) : where; | ||
function extendObjects(store, { etype, level, form }, objects) { | ||
const children = Object.keys(form).filter((c) => c !== "$"); | ||
if (!children.length) { | ||
return Object.values(objects); | ||
} | ||
return Object.entries(objects).map(([eid, parent]) => { | ||
const childResults = children.map((label) => { | ||
try { | ||
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 }; | ||
} | ||
catch (e) { | ||
if (e instanceof AttrNotFoundError) { | ||
return { [label]: [] }; | ||
} | ||
throw e; | ||
} | ||
}); | ||
return childResults.reduce((parent, child) => { | ||
return Object.assign(Object.assign({}, parent), child); | ||
}, parent); | ||
}); | ||
} | ||
// Query | ||
// resolveObjects | ||
// ----------------- | ||
function runDatalog(store, dq) { | ||
function shouldIgnoreAttr(attrs, id) { | ||
const attr = attrs[id]; | ||
return attr["value-type"] === "ref" && attr["forward-identity"][2] !== "id"; | ||
} | ||
function runDataloadAndReturnObjects(store, dq) { | ||
return datalogQuery(store, dq) | ||
@@ -168,3 +199,3 @@ .sort((tripleA, tripleB) => { | ||
.reduce((res, [e, a, v]) => { | ||
if (ignoredAttr(store.attrs, a)) { | ||
if (shouldIgnoreAttr(store.attrs, a)) { | ||
return res; | ||
@@ -179,11 +210,33 @@ } | ||
} | ||
function queryParents(store, { etype, level, form, join }) { | ||
/** | ||
* Given a query like: | ||
* | ||
* { | ||
* users: { | ||
* $: { where: { name: "Joe" } }, | ||
* }, | ||
* }; | ||
* | ||
* `resolveObjects`, turns where clause: `{ name: "Joe" }` | ||
* into a datalog query. We then run the datalog query, | ||
* and reduce all the triples into objects. | ||
*/ | ||
function resolveObjects(store, { etype, level, form, join }) { | ||
var _a; | ||
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 }); | ||
return runDataloadAndReturnObjects(store, { where, find }); | ||
} | ||
function guardedQueryParents(store, opts) { | ||
/** | ||
* It's possible that we query | ||
* for an attribute that doesn't exist yet. | ||
* | ||
* { users: { $: { where: { nonExistentProperty: "foo" } } } } | ||
* | ||
* This swallows the missing attr error and returns | ||
* an empty result instead | ||
*/ | ||
function guardedResolveObjects(store, opts) { | ||
try { | ||
return queryParents(store, opts); | ||
return resolveObjects(store, opts); | ||
} | ||
@@ -197,34 +250,18 @@ catch (e) { | ||
} | ||
function extendParents(store, { etype, level, form }, parents) { | ||
const children = Object.keys(form).filter((c) => c !== "$"); | ||
if (!children.length) { | ||
return Object.values(parents); | ||
} | ||
return Object.entries(parents).map(([eid, parent]) => { | ||
const childResults = children.map((label) => { | ||
try { | ||
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 }; | ||
} | ||
catch (e) { | ||
if (e instanceof AttrNotFoundError) { | ||
return { [label]: [] }; | ||
} | ||
throw e; | ||
} | ||
}); | ||
return childResults.reduce((parent, child) => { | ||
return Object.assign(Object.assign({}, parent), child); | ||
}, parent); | ||
}); | ||
} | ||
/** | ||
* Given a query like: | ||
* | ||
* { | ||
* users: { | ||
* $: { where: { name: "Joe" } }, | ||
* posts: {}, | ||
* }, | ||
* }; | ||
* | ||
* `guardResolveObjects` will return the relevant `users` objects | ||
* `extendObjects` will then extend each `user` object with relevant `posts`. | ||
*/ | ||
function queryOne(store, opts) { | ||
const parents = guardedQueryParents(store, opts); | ||
return extendParents(store, opts, parents); | ||
const objects = guardedResolveObjects(store, opts); | ||
return extendObjects(store, opts, objects); | ||
} | ||
@@ -231,0 +268,0 @@ export default function query(store, q) { |
@@ -30,2 +30,4 @@ /** | ||
}>; | ||
/** @type BroadcastChannel | undefined */ | ||
_broadcastChannel: BroadcastChannel | undefined; | ||
_presence: {}; | ||
@@ -141,2 +143,3 @@ _broadcastSubs: {}; | ||
changeCurrentUser(newUser: any): Promise<void>; | ||
updateUser(newUser: any): void; | ||
sendMagicCode({ email }: { | ||
@@ -143,0 +146,0 @@ email: any; |
@@ -38,2 +38,3 @@ // @ts-check | ||
const OAUTH_REDIRECT_PARAM = "_instant_oauth_redirect"; | ||
const currentUserKey = `currentUser`; | ||
/** | ||
@@ -206,2 +207,13 @@ * @template {import('./presence').RoomSchemaShape} [RoomSchema = {}] | ||
} | ||
if ("BroadcastChannel" in window && | ||
typeof BroadcastChannel === "function") { | ||
this._broadcastChannel = new BroadcastChannel("@instantdb"); | ||
this._broadcastChannel.addEventListener("message", (e) => __awaiter(this, void 0, void 0, function* () { | ||
var _a; | ||
if (((_a = e.data) === null || _a === void 0 ? void 0 : _a.type) === "auth") { | ||
const res = yield this.getCurrentUser(); | ||
this.updateUser(res.user); | ||
} | ||
})); | ||
} | ||
this._oauthCallbackResponse = this._oauthLoginInit(); | ||
@@ -775,4 +787,3 @@ this._persister = new Storage(`instant_${this.config.appId}_3`); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const k = `currentUser`; | ||
yield this._persister.setItem(k, JSON.stringify(user)); | ||
yield this._persister.setItem(currentUserKey, JSON.stringify(user)); | ||
}); | ||
@@ -782,3 +793,2 @@ } | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const k = `currentUser`; | ||
const oauthResp = yield this._waitForOAuthCallbackResponse(); | ||
@@ -788,3 +798,3 @@ if (oauthResp === null || oauthResp === void 0 ? void 0 : oauthResp.error) { | ||
} | ||
const user = yield this._persister.getItem(k); | ||
const user = yield this._persister.getItem(currentUserKey); | ||
return { user: JSON.parse(user) }; | ||
@@ -794,2 +804,3 @@ }); | ||
changeCurrentUser(newUser) { | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -799,13 +810,22 @@ yield this.setCurrentUser(newUser); | ||
// as they are no longer valid for the new user | ||
this.querySubs.set((prev) => { | ||
Object.keys(prev).forEach((k) => { | ||
delete prev[k].result; | ||
}); | ||
return prev; | ||
this.updateUser(newUser); | ||
try { | ||
(_a = this._broadcastChannel) === null || _a === void 0 ? void 0 : _a.postMessage({ type: "auth" }); | ||
} | ||
catch (error) { | ||
console.error("Error posting message to broadcast channel", error); | ||
} | ||
}); | ||
} | ||
updateUser(newUser) { | ||
this.querySubs.set((prev) => { | ||
Object.keys(prev).forEach((k) => { | ||
delete prev[k].result; | ||
}); | ||
this._reconnectTimeoutMs = 0; | ||
this._ws.close(); | ||
this._oauthCallbackResponse = null; | ||
this.notifyAuthSubs({ user: newUser }); | ||
return prev; | ||
}); | ||
this._reconnectTimeoutMs = 0; | ||
this._ws.close(); | ||
this._oauthCallbackResponse = null; | ||
this.notifyAuthSubs({ user: newUser }); | ||
} | ||
@@ -812,0 +832,0 @@ sendMagicCode({ email }) { |
@@ -30,2 +30,4 @@ /** | ||
}>; | ||
/** @type BroadcastChannel | undefined */ | ||
_broadcastChannel: BroadcastChannel | undefined; | ||
_presence: {}; | ||
@@ -141,2 +143,3 @@ _broadcastSubs: {}; | ||
changeCurrentUser(newUser: any): Promise<void>; | ||
updateUser(newUser: any): void; | ||
sendMagicCode({ email }: { | ||
@@ -143,0 +146,0 @@ email: any; |
@@ -66,2 +66,3 @@ "use strict"; | ||
const OAUTH_REDIRECT_PARAM = "_instant_oauth_redirect"; | ||
const currentUserKey = `currentUser`; | ||
/** | ||
@@ -234,2 +235,13 @@ * @template {import('./presence').RoomSchemaShape} [RoomSchema = {}] | ||
} | ||
if ("BroadcastChannel" in window && | ||
typeof BroadcastChannel === "function") { | ||
this._broadcastChannel = new BroadcastChannel("@instantdb"); | ||
this._broadcastChannel.addEventListener("message", (e) => __awaiter(this, void 0, void 0, function* () { | ||
var _a; | ||
if (((_a = e.data) === null || _a === void 0 ? void 0 : _a.type) === "auth") { | ||
const res = yield this.getCurrentUser(); | ||
this.updateUser(res.user); | ||
} | ||
})); | ||
} | ||
this._oauthCallbackResponse = this._oauthLoginInit(); | ||
@@ -803,4 +815,3 @@ this._persister = new Storage(`instant_${this.config.appId}_3`); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const k = `currentUser`; | ||
yield this._persister.setItem(k, JSON.stringify(user)); | ||
yield this._persister.setItem(currentUserKey, JSON.stringify(user)); | ||
}); | ||
@@ -810,3 +821,2 @@ } | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const k = `currentUser`; | ||
const oauthResp = yield this._waitForOAuthCallbackResponse(); | ||
@@ -816,3 +826,3 @@ if (oauthResp === null || oauthResp === void 0 ? void 0 : oauthResp.error) { | ||
} | ||
const user = yield this._persister.getItem(k); | ||
const user = yield this._persister.getItem(currentUserKey); | ||
return { user: JSON.parse(user) }; | ||
@@ -822,2 +832,3 @@ }); | ||
changeCurrentUser(newUser) { | ||
var _a; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -827,13 +838,22 @@ yield this.setCurrentUser(newUser); | ||
// as they are no longer valid for the new user | ||
this.querySubs.set((prev) => { | ||
Object.keys(prev).forEach((k) => { | ||
delete prev[k].result; | ||
}); | ||
return prev; | ||
this.updateUser(newUser); | ||
try { | ||
(_a = this._broadcastChannel) === null || _a === void 0 ? void 0 : _a.postMessage({ type: "auth" }); | ||
} | ||
catch (error) { | ||
console.error("Error posting message to broadcast channel", error); | ||
} | ||
}); | ||
} | ||
updateUser(newUser) { | ||
this.querySubs.set((prev) => { | ||
Object.keys(prev).forEach((k) => { | ||
delete prev[k].result; | ||
}); | ||
this._reconnectTimeoutMs = 0; | ||
this._ws.close(); | ||
this._oauthCallbackResponse = null; | ||
this.notifyAuthSubs({ user: newUser }); | ||
return prev; | ||
}); | ||
this._reconnectTimeoutMs = 0; | ||
this._ws.close(); | ||
this._oauthCallbackResponse = null; | ||
this.notifyAuthSubs({ user: newUser }); | ||
} | ||
@@ -840,0 +860,0 @@ sendMagicCode({ email }) { |
{ | ||
"name": "@instantdb/core", | ||
"version": "0.10.5", | ||
"version": "0.10.6", | ||
"description": "Instant's core local abstraction", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
import { query as datalogQuery } from "./datalog"; | ||
class AttrNotFoundError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = "AttrNotFoundError"; | ||
} | ||
} | ||
// (XXX) copied | ||
function getAttrByFwdIdentName(attrs, inputEtype, inputIdentName) { | ||
@@ -16,3 +10,3 @@ return Object.values(attrs).find((attr) => { | ||
} | ||
// (XXX) copied | ||
function getAttrByReverseIdentName(attrs, inputEtype, inputIdentName) { | ||
@@ -27,3 +21,3 @@ return Object.values(attrs).find((attr) => { | ||
// Parts | ||
// Pattern variables | ||
// ----------------- | ||
@@ -46,10 +40,12 @@ | ||
function ignoredAttr(attrs, id) { | ||
const attr = attrs[id]; | ||
return attr["value-type"] === "ref" && attr["forward-identity"][2] !== "id"; | ||
} | ||
// Where | ||
// ----------------- | ||
class AttrNotFoundError extends Error { | ||
constructor(message) { | ||
super(message); | ||
this.name = "AttrNotFoundError"; | ||
} | ||
} | ||
function idAttr(store, ns) { | ||
@@ -107,13 +103,13 @@ const attr = getAttrByFwdIdentName(store.attrs, ns, "id"); | ||
? [ | ||
makeVar(fwdEtype, level), | ||
attr.id, | ||
makeVar(revEtype, nextLevel), | ||
wildcard("time"), | ||
] | ||
makeVar(fwdEtype, level), | ||
attr.id, | ||
makeVar(revEtype, nextLevel), | ||
wildcard("time"), | ||
] | ||
: [ | ||
makeVar(fwdEtype, nextLevel), | ||
attr.id, | ||
makeVar(revEtype, level), | ||
wildcard("time"), | ||
]; | ||
makeVar(fwdEtype, nextLevel), | ||
attr.id, | ||
makeVar(revEtype, level), | ||
wildcard("time"), | ||
]; | ||
@@ -169,2 +165,6 @@ const nextEtype = fwdAttr ? revEtype : fwdEtype; | ||
function withJoin(where, join) { | ||
return join ? [join].concat(where) : where; | ||
} | ||
function makeWhere(store, etype, level, where) { | ||
@@ -193,3 +193,3 @@ if (!where) { | ||
// Relations | ||
// extendObjects | ||
// ----------------- | ||
@@ -203,10 +203,46 @@ | ||
function withJoin(where, join) { | ||
return join ? [join].concat(where) : where; | ||
function extendObjects(store, { etype, level, form }, objects) { | ||
const children = Object.keys(form).filter((c) => c !== "$"); | ||
if (!children.length) { | ||
return Object.values(objects); | ||
} | ||
return Object.entries(objects).map(([eid, parent]) => { | ||
const childResults = children.map((label) => { | ||
try { | ||
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 }; | ||
} catch (e) { | ||
if (e instanceof AttrNotFoundError) { | ||
return { [label]: [] }; | ||
} | ||
throw e; | ||
} | ||
}); | ||
return childResults.reduce((parent, child) => { | ||
return { ...parent, ...child }; | ||
}, parent); | ||
}); | ||
} | ||
// Query | ||
// resolveObjects | ||
// ----------------- | ||
function runDatalog(store, dq) { | ||
function shouldIgnoreAttr(attrs, id) { | ||
const attr = attrs[id]; | ||
return attr["value-type"] === "ref" && attr["forward-identity"][2] !== "id"; | ||
} | ||
function runDataloadAndReturnObjects(store, dq) { | ||
return datalogQuery(store, dq) | ||
@@ -219,3 +255,3 @@ .sort((tripleA, tripleB) => { | ||
.reduce((res, [e, a, v]) => { | ||
if (ignoredAttr(store.attrs, a)) { | ||
if (shouldIgnoreAttr(store.attrs, a)) { | ||
return res; | ||
@@ -231,11 +267,33 @@ } | ||
function queryParents(store, { etype, level, form, join }) { | ||
/** | ||
* Given a query like: | ||
* | ||
* { | ||
* users: { | ||
* $: { where: { name: "Joe" } }, | ||
* }, | ||
* }; | ||
* | ||
* `resolveObjects`, turns where clause: `{ name: "Joe" }` | ||
* into a datalog query. We then run the datalog query, | ||
* and reduce all the triples into objects. | ||
*/ | ||
function resolveObjects(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 }); | ||
return runDataloadAndReturnObjects(store, { where, find }); | ||
} | ||
function guardedQueryParents(store, opts) { | ||
/** | ||
* It's possible that we query | ||
* for an attribute that doesn't exist yet. | ||
* | ||
* { users: { $: { where: { nonExistentProperty: "foo" } } } } | ||
* | ||
* This swallows the missing attr error and returns | ||
* an empty result instead | ||
*/ | ||
function guardedResolveObjects(store, opts) { | ||
try { | ||
return queryParents(store, opts); | ||
return resolveObjects(store, opts); | ||
} catch (e) { | ||
@@ -248,41 +306,18 @@ if (e instanceof AttrNotFoundError) { | ||
} | ||
function extendParents(store, { etype, level, form }, parents) { | ||
const children = Object.keys(form).filter((c) => c !== "$"); | ||
if (!children.length) { | ||
return Object.values(parents); | ||
} | ||
return Object.entries(parents).map(([eid, parent]) => { | ||
const childResults = children.map((label) => { | ||
try { | ||
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 }; | ||
} catch (e) { | ||
if (e instanceof AttrNotFoundError) { | ||
return { [label]: [] }; | ||
} | ||
throw e; | ||
} | ||
}); | ||
return childResults.reduce((parent, child) => { | ||
return { ...parent, ...child }; | ||
}, parent); | ||
}); | ||
} | ||
/** | ||
* Given a query like: | ||
* | ||
* { | ||
* users: { | ||
* $: { where: { name: "Joe" } }, | ||
* posts: {}, | ||
* }, | ||
* }; | ||
* | ||
* `guardResolveObjects` will return the relevant `users` objects | ||
* `extendObjects` will then extend each `user` object with relevant `posts`. | ||
*/ | ||
function queryOne(store, opts) { | ||
const parents = guardedQueryParents(store, opts); | ||
return extendParents(store, opts, parents); | ||
const objects = guardedResolveObjects(store, opts); | ||
return extendObjects(store, opts, objects); | ||
} | ||
@@ -289,0 +324,0 @@ |
@@ -35,2 +35,4 @@ // @ts-check | ||
const currentUserKey = `currentUser`; | ||
/** | ||
@@ -61,2 +63,5 @@ * @template {import('./presence').RoomSchemaShape} [RoomSchema = {}] | ||
/** @type BroadcastChannel | undefined */ | ||
_broadcastChannel; | ||
// (XXX-EPH): Keeping track of roomId -> { result, cbs } here | ||
@@ -82,2 +87,15 @@ _presence = {}; | ||
if ( | ||
"BroadcastChannel" in window && | ||
typeof BroadcastChannel === "function" | ||
) { | ||
this._broadcastChannel = new BroadcastChannel("@instantdb"); | ||
this._broadcastChannel.addEventListener("message", async (e) => { | ||
if (e.data?.type === "auth") { | ||
const res = await this.getCurrentUser(); | ||
this.updateUser(res.user); | ||
} | ||
}); | ||
} | ||
this._oauthCallbackResponse = this._oauthLoginInit(); | ||
@@ -893,8 +911,6 @@ | ||
async setCurrentUser(user) { | ||
const k = `currentUser`; | ||
await this._persister.setItem(k, JSON.stringify(user)); | ||
await this._persister.setItem(currentUserKey, JSON.stringify(user)); | ||
} | ||
async getCurrentUser() { | ||
const k = `currentUser`; | ||
const oauthResp = await this._waitForOAuthCallbackResponse(); | ||
@@ -904,3 +920,3 @@ if (oauthResp?.error) { | ||
} | ||
const user = await this._persister.getItem(k); | ||
const user = await this._persister.getItem(currentUserKey); | ||
return { user: JSON.parse(user) }; | ||
@@ -913,2 +929,12 @@ } | ||
// as they are no longer valid for the new user | ||
this.updateUser(newUser); | ||
try { | ||
this._broadcastChannel?.postMessage({ type: "auth" }); | ||
} catch (error) { | ||
console.error("Error posting message to broadcast channel", error); | ||
} | ||
} | ||
updateUser(newUser) { | ||
this.querySubs.set((prev) => { | ||
@@ -915,0 +941,0 @@ Object.keys(prev).forEach((k) => { |
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
1560335
35059