@instantdb/core
Version:
Instant's core local abstraction
502 lines • 17.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.version = exports.Storage = exports.Auth = exports.InstantCoreDatabase = exports.WindowNetworkListener = exports.IndexedDBStorage = exports.weakHash = exports.getOps = exports.i = exports.InstantAPIError = exports.lookup = exports.txInit = exports.tx = exports.id = exports.init_experimental = void 0;
exports.init = init;
exports.coerceQuery = coerceQuery;
const Reactor_js_1 = __importDefault(require("./Reactor.js"));
const instatx_js_1 = require("./instatx.js");
Object.defineProperty(exports, "tx", { enumerable: true, get: function () { return instatx_js_1.tx; } });
Object.defineProperty(exports, "txInit", { enumerable: true, get: function () { return instatx_js_1.txInit; } });
Object.defineProperty(exports, "lookup", { enumerable: true, get: function () { return instatx_js_1.lookup; } });
Object.defineProperty(exports, "getOps", { enumerable: true, get: function () { return instatx_js_1.getOps; } });
const weakHash_js_1 = __importDefault(require("./utils/weakHash.js"));
exports.weakHash = weakHash_js_1.default;
const uuid_js_1 = __importDefault(require("./utils/uuid.js"));
exports.id = uuid_js_1.default;
const IndexedDBStorage_js_1 = __importDefault(require("./IndexedDBStorage.js"));
exports.IndexedDBStorage = IndexedDBStorage_js_1.default;
const WindowNetworkListener_js_1 = __importDefault(require("./WindowNetworkListener.js"));
exports.WindowNetworkListener = WindowNetworkListener_js_1.default;
const schema_js_1 = require("./schema.js");
Object.defineProperty(exports, "i", { enumerable: true, get: function () { return schema_js_1.i; } });
const devtool_js_1 = require("./devtool.js");
const version_js_1 = __importDefault(require("./version.js"));
exports.version = version_js_1.default;
const fetch_js_1 = require("./utils/fetch.js");
Object.defineProperty(exports, "InstantAPIError", { enumerable: true, get: function () { return fetch_js_1.InstantAPIError; } });
const defaultOpenDevtool = true;
// consts
const defaultConfig = {
apiURI: 'https://api.instantdb.com',
websocketURI: 'wss://api.instantdb.com/runtime/session',
};
// hmr
function initSchemaHashStore() {
var _a;
globalThis.__instantDbSchemaHashStore =
(_a = globalThis.__instantDbSchemaHashStore) !== null && _a !== void 0 ? _a : new WeakMap();
return globalThis.__instantDbSchemaHashStore;
}
function initGlobalInstantCoreStore() {
var _a;
globalThis.__instantDbStore = (_a = globalThis.__instantDbStore) !== null && _a !== void 0 ? _a : {};
return globalThis.__instantDbStore;
}
function reactorKey(config) {
// @ts-expect-error
const adminToken = config.__adminToken;
return (config.appId +
'_' +
(config.websocketURI || 'default_ws_uri') +
'_' +
(config.apiURI || 'default_api_uri') +
'_' +
(adminToken || 'client_only'));
}
const globalInstantCoreStore = initGlobalInstantCoreStore();
const schemaHashStore = initSchemaHashStore();
/**
* Functions to log users in and out.
*
* @see https://instantdb.com/docs/auth
*/
class Auth {
constructor(db) {
this.db = db;
/**
* Sends a magic code to the user's email address.
*
* Once you send the magic code, see {@link auth.signInWithMagicCode} to let the
* user verify.
*
* @see https://instantdb.com/docs/auth
* @example
* db.auth.sendMagicCode({email: "example@gmail.com"})
* .catch((err) => console.error(err.body?.message))
*/
this.sendMagicCode = (params) => {
return this.db.sendMagicCode(params);
};
/**
* Verify a magic code that was sent to the user's email address.
*
* @see https://instantdb.com/docs/auth
*
* @example
* db.auth.signInWithMagicCode({email: "example@gmail.com", code: "123456"})
* .catch((err) => console.error(err.body?.message))
*/
this.signInWithMagicCode = (params) => {
return this.db.signInWithMagicCode(params);
};
/**
* Sign in a user with a refresh token
*
* @see https://instantdb.com/docs/backend#frontend-auth-sign-in-with-token
*
* @example
* // Get the token from your backend
* const token = await fetch('/signin', ...);
* //Sign in
* db.auth.signInWithToken(token);
*/
this.signInWithToken = (token) => {
return this.db.signInWithCustomToken(token);
};
/**
* Create an authorization url to sign in with an external provider
*
* @see https://instantdb.com/docs/auth
*
* @example
* // Get the authorization url from your backend
* const url = db.auth.createAuthorizationUrl({
* clientName: "google",
* redirectURL: window.location.href,
* });
*
* // Put it in a sign in link
* <a href={url}>Log in with Google</a>
*/
this.createAuthorizationURL = (params) => {
return this.db.createAuthorizationURL(params);
};
/**
* Sign in with the id_token from an external provider like Google
*
* @see https://instantdb.com/docs/auth
* @example
* db.auth
* .signInWithIdToken({
* // Token from external service
* idToken: id_token,
* // The name you gave the client when you registered it with Instant
* clientName: "google",
* // The nonce, if any, that you used when you initiated the auth flow
* // with the external service.
* nonce: your_nonce
* })
* .catch((err) => console.error(err.body?.message));
*
*/
this.signInWithIdToken = (params) => {
return this.db.signInWithIdToken(params);
};
/**
* Sign in with the id_token from an external provider like Google
*
* @see https://instantdb.com/docs/auth
* @example
* db.auth
* .exchangeOAuthCode({
* // code received in redirect from OAuth callback
* code: code
* // The PKCE code_verifier, if any, that you used when you
* // initiated the auth flow
* codeVerifier: your_code_verifier
* })
* .catch((err) => console.error(err.body?.message));
*
*/
this.exchangeOAuthCode = (params) => {
return this.db.exchangeCodeForToken(params);
};
/**
* OpenID Discovery path for use with tools like
* expo-auth-session that use auto-discovery of
* OAuth parameters.
*
* @see https://instantdb.com/docs/auth
* @example
* const discovery = useAutoDiscovery(
* db.auth.issuerURI()
* );
*/
this.issuerURI = () => {
return this.db.issuerURI();
};
/**
* Sign out the current user
*/
this.signOut = (opts = { invalidateToken: true }) => {
return this.db.signOut(opts);
};
}
}
exports.Auth = Auth;
/**
* Functions to manage file storage.
*/
class Storage {
constructor(db) {
this.db = db;
/**
* Uploads file at the provided path.
*
* @see https://instantdb.com/docs/storage
* @example
* const [file] = e.target.files; // result of file input
* const data = await db.storage.uploadFile('photos/demo.png', file);
*/
this.uploadFile = (path, file, opts = {}) => {
return this.db.uploadFile(path, file, opts);
};
/**
* Deletes a file by path name.
*
* @see https://instantdb.com/docs/storage
* @example
* await db.storage.delete('photos/demo.png');
*/
this.delete = (pathname) => {
return this.db.deleteFile(pathname);
};
// Deprecated Storage API (Jan 2025)
// ---------------------------------
/**
* @deprecated. Use `db.storage.uploadFile` instead
* remove in the future.
*/
this.upload = (pathname, file) => {
return this.db.upload(pathname, file);
};
/**
* @deprecated Use `db.storage.uploadFile` instead
*/
this.put = this.upload;
/**
* @deprecated. getDownloadUrl will be removed in the future.
* Use `useQuery` instead to query and fetch for valid urls
*
* db.useQuery({
* $files: {
* $: {
* where: {
* path: "moop.png"
* }
* }
* }
* })
*/
this.getDownloadUrl = (pathname) => {
return this.db.getDownloadUrl(pathname);
};
}
}
exports.Storage = Storage;
// util
function coerceQuery(o) {
// stringify and parse to remove undefined values
return JSON.parse(JSON.stringify(o));
}
class InstantCoreDatabase {
constructor(reactor) {
this.tx = (0, instatx_js_1.txInit)();
this._reactor = reactor;
this.auth = new Auth(this._reactor);
this.storage = new Storage(this._reactor);
}
/**
* Use this to write data! You can create, update, delete, and link objects
*
* @see https://instantdb.com/docs/instaml
*
* @example
* // Create a new object in the `goals` namespace
* const goalId = id();
* db.transact(db.tx.goals[goalId].update({title: "Get fit"}))
*
* // Update the title
* db.transact(db.tx.goals[goalId].update({title: "Get super fit"}))
*
* // Delete it
* db.transact(db.tx.goals[goalId].delete())
*
* // Or create an association:
* todoId = id();
* db.transact([
* db.tx.todos[todoId].update({ title: 'Go on a run' }),
* db.tx.goals[goalId].link({todos: todoId}),
* ])
*/
transact(chunks) {
return this._reactor.pushTx(chunks);
}
getLocalId(name) {
return this._reactor.getLocalId(name);
}
/**
* Use this to query your data!
*
* @see https://instantdb.com/docs/instaql
*
* @example
* // listen to all goals
* db.subscribeQuery({ goals: {} }, (resp) => {
* console.log(resp.data.goals)
* })
*
* // goals where the title is "Get Fit"
* db.subscribeQuery(
* { goals: { $: { where: { title: "Get Fit" } } } },
* (resp) => {
* console.log(resp.data.goals)
* }
* )
*
* // all goals, _alongside_ their todos
* db.subscribeQuery({ goals: { todos: {} } }, (resp) => {
* console.log(resp.data.goals)
* });
*/
subscribeQuery(query, cb, opts) {
return this._reactor.subscribeQuery(query, cb, opts);
}
/**
* Listen for the logged in state. This is useful
* for deciding when to show a login screen.
*
* @see https://instantdb.com/docs/auth
* @example
* const unsub = db.subscribeAuth((auth) => {
* if (auth.user) {
* console.log('logged in as', auth.user.email)
* } else {
* console.log('logged out')
* }
* })
*/
subscribeAuth(cb) {
return this._reactor.subscribeAuth(cb);
}
/**
* One time query for the logged in state. This is useful
* for scenarios where you want to know the current auth
* state without subscribing to changes.
*
* @see https://instantdb.com/docs/auth
* @example
* const user = await db.getAuth();
* console.log('logged in as', user.email)
*/
getAuth() {
return this._reactor.getAuth();
}
/**
* Listen for connection status changes to Instant. This is useful
* for building things like connectivity indicators
*
* @see https://www.instantdb.com/docs/patterns#connection-status
* @example
* const unsub = db.subscribeConnectionStatus((status) => {
* const connectionState =
* status === 'connecting' || status === 'opened'
* ? 'authenticating'
* : status === 'authenticated'
* ? 'connected'
* : status === 'closed'
* ? 'closed'
* : status === 'errored'
* ? 'errored'
* : 'unexpected state';
*
* console.log('Connection status:', connectionState);
* });
*/
subscribeConnectionStatus(cb) {
return this._reactor.subscribeConnectionStatus(cb);
}
/**
* Join a room to publish and subscribe to topics and presence.
*
* @see https://instantdb.com/docs/presence-and-topics
* @example
* // init
* const db = init();
* const room = db.joinRoom(roomType, roomId);
* // usage
* const unsubscribeTopic = room.subscribeTopic("foo", console.log);
* const unsubscribePresence = room.subscribePresence({}, console.log);
* room.publishTopic("hello", { message: "hello world!" });
* room.publishPresence({ name: "joe" });
* // later
* unsubscribePresence();
* unsubscribeTopic();
* room.leaveRoom();
*/
joinRoom(roomType = '_defaultRoomType', roomId = '_defaultRoomId', opts) {
const leaveRoom = this._reactor.joinRoom(roomId, opts === null || opts === void 0 ? void 0 : opts.initialPresence);
return {
leaveRoom,
subscribeTopic: (topic, onEvent) => this._reactor.subscribeTopic(roomId, topic, onEvent),
subscribePresence: (opts, onChange) => this._reactor.subscribePresence(roomType, roomId, opts, onChange),
publishTopic: (topic, data) => this._reactor.publishTopic({ roomType, roomId, topic, data }),
publishPresence: (data) => this._reactor.publishPresence(roomType, roomId, data),
getPresence: (opts) => this._reactor.getPresence(roomType, roomId, opts),
};
}
shutdown() {
delete globalInstantCoreStore[reactorKey(this._reactor.config)];
this._reactor.shutdown();
}
/**
* Use this for one-off queries.
* Returns local data if available, otherwise fetches from the server.
* Because we want to avoid stale data, this method will throw an error
* if the user is offline or there is no active connection to the server.
*
* @see https://instantdb.com/docs/instaql
*
* @example
*
* const resp = await db.queryOnce({ goals: {} });
* console.log(resp.data.goals)
*/
queryOnce(query, opts) {
return this._reactor.queryOnce(query, opts);
}
}
exports.InstantCoreDatabase = InstantCoreDatabase;
function schemaHash(schema) {
if (!schema) {
return '0';
}
if (schemaHashStore.get(schema)) {
return schemaHashStore.get(schema);
}
const hash = (0, weakHash_js_1.default)(schema);
schemaHashStore.set(schema, hash);
return hash;
}
function schemaChanged(existingClient, newSchema) {
return (schemaHash(existingClient._reactor.config.schema) !== schemaHash(newSchema));
}
/**
*
* The first step: init your application!
*
* Visit https://instantdb.com/dash to get your `appId` :)
*
* @example
* import { init } from "@instantdb/core"
*
* const db = init({ appId: "my-app-id" })
*
* // You can also provide a schema for type safety and editor autocomplete!
*
* import { init } from "@instantdb/core"
* import schema from ""../instant.schema.ts";
*
* const db = init({ appId: "my-app-id", schema })
*
* // To learn more: https://instantdb.com/docs/modeling-data
*/
function init(config, Storage, NetworkListener, versions) {
const existingClient = globalInstantCoreStore[reactorKey(config)];
if (existingClient) {
if (schemaChanged(existingClient, config.schema)) {
existingClient._reactor.updateSchema(config.schema);
}
return existingClient;
}
const reactor = new Reactor_js_1.default(Object.assign(Object.assign(Object.assign({}, defaultConfig), config), { cardinalityInference: config.schema ? true : false }), Storage || IndexedDBStorage_js_1.default, NetworkListener || WindowNetworkListener_js_1.default, Object.assign(Object.assign({}, (versions || {})), { '@instantdb/core': version_js_1.default }));
const client = new InstantCoreDatabase(reactor);
globalInstantCoreStore[reactorKey(config)] = client;
handleDevtool(config.appId, config.devtool);
return client;
}
function handleDevtool(appId, devtool) {
if (typeof window === 'undefined' ||
typeof window.location === 'undefined' ||
typeof document === 'undefined') {
return;
}
if (typeof devtool === 'boolean' && !devtool) {
return;
}
const config = Object.assign({ position: 'bottom-right', allowedHosts: ['localhost'] }, (typeof devtool === 'object' ? devtool : {}));
if (!config.allowedHosts.includes(window.location.hostname)) {
return;
}
(0, devtool_js_1.createDevtool)(appId, config);
}
/**
* @deprecated
* `init_experimental` is deprecated. You can replace it with `init`.
*
* @example
*
* // Before
* import { init_experimental } from "@instantdb/core"
* const db = init_experimental({ ... });
*
* // After
* import { init } from "@instantdb/core"
* const db = init({ ... });
*/
const init_experimental = init;
exports.init_experimental = init_experimental;
//# sourceMappingURL=index.js.map