diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000..e10ccf5 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,13 @@ +{ + "servers": { + "convex": { + "command": "npx", + "args": [ + "-y", + "convex@latest", + "mcp", + "start" + ] + } + } +} \ No newline at end of file diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 339832b..82c7bb6 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -11,6 +11,7 @@ import type * as github_action from "../github/action.js"; import type * as github_starrred from "../github/starrred.js"; import type * as http from "../http.js"; +import type * as nCode from "../nCode.js"; import type { ApiFromModules, @@ -22,6 +23,7 @@ declare const fullApi: ApiFromModules<{ "github/action": typeof github_action; "github/starrred": typeof github_starrred; http: typeof http; + nCode: typeof nCode; }>; /** @@ -50,4 +52,50 @@ export declare const internal: FilterApi< FunctionReference >; -export declare const components: {}; +export declare const components: { + nCode: { + action: { + create: FunctionReference< + "mutation", + "internal", + { + code: string; + data: any; + description: string; + slug?: string; + title: string; + type: string; + userId: string; + }, + any + >; + deleteBySlug: FunctionReference< + "mutation", + "internal", + { slug: string }, + any + >; + getByCode: FunctionReference<"query", "internal", { code: string }, any>; + getBySlug: FunctionReference<"query", "internal", { slug: string }, any>; + getList: FunctionReference< + "query", + "internal", + { type?: string; userId?: string }, + any + >; + updateBySlug: FunctionReference< + "mutation", + "internal", + { + code?: string; + data?: any; + description?: string; + slug: string; + title?: string; + type?: string; + }, + any + >; + }; + }; +}; diff --git a/convex/auth.config.ts b/convex/auth.config.ts index 5c1f549..20d1ea9 100644 --- a/convex/auth.config.ts +++ b/convex/auth.config.ts @@ -1,5 +1,3 @@ -import { AuthConfig } from "convex/server"; - export default { providers: [ { diff --git a/convex/components/n-code/_generated/api.ts b/convex/components/n-code/_generated/api.ts new file mode 100644 index 0000000..2b3aead --- /dev/null +++ b/convex/components/n-code/_generated/api.ts @@ -0,0 +1,50 @@ +/* eslint-disable */ +/** + * Generated `api` utility. + * + * THIS CODE IS AUTOMATICALLY GENERATED. + * + * To regenerate, run `npx convex dev`. + * @module + */ + +import type * as action from "../action.js"; + +import type { + ApiFromModules, + FilterApi, + FunctionReference, +} from "convex/server"; +import { anyApi, componentsGeneric } from "convex/server"; + +const fullApi: ApiFromModules<{ + action: typeof action; +}> = anyApi as any; + +/** + * A utility for referencing Convex functions in your app's public API. + * + * Usage: + * ```js + * const myFunctionReference = api.myModule.myFunction; + * ``` + */ +export const api: FilterApi< + typeof fullApi, + FunctionReference +> = anyApi as any; + +/** + * A utility for referencing Convex functions in your app's internal API. + * + * Usage: + * ```js + * const myFunctionReference = internal.myModule.myFunction; + * ``` + */ +export const internal: FilterApi< + typeof fullApi, + FunctionReference +> = anyApi as any; + +export const components = componentsGeneric() as unknown as {}; diff --git a/convex/components/n-code/_generated/component.ts b/convex/components/n-code/_generated/component.ts new file mode 100644 index 0000000..8282d47 --- /dev/null +++ b/convex/components/n-code/_generated/component.ts @@ -0,0 +1,85 @@ +/* eslint-disable */ +/** + * Generated `ComponentApi` utility. + * + * THIS CODE IS AUTOMATICALLY GENERATED. + * + * To regenerate, run `npx convex dev`. + * @module + */ + +import type { FunctionReference } from "convex/server"; + +/** + * A utility for referencing a Convex component's exposed API. + * + * Useful when expecting a parameter like `components.myComponent`. + * Usage: + * ```ts + * async function myFunction(ctx: QueryCtx, component: ComponentApi) { + * return ctx.runQuery(component.someFile.someQuery, { ...args }); + * } + * ``` + */ +export type ComponentApi = + { + action: { + create: FunctionReference< + "mutation", + "internal", + { + code: string; + data: any; + description: string; + slug?: string; + title: string; + type: string; + userId: string; + }, + any, + Name + >; + deleteBySlug: FunctionReference< + "mutation", + "internal", + { slug: string }, + any, + Name + >; + getByCode: FunctionReference< + "query", + "internal", + { code: string }, + any, + Name + >; + getBySlug: FunctionReference< + "query", + "internal", + { slug: string }, + any, + Name + >; + getList: FunctionReference< + "query", + "internal", + { type?: string; userId?: string }, + any, + Name + >; + updateBySlug: FunctionReference< + "mutation", + "internal", + { + code?: string; + data?: any; + description?: string; + slug: string; + title?: string; + type?: string; + }, + any, + Name + >; + }; + }; diff --git a/convex/components/n-code/_generated/dataModel.ts b/convex/components/n-code/_generated/dataModel.ts new file mode 100644 index 0000000..f97fd19 --- /dev/null +++ b/convex/components/n-code/_generated/dataModel.ts @@ -0,0 +1,60 @@ +/* eslint-disable */ +/** + * Generated data model types. + * + * THIS CODE IS AUTOMATICALLY GENERATED. + * + * To regenerate, run `npx convex dev`. + * @module + */ + +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; + +/** + * The type of a document stored in Convex. + * + * @typeParam TableName - A string literal type of the table name (like "users"). + */ +export type Doc = 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(tableName, 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 = + GenericId; + +/** + * 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; diff --git a/convex/components/n-code/_generated/server.ts b/convex/components/n-code/_generated/server.ts new file mode 100644 index 0000000..739b02f --- /dev/null +++ b/convex/components/n-code/_generated/server.ts @@ -0,0 +1,156 @@ +/* eslint-disable */ +/** + * Generated utilities for implementing server-side Convex query and mutation functions. + * + * THIS CODE IS AUTOMATICALLY GENERATED. + * + * To regenerate, run `npx convex dev`. + * @module + */ + +import type { + ActionBuilder, + HttpActionBuilder, + MutationBuilder, + QueryBuilder, + GenericActionCtx, + GenericMutationCtx, + GenericQueryCtx, + GenericDatabaseReader, + GenericDatabaseWriter, +} from "convex/server"; +import { + actionGeneric, + httpActionGeneric, + queryGeneric, + mutationGeneric, + internalActionGeneric, + internalMutationGeneric, + internalQueryGeneric, +} from "convex/server"; +import type { DataModel } from "./dataModel.js"; + +/** + * Define a query in this Convex app's public API. + * + * This function will be allowed to read your Convex database and will be accessible from the client. + * + * @param func - The query function. It receives a {@link QueryCtx} as its first argument. + * @returns The wrapped query. Include this as an `export` to name it and make it accessible. + */ +export const query: QueryBuilder = queryGeneric; + +/** + * Define a query that is only accessible from other Convex functions (but not from the client). + * + * This function will be allowed to read from your Convex database. It will not be accessible from the client. + * + * @param func - The query function. It receives a {@link QueryCtx} as its first argument. + * @returns The wrapped query. Include this as an `export` to name it and make it accessible. + */ +export const internalQuery: QueryBuilder = + internalQueryGeneric; + +/** + * Define a mutation in this Convex app's public API. + * + * This function will be allowed to modify your Convex database and will be accessible from the client. + * + * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. + * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. + */ +export const mutation: MutationBuilder = mutationGeneric; + +/** + * Define a mutation that is only accessible from other Convex functions (but not from the client). + * + * This function will be allowed to modify your Convex database. It will not be accessible from the client. + * + * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. + * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. + */ +export const internalMutation: MutationBuilder = + internalMutationGeneric; + +/** + * Define an action in this Convex app's public API. + * + * An action is a function which can execute any JavaScript code, including non-deterministic + * code and code with side-effects, like calling third-party services. + * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. + * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. + * + * @param func - The action. It receives an {@link ActionCtx} as its first argument. + * @returns The wrapped action. Include this as an `export` to name it and make it accessible. + */ +export const action: ActionBuilder = actionGeneric; + +/** + * Define an action that is only accessible from other Convex functions (but not from the client). + * + * @param func - The function. It receives an {@link ActionCtx} as its first argument. + * @returns The wrapped function. Include this as an `export` to name it and make it accessible. + */ +export const internalAction: ActionBuilder = + internalActionGeneric; + +/** + * Define an HTTP action. + * + * The wrapped function will be used to respond to HTTP requests received + * by a Convex deployment if the requests matches the path and method where + * this action is routed. Be sure to route your httpAction in `convex/http.js`. + * + * @param func - The function. It receives an {@link ActionCtx} as its first argument + * and a Fetch API `Request` object as its second. + * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up. + */ +export const httpAction: HttpActionBuilder = httpActionGeneric; + +/** + * A set of services for use within Convex query functions. + * + * The query context is passed as the first argument to any Convex query + * function run on the server. + * + * If you're using code generation, use the `QueryCtx` type in `convex/_generated/server.d.ts` instead. + */ +export type QueryCtx = GenericQueryCtx; + +/** + * A set of services for use within Convex mutation functions. + * + * The mutation context is passed as the first argument to any Convex mutation + * function run on the server. + * + * If you're using code generation, use the `MutationCtx` type in `convex/_generated/server.d.ts` instead. + */ +export type MutationCtx = GenericMutationCtx; + +/** + * A set of services for use within Convex action functions. + * + * The action context is passed as the first argument to any Convex action + * function run on the server. + */ +export type ActionCtx = GenericActionCtx; + +/** + * An interface to read from the database within Convex query functions. + * + * The two entry points are {@link DatabaseReader.get}, which fetches a single + * document by its {@link Id}, or {@link DatabaseReader.query}, which starts + * building a query. + */ +export type DatabaseReader = GenericDatabaseReader; + +/** + * An interface to read from and write to the database within Convex mutation + * functions. + * + * Convex guarantees that all writes within a single mutation are + * executed atomically, so you never have to worry about partial writes leaving + * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control) + * for the guarantees Convex provides your functions. + */ +export type DatabaseWriter = GenericDatabaseWriter; diff --git a/convex/components/n-code/action.ts b/convex/components/n-code/action.ts new file mode 100644 index 0000000..b2d359d --- /dev/null +++ b/convex/components/n-code/action.ts @@ -0,0 +1,139 @@ +import { query, mutation } from "./_generated/server.ts"; +import { v } from "convex/values"; +import { customAlphabet } from "nanoid"; + +const randomId = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 10); +/** + * 查询 nCode 列表 + */ +export const getList = query({ + args: { + userId: v.optional(v.string()), + type: v.optional(v.string()), + }, + handler: async (ctx, args) => { + // 优先使用索引过滤 + if (args.userId) { + let results = await ctx.db + .query("shortLink") + .withIndex("by_userId", (q) => q.eq("userId", args.userId!)) + .collect(); + if (args.type) { + results = results.filter((item) => item.type === args.type); + } + return results; + } + if (args.type) { + return await ctx.db + .query("shortLink") + .withIndex("by_type", (q) => q.eq("type", args.type!)) + .collect(); + } + return await ctx.db.query("shortLink").collect(); + }, +}); + +/** + * 根据业务 id 查询单条记录 + */ +export const getBySlug = query({ + args: { slug: v.string() }, + handler: async (ctx, args) => { + return await ctx.db + .query("shortLink") + .withIndex("by_slug", (q) => q.eq("slug", args.slug)) + .first(); + }, +}); + +/** + * 根据 code 查询单条记录 + */ +export const getByCode = query({ + args: { + code: v.string(), + }, + handler: async (ctx, args) => { + const result = await ctx.db + .query("shortLink") + .withIndex("by_code", (q) => q.eq("code", args.code)) + .first(); + return result; + }, +}); + +/** + * 创建 nCode + */ +export const create = mutation({ + args: { + slug: v.optional(v.string()), + code: v.string(), + type: v.string(), + data: v.any(), + title: v.string(), + description: v.string(), + userId: v.string(), + }, + handler: async (ctx, args) => { + const slug = args.slug ?? randomId(); + // 唯一性检查 + const existing = await ctx.db + .query("shortLink") + .withIndex("by_slug", (q) => q.eq("slug", slug)) + .first(); + if (existing) { + throw new Error(`nCode with slug "${slug}" already exists`); + } + const result = await ctx.db.insert("shortLink", { + version: "1.0", data: { + ...args.data, + permission: { + share: 'public', + ...(args.data?.permission || {}) + } + }, ...args, slug + }); + return result; + }, +}); + +/** + * 更新 nCode + */ +export const updateBySlug = mutation({ + args: { + slug: v.string(), + code: v.optional(v.string()), + type: v.optional(v.string()), + data: v.optional(v.any()), + title: v.optional(v.string()), + description: v.optional(v.string()), + }, + handler: async (ctx, args) => { + const { slug, ...rest } = args; + const record = await ctx.db + .query("shortLink") + .withIndex("by_slug", (q) => q.eq("slug", slug)) + .first(); + if (!record) throw new Error(`nCode with slug "${slug}" not found`); + await ctx.db.patch(record._id, rest); + }, +}); + +/** + * 删除 nCode + */ +export const deleteBySlug = mutation({ + args: { + slug: v.string(), + }, + handler: async (ctx, args) => { + const record = await ctx.db + .query("shortLink") + .withIndex("by_slug", (q) => q.eq("slug", args.slug)) + .first(); + if (!record) throw new Error(`nCode with slug "${args.slug}" not found`); + await ctx.db.delete(record._id); + }, +}); diff --git a/convex/components/n-code/convex.config.ts b/convex/components/n-code/convex.config.ts new file mode 100644 index 0000000..fff1fc3 --- /dev/null +++ b/convex/components/n-code/convex.config.ts @@ -0,0 +1,3 @@ +import { defineComponent } from "convex/server"; +const component = defineComponent("nCode"); +export default component; \ No newline at end of file diff --git a/convex/components/n-code/schema.ts b/convex/components/n-code/schema.ts new file mode 100644 index 0000000..ccb6220 --- /dev/null +++ b/convex/components/n-code/schema.ts @@ -0,0 +1,50 @@ +import { defineSchema, defineTable } from "convex/server"; +import { v } from "convex/values"; + +export type Permission = { + share?: 'public' | 'private' | 'protected'; + usernames?: string[]; // 仅当 type 为 'protected' 时有效 + password?: string; // 仅当 type 为 'protected' 时有效 + 'expiration-time'?: string; // ISO 8601 格式的过期时间 +} + +export default defineSchema({ + // Other tables here... + shortLink: defineTable({ + // 对外暴露的唯一业务 ID,nanoid 生成 + slug: v.string(), + // 协作码,管理员才能编辑, 6-12 位随机字符串,唯一 + code: v.string(), + // 码的类型,link, agent,默认值为 link + type: v.string(), + // 执行动作 + data: v.object({ + // action: v.string(), // 动作类型,如 "open_url", "run_agent" 等 + permission: v.optional(v.any()), // 权限设置 + link: v.optional(v.string()), // 仅当 type 为 'link' 时有效,表示跳转链接 + }), + // 码的标题 + title: v.string(), + // 描述定义 + description: v.string(), + // 标签 + tags: v.optional(v.array(v.string())), + // 绑定用户 + userId: v.string(), + + version: v.optional(v.string()), // 版本号,默认为 "1.0" + + }) + .index("by_slug", ["slug"]) + .index("by_userId", ["userId"]) + .index("by_code", ["code"]) + .index("by_type", ["type"]), + desc: defineTable({ + slug: v.string(), + // 卡片的正面图片 URL + front: v.optional(v.string()), + // 卡片的背面图片 URL + back: v.optional(v.string()), + status: v.optional(v.string()), // 生成状态:active, completed, failed, sold(卖掉了) + }).index("by_slug", ["slug"]), +}); \ No newline at end of file diff --git a/convex/convex.config.ts b/convex/convex.config.ts new file mode 100644 index 0000000..c7f60cb --- /dev/null +++ b/convex/convex.config.ts @@ -0,0 +1,7 @@ +import { defineApp } from "convex/server"; +import NCode from './components/n-code/convex.config.ts'; +const app = defineApp(); + +app.use(NCode); + +export default app; \ No newline at end of file diff --git a/convex/github/action.ts b/convex/github/action.ts index 8b4e47a..b058851 100644 --- a/convex/github/action.ts +++ b/convex/github/action.ts @@ -8,6 +8,14 @@ export const getList = query({ return results; }, }); +export const get = query({ + args: { + id: v.id("github_starred"), + }, + handler: async (ctx, args) => { + return await ctx.db.get(args.id); + }, +}); export const createStarred = mutation({ args: { diff --git a/convex/nCode.ts b/convex/nCode.ts new file mode 100644 index 0000000..f770bb8 --- /dev/null +++ b/convex/nCode.ts @@ -0,0 +1,85 @@ +/** + * 顶层包装文件 —— 将 nCode Component 的函数暴露为公共 API。 + * + * Convex Component 内部函数对客户端是 internal 级别, + * 必须在顶层 app 中用 ctx.runQuery / ctx.runMutation 做代理才能被客户端访问。 + * + * 客户端调用示例: + * import { api } from "../convex/_generated/api"; + * const list = useQuery(api.nCode.getList, { userId: "xxx" }); + */ +import { query, mutation } from "./_generated/server.js"; +import { components } from "./_generated/api.js"; +import { v } from "convex/values"; + +/** 查询列表,支持按 userId / type 过滤 */ +export const getList = query({ + args: { + userId: v.optional(v.string()), + type: v.optional(v.string()), + }, + handler: async (ctx, args) => { + return await ctx.runQuery(components.nCode.action.getList, args); + }, +}); + +/** 根据 code 查单条记录 */ +export const getByCode = query({ + args: { + code: v.string(), + }, + handler: async (ctx, args) => { + return await ctx.runQuery(components.nCode.action.getByCode, args); + }, +}); + +/** 根据业务 id 查单条记录 */ +export const getBySlug = query({ + args: { + slug: v.string(), + }, + handler: async (ctx, args) => { + return await ctx.runQuery(components.nCode.action.getBySlug, args); + }, +}); + +/** 创建 nCode */ +export const create = mutation({ + args: { + slug: v.optional(v.string()), + code: v.string(), + type: v.string(), + data: v.object({}), + title: v.string(), + description: v.string(), + userId: v.string(), + }, + handler: async (ctx, args) => { + return await ctx.runMutation(components.nCode.action.create, args); + }, +}); + +/** 更新 nCode */ +export const updateBySlug = mutation({ + args: { + slug: v.string(), + code: v.optional(v.string()), + type: v.optional(v.string()), + data: v.optional(v.object({})), + title: v.optional(v.string()), + description: v.optional(v.string()), + }, + handler: async (ctx, args) => { + return await ctx.runMutation(components.nCode.action.updateBySlug, args); + }, +}); + +/** 删除 nCode */ +export const deleteBySlug = mutation({ + args: { + slug: v.string(), + }, + handler: async (ctx, args) => { + return await ctx.runMutation(components.nCode.action.deleteBySlug, args); + }, +}); diff --git a/convex/schema.ts b/convex/schema.ts index a62d4a9..67d3034 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -27,4 +27,5 @@ export default defineSchema({ expiresAt: v.optional(v.string()), token: v.optional(v.string()), }).index("token", ["token"]), + }); \ No newline at end of file diff --git a/package.json b/package.json index c9e4d27..8d77478 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "dev": "bunx convex dev" + "dev": "bunx convex dev", + "mcp": "npx -y convex@latest mcp start --project-dir ./convex" }, "files": [ "convex", @@ -23,7 +24,8 @@ "dependencies": { "@kevisual/auth": "^2.0.3", "convex": "1.32.0", - "jose": "^6.1.3" + "jose": "^6.1.3", + "nanoid": "^5.1.6" }, "exports": { ".": "./convex/_generated/api.js" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fdf103e..395c0cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: jose: specifier: ^6.1.3 version: 6.1.3 + nanoid: + specifier: ^5.1.6 + version: 5.1.6 devDependencies: '@kevisual/types': specifier: ^0.0.12 @@ -225,6 +228,11 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + nanoid@5.1.6: + resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} + engines: {node: ^18 || >=20} + hasBin: true + prettier@3.8.1: resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} engines: {node: '>=14'} @@ -381,6 +389,8 @@ snapshots: jose@6.1.3: {} + nanoid@5.1.6: {} + prettier@3.8.1: {} undici-types@7.18.2: {} diff --git a/src/convex.ts b/src/convex.ts new file mode 100644 index 0000000..8aabf98 --- /dev/null +++ b/src/convex.ts @@ -0,0 +1,15 @@ +import { api } from '../convex/_generated/api.js'; +import { ConvexClient, AuthTokenFetcher } from "convex/browser"; +const url = process.env["CONVEX_URL"] +const client = new ConvexClient(url!); + +const token = process.env["KEVISUAL_CONVEX_TOKEN"] +const authTokenFetcher: AuthTokenFetcher = async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => { + console.log("AuthTokenFetcher called, forceRefreshToken:", forceRefreshToken); + return token; +} +client.setAuth(authTokenFetcher, (isAuthenticated) => { + console.log("Auth isAuthenticated:", isAuthenticated); +}); + +export { client, api } diff --git a/test/get-list.ts b/test/get-list.ts new file mode 100644 index 0000000..e7455cd --- /dev/null +++ b/test/get-list.ts @@ -0,0 +1,19 @@ +import { client, api } from '../src/convex.ts' + + +// client.query(api.nCode.getList, {}).then((res) => { +// console.log('getList', res) +// }) + +const createNCode = async () => { + const res = await client.mutation(api.nCode.create, { + code: Math.random().toString(36).substring(2, 8), + type: "link", + data: {}, + title: "测试码", + description: "这是一个测试码", + userId: "test-user-id", + }) + console.log('createNCode', res) +} +createNCode() \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 6309108..c7325d7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ }, "include": [ "src/**/*", + "test/**/*", "convex/**/*", ], } \ No newline at end of file