@instantdb/core
Version:
Instant's core local abstraction
284 lines • 10.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FrameworkClient = exports.isServer = void 0;
// The FrameworkClient class is a mini version of a query store that allows making queries on both the frontend and backend
// you can register queries, await their results and serialize them over a server/client boundary.
// The class is generic so that it can be a good starting off point to make other ssr adapters.
const index_ts_1 = require("./index.js");
const s = __importStar(require("./store.js"));
const instaql_js_1 = __importDefault(require("./instaql.js"));
const linkIndex_ts_1 = require("./utils/linkIndex.js");
exports.isServer = typeof window === 'undefined' || 'Deno' in globalThis;
class FrameworkClient {
params;
db;
// stores all of the query promises so that ssr can read them
// and send the relevant results alongside the html that resulted in the query resolving
resultMap = new Map();
queryResolvedCallbacks = [];
constructor(params) {
this.params = params;
this.db = params.db;
this.resultMap = new Map();
}
subscribe = (callback) => {
this.queryResolvedCallbacks.push(callback);
};
// Runs on the client when ssr gets html script tags
addQueryResult = (queryKey, value) => {
this.resultMap.set(queryKey, {
type: value.type,
status: 'success',
data: value,
promise: null,
error: null,
});
// send the result to the client
if (!exports.isServer) {
// make sure the attrs are there to create stores
if (!this.db._reactor.attrs) {
this.db._reactor._setAttrs(value.attrs);
}
this.db._reactor._addQueryData(value.query, value, !!this.db._reactor.config.schema);
}
};
removeCachedQueryResult = (queryHash) => {
this.resultMap.delete(queryHash);
};
// Run a query on the client and return a promise with the result
queryClient = (query_, opts) => {
const { hash, query } = this.hashQuery(query_, opts);
let resolve;
let reject;
const promise = new Promise((resolvePromise, rejectPromise) => {
resolve = resolvePromise;
reject = rejectPromise;
});
let entry = {
status: 'pending',
type: 'session',
data: undefined,
error: undefined,
promise: promise,
};
let unsub = null;
let unsubImmediately = false;
unsub = this.db.subscribeQuery(query, (res) => {
if (res.error) {
entry.status = 'error';
entry.error = res.error;
entry.promise = null;
reject(res.error);
}
else {
entry.status = 'success';
entry.data = res;
entry.promise = null;
resolve(res);
}
if (unsub !== null) {
unsub();
}
else {
unsubImmediately;
}
});
// We may have gotten the result inside of subscribeQuery before
// we defined the `unsub` function
if (unsubImmediately) {
unsub();
}
this.resultMap.set(hash, entry);
return promise;
};
// creates an entry in the results map
// and returns the same thing added to the map
query = (_query, opts) => {
const { hash, query } = this.hashQuery(_query, opts);
if (this.db._reactor.status === 'authenticated') {
const promise = this.db.queryOnce(_query, opts);
let entry = {
status: 'pending',
type: 'session',
data: undefined,
error: undefined,
promise: promise,
};
promise.then((result) => {
entry.status = 'success';
entry.data = result;
entry.promise = null;
});
promise.catch((error) => {
entry.status = 'error';
entry.error = error;
entry.promise = null;
});
this.resultMap.set(hash, entry);
return entry;
}
const promise = this.getTriplesAndAttrsForQuery(query);
let entry = {
status: 'pending',
type: 'http',
data: undefined,
error: undefined,
promise: promise,
};
promise.then((result) => {
entry.status = 'success';
entry.data = result;
entry.promise = null;
});
promise.catch((error) => {
entry.status = 'error';
entry.error = error;
entry.promise = null;
});
promise.then((result) => {
this.queryResolvedCallbacks.forEach((callback) => {
callback({
queryHash: hash,
query: query,
attrs: result.attrs,
triples: result.triples,
pageInfo: result.pageInfo,
});
});
});
this.resultMap.set(hash, entry);
return entry;
};
getExistingResultForQuery = (_query, opts) => {
const { hash } = this.hashQuery(_query, opts);
return this.resultMap.get(hash);
};
// creates a query result from a set of triples, query, and attrs
// can be run server side or client side
completeIsomorphic = (query, triples, attrs, pageInfo) => {
const attrMap = {};
attrs.forEach((attr) => {
attrMap[attr.id] = attr;
});
const enableCardinalityInference = Boolean(this.db?._reactor?.config?.schema) &&
('cardinalityInference' in this.db?._reactor?.config
? Boolean(this.db?._reactor.config?.cardinalityInference)
: true);
const attrsStore = new s.AttrsStoreClass(attrs.reduce((acc, attr) => {
acc[attr.id] = attr;
return acc;
}, {}), (0, linkIndex_ts_1.createLinkIndex)(this.db?._reactor.config.schema));
const store = s.createStore(attrsStore, triples, enableCardinalityInference, this.params.db._reactor.config.useDateObjects || false);
const resp = (0, instaql_js_1.default)({
store: store,
attrsStore: attrsStore,
pageInfo: pageInfo,
aggregate: undefined,
}, query);
return resp;
};
hashQuery = (_query, opts) => {
if (_query && opts && 'ruleParams' in opts) {
_query = { $$ruleParams: opts['ruleParams'], ..._query };
}
const query = _query ? (0, index_ts_1.coerceQuery)(_query) : null;
return { hash: (0, index_ts_1.weakHash)(query), query: query };
};
// Run by the server to get triples and attrs
getTriplesAndAttrsForQuery = async (query) => {
try {
const response = await fetch(`${this.db._reactor.config.apiURI}/runtime/framework/query`, {
method: 'POST',
headers: {
'app-id': this.params.db._reactor.config.appId,
'Content-Type': 'application/json',
Authorization: this.params.token
? `Bearer ${this.params.token}`
: undefined,
},
body: JSON.stringify({
query: query,
}),
});
if (!response.ok) {
try {
const data = await response.json();
if ('message' in data) {
throw new index_ts_1.InstantAPIError({ body: data, status: response.status });
}
else {
throw new Error('Error getting triples from server');
}
}
catch (e) {
if (e instanceof index_ts_1.InstantError) {
throw e;
}
throw new Error('Error getting triples from server');
}
}
const data = await response.json();
const attrs = data?.attrs;
if (!attrs) {
throw new Error('No attrs');
}
// TODO: make safer
const triples = data.result?.[0].data?.['datalog-result']?.['join-rows'][0];
const pageInfo = data.result?.[0]?.data?.['page-info'];
return {
attrs,
triples,
type: 'http',
queryHash: this.hashQuery(query).hash,
query,
pageInfo,
};
}
catch (err) {
if (err instanceof index_ts_1.InstantError) {
throw err;
}
const errWithMessage = new Error('Error getting triples from framework client');
errWithMessage.cause = err;
throw errWithMessage;
}
};
}
exports.FrameworkClient = FrameworkClient;
//# sourceMappingURL=framework.js.map