UNPKG

convex

Version:

Client for the Convex Cloud

327 lines (315 loc) 10.8 kB
"use strict"; import { toComponentDefinitionPath } from "../lib/components/definition/directoryStructure.js"; import { header } from "./common.js"; import { validatorToType } from "./validator_helpers.js"; export function noSchemaDataModelDTS() { return ` ${header("Generated data model types.")} import { AnyDataModel } from "convex/server"; import type { GenericId } from "convex/values"; /** * No \`schema.ts\` file found! * * This generated code has permissive types like \`Doc = any\` because * Convex doesn't know your schema. If you'd like more type safety, see * https://docs.convex.dev/using/schemas for instructions on how to add a * schema file. * * After you change a schema, rerun codegen with \`npx convex dev\`. */ /** * The names of all of your Convex tables. */ export type TableNames = string; /** * The type of a document stored in Convex. */ export type Doc = any; /** * An identifier for a document in Convex. * * Convex documents are uniquely identified by their \`Id\`, which is accessible * on the \`_id\` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). * * Documents can be loaded using \`db.get(id)\` in query and mutation functions. * * IDs are just strings at runtime, but this type can be used to distinguish them from other * strings when type checking. */ export type Id<TableName extends TableNames = TableNames> = GenericId<TableName>; /** * A type describing your Convex data model. * * This type includes information about what tables you have, the type of * documents stored in those tables, and the indexes defined on them. * * This type is used to parameterize methods like \`queryGeneric\` and * \`mutationGeneric\` to make them type-safe. */ export type DataModel = AnyDataModel;`; } export function dynamicDataModelDTS() { return ` ${header("Generated data model types.")} import type { DataModelFromSchemaDefinition, DocumentByName, TableNamesInDataModel, SystemTableNames } from "convex/server"; import type { GenericId } from "convex/values"; import schema from "../schema.js"; /** * The names of all of your Convex tables. */ export type TableNames = TableNamesInDataModel<DataModel>; /** * The type of a document stored in Convex. * * @typeParam TableName - A string literal type of the table name (like "users"). */ export type Doc<TableName extends TableNames> = DocumentByName<DataModel, TableName>; /** * An identifier for a document in Convex. * * Convex documents are uniquely identified by their \`Id\`, which is accessible * on the \`_id\` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). * * Documents can be loaded using \`db.get(id)\` in query and mutation functions. * * IDs are just strings at runtime, but this type can be used to distinguish them from other * strings when type checking. * * @typeParam TableName - A string literal type of the table name (like "users"). */ export type Id<TableName extends TableNames | SystemTableNames> = GenericId<TableName>; /** * A type describing your Convex data model. * * This type includes information about what tables you have, the type of * documents stored in those tables, and the indexes defined on them. * * This type is used to parameterize methods like \`queryGeneric\` and * \`mutationGeneric\` to make them type-safe. */ export type DataModel = DataModelFromSchemaDefinition<typeof schema>; `; } export async function staticDataModelDTS(ctx, startPush, rootComponent, componentDirectory) { const definitionPath = toComponentDefinitionPath( rootComponent, componentDirectory ); const analysis = startPush.analysis[definitionPath]; if (!analysis) { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `No analysis found for component ${definitionPath} orig: ${definitionPath} in ${Object.keys(startPush.analysis).toString()}` }); } if (!analysis.schema) { return noSchemaDataModelDTS(); } const lines = [ header("Generated data model types."), `import type { DocumentByName, TableNamesInDataModel, SystemTableNames, AnyDataModel } from "convex/server";`, `import type { GenericId } from "convex/values";` ]; for await (const line of codegenDataModel(ctx, analysis.schema)) { lines.push(line); } lines.push(` /** * The names of all of your Convex tables. */ export type TableNames = TableNamesInDataModel<DataModel>; /** * The type of a document stored in Convex. * * @typeParam TableName - A string literal type of the table name (like "users"). */ export type Doc<TableName extends TableNames> = DocumentByName<DataModel, TableName>; /** * An identifier for a document in Convex. * * Convex documents are uniquely identified by their \`Id\`, which is accessible * on the \`_id\` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). * * Documents can be loaded using \`db.get(id)\` in query and mutation functions. * * IDs are just strings at runtime, but this type can be used to distinguish them from other * strings when type checking. * * @typeParam TableName - A string literal type of the table name (like "users"). */ export type Id<TableName extends TableNames | SystemTableNames> = GenericId<TableName>; `); return lines.join("\n"); } async function* codegenDataModel(ctx, schema) { yield ` /** * A type describing your Convex data model. * * This type includes information about what tables you have, the type of * documents stored in those tables, and the indexes defined on them. * * This type is used to parameterize methods like \`queryGeneric\` and * \`mutationGeneric\` to make them type-safe. */ `; const tables = [...schema.tables]; tables.sort((a, b) => a.tableName.localeCompare(b.tableName)); yield `export type DataModel = {`; for (const table of tables) { yield ` ${table.tableName}:`; yield* codegenTable(ctx, table); yield `,`; } yield `}`; if (!schema.schemaValidation) { yield ` & AnyDataModel`; } yield `;`; } async function* codegenTable(ctx, table) { const documentType = await addSystemFields( ctx, table.tableName, table.documentType ); const indexJson = {}; for (const index of table.indexes) { indexJson[index.indexDescriptor] = index.fields; } yield `{`; yield ` document: ${validatorToType(documentType, true)},`; const fieldPaths = /* @__PURE__ */ new Set(); for (const fieldPath of extractFieldPaths(documentType)) { fieldPaths.add(fieldPath.join(".")); } yield ` fieldPaths: ${stringLiteralUnionType(Array.from(fieldPaths).sort())},`; yield ` indexes: {`; const systemIndexes = { by_id: ["_id"], by_creation_time: ["_creationTime"] }; const indexes = {}; for (const [indexDescriptor, fields] of Object.entries(systemIndexes)) { indexes[indexDescriptor] = fields; } for (const index of table.indexes) { if (indexes[index.indexDescriptor]) { yield await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Duplicate index name ${index.indexDescriptor} in table ${table.tableName}.` }); } indexes[index.indexDescriptor] = index.fields; } for (const [indexDescriptor, fields] of Object.entries(indexes)) { yield ` "${indexDescriptor}": ${JSON.stringify(fields)},`; } yield ` },`; yield ` searchIndexes: {`; for (const index of table.searchIndexes ?? []) { yield ` "${index.indexDescriptor}": {`; yield ` searchField: "${index.searchField}",`; yield ` filterFields: ${stringLiteralUnionType(index.filterFields)},`; yield ` },`; } yield ` },`; yield ` vectorIndexes: {`; for (const index of table.vectorIndexes ?? []) { yield ` "${index.indexDescriptor}": {`; yield ` vectorField: "${index.vectorField}",`; yield ` dimensions: ${index.dimensions},`; yield ` filterFields: ${stringLiteralUnionType(index.filterFields)},`; yield ` },`; } yield ` },`; yield `}`; } const SYSTEM_FIELDS = ["_id", "_creationTime"]; async function addSystemFields(ctx, tableName, validator) { if (validator.type === "object") { return addSystemFieldsToObject(ctx, tableName, validator); } else if (validator.type === "any") { return { type: "any" }; } else if (validator.type === "union") { const newSubValidators = []; for (const subValidator of validator.value) { const newSubValidator = await addSystemFieldsToObject( ctx, tableName, subValidator ); newSubValidators.push(newSubValidator); } return { type: "union", value: newSubValidators }; } else { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `Invalid top-level validator for ${tableName}.` }); } } async function addSystemFieldsToObject(ctx, tableName, validator) { if (validator.type !== "object") { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `System fields can only be added to objects.` }); } for (const systemField of SYSTEM_FIELDS) { if (Object.hasOwn(validator.value, systemField)) { return await ctx.crash({ exitCode: 1, errorType: "fatal", printedMessage: `System field ${systemField} present in table ${tableName}.` }); } } return { type: "object", value: { ...validator.value, _id: { fieldType: { type: "id", tableName }, optional: false }, _creationTime: { fieldType: { type: "number" }, optional: false } } }; } function* extractFieldPaths(validator) { if (validator.type === "object") { for (const [fieldName, fieldValidator] of Object.entries(validator.value)) { for (const subFieldPath of extractFieldPaths(fieldValidator.fieldType)) { yield [fieldName, ...subFieldPath]; } } } else if (validator.type === "union") { for (const subValidator of validator.value) { yield* extractFieldPaths(subValidator); } } else { yield []; } } function stringLiteralUnionType(fields) { if (fields.length === 0) { return "never"; } else if (fields.length === 1) { return `"${fields[0]}"`; } else { return fields.map((field) => `"${field}"`).join(" | "); } } //# sourceMappingURL=dataModel.js.map