Compare commits

...

7 Commits

22 changed files with 915 additions and 74 deletions

3
.npmrc Normal file
View File

@@ -0,0 +1,3 @@
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
//npm.cnb.cool/kevisual/registry/-/packages/:_authToken=${CNB_API_KEY}
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

13
.vscode/mcp.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"servers": {
"convex": {
"command": "npx",
"args": [
"-y",
"convex@latest",
"mcp",
"start"
]
}
}
}

View File

@@ -8,8 +8,10 @@
* @module * @module
*/ */
import type * as github_action from "../github/action.js";
import type * as github_starrred from "../github/starrred.js"; import type * as github_starrred from "../github/starrred.js";
import type * as http from "../http.js"; import type * as http from "../http.js";
import type * as nCode from "../nCode.js";
import type { import type {
ApiFromModules, ApiFromModules,
@@ -18,8 +20,10 @@ import type {
} from "convex/server"; } from "convex/server";
declare const fullApi: ApiFromModules<{ declare const fullApi: ApiFromModules<{
"github/action": typeof github_action;
"github/starrred": typeof github_starrred; "github/starrred": typeof github_starrred;
http: typeof http; http: typeof http;
nCode: typeof nCode;
}>; }>;
/** /**
@@ -48,4 +52,50 @@ export declare const internal: FilterApi<
FunctionReference<any, "internal"> FunctionReference<any, "internal">
>; >;
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
>;
};
};
};

View File

@@ -1,5 +1,3 @@
import { AuthConfig } from "convex/server";
export default { export default {
providers: [ providers: [
{ {

View File

@@ -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<any, "public">
> = 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<any, "internal">
> = anyApi as any;
export const components = componentsGeneric() as unknown as {};

View File

@@ -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<Name extends string | undefined = string | undefined> =
{
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
>;
};
};

View File

@@ -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<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(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<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>;

View File

@@ -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<DataModel, "public"> = 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<DataModel, "internal"> =
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<DataModel, "public"> = 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<DataModel, "internal"> =
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<DataModel, "public"> = 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<DataModel, "internal"> =
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<DataModel>;
/**
* 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<DataModel>;
/**
* 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<DataModel>;
/**
* 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<DataModel>;
/**
* 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<DataModel>;

View File

@@ -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);
},
});

View File

@@ -0,0 +1,3 @@
import { defineComponent } from "convex/server";
const component = defineComponent("nCode");
export default component;

View File

@@ -0,0 +1,58 @@
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 格式的过期时间
}
// shortLink的使用方式
// link模式linkType默认为redirect使用302跳转到目标地址
// 1. 短链跳转,直接访问对用的地址
// 2. 请求模式,
// 1. 访问短链去请求另一个接口内容是code: 200判断是否执行成功默认不透传结果
//
export default defineSchema({
// Other tables here...
shortLink: defineTable({
// 对外暴露的唯一业务 IDnanoid 生成
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()), // 权限设置
// 仅当 type 为 'link' 时有效,表示跳转链接
link: v.optional(v.string()),
// 仅当 type 为 'link' 时有效,表示链接类型,如 'redirect' 等,默认为 'redirect'
linkType: v.optional(v.string()),
}),
// 码的标题
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"]),
});

7
convex/convex.config.ts Normal file
View File

@@ -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;

75
convex/github/action.ts Normal file
View File

@@ -0,0 +1,75 @@
import { query, mutation, action } from "../_generated/server.js";
import { v } from "convex/values";
export const getList = query({
args: {},
handler: async (ctx) => {
const results = await ctx.db.query("github_starred").collect();
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: {
repo_id: v.number(),
title: v.string(),
description: v.string(),
author: v.string(),
stars: v.number(),
last_updated: v.string(),
link: v.string(),
auto: v.optional(v.string()),
summary: v.optional(v.string()),
tags: v.optional(v.array(v.string())),
},
handler: async (ctx, values) => {
const repo_id = values.repo_id;
const existing = await ctx.db.query("github_starred").withIndex("by_repo_id", (q) =>
q.eq("repo_id", repo_id)
).first();
if (existing) {
// 更新已有记录
const updated = await ctx.db.patch("github_starred", existing._id, values);
return updated;
}
// 创建新记录
const result = await ctx.db.insert("github_starred", values);
return result;
}
});
export const updateById = mutation({
args: {
id: v.id("github_starred"),
repo_id: v.number(),
title: v.string(),
description: v.string(),
author: v.string(),
stars: v.number(),
last_updated: v.string(),
link: v.string(),
auto: v.optional(v.string()),
summary: v.optional(v.string()),
},
handler: async (ctx, args) => {
const { id, ...rest } = args;
await ctx.db.patch(id, rest);
},
});
export const deleteById = mutation({
args: {
id: v.id("github_starred"),
},
handler: async (ctx, args) => {
await ctx.db.delete(args.id);
},
});

View File

@@ -2,38 +2,17 @@ import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server.js"; import { httpAction } from "./_generated/server.js";
const http = httpRouter(); const http = httpRouter();
http.route({ const handler = httpAction(async (ctx, request) => {
path: "/auth", return new Response(JSON.stringify({ message: "Hello!" }), {
method: "POST", status: 200,
handler: httpAction(async (ctx, request) => { headers: { "Content-Type": "application/json" },
// 处理请求并返回响应 });
return new Response(JSON.stringify({ message: "Hello from custom endpoint!" }), { });
status: 200,
headers: { "Content-Type": "application/json" }, // 分开注册
}); http.route({ path: "/api/router", method: "GET", handler });
}), http.route({ path: "/api/router", method: "POST", handler });
}) http.route({ path: "/api/router", method: "OPTIONS", handler });
// https://api-convex.kevisual.cn/root/convex/jwks.json
http.route({
path: '/root/convex/jwks.json',
method: 'GET',
handler: httpAction(async (ctx, request) => {
// 返回 JWKS 数据
const jwks = {
"keys": [
{
"kty": "RSA",
"n": "km4cjJJOMFkl2G5qWMuFmWwF7rmeqRYzYdR8SddKeeMW0e9yIf5pv2Mfwv0aMJUpb-_j3j9M7whx_SEGc_2jx1vxCu1AlYURhnnLTWdsR-ZRPr2LK9UstMrgpWV425R2RliqXTDTYlSxUUlD9nPue_tqbfwN2aM9MCarm67xK_ZCcKRlW9o9L2-9UMfzRA7uiy4VQtOemP0PTXp-E9RxNiMPOQXIRls9wTW_EkDT3dGy7JCZhj7_qib3T8k9m84SwU7wI2R_3IH4DcHSMAn1BRRMXZ1_wPhbP39laNtdJgbDjGCqUccGhLUaoo2WGkZ52eb7NPqamp0K1Dh2jwTIJQ",
"e": "AQAB",
"kid": "kid-key-1"
}
]
};
return new Response(JSON.stringify(jwks), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}),
})
export default http; export default http;

85
convex/nCode.ts Normal file
View File

@@ -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);
},
});

View File

@@ -4,16 +4,17 @@ import { v } from "convex/values";
export default defineSchema({ export default defineSchema({
// Other tables here... // Other tables here...
github_starred: defineTable({ github_starred: defineTable({
author: v.string(), repo_id: v.number(),
auto: v.string(), title: v.string(),
description: v.string(), description: v.string(),
author: v.string(),
stars: v.number(),
last_updated: v.string(), last_updated: v.string(),
link: v.string(), link: v.string(),
repo_id: v.float64(), auto: v.optional(v.string()),
stars: v.float64(), summary: v.optional(v.string()),
summary: v.string(), }).index("by_repo_id", ["repo_id"])
title: v.string(), .index("by_title", ["title"]),
}),
users: defineTable({ users: defineTable({
userId: v.string(), // 外部系统的用户 ID userId: v.string(), // 外部系统的用户 ID
name: v.string(), name: v.string(),
@@ -26,4 +27,5 @@ export default defineSchema({
expiresAt: v.optional(v.string()), expiresAt: v.optional(v.string()),
token: v.optional(v.string()), token: v.optional(v.string()),
}).index("token", ["token"]), }).index("token", ["token"]),
}); });

View File

@@ -1,10 +1,11 @@
{ {
"name": "@kevisual/convex", "name": "@kevisual/convex",
"version": "0.0.1", "version": "0.0.3",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"dev": "bunx convex dev" "dev": "bunx convex dev",
"mcp": "npx -y convex@latest mcp start --project-dir ./convex"
}, },
"files": [ "files": [
"convex", "convex",
@@ -13,17 +14,22 @@
"keywords": [], "keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)", "author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT", "license": "MIT",
"packageManager": "pnpm@10.28.2", "packageManager": "pnpm@10.30.3",
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@kevisual/types": "^0.0.12", "@kevisual/types": "^0.0.12",
"@types/bun": "^1.3.8", "@types/bun": "^1.3.9",
"@types/node": "^25.2.0" "@types/node": "^25.3.3"
}, },
"dependencies": { "dependencies": {
"@kevisual/auth": "^2.0.3", "@kevisual/auth": "^2.0.3",
"convex": "1.31.7", "convex": "1.32.0",
"jose": "^6.1.3" "jose": "^6.1.3",
"nanoid": "^5.1.6"
},
"exports": {
".": "./convex/_generated/api.js",
"./admin.ts": "./convex/admin.ts"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

76
pnpm-lock.yaml generated
View File

@@ -12,21 +12,24 @@ importers:
specifier: ^2.0.3 specifier: ^2.0.3
version: 2.0.3 version: 2.0.3
convex: convex:
specifier: 1.31.7 specifier: 1.32.0
version: 1.31.7 version: 1.32.0
jose: jose:
specifier: ^6.1.3 specifier: ^6.1.3
version: 6.1.3 version: 6.1.3
nanoid:
specifier: ^5.1.6
version: 5.1.6
devDependencies: devDependencies:
'@kevisual/types': '@kevisual/types':
specifier: ^0.0.12 specifier: ^0.0.12
version: 0.0.12 version: 0.0.12
'@types/bun': '@types/bun':
specifier: ^1.3.8 specifier: ^1.3.9
version: 1.3.8 version: 1.3.9
'@types/node': '@types/node':
specifier: ^25.2.0 specifier: ^25.3.3
version: 25.2.0 version: 25.3.3
packages: packages:
@@ -192,17 +195,17 @@ packages:
'@kevisual/types@0.0.12': '@kevisual/types@0.0.12':
resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==} resolution: {integrity: sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q==}
'@types/bun@1.3.8': '@types/bun@1.3.9':
resolution: {integrity: sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA==} resolution: {integrity: sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw==}
'@types/node@25.2.0': '@types/node@25.3.3':
resolution: {integrity: sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==} resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==}
bun-types@1.3.8: bun-types@1.3.9:
resolution: {integrity: sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q==} resolution: {integrity: sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg==}
convex@1.31.7: convex@1.32.0:
resolution: {integrity: sha512-PtNMe1mAIOvA8Yz100QTOaIdgt2rIuWqencVXrb4McdhxBHZ8IJ1eXTnrgCC9HydyilGT1pOn+KNqT14mqn9fQ==} resolution: {integrity: sha512-5FlajdLpW75pdLS+/CgGH5H6yeRuA+ru50AKJEYbJpmyILUS+7fdTvsdTaQ7ZFXMv0gE8mX4S+S3AtJ94k0mfw==}
engines: {node: '>=18.0.0', npm: '>=7.0.0'} engines: {node: '>=18.0.0', npm: '>=7.0.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -225,13 +228,30 @@ packages:
jose@6.1.3: jose@6.1.3:
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} 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: prettier@3.8.1:
resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
undici-types@7.16.0: undici-types@7.18.2:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
ws@8.18.0:
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
snapshots: snapshots:
@@ -317,22 +337,26 @@ snapshots:
'@kevisual/types@0.0.12': {} '@kevisual/types@0.0.12': {}
'@types/bun@1.3.8': '@types/bun@1.3.9':
dependencies: dependencies:
bun-types: 1.3.8 bun-types: 1.3.9
'@types/node@25.2.0': '@types/node@25.3.3':
dependencies: dependencies:
undici-types: 7.16.0 undici-types: 7.18.2
bun-types@1.3.8: bun-types@1.3.9:
dependencies: dependencies:
'@types/node': 25.2.0 '@types/node': 25.3.3
convex@1.31.7: convex@1.32.0:
dependencies: dependencies:
esbuild: 0.27.0 esbuild: 0.27.0
prettier: 3.8.1 prettier: 3.8.1
ws: 8.18.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
esbuild@0.27.0: esbuild@0.27.0:
optionalDependencies: optionalDependencies:
@@ -365,6 +389,10 @@ snapshots:
jose@6.1.3: {} jose@6.1.3: {}
nanoid@5.1.6: {}
prettier@3.8.1: {} prettier@3.8.1: {}
undici-types@7.16.0: {} undici-types@7.18.2: {}
ws@8.18.0: {}

9
readme.md Normal file
View File

@@ -0,0 +1,9 @@
# 基于convex做一些小玩具
## 安装
```json
{
"@kevisual/convex":"git+https://cnb.cool/kevisual/convex"
}
```

15
src/admin.ts Normal file
View File

@@ -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 }

19
test/get-list.ts Normal file
View File

@@ -0,0 +1,19 @@
import { client, api } from '../src/admin.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()

View File

@@ -18,6 +18,7 @@
}, },
"include": [ "include": [
"src/**/*", "src/**/*",
"test/**/*",
"convex/**/*", "convex/**/*",
], ],
} }