@instantdb/core
Advanced tools
Comparing version 0.10.18 to 0.10.19
@@ -19,4 +19,4 @@ import { test, expect } from "vitest"; | ||
expect( | ||
query(store, { users: {} }) | ||
.users.map((x) => x.handle) | ||
query({ store }, { users: {} }) | ||
.data.users.map((x) => x.handle) | ||
.sort(), | ||
@@ -28,4 +28,4 @@ ).toEqual(["alex", "joe", "nicolegf", "stopa"]); | ||
expect( | ||
query(store, { users: { $: { where: { handle: "joe" } } } }) | ||
.users.map((x) => x.handle) | ||
query({ store }, { users: { $: { where: { handle: "joe" } } } }) | ||
.data.users.map((x) => x.handle) | ||
.sort(), | ||
@@ -38,3 +38,4 @@ ).toEqual(["joe"]); | ||
Object.keys( | ||
query(store, { users: { $: { where: { handle: "joe" } } } }).users[0], | ||
query({ store }, { users: { $: { where: { handle: "joe" } } } }).data | ||
.users[0], | ||
).sort(), | ||
@@ -46,13 +47,16 @@ ).toEqual(["createdAt", "email", "fullName", "handle", "id"]); | ||
expect( | ||
query(store, { | ||
users: { | ||
$: { | ||
where: { | ||
"bookshelves.books.title": "The Count of Monte Cristo", | ||
handle: "stopa", | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
$: { | ||
where: { | ||
"bookshelves.books.title": "The Count of Monte Cristo", | ||
handle: "stopa", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
.users.map((x) => x.handle) | ||
) | ||
.data.users.map((x) => x.handle) | ||
.sort(), | ||
@@ -62,13 +66,16 @@ ).toEqual(["stopa"]); | ||
expect( | ||
query(store, { | ||
users: { | ||
$: { | ||
where: { | ||
"bookshelves.books.title": "Title nobody has", | ||
handle: "stopa", | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
$: { | ||
where: { | ||
"bookshelves.books.title": "Title nobody has", | ||
handle: "stopa", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
.users.map((x) => x.handle) | ||
) | ||
.data.users.map((x) => x.handle) | ||
.sort(), | ||
@@ -80,12 +87,15 @@ ).toEqual([]); | ||
expect( | ||
query(store, { | ||
users: { | ||
$: { | ||
where: { | ||
handle: { in: ["stopa", "joe"] }, | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
$: { | ||
where: { | ||
handle: { in: ["stopa", "joe"] }, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
.users.map((x) => x.handle) | ||
) | ||
.data.users.map((x) => x.handle) | ||
.sort(), | ||
@@ -97,15 +107,18 @@ ).toEqual(["joe", "stopa"]); | ||
expect( | ||
query(store, { | ||
users: { | ||
$: { | ||
where: { | ||
and: [ | ||
{ "bookshelves.books.title": "The Count of Monte Cristo" }, | ||
{ "bookshelves.books.title": "Antifragile" }, | ||
], | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
$: { | ||
where: { | ||
and: [ | ||
{ "bookshelves.books.title": "The Count of Monte Cristo" }, | ||
{ "bookshelves.books.title": "Antifragile" }, | ||
], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
.users.map((x) => x.handle) | ||
) | ||
.data.users.map((x) => x.handle) | ||
.sort(), | ||
@@ -216,10 +229,13 @@ ).toEqual(["nicolegf", "stopa"]); | ||
expect( | ||
query(store, { | ||
users: { | ||
$: { | ||
where: whereQuery, | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
$: { | ||
where: whereQuery, | ||
}, | ||
}, | ||
}, | ||
}) | ||
.users.map((x) => x.handle) | ||
) | ||
.data.users.map((x) => x.handle) | ||
.sort(), | ||
@@ -231,8 +247,14 @@ ).toEqual(expected); | ||
expect( | ||
query(store, { | ||
users: { | ||
bookshelves: {}, | ||
$: { where: { handle: "alex" } }, | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
bookshelves: {}, | ||
$: { where: { handle: "alex" } }, | ||
}, | ||
}, | ||
}).users.map((x) => [x.handle, x.bookshelves.map((x) => x.name).sort()]), | ||
).data.users.map((x) => [ | ||
x.handle, | ||
x.bookshelves.map((x) => x.name).sort(), | ||
]), | ||
).toEqual([["alex", ["Nonfiction", "Short Stories"]]]); | ||
@@ -243,8 +265,14 @@ }); | ||
expect( | ||
query(store, { | ||
bookshelves: { | ||
users: {}, | ||
$: { where: { name: "Short Stories" } }, | ||
query( | ||
{ store }, | ||
{ | ||
bookshelves: { | ||
users: {}, | ||
$: { where: { name: "Short Stories" } }, | ||
}, | ||
}, | ||
}).bookshelves.map((x) => [x.name, x.users.map((x) => x.handle).sort()]), | ||
).data.bookshelves.map((x) => [ | ||
x.name, | ||
x.users.map((x) => x.handle).sort(), | ||
]), | ||
).toEqual([["Short Stories", ["alex"]]]); | ||
@@ -255,21 +283,24 @@ }); | ||
expect( | ||
query(store, { | ||
users: { | ||
bookshelves: { books: {} }, | ||
$: { where: { handle: "alex" } }, | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
bookshelves: { books: {} }, | ||
$: { where: { handle: "alex" } }, | ||
}, | ||
}, | ||
}) | ||
.users.flatMap((x) => x.bookshelves) | ||
) | ||
.data.users.flatMap((x) => x.bookshelves) | ||
.flatMap((x) => x.books) | ||
.map((x) => x.title), | ||
).toEqual([ | ||
`"Surely You're Joking, Mr. Feynman!": Adventures of a Curious Character`, | ||
'"What Do You Care What Other People Think?": Further Adventures of a Curious Character', | ||
"The Spy and the Traitor", | ||
"Antifragile", | ||
"Atomic Habits", | ||
"Catch and Kill", | ||
"The Paper Menagerie and Other Stories", | ||
"Stories of Your Life and Others", | ||
"Aesop's Fables", | ||
"Antifragile", | ||
"Catch and Kill", | ||
"The Spy and the Traitor", | ||
`"Surely You're Joking, Mr. Feynman!": Adventures of a Curious Character`, | ||
'"What Do You Care What Other People Think?": Further Adventures of a Curious Character', | ||
"Atomic Habits", | ||
]); | ||
@@ -280,12 +311,15 @@ }); | ||
expect( | ||
query(store, { | ||
users: { | ||
bookshelves: { | ||
books: {}, | ||
$: { where: { name: "Short Stories" } }, | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
bookshelves: { | ||
books: {}, | ||
$: { where: { name: "Short Stories" } }, | ||
}, | ||
$: { where: { handle: "alex" } }, | ||
}, | ||
$: { where: { handle: "alex" } }, | ||
}, | ||
}) | ||
.users.flatMap((x) => x.bookshelves) | ||
) | ||
.data.users.flatMap((x) => x.bookshelves) | ||
.flatMap((x) => x.books) | ||
@@ -302,14 +336,17 @@ .map((x) => x.title), | ||
expect( | ||
query(store, { | ||
users: { | ||
bookshelves: { | ||
books: {}, | ||
$: { | ||
where: { or: [{ name: "Short Stories" }] }, | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
bookshelves: { | ||
books: {}, | ||
$: { | ||
where: { or: [{ name: "Short Stories" }] }, | ||
}, | ||
}, | ||
$: { where: { handle: "alex" } }, | ||
}, | ||
$: { where: { handle: "alex" } }, | ||
}, | ||
}) | ||
.users.flatMap((x) => x.bookshelves) | ||
) | ||
.data.users.flatMap((x) => x.bookshelves) | ||
.flatMap((x) => x.books) | ||
@@ -326,14 +363,17 @@ .map((x) => x.title), | ||
expect( | ||
query(store, { | ||
users: { | ||
bookshelves: { | ||
books: {}, | ||
$: { | ||
where: { and: [{ name: "Short Stories" }, { order: 0 }] }, | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
bookshelves: { | ||
books: {}, | ||
$: { | ||
where: { and: [{ name: "Short Stories" }, { order: 0 }] }, | ||
}, | ||
}, | ||
$: { where: { handle: "alex" } }, | ||
}, | ||
$: { where: { handle: "alex" } }, | ||
}, | ||
}) | ||
.users.flatMap((x) => x.bookshelves) | ||
) | ||
.data.users.flatMap((x) => x.bookshelves) | ||
.flatMap((x) => x.books) | ||
@@ -350,7 +390,10 @@ .map((x) => x.title), | ||
expect( | ||
query(store, { | ||
users: { | ||
$: { where: { "bookshelves.books.title": "Aesop's Fables" } }, | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
$: { where: { "bookshelves.books.title": "Aesop's Fables" } }, | ||
}, | ||
}, | ||
}).users.map((x) => x.handle), | ||
).data.users.map((x) => x.handle), | ||
).toEqual(["alex"]); | ||
@@ -360,3 +403,3 @@ }); | ||
test("Missing etype", () => { | ||
expect(query(store, { moopy: {} })).toEqual({ moopy: [] }); | ||
expect(query({ store }, { moopy: {} }).data).toEqual({ moopy: [] }); | ||
}); | ||
@@ -366,9 +409,12 @@ | ||
expect( | ||
query(store, { | ||
users: { | ||
moopy: {}, | ||
$: { where: { handle: "joe" } }, | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
moopy: {}, | ||
$: { where: { handle: "joe" } }, | ||
}, | ||
}, | ||
}) | ||
.users.map((x) => [x.handle, x.moopy]) | ||
) | ||
.data.users.map((x) => [x.handle, x.moopy]) | ||
.sort(), | ||
@@ -380,7 +426,10 @@ ).toEqual([["joe", []]]); | ||
expect( | ||
query(store, { | ||
users: { | ||
$: { where: { "bookshelves.moopy": "joe" } }, | ||
query( | ||
{ store }, | ||
{ | ||
users: { | ||
$: { where: { "bookshelves.moopy": "joe" } }, | ||
}, | ||
}, | ||
}), | ||
).data, | ||
).toEqual({ users: [] }); | ||
@@ -391,9 +440,12 @@ }); | ||
expect( | ||
query(store, { | ||
bookshelves: { | ||
books: {}, | ||
users: {}, | ||
$: { where: { name: "Short Stories" } }, | ||
query( | ||
{ store }, | ||
{ | ||
bookshelves: { | ||
books: {}, | ||
users: {}, | ||
$: { where: { name: "Short Stories" } }, | ||
}, | ||
}, | ||
}).bookshelves.map((x) => [ | ||
).data.bookshelves.map((x) => [ | ||
x.name, | ||
@@ -417,7 +469,10 @@ x.users.map((x) => x.handle).sort(), | ||
test("objects are created by etype", () => { | ||
const stopa = query(store, { | ||
users: { | ||
$: { where: { handle: "stopa" } }, | ||
const stopa = query( | ||
{ store }, | ||
{ | ||
users: { | ||
$: { where: { handle: "stopa" } }, | ||
}, | ||
}, | ||
}).users[0]; | ||
).data.users[0]; | ||
expect(stopa.email).toEqual("stepan.p@gmail.com"); | ||
@@ -429,7 +484,10 @@ const chunk = tx.user[stopa.id].update({ | ||
const newStore = transact(store, txSteps); | ||
const newStopa = query(newStore, { | ||
users: { | ||
$: { where: { handle: "stopa" } }, | ||
const newStopa = query( | ||
{ store: newStore }, | ||
{ | ||
users: { | ||
$: { where: { handle: "stopa" } }, | ||
}, | ||
}, | ||
}).users[0]; | ||
).data.users[0]; | ||
expect(newStopa.email).toEqual("stepan.p@gmail.com"); | ||
@@ -439,7 +497,10 @@ }); | ||
test("object values", () => { | ||
const stopa = query(store, { | ||
users: { | ||
$: { where: { handle: "stopa" } }, | ||
const stopa = query( | ||
{ store }, | ||
{ | ||
users: { | ||
$: { where: { handle: "stopa" } }, | ||
}, | ||
}, | ||
}).users[0]; | ||
).data.users[0]; | ||
expect(stopa.email).toEqual("stepan.p@gmail.com"); | ||
@@ -452,9 +513,125 @@ const chunk = tx.users[stopa.id].update({ | ||
const newStore = transact(store, txSteps); | ||
const newStopa = query(newStore, { | ||
users: { | ||
$: { where: { handle: "stopa" } }, | ||
const newStopa = query( | ||
{ store: newStore }, | ||
{ | ||
users: { | ||
$: { where: { handle: "stopa" } }, | ||
}, | ||
}, | ||
}).users[0]; | ||
).data.users[0]; | ||
expect(newStopa.jsonField).toEqual({ hello: "world" }); | ||
}); | ||
test("pagination limit", () => { | ||
const books = query( | ||
{ store }, | ||
{ | ||
books: { | ||
$: { | ||
limit: 10, | ||
}, | ||
}, | ||
}, | ||
).data.books; | ||
expect(books.length).toEqual(10); | ||
}); | ||
test("pagination offset waits for pageInfo", () => { | ||
// If we don't have the pageInfo from the server, we have to | ||
// wait to know which items in the store we should return. | ||
// Otherwise, we might render optimistic changes for items | ||
// that aren't in our range. | ||
const booksWithOffset = query( | ||
{ store }, | ||
{ | ||
books: { | ||
$: { | ||
offset: 10, | ||
limit: 5, | ||
}, | ||
}, | ||
}, | ||
).data.books; | ||
expect(booksWithOffset.length).toEqual(0); | ||
const booksWithPageInfo = query( | ||
{ | ||
store, | ||
pageInfo: { | ||
books: { | ||
"start-cursor": [ | ||
"000212ec-fe77-473d-9494-d29898c53b7a", | ||
"6eebf15a-ed3c-4442-8869-a44a7c85a1be", | ||
"000212ec-fe77-473d-9494-d29898c53b7a", | ||
1718118155976, | ||
], | ||
"end-cursor": [ | ||
"0270a27f-1363-4f6d-93c0-39cc43d92a78", | ||
"6eebf15a-ed3c-4442-8869-a44a7c85a1be", | ||
"0270a27f-1363-4f6d-93c0-39cc43d92a78", | ||
1718118151976, | ||
], | ||
}, | ||
}, | ||
}, | ||
{ | ||
books: { | ||
$: { | ||
offset: 10, | ||
limit: 5, | ||
order: { serverCreatedAt: "desc" }, | ||
}, | ||
}, | ||
}, | ||
).data.books; | ||
expect(booksWithPageInfo.map((b) => b.title)).toEqual([ | ||
"Norse Mythology", | ||
"Love-at-Arms", | ||
"The Young Lions", | ||
"The Hounds of God", | ||
"Which Comes First, Cardio or Weights?", | ||
]); | ||
const booksWithPageInfoAsc = query( | ||
{ | ||
store, | ||
pageInfo: { | ||
books: { | ||
"start-cursor": [ | ||
"f11c998f-d951-426b-b2b1-ffcb8d17bac5", | ||
"6eebf15a-ed3c-4442-8869-a44a7c85a1be", | ||
"f11c998f-d951-426b-b2b1-ffcb8d17bac5", | ||
1718117715976, | ||
], | ||
"end-cursor": [ | ||
"f1c15604-93cd-4189-bb9a-d4ee97b95f32", | ||
"6eebf15a-ed3c-4442-8869-a44a7c85a1be", | ||
"f1c15604-93cd-4189-bb9a-d4ee97b95f32", | ||
1718117721976, | ||
], | ||
}, | ||
}, | ||
}, | ||
{ | ||
books: { | ||
$: { | ||
offset: 10, | ||
limit: 5, | ||
order: { serverCreatedAt: "asc" }, | ||
}, | ||
}, | ||
}, | ||
).data.books; | ||
expect(booksWithPageInfoAsc.map((b) => b.title)).toEqual([ | ||
"Sum", | ||
"Insurgent", | ||
"The Rational Male", | ||
"The Restaurant at the End of the Universe", | ||
"Bardelys the Magnificent", | ||
]); | ||
}); |
@@ -10,3 +10,2 @@ // https://www.npmjs.com/package/fake-indexeddb | ||
import zenecaTriples from "./data/zenecaTriples.json"; | ||
import oldStore from "./data/oldStore.json"; | ||
import uuid from "../../src/utils/uuid"; | ||
@@ -13,0 +12,0 @@ |
@@ -72,5 +72,5 @@ import { test, expect } from "vitest"; | ||
const newStore = transact(store, txSteps); | ||
expect(query(newStore, { users: {} }).users.map((x) => x.handle)).contains( | ||
"bobby", | ||
); | ||
expect( | ||
query({ store: newStore }, { users: {} }).data.users.map((x) => x.handle), | ||
).contains("bobby"); | ||
@@ -110,8 +110,11 @@ checkIndexIntegrity(newStore); | ||
expect( | ||
query(newStore, { | ||
users: { | ||
$: { where: { handle: "bobby" } }, | ||
bookshelves: {}, | ||
query( | ||
{ store: newStore }, | ||
{ | ||
users: { | ||
$: { where: { handle: "bobby" } }, | ||
bookshelves: {}, | ||
}, | ||
}, | ||
}).users.map((x) => [x.handle, x.bookshelves.map((x) => x.name)]), | ||
).data.users.map((x) => [x.handle, x.bookshelves.map((x) => x.name)]), | ||
).toEqual([["bobby", ["my books"]]]); | ||
@@ -135,8 +138,11 @@ checkIndexIntegrity(newStore); | ||
expect( | ||
query(secondStore, { | ||
users: { | ||
$: { where: { handle: "bobby" } }, | ||
bookshelves: {}, | ||
query( | ||
{ store: secondStore }, | ||
{ | ||
users: { | ||
$: { where: { handle: "bobby" } }, | ||
bookshelves: {}, | ||
}, | ||
}, | ||
}).users.map((x) => [x.handle, x.bookshelves.map((x) => x.name)]), | ||
).data.users.map((x) => [x.handle, x.bookshelves.map((x) => x.name)]), | ||
).toEqual([["bobby", ["my second books"]]]); | ||
@@ -168,8 +174,11 @@ checkIndexIntegrity(secondStore); | ||
expect( | ||
query(newStore, { | ||
users: { | ||
$: { where: { handle: "bobby" } }, | ||
bookshelves: {}, | ||
query( | ||
{ store: newStore }, | ||
{ | ||
users: { | ||
$: { where: { handle: "bobby" } }, | ||
bookshelves: {}, | ||
}, | ||
}, | ||
}).users.map((x) => [x.handle, x.bookshelves.map((x) => x.name)]), | ||
).data.users.map((x) => [x.handle, x.bookshelves.map((x) => x.name)]), | ||
).toEqual([["bobby", ["my books 1", "my books 2"]]]); | ||
@@ -193,8 +202,11 @@ checkIndexIntegrity(newStore); | ||
expect( | ||
query(secondStore, { | ||
users: { | ||
$: { where: { handle: "bobby" } }, | ||
bookshelves: {}, | ||
query( | ||
{ store: secondStore }, | ||
{ | ||
users: { | ||
$: { where: { handle: "bobby" } }, | ||
bookshelves: {}, | ||
}, | ||
}, | ||
}).users.map((x) => [x.handle, x.bookshelves.map((x) => x.name)]), | ||
).data.users.map((x) => [x.handle, x.bookshelves.map((x) => x.name)]), | ||
).toEqual([["bobby", ["my books 3"]]]); | ||
@@ -265,8 +277,11 @@ checkIndexIntegrity(secondStore); | ||
expect( | ||
query(newStore, { | ||
users: { | ||
$: { where: { handle: "bobby" } }, | ||
colors: {}, | ||
query( | ||
{ store: newStore }, | ||
{ | ||
users: { | ||
$: { where: { handle: "bobby" } }, | ||
colors: {}, | ||
}, | ||
}, | ||
}).users.map((x) => [x.handle, x.colors.map((x) => x.name)]), | ||
).data.users.map((x) => [x.handle, x.colors.map((x) => x.name)]), | ||
).toEqual([["bobby", ["red"]]]); | ||
@@ -279,3 +294,6 @@ | ||
expect( | ||
query(store, { users: {} }).users.map((x) => [x.handle, x.fullName]), | ||
query({ store }, { users: {} }).data.users.map((x) => [ | ||
x.handle, | ||
x.fullName, | ||
]), | ||
).toEqual([ | ||
@@ -294,3 +312,6 @@ ["joe", "Joe Averbukh"], | ||
expect( | ||
query(newStore, { users: {} }).users.map((x) => [x.handle, x.fullName]), | ||
query({ store: newStore }, { users: {} }).data.users.map((x) => [ | ||
x.handle, | ||
x.fullName, | ||
]), | ||
).toEqual([ | ||
@@ -308,3 +329,6 @@ ["joe", undefined], | ||
expect( | ||
query(store, { users: {} }).users.map((x) => [x.handle, x.fullName]), | ||
query({ store }, { users: {} }).data.users.map((x) => [ | ||
x.handle, | ||
x.fullName, | ||
]), | ||
).toEqual([ | ||
@@ -332,3 +356,6 @@ ["joe", "Joe Averbukh"], | ||
expect( | ||
query(newStore, { users: {} }).users.map((x) => [x.handle, x.fullNamez]), | ||
query({ store: newStore }, { users: {} }).data.users.map((x) => [ | ||
x.handle, | ||
x.fullNamez, | ||
]), | ||
).toEqual([ | ||
@@ -345,2 +372,2 @@ ["joe", "Joe Averbukh"], | ||
expect(store).toEqual(newStore); | ||
}); | ||
}); |
@@ -1,2 +0,2 @@ | ||
import { QueryResponse } from "./queryTypes"; | ||
import { QueryResponse, PageInfoResponse } from "./queryTypes"; | ||
export declare type User = { | ||
@@ -38,2 +38,3 @@ id: string; | ||
data: undefined; | ||
pageInfo: undefined; | ||
} | { | ||
@@ -45,2 +46,3 @@ isLoading: false; | ||
data: undefined; | ||
pageInfo: undefined; | ||
} | { | ||
@@ -50,3 +52,4 @@ isLoading: false; | ||
data: QueryResponse<Q, Schema>; | ||
pageInfo: PageInfoResponse<Q>; | ||
}; | ||
//# sourceMappingURL=clientTypes.d.ts.map |
@@ -7,3 +7,3 @@ import Reactor from "./Reactor"; | ||
import WindowNetworkListener from "./WindowNetworkListener"; | ||
import { Query, QueryResponse, Exactly, InstantObject } from "./queryTypes"; | ||
import { Query, QueryResponse, PageInfoResponse, Exactly, InstantObject } from "./queryTypes"; | ||
import { QueryState, AuthState, User, AuthResult } from "./clientTypes"; | ||
@@ -34,5 +34,7 @@ import { PresenceOpts, PresenceResponse, PresenceSlice, RoomSchemaShape } from "./presence"; | ||
data: undefined; | ||
pageInfo: undefined; | ||
} | { | ||
error: undefined; | ||
data: QueryResponse<Q, Schema>; | ||
pageInfo: PageInfoResponse<Q>; | ||
}; | ||
@@ -39,0 +41,0 @@ declare type LifecycleSubscriptionState<Q, Schema> = SubscriptionState<Q, Schema> & { |
@@ -1,2 +0,7 @@ | ||
export default function query(store: any, q: any): {}; | ||
export default function query({ store, pageInfo }: { | ||
store: any; | ||
pageInfo: any; | ||
}, q: any): { | ||
data: {}; | ||
}; | ||
//# sourceMappingURL=instaql.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const datalog_1 = require("./datalog"); | ||
const uuid_1 = require("./utils/uuid"); | ||
// (XXX) copied | ||
@@ -224,10 +225,37 @@ function getAttrByFwdIdentName(attrs, inputEtype, inputIdentName) { | ||
} | ||
function runDataloadAndReturnObjects(store, etype, dq) { | ||
return (0, datalog_1.query)(store, dq) | ||
function cursorCompare(direction, typ) { | ||
switch (direction) { | ||
case "asc": | ||
switch (typ) { | ||
case "number": | ||
return (x, y) => x < y; | ||
case "uuid": | ||
return (x, y) => (0, uuid_1.uuidCompare)(x, y) === -1; | ||
} | ||
case "desc": | ||
switch (typ) { | ||
case "number": | ||
return (x, y) => x > y; | ||
case "uuid": | ||
return (x, y) => (0, uuid_1.uuidCompare)(x, y) === 1; | ||
} | ||
} | ||
} | ||
function isBefore(startCursor, direction, [e, a, _v, t]) { | ||
return (startCursor[1] === a && | ||
(cursorCompare(direction, "number")(t, startCursor[3]) || | ||
(t === startCursor[3] && | ||
cursorCompare(direction, "uuid")(e, startCursor[0])))); | ||
} | ||
function runDataloadAndReturnObjects(store, etype, direction, pageInfo, dq) { | ||
const startCursor = pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo["start-cursor"]; | ||
const toRemove = []; | ||
const res = (0, datalog_1.query)(store, dq) | ||
.sort((tripleA, tripleB) => { | ||
const tsA = tripleA[3]; | ||
const tsB = tripleB[3]; | ||
return tsA - tsB; | ||
return direction === "desc" ? tsB - tsA : tsA - tsB; | ||
}) | ||
.reduce((res, [e, a, v]) => { | ||
.reduce((res, triple) => { | ||
const [e, a, v] = triple; | ||
if (shouldIgnoreAttr(store.attrs, a)) { | ||
@@ -241,2 +269,5 @@ return res; | ||
} | ||
if (startCursor && isBefore(startCursor, direction, triple)) { | ||
toRemove.push(e); | ||
} | ||
res[e] = res[e] || {}; | ||
@@ -246,2 +277,7 @@ res[e][label] = v; | ||
}, {}); | ||
// remove anything before our start cursor | ||
for (const e of toRemove) { | ||
delete res[e]; | ||
} | ||
return res; | ||
} | ||
@@ -261,7 +297,23 @@ /** | ||
*/ | ||
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); | ||
function resolveObjects(store, { etype, level, form, join, pageInfo }) { | ||
var _a, _b, _c, _d, _e, _g, _h; | ||
const limit = (_a = form.$) === null || _a === void 0 ? void 0 : _a.limit; | ||
const offset = (_b = form.$) === null || _b === void 0 ? void 0 : _b.offset; | ||
const before = (_c = form.$) === null || _c === void 0 ? void 0 : _c.before; | ||
const after = (_d = form.$) === null || _d === void 0 ? void 0 : _d.after; | ||
// Wait for server to tell us where we start if we don't start from the beginning | ||
if ((offset || before || after) && (!pageInfo || !pageInfo["start-cursor"])) { | ||
return []; | ||
} | ||
const where = withJoin(makeWhere(store, etype, level, (_e = form.$) === null || _e === void 0 ? void 0 : _e.where), join); | ||
const find = makeFind(makeVarImpl, etype, level); | ||
return runDataloadAndReturnObjects(store, etype, { where, find }); | ||
const objs = runDataloadAndReturnObjects(store, etype, ((_h = (_g = form.$) === null || _g === void 0 ? void 0 : _g.order) === null || _h === void 0 ? void 0 : _h.serverCreatedAt) || "asc", pageInfo, { where, find }); | ||
if (limit != null) { | ||
const entries = Object.entries(objs); | ||
if (entries.length <= limit) { | ||
return objs; | ||
} | ||
return Object.fromEntries(entries.slice(0, limit)); | ||
} | ||
return objs; | ||
} | ||
@@ -305,9 +357,26 @@ /** | ||
} | ||
function query(store, q) { | ||
return Object.keys(q).reduce((res, k) => { | ||
res[k] = queryOne(store, { etype: k, form: q[k], level: 0 }); | ||
function formatPageInfo(pageInfo) { | ||
const res = {}; | ||
for (const [k, v] of Object.entries(pageInfo)) { | ||
res[k] = { startCursor: v["start-cursor"], endCursor: v["end-cursor"] }; | ||
} | ||
return res; | ||
} | ||
function query({ store, pageInfo }, q) { | ||
const data = Object.keys(q).reduce((res, k) => { | ||
res[k] = queryOne(store, { | ||
etype: k, | ||
form: q[k], | ||
level: 0, | ||
pageInfo: pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo[k], | ||
}); | ||
return res; | ||
}, {}); | ||
const result = { data }; | ||
if (pageInfo) { | ||
result.pageInfo = formatPageInfo(pageInfo); | ||
} | ||
return result; | ||
} | ||
exports.default = query; | ||
//# sourceMappingURL=instaql.js.map |
@@ -1,2 +0,2 @@ | ||
import { QueryResponse } from "./queryTypes"; | ||
import { QueryResponse, PageInfoResponse } from "./queryTypes"; | ||
export declare type User = { | ||
@@ -38,2 +38,3 @@ id: string; | ||
data: undefined; | ||
pageInfo: undefined; | ||
} | { | ||
@@ -45,2 +46,3 @@ isLoading: false; | ||
data: undefined; | ||
pageInfo: undefined; | ||
} | { | ||
@@ -50,3 +52,4 @@ isLoading: false; | ||
data: QueryResponse<Q, Schema>; | ||
pageInfo: PageInfoResponse<Q>; | ||
}; | ||
//# sourceMappingURL=clientTypes.d.ts.map |
@@ -7,3 +7,3 @@ import Reactor from "./Reactor"; | ||
import WindowNetworkListener from "./WindowNetworkListener"; | ||
import { Query, QueryResponse, Exactly, InstantObject } from "./queryTypes"; | ||
import { Query, QueryResponse, PageInfoResponse, Exactly, InstantObject } from "./queryTypes"; | ||
import { QueryState, AuthState, User, AuthResult } from "./clientTypes"; | ||
@@ -34,5 +34,7 @@ import { PresenceOpts, PresenceResponse, PresenceSlice, RoomSchemaShape } from "./presence"; | ||
data: undefined; | ||
pageInfo: undefined; | ||
} | { | ||
error: undefined; | ||
data: QueryResponse<Q, Schema>; | ||
pageInfo: PageInfoResponse<Q>; | ||
}; | ||
@@ -39,0 +41,0 @@ declare type LifecycleSubscriptionState<Q, Schema> = SubscriptionState<Q, Schema> & { |
@@ -1,2 +0,7 @@ | ||
export default function query(store: any, q: any): {}; | ||
export default function query({ store, pageInfo }: { | ||
store: any; | ||
pageInfo: any; | ||
}, q: any): { | ||
data: {}; | ||
}; | ||
//# sourceMappingURL=instaql.d.ts.map |
import { query as datalogQuery } from "./datalog"; | ||
import { uuidCompare } from "./utils/uuid"; | ||
// (XXX) copied | ||
@@ -222,10 +223,37 @@ function getAttrByFwdIdentName(attrs, inputEtype, inputIdentName) { | ||
} | ||
function runDataloadAndReturnObjects(store, etype, dq) { | ||
return datalogQuery(store, dq) | ||
function cursorCompare(direction, typ) { | ||
switch (direction) { | ||
case "asc": | ||
switch (typ) { | ||
case "number": | ||
return (x, y) => x < y; | ||
case "uuid": | ||
return (x, y) => uuidCompare(x, y) === -1; | ||
} | ||
case "desc": | ||
switch (typ) { | ||
case "number": | ||
return (x, y) => x > y; | ||
case "uuid": | ||
return (x, y) => uuidCompare(x, y) === 1; | ||
} | ||
} | ||
} | ||
function isBefore(startCursor, direction, [e, a, _v, t]) { | ||
return (startCursor[1] === a && | ||
(cursorCompare(direction, "number")(t, startCursor[3]) || | ||
(t === startCursor[3] && | ||
cursorCompare(direction, "uuid")(e, startCursor[0])))); | ||
} | ||
function runDataloadAndReturnObjects(store, etype, direction, pageInfo, dq) { | ||
const startCursor = pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo["start-cursor"]; | ||
const toRemove = []; | ||
const res = datalogQuery(store, dq) | ||
.sort((tripleA, tripleB) => { | ||
const tsA = tripleA[3]; | ||
const tsB = tripleB[3]; | ||
return tsA - tsB; | ||
return direction === "desc" ? tsB - tsA : tsA - tsB; | ||
}) | ||
.reduce((res, [e, a, v]) => { | ||
.reduce((res, triple) => { | ||
const [e, a, v] = triple; | ||
if (shouldIgnoreAttr(store.attrs, a)) { | ||
@@ -239,2 +267,5 @@ return res; | ||
} | ||
if (startCursor && isBefore(startCursor, direction, triple)) { | ||
toRemove.push(e); | ||
} | ||
res[e] = res[e] || {}; | ||
@@ -244,2 +275,7 @@ res[e][label] = v; | ||
}, {}); | ||
// remove anything before our start cursor | ||
for (const e of toRemove) { | ||
delete res[e]; | ||
} | ||
return res; | ||
} | ||
@@ -259,7 +295,23 @@ /** | ||
*/ | ||
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); | ||
function resolveObjects(store, { etype, level, form, join, pageInfo }) { | ||
var _a, _b, _c, _d, _e, _g, _h; | ||
const limit = (_a = form.$) === null || _a === void 0 ? void 0 : _a.limit; | ||
const offset = (_b = form.$) === null || _b === void 0 ? void 0 : _b.offset; | ||
const before = (_c = form.$) === null || _c === void 0 ? void 0 : _c.before; | ||
const after = (_d = form.$) === null || _d === void 0 ? void 0 : _d.after; | ||
// Wait for server to tell us where we start if we don't start from the beginning | ||
if ((offset || before || after) && (!pageInfo || !pageInfo["start-cursor"])) { | ||
return []; | ||
} | ||
const where = withJoin(makeWhere(store, etype, level, (_e = form.$) === null || _e === void 0 ? void 0 : _e.where), join); | ||
const find = makeFind(makeVarImpl, etype, level); | ||
return runDataloadAndReturnObjects(store, etype, { where, find }); | ||
const objs = runDataloadAndReturnObjects(store, etype, ((_h = (_g = form.$) === null || _g === void 0 ? void 0 : _g.order) === null || _h === void 0 ? void 0 : _h.serverCreatedAt) || "asc", pageInfo, { where, find }); | ||
if (limit != null) { | ||
const entries = Object.entries(objs); | ||
if (entries.length <= limit) { | ||
return objs; | ||
} | ||
return Object.fromEntries(entries.slice(0, limit)); | ||
} | ||
return objs; | ||
} | ||
@@ -303,8 +355,25 @@ /** | ||
} | ||
export default function query(store, q) { | ||
return Object.keys(q).reduce((res, k) => { | ||
res[k] = queryOne(store, { etype: k, form: q[k], level: 0 }); | ||
function formatPageInfo(pageInfo) { | ||
const res = {}; | ||
for (const [k, v] of Object.entries(pageInfo)) { | ||
res[k] = { startCursor: v["start-cursor"], endCursor: v["end-cursor"] }; | ||
} | ||
return res; | ||
} | ||
export default function query({ store, pageInfo }, q) { | ||
const data = Object.keys(q).reduce((res, k) => { | ||
res[k] = queryOne(store, { | ||
etype: k, | ||
form: q[k], | ||
level: 0, | ||
pageInfo: pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo[k], | ||
}); | ||
return res; | ||
}, {}); | ||
const result = { data }; | ||
if (pageInfo) { | ||
result.pageInfo = formatPageInfo(pageInfo); | ||
} | ||
return result; | ||
} | ||
//# sourceMappingURL=instaql.js.map |
@@ -16,5 +16,22 @@ declare type NonEmpty<T> = { | ||
declare type WhereClause = WhereClauseWithCombinaton | (WhereClauseWithCombinaton & BaseWhereClause); | ||
/** | ||
* A tuple representing a cursor. | ||
* These should not be constructed manually. The current format | ||
* is an implementation detail that may change in the future. | ||
* Use the `endCursor` or `startCursor` from the PageInfoResponse as the | ||
* `before` or `after` field in the query options. | ||
*/ | ||
declare type Cursor = [string, string, any, number]; | ||
declare type Direction = "asc" | "desc"; | ||
declare type Order = { | ||
serverCreatedAt: Direction; | ||
}; | ||
declare type $Option = { | ||
$?: { | ||
where: WhereClause; | ||
where?: WhereClause; | ||
order?: Order; | ||
limit?: number; | ||
offset?: number; | ||
after?: Cursor; | ||
before?: Cursor; | ||
}; | ||
@@ -46,2 +63,8 @@ }; | ||
}, Schema>; | ||
declare type PageInfoResponse<T> = { | ||
[K in keyof T]: { | ||
startCursor: Cursor; | ||
endCursor: Cursor; | ||
}; | ||
}; | ||
/** | ||
@@ -72,3 +95,3 @@ * (XXX) | ||
}; | ||
export { Query, QueryResponse, InstantObject, Exactly }; | ||
export { Query, QueryResponse, PageInfoResponse, InstantObject, Exactly }; | ||
//# sourceMappingURL=queryTypes.d.ts.map |
@@ -56,2 +56,26 @@ // Query | ||
}); | ||
// Pagination | ||
const t6 = dummyQuery({ | ||
users: { $: { limit: 10 } }, | ||
}); | ||
const t7 = dummyQuery({ | ||
users: { $: { limit: 10, offset: 10 } }, | ||
}); | ||
const t8 = dummyQuery({ | ||
users: { $: { where: { foo: 1 }, limit: 10, offset: 10 } }, | ||
}); | ||
const cursor = [ | ||
"61935703-bec6-4ade-ad9b-8bf382b92f69", | ||
"995f5a9b-9ae1-4e59-97d1-df33afb44aee", | ||
"61935703-bec6-4ade-ad9b-8bf382b92f69", | ||
10, | ||
]; | ||
const t9 = dummyQuery({ | ||
users: { | ||
$: { where: { foo: 1 }, after: cursor }, | ||
}, | ||
}); | ||
const t10 = dummyQuery({ | ||
users: { $: { before: cursor } }, | ||
}); | ||
// ------------------ | ||
@@ -58,0 +82,0 @@ // Bad $ clauses fail |
@@ -71,5 +71,8 @@ /** | ||
/** Runs instaql on a query and a store */ | ||
dataForResult(q: any, { store }: { | ||
dataForResult(q: any, { store, pageInfo }: { | ||
store: any; | ||
}): {}; | ||
pageInfo: any; | ||
}): { | ||
data: {}; | ||
}; | ||
/** Re-run instaql and call all callbacks with new data */ | ||
@@ -76,0 +79,0 @@ notifyOne: (hash: any) => void; |
@@ -128,7 +128,7 @@ // @ts-check | ||
return; // No store data, no need to notify | ||
const data = this.dataForResult(q, result); | ||
if (areObjectsDeepEqual(data, iqlResult)) | ||
const resp = this.dataForResult(q, result); | ||
if (areObjectsDeepEqual(resp.data, iqlResult)) | ||
return; // No change, no need to notify | ||
this.querySubs.currentValue[hash].iqlResult = data; | ||
cbs.forEach((cb) => cb({ data })); | ||
this.querySubs.currentValue[hash].iqlResult = result.data; | ||
cbs.forEach((cb) => cb(resp)); | ||
}; | ||
@@ -286,3 +286,3 @@ this.notifyQueryError = (hash, msg) => { | ||
_handleReceive(msg) { | ||
var _a, _b; | ||
var _a, _b, _c, _d; | ||
switch (msg.op) { | ||
@@ -302,6 +302,7 @@ case "init-ok": | ||
const hash = weakHash(q); | ||
const pageInfo = (_b = (_a = result === null || result === void 0 ? void 0 : result[0]) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b["page-info"]; | ||
const triples = extractTriples(result); | ||
const store = s.createStore(this.attrs, triples); | ||
this.querySubs.set((prev) => { | ||
prev[hash].result = { store }; | ||
prev[hash].result = { store, pageInfo }; | ||
return prev; | ||
@@ -316,2 +317,3 @@ }); | ||
const updates = computations.map((x) => { | ||
var _a, _b; | ||
const q = x["instaql-query"]; | ||
@@ -322,7 +324,8 @@ const result = x["instaql-result"]; | ||
const store = s.createStore(this.attrs, triples); | ||
return { hash, store }; | ||
const pageInfo = (_b = (_a = result === null || result === void 0 ? void 0 : result[0]) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b["page-info"]; | ||
return { hash, store, pageInfo }; | ||
}); | ||
updates.forEach(({ hash, store }) => { | ||
updates.forEach(({ hash, store, pageInfo }) => { | ||
this.querySubs.set((prev) => { | ||
prev[hash].result = { store }; | ||
prev[hash].result = { store, pageInfo }; | ||
return prev; | ||
@@ -383,3 +386,3 @@ }); | ||
// (XXX): Error handling is spaghetti right now. | ||
const errorQ = msg.q || ((_a = msg["original-event"]) === null || _a === void 0 ? void 0 : _a.q); | ||
const errorQ = msg.q || ((_c = msg["original-event"]) === null || _c === void 0 ? void 0 : _c.q); | ||
const { "client-event-id": errorEventId } = msg; | ||
@@ -419,3 +422,3 @@ const errorObj = Object.assign({}, msg); | ||
} | ||
const isInitError = ((_b = msg["original-event"]) === null || _b === void 0 ? void 0 : _b.op) === "init"; | ||
const isInitError = ((_d = msg["original-event"]) === null || _d === void 0 ? void 0 : _d.op) === "init"; | ||
if (isInitError) { | ||
@@ -469,3 +472,3 @@ const errorMessage = { | ||
else if (prevResult) { | ||
cb({ data: this.dataForResult(q, prevResult) }); | ||
cb(this.dataForResult(q, prevResult)); | ||
} | ||
@@ -561,8 +564,8 @@ return () => { | ||
/** Runs instaql on a query and a store */ | ||
dataForResult(q, { store }) { | ||
dataForResult(q, { store, pageInfo }) { | ||
const muts = this._rewriteMutations(store.attrs, this.pendingMutations.currentValue); | ||
const txSteps = [...muts.values()].flatMap((x) => x["tx-steps"]); | ||
const newStore = s.transact(store, txSteps); | ||
const data = instaql(newStore, q); | ||
return data; | ||
const resp = instaql({ store: newStore, pageInfo }, q); | ||
return resp; | ||
} | ||
@@ -569,0 +572,0 @@ /** Re-compute all subscriptions */ |
import { v4 } from "uuid"; | ||
export declare function uuidCompare(uuid_a: string, uuid_b: string): 1 | -1 | 0; | ||
export default v4; | ||
//# sourceMappingURL=uuid.d.ts.map |
@@ -1,3 +0,18 @@ | ||
import { v4 } from "uuid"; | ||
import { parse, v4 } from "uuid"; | ||
export function uuidCompare(uuid_a, uuid_b) { | ||
const bytes_a = parse(uuid_a); | ||
const bytes_b = parse(uuid_b); | ||
for (let i = 0; i < 16; i++) { | ||
const a = bytes_a[i]; | ||
const b = bytes_b[i]; | ||
if (a < b) { | ||
return -1; | ||
} | ||
if (b > a) { | ||
return 1; | ||
} | ||
} | ||
return 0; | ||
} | ||
export default v4; | ||
//# sourceMappingURL=uuid.js.map |
@@ -16,5 +16,22 @@ declare type NonEmpty<T> = { | ||
declare type WhereClause = WhereClauseWithCombinaton | (WhereClauseWithCombinaton & BaseWhereClause); | ||
/** | ||
* A tuple representing a cursor. | ||
* These should not be constructed manually. The current format | ||
* is an implementation detail that may change in the future. | ||
* Use the `endCursor` or `startCursor` from the PageInfoResponse as the | ||
* `before` or `after` field in the query options. | ||
*/ | ||
declare type Cursor = [string, string, any, number]; | ||
declare type Direction = "asc" | "desc"; | ||
declare type Order = { | ||
serverCreatedAt: Direction; | ||
}; | ||
declare type $Option = { | ||
$?: { | ||
where: WhereClause; | ||
where?: WhereClause; | ||
order?: Order; | ||
limit?: number; | ||
offset?: number; | ||
after?: Cursor; | ||
before?: Cursor; | ||
}; | ||
@@ -46,2 +63,8 @@ }; | ||
}, Schema>; | ||
declare type PageInfoResponse<T> = { | ||
[K in keyof T]: { | ||
startCursor: Cursor; | ||
endCursor: Cursor; | ||
}; | ||
}; | ||
/** | ||
@@ -72,3 +95,3 @@ * (XXX) | ||
}; | ||
export { Query, QueryResponse, InstantObject, Exactly }; | ||
export { Query, QueryResponse, PageInfoResponse, InstantObject, Exactly }; | ||
//# sourceMappingURL=queryTypes.d.ts.map |
@@ -58,2 +58,26 @@ "use strict"; | ||
}); | ||
// Pagination | ||
const t6 = dummyQuery({ | ||
users: { $: { limit: 10 } }, | ||
}); | ||
const t7 = dummyQuery({ | ||
users: { $: { limit: 10, offset: 10 } }, | ||
}); | ||
const t8 = dummyQuery({ | ||
users: { $: { where: { foo: 1 }, limit: 10, offset: 10 } }, | ||
}); | ||
const cursor = [ | ||
"61935703-bec6-4ade-ad9b-8bf382b92f69", | ||
"995f5a9b-9ae1-4e59-97d1-df33afb44aee", | ||
"61935703-bec6-4ade-ad9b-8bf382b92f69", | ||
10, | ||
]; | ||
const t9 = dummyQuery({ | ||
users: { | ||
$: { where: { foo: 1 }, after: cursor }, | ||
}, | ||
}); | ||
const t10 = dummyQuery({ | ||
users: { $: { before: cursor } }, | ||
}); | ||
// ------------------ | ||
@@ -60,0 +84,0 @@ // Bad $ clauses fail |
@@ -71,5 +71,8 @@ /** | ||
/** Runs instaql on a query and a store */ | ||
dataForResult(q: any, { store }: { | ||
dataForResult(q: any, { store, pageInfo }: { | ||
store: any; | ||
}): {}; | ||
pageInfo: any; | ||
}): { | ||
data: {}; | ||
}; | ||
/** Re-run instaql and call all callbacks with new data */ | ||
@@ -76,0 +79,0 @@ notifyOne: (hash: any) => void; |
@@ -156,7 +156,7 @@ "use strict"; | ||
return; // No store data, no need to notify | ||
const data = this.dataForResult(q, result); | ||
if ((0, object_1.areObjectsDeepEqual)(data, iqlResult)) | ||
const resp = this.dataForResult(q, result); | ||
if ((0, object_1.areObjectsDeepEqual)(resp.data, iqlResult)) | ||
return; // No change, no need to notify | ||
this.querySubs.currentValue[hash].iqlResult = data; | ||
cbs.forEach((cb) => cb({ data })); | ||
this.querySubs.currentValue[hash].iqlResult = result.data; | ||
cbs.forEach((cb) => cb(resp)); | ||
}; | ||
@@ -314,3 +314,3 @@ this.notifyQueryError = (hash, msg) => { | ||
_handleReceive(msg) { | ||
var _a, _b; | ||
var _a, _b, _c, _d; | ||
switch (msg.op) { | ||
@@ -330,6 +330,7 @@ case "init-ok": | ||
const hash = (0, weakHash_1.default)(q); | ||
const pageInfo = (_b = (_a = result === null || result === void 0 ? void 0 : result[0]) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b["page-info"]; | ||
const triples = (0, triples_1.extractTriples)(result); | ||
const store = s.createStore(this.attrs, triples); | ||
this.querySubs.set((prev) => { | ||
prev[hash].result = { store }; | ||
prev[hash].result = { store, pageInfo }; | ||
return prev; | ||
@@ -344,2 +345,3 @@ }); | ||
const updates = computations.map((x) => { | ||
var _a, _b; | ||
const q = x["instaql-query"]; | ||
@@ -350,7 +352,8 @@ const result = x["instaql-result"]; | ||
const store = s.createStore(this.attrs, triples); | ||
return { hash, store }; | ||
const pageInfo = (_b = (_a = result === null || result === void 0 ? void 0 : result[0]) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b["page-info"]; | ||
return { hash, store, pageInfo }; | ||
}); | ||
updates.forEach(({ hash, store }) => { | ||
updates.forEach(({ hash, store, pageInfo }) => { | ||
this.querySubs.set((prev) => { | ||
prev[hash].result = { store }; | ||
prev[hash].result = { store, pageInfo }; | ||
return prev; | ||
@@ -411,3 +414,3 @@ }); | ||
// (XXX): Error handling is spaghetti right now. | ||
const errorQ = msg.q || ((_a = msg["original-event"]) === null || _a === void 0 ? void 0 : _a.q); | ||
const errorQ = msg.q || ((_c = msg["original-event"]) === null || _c === void 0 ? void 0 : _c.q); | ||
const { "client-event-id": errorEventId } = msg; | ||
@@ -447,3 +450,3 @@ const errorObj = Object.assign({}, msg); | ||
} | ||
const isInitError = ((_b = msg["original-event"]) === null || _b === void 0 ? void 0 : _b.op) === "init"; | ||
const isInitError = ((_d = msg["original-event"]) === null || _d === void 0 ? void 0 : _d.op) === "init"; | ||
if (isInitError) { | ||
@@ -497,3 +500,3 @@ const errorMessage = { | ||
else if (prevResult) { | ||
cb({ data: this.dataForResult(q, prevResult) }); | ||
cb(this.dataForResult(q, prevResult)); | ||
} | ||
@@ -589,8 +592,8 @@ return () => { | ||
/** Runs instaql on a query and a store */ | ||
dataForResult(q, { store }) { | ||
dataForResult(q, { store, pageInfo }) { | ||
const muts = this._rewriteMutations(store.attrs, this.pendingMutations.currentValue); | ||
const txSteps = [...muts.values()].flatMap((x) => x["tx-steps"]); | ||
const newStore = s.transact(store, txSteps); | ||
const data = (0, instaql_1.default)(newStore, q); | ||
return data; | ||
const resp = (0, instaql_1.default)({ store: newStore, pageInfo }, q); | ||
return resp; | ||
} | ||
@@ -597,0 +600,0 @@ /** Re-compute all subscriptions */ |
import { v4 } from "uuid"; | ||
export declare function uuidCompare(uuid_a: string, uuid_b: string): 1 | -1 | 0; | ||
export default v4; | ||
//# sourceMappingURL=uuid.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.uuidCompare = void 0; | ||
const uuid_1 = require("uuid"); | ||
function uuidCompare(uuid_a, uuid_b) { | ||
const bytes_a = (0, uuid_1.parse)(uuid_a); | ||
const bytes_b = (0, uuid_1.parse)(uuid_b); | ||
for (let i = 0; i < 16; i++) { | ||
const a = bytes_a[i]; | ||
const b = bytes_b[i]; | ||
if (a < b) { | ||
return -1; | ||
} | ||
if (b > a) { | ||
return 1; | ||
} | ||
} | ||
return 0; | ||
} | ||
exports.uuidCompare = uuidCompare; | ||
exports.default = uuid_1.v4; | ||
//# sourceMappingURL=uuid.js.map |
{ | ||
"name": "@instantdb/core", | ||
"version": "0.10.18", | ||
"version": "0.10.19", | ||
"description": "Instant's core local abstraction", | ||
@@ -31,3 +31,3 @@ "main": "dist/index.js", | ||
"typescript": "^4.6.4", | ||
"vitest": "^0.21.0" | ||
"vitest": "^1.6.0" | ||
}, | ||
@@ -34,0 +34,0 @@ "dependencies": { |
@@ -1,2 +0,2 @@ | ||
import { QueryResponse } from "./queryTypes"; | ||
import { QueryResponse, PageInfoResponse } from "./queryTypes"; | ||
export type User = { id: string; email: string; refresh_token: string }; | ||
@@ -28,4 +28,14 @@ | ||
export type QueryState<Q, Schema = {}> = | ||
| { isLoading: true; error: undefined; data: undefined } | ||
| { isLoading: false; error: { message: string }; data: undefined } | ||
| { isLoading: false; error: undefined; data: QueryResponse<Q, Schema> }; | ||
| { isLoading: true; error: undefined; data: undefined; pageInfo: undefined } | ||
| { | ||
isLoading: false; | ||
error: { message: string }; | ||
data: undefined; | ||
pageInfo: undefined; | ||
} | ||
| { | ||
isLoading: false; | ||
error: undefined; | ||
data: QueryResponse<Q, Schema>; | ||
pageInfo: PageInfoResponse<Q>; | ||
}; |
@@ -7,3 +7,9 @@ import Reactor from "./Reactor"; | ||
import WindowNetworkListener from "./WindowNetworkListener"; | ||
import { Query, QueryResponse, Exactly, InstantObject } from "./queryTypes"; | ||
import { | ||
Query, | ||
QueryResponse, | ||
PageInfoResponse, | ||
Exactly, | ||
InstantObject, | ||
} from "./queryTypes"; | ||
import { QueryState, AuthState, User, AuthResult } from "./clientTypes"; | ||
@@ -54,4 +60,8 @@ import { assert } from "./utils/error"; | ||
type SubscriptionState<Q, Schema> = | ||
| { error: { message: string }; data: undefined } | ||
| { error: undefined; data: QueryResponse<Q, Schema> }; | ||
| { error: { message: string }; data: undefined; pageInfo: undefined } | ||
| { | ||
error: undefined; | ||
data: QueryResponse<Q, Schema>; | ||
pageInfo: PageInfoResponse<Q>; | ||
}; | ||
@@ -283,3 +293,3 @@ type LifecycleSubscriptionState<Q, Schema> = SubscriptionState<Q, Schema> & { | ||
class Auth { | ||
constructor(private db: Reactor) { } | ||
constructor(private db: Reactor) {} | ||
@@ -474,3 +484,3 @@ /** | ||
"Uh oh! Looks like `init` hasn't run yet. Make sure `init` runs " + | ||
"before your first `useQuery` or `transact`.", | ||
"before your first `useQuery` or `transact`.", | ||
); | ||
@@ -477,0 +487,0 @@ return _GLOBAL_DB; |
import { query as datalogQuery } from "./datalog"; | ||
import { uuidCompare } from "./utils/uuid"; | ||
@@ -305,10 +306,41 @@ // (XXX) copied | ||
function runDataloadAndReturnObjects(store, etype, dq) { | ||
return datalogQuery(store, dq) | ||
function cursorCompare(direction, typ) { | ||
switch (direction) { | ||
case "asc": | ||
switch (typ) { | ||
case "number": | ||
return (x, y) => x < y; | ||
case "uuid": | ||
return (x, y) => uuidCompare(x, y) === -1; | ||
} | ||
case "desc": | ||
switch (typ) { | ||
case "number": | ||
return (x, y) => x > y; | ||
case "uuid": | ||
return (x, y) => uuidCompare(x, y) === 1; | ||
} | ||
} | ||
} | ||
function isBefore(startCursor, direction, [e, a, _v, t]) { | ||
return ( | ||
startCursor[1] === a && | ||
(cursorCompare(direction, "number")(t, startCursor[3]) || | ||
(t === startCursor[3] && | ||
cursorCompare(direction, "uuid")(e, startCursor[0]))) | ||
); | ||
} | ||
function runDataloadAndReturnObjects(store, etype, direction, pageInfo, dq) { | ||
const startCursor = pageInfo?.["start-cursor"]; | ||
const toRemove = []; | ||
const res = datalogQuery(store, dq) | ||
.sort((tripleA, tripleB) => { | ||
const tsA = tripleA[3]; | ||
const tsB = tripleB[3]; | ||
return tsA - tsB; | ||
return direction === "desc" ? tsB - tsA : tsA - tsB; | ||
}) | ||
.reduce((res, [e, a, v]) => { | ||
.reduce((res, triple) => { | ||
const [e, a, v] = triple; | ||
if (shouldIgnoreAttr(store.attrs, a)) { | ||
@@ -322,2 +354,7 @@ return res; | ||
} | ||
if (startCursor && isBefore(startCursor, direction, triple)) { | ||
toRemove.push(e); | ||
} | ||
res[e] = res[e] || {}; | ||
@@ -327,2 +364,8 @@ res[e][label] = v; | ||
}, {}); | ||
// remove anything before our start cursor | ||
for (const e of toRemove) { | ||
delete res[e]; | ||
} | ||
return res; | ||
} | ||
@@ -343,6 +386,31 @@ | ||
*/ | ||
function resolveObjects(store, { etype, level, form, join }) { | ||
function resolveObjects(store, { etype, level, form, join, pageInfo }) { | ||
const limit = form.$?.limit; | ||
const offset = form.$?.offset; | ||
const before = form.$?.before; | ||
const after = form.$?.after; | ||
// Wait for server to tell us where we start if we don't start from the beginning | ||
if ((offset || before || after) && (!pageInfo || !pageInfo["start-cursor"])) { | ||
return []; | ||
} | ||
const where = withJoin(makeWhere(store, etype, level, form.$?.where), join); | ||
const find = makeFind(makeVarImpl, etype, level); | ||
return runDataloadAndReturnObjects(store, etype, { where, find }); | ||
const objs = runDataloadAndReturnObjects( | ||
store, | ||
etype, | ||
form.$?.order?.serverCreatedAt || "asc", | ||
pageInfo, | ||
{ where, find }, | ||
); | ||
if (limit != null) { | ||
const entries = Object.entries(objs); | ||
if (entries.length <= limit) { | ||
return objs; | ||
} | ||
return Object.fromEntries(entries.slice(0, limit)); | ||
} | ||
return objs; | ||
} | ||
@@ -387,7 +455,27 @@ | ||
export default function query(store, q) { | ||
return Object.keys(q).reduce((res, k) => { | ||
res[k] = queryOne(store, { etype: k, form: q[k], level: 0 }); | ||
function formatPageInfo(pageInfo) { | ||
const res = {}; | ||
for (const [k, v] of Object.entries(pageInfo)) { | ||
res[k] = { startCursor: v["start-cursor"], endCursor: v["end-cursor"] }; | ||
} | ||
return res; | ||
} | ||
export default function query({ store, pageInfo }, q) { | ||
const data = Object.keys(q).reduce((res, k) => { | ||
res[k] = queryOne(store, { | ||
etype: k, | ||
form: q[k], | ||
level: 0, | ||
pageInfo: pageInfo?.[k], | ||
}); | ||
return res; | ||
}, {}); | ||
const result = { data }; | ||
if (pageInfo) { | ||
result.pageInfo = formatPageInfo(pageInfo); | ||
} | ||
return result; | ||
} |
@@ -28,4 +28,26 @@ // Query | ||
type $Option = { $?: { where: WhereClause } }; | ||
/** | ||
* A tuple representing a cursor. | ||
* These should not be constructed manually. The current format | ||
* is an implementation detail that may change in the future. | ||
* Use the `endCursor` or `startCursor` from the PageInfoResponse as the | ||
* `before` or `after` field in the query options. | ||
*/ | ||
type Cursor = [string, string, any, number]; | ||
type Direction = "asc" | "desc"; | ||
type Order = { serverCreatedAt: Direction }; | ||
type $Option = { | ||
$?: { | ||
where?: WhereClause; | ||
order?: Order; | ||
limit?: number; | ||
offset?: number; | ||
after?: Cursor; | ||
before?: Cursor; | ||
}; | ||
}; | ||
type Subquery = { [namespace: string]: NamespaceVal }; | ||
@@ -65,2 +87,6 @@ | ||
type PageInfoResponse<T> = { | ||
[K in keyof T]: { startCursor: Cursor; endCursor: Cursor }; | ||
}; | ||
/** | ||
@@ -92,3 +118,3 @@ * (XXX) | ||
export { Query, QueryResponse, InstantObject, Exactly }; | ||
export { Query, QueryResponse, PageInfoResponse, InstantObject, Exactly }; | ||
@@ -173,3 +199,28 @@ // -------- | ||
}); | ||
// Pagination | ||
const t6 = dummyQuery({ | ||
users: { $: { limit: 10 } }, | ||
}); | ||
const t7 = dummyQuery({ | ||
users: { $: { limit: 10, offset: 10 } }, | ||
}); | ||
const t8 = dummyQuery({ | ||
users: { $: { where: { foo: 1 }, limit: 10, offset: 10 } }, | ||
}); | ||
const cursor: Cursor = [ | ||
"61935703-bec6-4ade-ad9b-8bf382b92f69", | ||
"995f5a9b-9ae1-4e59-97d1-df33afb44aee", | ||
"61935703-bec6-4ade-ad9b-8bf382b92f69", | ||
10, | ||
]; | ||
const t9 = dummyQuery({ | ||
users: { | ||
$: { where: { foo: 1 }, after: cursor }, | ||
}, | ||
}); | ||
const t10 = dummyQuery({ | ||
users: { $: { before: cursor } }, | ||
}); | ||
// ------------------ | ||
@@ -176,0 +227,0 @@ // Bad $ clauses fail |
@@ -260,6 +260,7 @@ // @ts-check | ||
const hash = weakHash(q); | ||
const pageInfo = result?.[0]?.data?.["page-info"]; | ||
const triples = extractTriples(result); | ||
const store = s.createStore(this.attrs, triples); | ||
this.querySubs.set((prev) => { | ||
prev[hash].result = { store }; | ||
prev[hash].result = { store, pageInfo }; | ||
return prev; | ||
@@ -279,7 +280,8 @@ }); | ||
const store = s.createStore(this.attrs, triples); | ||
return { hash, store }; | ||
const pageInfo = result?.[0]?.data?.["page-info"]; | ||
return { hash, store, pageInfo }; | ||
}); | ||
updates.forEach(({ hash, store }) => { | ||
updates.forEach(({ hash, store, pageInfo }) => { | ||
this.querySubs.set((prev) => { | ||
prev[hash].result = { store }; | ||
prev[hash].result = { store, pageInfo }; | ||
return prev; | ||
@@ -441,3 +443,3 @@ }); | ||
} else if (prevResult) { | ||
cb({ data: this.dataForResult(q, prevResult) }); | ||
cb(this.dataForResult(q, prevResult)); | ||
} | ||
@@ -554,3 +556,3 @@ return () => { | ||
/** Runs instaql on a query and a store */ | ||
dataForResult(q, { store }) { | ||
dataForResult(q, { store, pageInfo }) { | ||
const muts = this._rewriteMutations( | ||
@@ -562,4 +564,4 @@ store.attrs, | ||
const newStore = s.transact(store, txSteps); | ||
const data = instaql(newStore, q); | ||
return data; | ||
const resp = instaql({ store: newStore, pageInfo }, q); | ||
return resp; | ||
} | ||
@@ -580,7 +582,8 @@ | ||
const data = this.dataForResult(q, result); | ||
if (areObjectsDeepEqual(data, iqlResult)) return; // No change, no need to notify | ||
const resp = this.dataForResult(q, result); | ||
this.querySubs.currentValue[hash].iqlResult = data; | ||
cbs.forEach((cb) => cb({ data })); | ||
if (areObjectsDeepEqual(resp.data, iqlResult)) return; // No change, no need to notify | ||
this.querySubs.currentValue[hash].iqlResult = result.data; | ||
cbs.forEach((cb) => cb(resp)); | ||
}; | ||
@@ -587,0 +590,0 @@ |
@@ -1,3 +0,20 @@ | ||
import { v4 } from "uuid"; | ||
import { parse, v4 } from "uuid"; | ||
export function uuidCompare(uuid_a: string, uuid_b: string) { | ||
const bytes_a = parse(uuid_a); | ||
const bytes_b = parse(uuid_b); | ||
for (let i = 0; i < 16; i++) { | ||
const a = bytes_a[i]; | ||
const b = bytes_b[i]; | ||
if (a < b) { | ||
return -1; | ||
} | ||
if (b > a) { | ||
return 1; | ||
} | ||
} | ||
return 0; | ||
} | ||
export default v4; |
Sorry, the diff of this file is too big to display
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
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
2810453
39823