@instantdb/core
Advanced tools
Comparing version 0.10.10 to 0.10.11
@@ -87,2 +87,21 @@ import { test, expect } from "vitest"; | ||
test("Where and", () => { | ||
expect( | ||
query(store, { | ||
users: { | ||
$: { | ||
where: { | ||
and: [ | ||
{ "bookshelves.books.title": "The Count of Monte Cristo" }, | ||
{ "bookshelves.books.title": "Antifragile" }, | ||
], | ||
}, | ||
}, | ||
}, | ||
}) | ||
.users.map((x) => x.handle) | ||
.sort(), | ||
).toEqual(["nicolegf", "stopa"]); | ||
}); | ||
test.each([ | ||
@@ -166,2 +185,23 @@ [ | ||
], | ||
[ | ||
"with ands in ors", | ||
{ | ||
or: [ | ||
{ | ||
or: [ | ||
{ | ||
and: [ | ||
{ or: [{ handle: "stopa" }, { handle: "joe" }] }, | ||
{ email: "stepan.p@gmail.com" }, | ||
], | ||
}, | ||
], | ||
}, | ||
{ | ||
handle: "joe", | ||
}, | ||
], | ||
}, | ||
["joe", "stopa"], | ||
], | ||
])("Where OR %s", (_, whereQuery, expected) => { | ||
@@ -271,2 +311,25 @@ expect( | ||
test("Nested wheres with AND queries", () => { | ||
expect( | ||
query(store, { | ||
users: { | ||
bookshelves: { | ||
books: {}, | ||
$: { | ||
where: { and: [{ name: "Short Stories" }, { order: 0 }] }, | ||
}, | ||
}, | ||
$: { where: { handle: "alex" } }, | ||
}, | ||
}) | ||
.users.flatMap((x) => x.bookshelves) | ||
.flatMap((x) => x.books) | ||
.map((x) => x.title), | ||
).toEqual([ | ||
"The Paper Menagerie and Other Stories", | ||
"Stories of Your Life and Others", | ||
"Aesop's Fables", | ||
]); | ||
}); | ||
test("Deep where", () => { | ||
@@ -273,0 +336,0 @@ expect( |
@@ -63,2 +63,7 @@ "use strict"; | ||
} | ||
if (pattern.and) { | ||
return pattern.and.patterns.reduce((contexts, patterns) => { | ||
return queryWhere(store, patterns, contexts); | ||
}, contexts); | ||
} | ||
return contexts.flatMap((context) => querySingle(store, pattern, context)); | ||
@@ -65,0 +70,0 @@ } |
@@ -131,2 +131,5 @@ "use strict"; | ||
} | ||
function isAndClauses([k, v]) { | ||
return k === "and" && Array.isArray(v); | ||
} | ||
// Creates a makeVar that will namespace symbols for or clauses | ||
@@ -142,9 +145,9 @@ // to prevent conflicts, except for the base etype | ||
} | ||
function parseWhereOrClauses(makeVar, store, etype, level, whereValue) { | ||
function parseWhereClauses(makeVar, clauseType, /* 'or' | 'and' */ store, etype, level, whereValue) { | ||
const patterns = whereValue.map((w, i) => { | ||
const makeOrVar = genMakeVar(makeVar, etype, i); | ||
return parseWhere(makeOrVar, store, etype, level, w); | ||
const makeNamespacedVar = genMakeVar(makeVar, etype, i); | ||
return parseWhere(makeNamespacedVar, store, etype, level, w); | ||
}); | ||
const joinSym = makeVar(etype, level); | ||
return { or: { patterns, joinSym } }; | ||
return { [clauseType]: { patterns, joinSym } }; | ||
} | ||
@@ -154,4 +157,7 @@ function parseWhere(makeVar, store, etype, level, where) { | ||
if (isOrClauses([k, v])) { | ||
return parseWhereOrClauses(makeVar, store, etype, level, v); | ||
return parseWhereClauses(makeVar, "or", store, etype, level, v); | ||
} | ||
if (isAndClauses([k, v])) { | ||
return parseWhereClauses(makeVar, "and", store, etype, level, v); | ||
} | ||
const path = k.split("."); | ||
@@ -158,0 +164,0 @@ return whereCondAttrPats(makeVar, store, etype, level, path, v); |
@@ -58,2 +58,7 @@ // 1. patternMatch | ||
} | ||
if (pattern.and) { | ||
return pattern.and.patterns.reduce((contexts, patterns) => { | ||
return queryWhere(store, patterns, contexts); | ||
}, contexts); | ||
} | ||
return contexts.flatMap((context) => querySingle(store, pattern, context)); | ||
@@ -60,0 +65,0 @@ } |
@@ -129,2 +129,5 @@ import { query as datalogQuery } from "./datalog"; | ||
} | ||
function isAndClauses([k, v]) { | ||
return k === "and" && Array.isArray(v); | ||
} | ||
// Creates a makeVar that will namespace symbols for or clauses | ||
@@ -140,9 +143,9 @@ // to prevent conflicts, except for the base etype | ||
} | ||
function parseWhereOrClauses(makeVar, store, etype, level, whereValue) { | ||
function parseWhereClauses(makeVar, clauseType, /* 'or' | 'and' */ store, etype, level, whereValue) { | ||
const patterns = whereValue.map((w, i) => { | ||
const makeOrVar = genMakeVar(makeVar, etype, i); | ||
return parseWhere(makeOrVar, store, etype, level, w); | ||
const makeNamespacedVar = genMakeVar(makeVar, etype, i); | ||
return parseWhere(makeNamespacedVar, store, etype, level, w); | ||
}); | ||
const joinSym = makeVar(etype, level); | ||
return { or: { patterns, joinSym } }; | ||
return { [clauseType]: { patterns, joinSym } }; | ||
} | ||
@@ -152,4 +155,7 @@ function parseWhere(makeVar, store, etype, level, where) { | ||
if (isOrClauses([k, v])) { | ||
return parseWhereOrClauses(makeVar, store, etype, level, v); | ||
return parseWhereClauses(makeVar, "or", store, etype, level, v); | ||
} | ||
if (isAndClauses([k, v])) { | ||
return parseWhereClauses(makeVar, "and", store, etype, level, v); | ||
} | ||
const path = k.split("."); | ||
@@ -156,0 +162,0 @@ return whereCondAttrPats(makeVar, store, etype, level, path, v); |
@@ -8,9 +8,10 @@ declare type NonEmpty<T> = { | ||
declare type WhereClauseValue = string | number | boolean | NonEmpty<WhereArgs>; | ||
declare type WhereClauseWithOr = { | ||
or?: BaseWhereClause[] | WhereClauseValue; | ||
}; | ||
declare type BaseWhereClause = { | ||
[key: string]: WhereClauseValue; | ||
}; | ||
declare type WhereClause = WhereClauseWithOr | (WhereClauseWithOr & BaseWhereClause); | ||
declare type WhereClauseWithCombinaton = { | ||
or?: WhereClause[] | WhereClauseValue; | ||
and?: WhereClause[] | WhereClauseValue; | ||
}; | ||
declare type WhereClause = WhereClauseWithCombinaton | (WhereClauseWithCombinaton & BaseWhereClause); | ||
declare type $Option = { | ||
@@ -17,0 +18,0 @@ $?: { |
@@ -46,2 +46,12 @@ // Query | ||
}); | ||
const t3 = dummyQuery({ | ||
users: { $: { where: { and: [{ foo: 1 }] } } }, | ||
}); | ||
// You can have a field named and | ||
const t4 = dummyQuery({ | ||
users: { $: { where: { and: "fieldNamedAnd" } } }, | ||
}); | ||
const t5 = dummyQuery({ | ||
users: { $: { where: { and: [{ or: [{ foo: 1 }] }] } } }, | ||
}); | ||
// ------------------ | ||
@@ -48,0 +58,0 @@ // Bad $ clauses fail |
@@ -141,2 +141,3 @@ /** | ||
}>; | ||
_hasCurrentUser(): Promise<boolean>; | ||
changeCurrentUser(newUser: any): Promise<void>; | ||
@@ -143,0 +144,0 @@ updateUser(newUser: any): void; |
@@ -677,3 +677,3 @@ // @ts-check | ||
} | ||
// ----- | ||
// ---- | ||
// Auth | ||
@@ -686,2 +686,3 @@ _replaceUrlAfterOAuth() { | ||
if (url.searchParams.get(OAUTH_REDIRECT_PARAM)) { | ||
const startUrl = url.toString(); | ||
url.searchParams.delete(OAUTH_REDIRECT_PARAM); | ||
@@ -697,2 +698,31 @@ url.searchParams.delete("code"); | ||
history.replaceState(history.state, "", newPath); | ||
// navigation is part of the HTML spec, but not supported by Safari | ||
// or Firefox yet: | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API#browser_compatibility | ||
if ( | ||
// @ts-ignore (waiting for ts support) | ||
typeof navigation === "object" && | ||
// @ts-ignore (waiting for ts support) | ||
typeof navigation.addEventListener === "function" && | ||
// @ts-ignore (waiting for ts support) | ||
typeof navigation.removeEventListener === "function") { | ||
let ran = false; | ||
// The next.js app router will reset the URL when the router loads. | ||
// This puts it back after the router loads. | ||
const listener = (e) => { | ||
var _a; | ||
if (!ran) { | ||
ran = true; | ||
// @ts-ignore (waiting for ts support) | ||
navigation.removeEventListener("navigate", listener); | ||
if (!e.userInitiated && | ||
e.navigationType === "replace" && | ||
((_a = e.destination) === null || _a === void 0 ? void 0 : _a.url) === startUrl) { | ||
history.replaceState(history.state, "", newPath); | ||
} | ||
} | ||
}; | ||
// @ts-ignore (waiting for ts support) | ||
navigation.addEventListener("navigate", listener); | ||
} | ||
} | ||
@@ -705,3 +735,3 @@ } | ||
_oauthLoginInit() { | ||
var _a; | ||
var _a, _b, _c, _d; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -731,3 +761,3 @@ if (typeof window === "undefined" || | ||
appId: this.config.appId, | ||
code: code, | ||
code, | ||
}); | ||
@@ -738,3 +768,10 @@ this.setCurrentUser(user); | ||
catch (e) { | ||
const message = ((_a = e === null || e === void 0 ? void 0 : e.body) === null || _a === void 0 ? void 0 : _a.error) || "Error logging in."; | ||
if (((_a = e === null || e === void 0 ? void 0 : e.body) === null || _a === void 0 ? void 0 : _a.type) === "record-not-found" && | ||
((_c = (_b = e === null || e === void 0 ? void 0 : e.body) === null || _b === void 0 ? void 0 : _b.hint) === null || _c === void 0 ? void 0 : _c["record-type"]) === "app-oauth-code" && | ||
(yield this._hasCurrentUser())) { | ||
// We probably just weren't able to clean up the URL, so | ||
// let's just ignore this error | ||
return null; | ||
} | ||
const message = ((_d = e === null || e === void 0 ? void 0 : e.body) === null || _d === void 0 ? void 0 : _d.message) || "Error logging in."; | ||
return { error: { message } }; | ||
@@ -804,2 +841,8 @@ } | ||
} | ||
_hasCurrentUser() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const user = yield this._persister.getItem(currentUserKey); | ||
return JSON.parse(user) != null; | ||
}); | ||
} | ||
changeCurrentUser(newUser) { | ||
@@ -806,0 +849,0 @@ var _a; |
@@ -8,9 +8,10 @@ declare type NonEmpty<T> = { | ||
declare type WhereClauseValue = string | number | boolean | NonEmpty<WhereArgs>; | ||
declare type WhereClauseWithOr = { | ||
or?: BaseWhereClause[] | WhereClauseValue; | ||
}; | ||
declare type BaseWhereClause = { | ||
[key: string]: WhereClauseValue; | ||
}; | ||
declare type WhereClause = WhereClauseWithOr | (WhereClauseWithOr & BaseWhereClause); | ||
declare type WhereClauseWithCombinaton = { | ||
or?: WhereClause[] | WhereClauseValue; | ||
and?: WhereClause[] | WhereClauseValue; | ||
}; | ||
declare type WhereClause = WhereClauseWithCombinaton | (WhereClauseWithCombinaton & BaseWhereClause); | ||
declare type $Option = { | ||
@@ -17,0 +18,0 @@ $?: { |
@@ -48,2 +48,12 @@ "use strict"; | ||
}); | ||
const t3 = dummyQuery({ | ||
users: { $: { where: { and: [{ foo: 1 }] } } }, | ||
}); | ||
// You can have a field named and | ||
const t4 = dummyQuery({ | ||
users: { $: { where: { and: "fieldNamedAnd" } } }, | ||
}); | ||
const t5 = dummyQuery({ | ||
users: { $: { where: { and: [{ or: [{ foo: 1 }] }] } } }, | ||
}); | ||
// ------------------ | ||
@@ -50,0 +60,0 @@ // Bad $ clauses fail |
@@ -141,2 +141,3 @@ /** | ||
}>; | ||
_hasCurrentUser(): Promise<boolean>; | ||
changeCurrentUser(newUser: any): Promise<void>; | ||
@@ -143,0 +144,0 @@ updateUser(newUser: any): void; |
@@ -705,3 +705,3 @@ "use strict"; | ||
} | ||
// ----- | ||
// ---- | ||
// Auth | ||
@@ -714,2 +714,3 @@ _replaceUrlAfterOAuth() { | ||
if (url.searchParams.get(OAUTH_REDIRECT_PARAM)) { | ||
const startUrl = url.toString(); | ||
url.searchParams.delete(OAUTH_REDIRECT_PARAM); | ||
@@ -725,2 +726,31 @@ url.searchParams.delete("code"); | ||
history.replaceState(history.state, "", newPath); | ||
// navigation is part of the HTML spec, but not supported by Safari | ||
// or Firefox yet: | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API#browser_compatibility | ||
if ( | ||
// @ts-ignore (waiting for ts support) | ||
typeof navigation === "object" && | ||
// @ts-ignore (waiting for ts support) | ||
typeof navigation.addEventListener === "function" && | ||
// @ts-ignore (waiting for ts support) | ||
typeof navigation.removeEventListener === "function") { | ||
let ran = false; | ||
// The next.js app router will reset the URL when the router loads. | ||
// This puts it back after the router loads. | ||
const listener = (e) => { | ||
var _a; | ||
if (!ran) { | ||
ran = true; | ||
// @ts-ignore (waiting for ts support) | ||
navigation.removeEventListener("navigate", listener); | ||
if (!e.userInitiated && | ||
e.navigationType === "replace" && | ||
((_a = e.destination) === null || _a === void 0 ? void 0 : _a.url) === startUrl) { | ||
history.replaceState(history.state, "", newPath); | ||
} | ||
} | ||
}; | ||
// @ts-ignore (waiting for ts support) | ||
navigation.addEventListener("navigate", listener); | ||
} | ||
} | ||
@@ -733,3 +763,3 @@ } | ||
_oauthLoginInit() { | ||
var _a; | ||
var _a, _b, _c, _d; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -759,3 +789,3 @@ if (typeof window === "undefined" || | ||
appId: this.config.appId, | ||
code: code, | ||
code, | ||
}); | ||
@@ -766,3 +796,10 @@ this.setCurrentUser(user); | ||
catch (e) { | ||
const message = ((_a = e === null || e === void 0 ? void 0 : e.body) === null || _a === void 0 ? void 0 : _a.error) || "Error logging in."; | ||
if (((_a = e === null || e === void 0 ? void 0 : e.body) === null || _a === void 0 ? void 0 : _a.type) === "record-not-found" && | ||
((_c = (_b = e === null || e === void 0 ? void 0 : e.body) === null || _b === void 0 ? void 0 : _b.hint) === null || _c === void 0 ? void 0 : _c["record-type"]) === "app-oauth-code" && | ||
(yield this._hasCurrentUser())) { | ||
// We probably just weren't able to clean up the URL, so | ||
// let's just ignore this error | ||
return null; | ||
} | ||
const message = ((_d = e === null || e === void 0 ? void 0 : e.body) === null || _d === void 0 ? void 0 : _d.message) || "Error logging in."; | ||
return { error: { message } }; | ||
@@ -832,2 +869,8 @@ } | ||
} | ||
_hasCurrentUser() { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const user = yield this._persister.getItem(currentUserKey); | ||
return JSON.parse(user) != null; | ||
}); | ||
} | ||
changeCurrentUser(newUser) { | ||
@@ -834,0 +877,0 @@ var _a; |
{ | ||
"name": "@instantdb/core", | ||
"version": "0.10.10", | ||
"version": "0.10.11", | ||
"description": "Instant's core local abstraction", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -68,2 +68,7 @@ // 1. patternMatch | ||
} | ||
if (pattern.and) { | ||
return pattern.and.patterns.reduce((contexts, patterns) => { | ||
return queryWhere(store, patterns, contexts); | ||
}, contexts); | ||
} | ||
return contexts.flatMap((context) => querySingle(store, pattern, context)); | ||
@@ -70,0 +75,0 @@ } |
@@ -181,2 +181,6 @@ import { query as datalogQuery } from "./datalog"; | ||
function isAndClauses([k, v]) { | ||
return k === "and" && Array.isArray(v); | ||
} | ||
// Creates a makeVar that will namespace symbols for or clauses | ||
@@ -193,9 +197,16 @@ // to prevent conflicts, except for the base etype | ||
function parseWhereOrClauses(makeVar, store, etype, level, whereValue) { | ||
function parseWhereClauses( | ||
makeVar, | ||
clauseType, /* 'or' | 'and' */ | ||
store, | ||
etype, | ||
level, | ||
whereValue, | ||
) { | ||
const patterns = whereValue.map((w, i) => { | ||
const makeOrVar = genMakeVar(makeVar, etype, i); | ||
return parseWhere(makeOrVar, store, etype, level, w); | ||
const makeNamespacedVar = genMakeVar(makeVar, etype, i); | ||
return parseWhere(makeNamespacedVar, store, etype, level, w); | ||
}); | ||
const joinSym = makeVar(etype, level); | ||
return { or: { patterns, joinSym } }; | ||
return { [clauseType]: { patterns, joinSym } }; | ||
} | ||
@@ -206,4 +217,7 @@ | ||
if (isOrClauses([k, v])) { | ||
return parseWhereOrClauses(makeVar, store, etype, level, v); | ||
return parseWhereClauses(makeVar, "or", store, etype, level, v); | ||
} | ||
if (isAndClauses([k, v])) { | ||
return parseWhereClauses(makeVar, "and", store, etype, level, v); | ||
} | ||
const path = k.split("."); | ||
@@ -210,0 +224,0 @@ return whereCondAttrPats(makeVar, store, etype, level, path, v); |
@@ -15,3 +15,2 @@ // Query | ||
type WhereClauseWithOr = { or?: BaseWhereClause[] | WhereClauseValue }; | ||
type BaseWhereClause = { | ||
@@ -21,4 +20,11 @@ [key: string]: WhereClauseValue; | ||
type WhereClause = WhereClauseWithOr | (WhereClauseWithOr & BaseWhereClause); | ||
type WhereClauseWithCombinaton = { | ||
or?: WhereClause[] | WhereClauseValue; | ||
and?: WhereClause[] | WhereClauseValue; | ||
}; | ||
type WhereClause = | ||
| WhereClauseWithCombinaton | ||
| (WhereClauseWithCombinaton & BaseWhereClause); | ||
type $Option = { $?: { where: WhereClause } }; | ||
@@ -156,2 +162,12 @@ | ||
}); | ||
const t3 = dummyQuery({ | ||
users: { $: { where: { and: [{ foo: 1 }] } } }, | ||
}); | ||
// You can have a field named and | ||
const t4 = dummyQuery({ | ||
users: { $: { where: { and: "fieldNamedAnd" } } }, | ||
}); | ||
const t5 = dummyQuery({ | ||
users: { $: { where: { and: [{ or: [{ foo: 1 }] }] } } }, | ||
}); | ||
@@ -158,0 +174,0 @@ // ------------------ |
@@ -792,5 +792,4 @@ // @ts-check | ||
// ----- | ||
// ---- | ||
// Auth | ||
_replaceUrlAfterOAuth() { | ||
@@ -802,2 +801,3 @@ if (typeof URL === "undefined") { | ||
if (url.searchParams.get(OAUTH_REDIRECT_PARAM)) { | ||
const startUrl = url.toString(); | ||
url.searchParams.delete(OAUTH_REDIRECT_PARAM); | ||
@@ -814,2 +814,35 @@ url.searchParams.delete("code"); | ||
history.replaceState(history.state, "", newPath); | ||
// navigation is part of the HTML spec, but not supported by Safari | ||
// or Firefox yet: | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API#browser_compatibility | ||
if ( | ||
// @ts-ignore (waiting for ts support) | ||
typeof navigation === "object" && | ||
// @ts-ignore (waiting for ts support) | ||
typeof navigation.addEventListener === "function" && | ||
// @ts-ignore (waiting for ts support) | ||
typeof navigation.removeEventListener === "function" | ||
) { | ||
let ran = false; | ||
// The next.js app router will reset the URL when the router loads. | ||
// This puts it back after the router loads. | ||
const listener = (e) => { | ||
if (!ran) { | ||
ran = true; | ||
// @ts-ignore (waiting for ts support) | ||
navigation.removeEventListener("navigate", listener); | ||
if ( | ||
!e.userInitiated && | ||
e.navigationType === "replace" && | ||
e.destination?.url === startUrl | ||
) { | ||
history.replaceState(history.state, "", newPath); | ||
} | ||
} | ||
}; | ||
// @ts-ignore (waiting for ts support) | ||
navigation.addEventListener("navigate", listener); | ||
} | ||
} | ||
@@ -849,3 +882,3 @@ } | ||
appId: this.config.appId, | ||
code: code, | ||
code, | ||
}); | ||
@@ -855,3 +888,12 @@ this.setCurrentUser(user); | ||
} catch (e) { | ||
const message = e?.body?.error || "Error logging in."; | ||
if ( | ||
e?.body?.type === "record-not-found" && | ||
e?.body?.hint?.["record-type"] === "app-oauth-code" && | ||
(await this._hasCurrentUser()) | ||
) { | ||
// We probably just weren't able to clean up the URL, so | ||
// let's just ignore this error | ||
return null; | ||
} | ||
const message = e?.body?.message || "Error logging in."; | ||
return { error: { message } }; | ||
@@ -925,2 +967,7 @@ } | ||
async _hasCurrentUser() { | ||
const user = await this._persister.getItem(currentUserKey); | ||
return JSON.parse(user) != null; | ||
} | ||
async changeCurrentUser(newUser) { | ||
@@ -927,0 +974,0 @@ await this.setCurrentUser(newUser); |
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
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
1594510
35762