From cf3398e7d2f2acc323d33aa7e28e8ff6aafe4638 Mon Sep 17 00:00:00 2001 From: stopachka <stepan.p@gmail.com> Date: Tue, 26 Nov 2024 13:10:55 -0800 Subject: [PATCH 1/4] roar --- client/packages/cli/index.js | 10 ++++-- client/packages/core/src/schema.ts | 52 +++++++++++++----------------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/client/packages/cli/index.js b/client/packages/cli/index.js index 495edecd9..2373338e1 100644 --- a/client/packages/cli/index.js +++ b/client/packages/cli/index.js @@ -1559,7 +1559,7 @@ function generateSchemaTypescriptFile(id, schema, title, instantModuleName) { import { i } from "${instantModuleName ?? "@instantdb/core"}"; -const graph = i.graph( +const _schema = i.schema( ${ Object.keys(schema.blobs).length === 1 && Object.keys(schema.blobs)[0] === "$users" @@ -1584,6 +1584,12 @@ ${ ${indentLines(JSON.stringify(linksEntriesCode, null, " "), 1)} ); -export default graph; +// This helps Typescript display nicer intellisense +type _AppSchema = typeof _schema; +interface AppSchema extends _AppSchema {} +const schema: AppSchema = _schema; + +export type AppSchema; +export default schema; `; } diff --git a/client/packages/core/src/schema.ts b/client/packages/core/src/schema.ts index 8f4921e58..ee25711fb 100644 --- a/client/packages/core/src/schema.ts +++ b/client/packages/core/src/schema.ts @@ -13,37 +13,15 @@ import { // ========== // API - /** - * Accepts entities and links and merges them into a single graph definition. + * @deprecated Use `i.schema` instead. * - * @see https://instantdb.com/docs/schema#defining-entities * @example - * export default i.graph( - * { - * posts: i.entity({ - * title: i.string(), - * body: i.string(), - * }), - * comments: i.entity({ - * body: i.string(), - * }), - * }, - * { - * postsComments: { - * forward: { - * on: "posts", - * has: "many", - * label: "comments", - * }, - * reverse: { - * on: "comments", - * has: "one", - * label: "post", - * }, - * }, - * }, - * ); + * // Before + * i.graph(entities, links).withRoomSchema<RoomType>(); + * + * // After + * i.schema({ entities, links, rooms }) */ function graph< EntitiesWithoutLinks extends EntitiesDef, @@ -152,8 +130,22 @@ type LinksIndex = Record< Record<string, Record<string, { entityName: string; cardinality: string }>> >; -/** - * TODO +/** + * Lets you define a schema for your database. + * + * You can define entities, links between entities, and if you use + * presence, you can define rooms. + * + * You can push this schema to your database with the CLI, + * or use it inside `init_experimental`, to get typesafety and autocompletion. + * + * @see https://instantdb.com/docs/schema + * @example + * i.schema({ + * entities: { }, + * links: { }, + * rooms: { } + * }); */ function schema< EntitiesWithoutLinks extends EntitiesDef, From f6cdf8f5db6fcca719d7953d9a3c6bb7c28d7520 Mon Sep 17 00:00:00 2001 From: stopachka <stepan.p@gmail.com> Date: Tue, 26 Nov 2024 13:12:30 -0800 Subject: [PATCH 2/4] roar --- client/packages/core/src/schema.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/packages/core/src/schema.ts b/client/packages/core/src/schema.ts index ee25711fb..7858778ef 100644 --- a/client/packages/core/src/schema.ts +++ b/client/packages/core/src/schema.ts @@ -14,7 +14,8 @@ import { // API /** - * @deprecated Use `i.schema` instead. + * @deprecated + * `i.graph` is deprecated. Use `i.schema` instead. * * @example * // Before @@ -22,6 +23,9 @@ import { * * // After * i.schema({ entities, links, rooms }) + * + * @see + * https://instantdb.com/docs/schema */ function graph< EntitiesWithoutLinks extends EntitiesDef, From 0dbb2d812c644521160f4b298165eb860bc38509 Mon Sep 17 00:00:00 2001 From: stopachka <stepan.p@gmail.com> Date: Tue, 26 Nov 2024 13:44:53 -0800 Subject: [PATCH 3/4] roar --- client/packages/cli/index.js | 64 +++++++++++-------- .../core/__tests__/src/schema.test.ts | 19 +++--- .../strong-init-vite/instant.schema.ts | 47 +++++++------- 3 files changed, 66 insertions(+), 64 deletions(-) diff --git a/client/packages/cli/index.js b/client/packages/cli/index.js index 2373338e1..1497d8872 100644 --- a/client/packages/cli/index.js +++ b/client/packages/cli/index.js @@ -410,8 +410,8 @@ function printDotEnvInfo(source, appId) { const otherEnvs = Object.values(rest); otherEnvs.sort(); const otherEnvStr = otherEnvs.map((x) => " " + chalk.green(x)).join("\n"); - console.log(`Alternative names: \n${otherEnvStr}`); - console.log(terminalLink("Dashboard", appDashUrl(appId))); + console.log(`Alternative names: \n${otherEnvStr}\n`); + console.log(terminalLink("Dashboard", appDashUrl(appId)) + "\n"); } } @@ -1530,7 +1530,7 @@ function generateSchemaTypescriptFile(id, schema, title, instantModuleName) { const entitiesObjCode = `{\n${entitiesEntriesCode}\n}`; - const linksEntriesCode = Object.fromEntries( + const linksEntries = Object.fromEntries( sortedEntries(schema.refs).map(([_name, config]) => { const [, fe, flabel] = config["forward-identity"]; const [, re, rlabel] = config["reverse-identity"]; @@ -1552,44 +1552,52 @@ function generateSchemaTypescriptFile(id, schema, title, instantModuleName) { ]; }), ); + const linksEntriesCode = JSON.stringify(linksEntries, null, " "); - return ` -// ${appDashUrl(id)} -// Docs: https://www.instantdb.com/docs/schema - -import { i } from "${instantModuleName ?? "@instantdb/core"}"; - -const _schema = i.schema( -${ - Object.keys(schema.blobs).length === 1 && - Object.keys(schema.blobs)[0] === "$users" + const etypes = Object.keys(schema.blobs); + const hasOnlyUserTable = etypes.length === 1 && etypes[0] === "$users"; + const entitiesComment = hasOnlyUserTable ? ` // This section lets you define entities: think \`posts\`, \`comments\`, etc // Take a look at the docs to learn more: // https://www.instantdb.com/docs/schema#defining-entities `.trim() - : "" -} -${indentLines(entitiesObjCode, 1)}, -${ - Object.keys(schema.refs).length === 0 + : ""; + const hasNoLinks = Object.keys(linksEntries).length === 0; + const linksComment = hasNoLinks ? ` -// You can define links here. -// For example, if \`posts\` should have many \`comments\`. -// More in the docs: -// https://www.instantdb.com/docs/schema#defining-links -`.trim() - : "" -} -${indentLines(JSON.stringify(linksEntriesCode, null, " "), 1)} -); + // You can define links here. + // For example, if \`posts\` should have many \`comments\`. + // More in the docs: + // https://www.instantdb.com/docs/schema#defining-links + `.trim() + : ""; + + const roomsComment = ` +// If you use presence, you can define a room schema here +// https://www.instantdb.com/docs/schema#defining-rooms + `.trim(); + + return ` +// Docs: https://www.instantdb.com/docs/schema + +import { i } from "${instantModuleName ?? "@instantdb/core"}"; + +const _schema = i.schema({ + ${entitiesComment} + entities: ${entitiesObjCode}, + ${linksComment} + links: ${linksEntriesCode}, + ${roomsComment} + rooms: {} +}); // This helps Typescript display nicer intellisense type _AppSchema = typeof _schema; interface AppSchema extends _AppSchema {} const schema: AppSchema = _schema; -export type AppSchema; +export { type AppSchema } export default schema; `; } diff --git a/client/packages/core/__tests__/src/schema.test.ts b/client/packages/core/__tests__/src/schema.test.ts index 5123dfdfe..7efe4e892 100644 --- a/client/packages/core/__tests__/src/schema.test.ts +++ b/client/packages/core/__tests__/src/schema.test.ts @@ -1,11 +1,11 @@ import { test } from "vitest"; import { i } from "../../src"; -import type { InstaQLQueryResult } from "../../src/queryTypes"; +import type { InstaQLResult } from "../../src/queryTypes"; test("runs without exception", () => { - const graph = i.graph( - { + const schema = i.schema({ + entities: { users: i.entity({ name: i.string(), email: i.string().indexed().unique(), @@ -23,7 +23,7 @@ test("runs without exception", () => { body: i.string(), }), }, - { + links: { usersPosts: { forward: { on: "users", @@ -73,7 +73,8 @@ test("runs without exception", () => { }, }, }, - ); + rooms: {}, + }); const demoQuery = { users: { @@ -88,7 +89,7 @@ test("runs without exception", () => { }, }; - type Graph = typeof graph; + type Graph = typeof schema; // Explore derived types type Test1 = Graph["entities"]["users"]["links"]["_friends"]["entityName"]; @@ -103,11 +104,7 @@ test("runs without exception", () => { // - referrer is NOT an array (because cardinality is 'one') // - posts is not an array (because `$first`) const queryResult: DemoQueryResult = null as any; - type DemoQueryResult = InstaQLQueryResult< - Graph["entities"], - typeof demoQuery, - true - >; + type DemoQueryResult = InstaQLResult<Graph, typeof demoQuery>; queryResult?.users[0].friends[0]._friends[0].bio; queryResult?.users[0].posts[0].author?.junk; }); diff --git a/client/sandbox/strong-init-vite/instant.schema.ts b/client/sandbox/strong-init-vite/instant.schema.ts index 83d223d6c..dbd222d2c 100644 --- a/client/sandbox/strong-init-vite/instant.schema.ts +++ b/client/sandbox/strong-init-vite/instant.schema.ts @@ -1,33 +1,30 @@ +// Docs: https://www.instantdb.com/docs/schema + import { i } from "@instantdb/react"; -const _graph = i.graph( - { - messages: i.entity({ - content: i.string(), - }), +const _schema = i.schema({ + // This section lets you define entities: think `posts`, `comments`, etc + // Take a look at the docs to learn more: + // https://www.instantdb.com/docs/schema#defining-entities + entities: { $users: i.entity({ email: i.string().unique().indexed(), }), }, - { - messageCreator: { - forward: { - on: "messages", - has: "one", - label: "creator", - }, - reverse: { - on: "$users", - has: "many", - label: "createdMessages", - }, - }, - }, - -); + // You can define links here. + // For example, if `posts` should have many `comments`. + // More in the docs: + // https://www.instantdb.com/docs/schema#defining-links + links: {}, + // If you use presence, you can define a room schema here + // https://www.instantdb.com/docs/schema#defining-rooms + rooms: {}, +}); -type _Graph = typeof _graph; +// This helps Typescript display nicer intellisense +type _AppSchema = typeof _schema; +interface AppSchema extends _AppSchema {} +const schema: AppSchema = _schema; -export interface Graph extends _Graph {}; -const graph: Graph = _graph; -export default graph; +export { type AppSchema }; +export default schema; From 73d931bf5f54cd845682ecd0f5e1c149fc2e8f75 Mon Sep 17 00:00:00 2001 From: stopachka <stepan.p@gmail.com> Date: Tue, 26 Nov 2024 13:56:44 -0800 Subject: [PATCH 4/4] roar --- client/sandbox/cli-nodejs/instant.schema.ts | 11 ++--- .../strong-init-vite/instant.schema.ts | 8 +++- client/www/pages/docs/cli.md | 41 +++++++++++++------ client/www/pages/docs/patterns.md | 29 +++++++------ client/www/pages/docs/schema.md | 14 ++++--- 5 files changed, 66 insertions(+), 37 deletions(-) diff --git a/client/sandbox/cli-nodejs/instant.schema.ts b/client/sandbox/cli-nodejs/instant.schema.ts index 1a31a6a36..238e6e9cb 100644 --- a/client/sandbox/cli-nodejs/instant.schema.ts +++ b/client/sandbox/cli-nodejs/instant.schema.ts @@ -1,7 +1,7 @@ import { i } from "@instantdb/core"; -const graph = i.graph( - { +const schema = i.schema({ + entities: { authors: i.entity({ name: i.any(), userId: i.any(), @@ -14,7 +14,7 @@ const graph = i.graph( label: i.any(), }), }, - { + links: { authorsPosts: { forward: { on: "authors", @@ -40,6 +40,7 @@ const graph = i.graph( }, }, }, -); + rooms: {}, +}); -export default graph; +export default schema; diff --git a/client/sandbox/strong-init-vite/instant.schema.ts b/client/sandbox/strong-init-vite/instant.schema.ts index dbd222d2c..60030bd72 100644 --- a/client/sandbox/strong-init-vite/instant.schema.ts +++ b/client/sandbox/strong-init-vite/instant.schema.ts @@ -18,7 +18,13 @@ const _schema = i.schema({ links: {}, // If you use presence, you can define a room schema here // https://www.instantdb.com/docs/schema#defining-rooms - rooms: {}, + rooms: { + chat: { + presence: i.entity({ + nickname: i.string(), + }), + }, + }, }); // This helps Typescript display nicer intellisense diff --git a/client/www/pages/docs/cli.md b/client/www/pages/docs/cli.md index f201a4599..6cde7cb87 100644 --- a/client/www/pages/docs/cli.md +++ b/client/www/pages/docs/cli.md @@ -15,13 +15,13 @@ You can view all commands and flags with `npx instant-cli -h`. ## Configuration as code -Instant CLI relies on the presence of two core config files: `instant.schema.ts`, where you define your application's graph structure, and `instant.perms.ts`, where you define access control rules for all of your graph's constructs. +Instant CLI relies on the presence of two core config files: `instant.schema.ts`, where you define your application's data model, and `instant.perms.ts`, where you define access control rules for all of your graph's constructs. You can learn more about [schemas here](/docs/schema) here and [permissions here](/docs/permissions). ## App ID -The CLI looks for `INSTANT_APP_ID` in `process.env`. As a convenience, it will also check for `NEXT_PUBLIC_INSTANT_APP_ID`, `PUBLIC_INSTANT_APP_ID`, and `VITE_INSTANT_APP_ID` +The CLI looks for `INSTANT_APP_ID` in `process.env`. As a convenience, it will also check for `NEXT_PUBLIC_INSTANT_APP_ID`, `PUBLIC_INSTANT_APP_ID`, and `VITE_INSTANT_APP_ID`. ## Specifying an auth token @@ -50,7 +50,7 @@ Note, this command will open Instant's dashboard in a browser window and prompt npx instant-cli init ``` -Similar to `git init`, running `instant-cli init` will generate a new app id and add `instant.schema.ts` and `instant.perms.ts` files if none are present in your project's root directory. +Running `instant-cli init` will help you generate your `instant.schema.ts` and `instant.perms.ts` files. You can either create a new Instant app, or an import an existing one through this flow. ### Push schema @@ -58,17 +58,17 @@ Similar to `git init`, running `instant-cli init` will generate a new app id and npx instant-cli push schema ``` -`push schema` evals your `instant.schema.ts` file and applies it your app's production database. [Read more about schema as code](/docs/schema). +`push schema` evaluates your `instant.schema.ts` file and applies it your app's production database. [Read more about schema as code](/docs/schema). Note, to avoid accidental data loss, `push schema` does not delete entities or fields you've removed from your schema. You can manually delete them in the [Explorer](https://www.instantdb.com/dash?s=main&t=explorer). Here's an example `instant.schema.ts` file. ```ts -import { i } from '@instantdb/core'; +import { i } from '@instantdb/react'; -const graph = i.graph( - { +const _schema = i.schema({ + entities: { authors: i.entity({ userId: i.string(), name: i.string(), @@ -78,7 +78,7 @@ const graph = i.graph( content: i.string(), }), }, - { + links: { authorPosts: { forward: { on: 'authors', @@ -91,10 +91,23 @@ const graph = i.graph( label: 'author', }, }, - } -); + }, + rooms: { + chat: { + presence: i.entity({ + nickname: i.string() + }) + } + }, +}); -export default graph; +// This helps Typescript display nicer intellisense +type _AppSchema = typeof _schema; +interface AppSchema extends _AppSchema {} +const schema: AppSchema = _schema; + +export { type AppSchema }; +export default schema; ``` ### Push perms @@ -103,12 +116,12 @@ export default graph; npx instant-cli push perms ``` -`push perms` evals your `instant.perms.ts` file and applies it your app's production database. `instant.perms.ts` should export an object implementing Instant's standard permissions CEL+JSON format. [Read more about permissions in Instant](/docs/permissions). +`push perms` evaluates your `instant.perms.ts` file and applies it your app's production database. `instant.perms.ts` should export an object implementing Instant's standard permissions CEL+JSON format. [Read more about permissions in Instant](/docs/permissions). Here's an example `instant.perms.ts` file. ```ts -export default { +const rules = { allow: { posts: { bind: ['isAuthor', "auth.id in data.ref('authors.userId')"], @@ -121,6 +134,8 @@ export default { }, }, }; + +export default rules; ``` ### Pull: migrating from the dashboard diff --git a/client/www/pages/docs/patterns.md b/client/www/pages/docs/patterns.md index e053c65b3..144f98c54 100644 --- a/client/www/pages/docs/patterns.md +++ b/client/www/pages/docs/patterns.md @@ -41,10 +41,10 @@ permissions. Here's an example of limiting a user to creating at most 2 todos. ```typescript // instant.schema.ts // Here we define users, todos, and a link between them. -import { i } from '@instantdb/core'; +import { i } from '@instantdb/react'; -const graph = i.graph( - { +const schema = i.schema({ + entities: { users: i.entity({ email: i.string(), }), @@ -52,7 +52,7 @@ const graph = i.graph( label: i.string(), }), }, - { + links: { userTodos: { forward: { on: 'users', @@ -65,23 +65,26 @@ const graph = i.graph( label: 'owner', }, }, - } -); + }, + rooms: {} +}); -export default graph; +export default schema; ``` ```typescript -// instant.schema.ts +// instant.perms.ts // And now we reference the `owner` link for todos to check the number // of todos a user has created. // (Note): Make sure the `owner` link is already defined in the schema. // before you can reference it in the permissions. -export { - "todos": { - "allow": { - "create": "size(data.ref('owner.todos.id')) <= 2", +const rules = { + todos: { + allow: { + create: "size(data.ref('owner.todos.id')) <= 2", } } -} +}; + +export default rules; ``` diff --git a/client/www/pages/docs/schema.md b/client/www/pages/docs/schema.md index 16ee61e97..66738f842 100644 --- a/client/www/pages/docs/schema.md +++ b/client/www/pages/docs/schema.md @@ -6,17 +6,18 @@ title: Schema-as-code This file lives in the root of your project and will be consumed by [the Instant CLI](/docs/cli). You can apply your schema to the production database with `npx instant-cli push schema`. -The default export of `instant.schema.ts` should always be the result of a call to `i.graph`. +The default export of `instant.schema.ts` should always be the result of a call to `i.schema`. ```typescript // instant.schema.ts import { i } from '@instantdb/core'; -const graph = i.graph( - entitiesMap, // a map of `i.entity` definitions, see "Defining entities" below - linksMap // a description of links between your app's entities, see "Defining links" below -); +const schema = i.graph( + entities: entitiesMap, // a map of `i.entity` definitions, see "Defining entities" below + links: linksMap // a description of links between your app's entities, see "Defining links" below, + rooms: roomsMap // a description of your presence state +}); export default graph; ``` @@ -107,6 +108,9 @@ Links are bidirectional, and you can specify a name and cardinality for both the } ``` +## Defining rooms + + ## An example schema file Below we demonstrate a data model for a blog. First we define our core entities: authors, posts and tags.