This commit is contained in:
2026-01-25 19:18:59 +08:00
parent fea68a736f
commit 7c28690eaa
17 changed files with 501 additions and 150 deletions

View File

@@ -0,0 +1,46 @@
/* eslint-disable */
/**
* Generated `api` utility.
*
* THIS CODE IS AUTOMATICALLY GENERATED.
*
* To regenerate, run `npx convex dev`.
* @module
*/
import type {
ApiFromModules,
FilterApi,
FunctionReference,
} from "convex/server";
import { anyApi, componentsGeneric } from "convex/server";
const fullApi: ApiFromModules<{}> = 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,25 @@
/* 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> =
{};

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,5 @@
'use node';
import { defineComponent } from "convex/server";
const component = defineComponent("component");
export default component;

9
component/schema.ts Normal file
View File

@@ -0,0 +1,9 @@
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
// Define a messages table with an index.
export default defineSchema({
// test2: defineTable({
// title: v.string(),
// }),
});

26
component/test2.ts Normal file
View File

@@ -0,0 +1,26 @@
// import { query, mutation, action } from "./_generated/server";
// import { v } from "convex/values";
// export const get = query({
// args: {},
// handler: async (ctx) => {
// const auth = await ctx.auth.getUserIdentity();
// console.log("Query test2 called, auth:", auth);
// if (auth) {
// console.log("Authenticated user ID:", auth.subject);
// }
// return await ctx.db.query("test2").collect();
// },
// });
// export const get2 = query({
// args: {},
// handler: async (ctx) => {
// const auth = await ctx.auth.getUserIdentity();
// console.log("Query test2 called, auth:", auth);
// if (auth) {
// console.log("Authenticated user ID:", auth.subject);
// }
// return await ctx.db.query("test2").collect();
// },
// });

View File

@@ -8,11 +8,8 @@
* @module
*/
import type * as abcv from "../abcv.js";
import type * as actions_jwt from "../actions/jwt.js";
import type * as actions_redis from "../actions/redis.js";
import type * as http from "../http.js";
import type * as xiong from "../xiong.js";
import type {
ApiFromModules,
@@ -21,11 +18,8 @@ import type {
} from "convex/server";
declare const fullApi: ApiFromModules<{
abcv: typeof abcv;
"actions/jwt": typeof actions_jwt;
"actions/redis": typeof actions_redis;
http: typeof http;
xiong: typeof xiong;
}>;
/**
@@ -54,4 +48,6 @@ export declare const internal: FilterApi<
FunctionReference<any, "internal">
>;
export declare const components: {};
export declare const components: {
component: {};
};

View File

@@ -1,30 +1,30 @@
import { query, mutation, action } from "./_generated/server";
import { Kevisual } from '@kevisual/ai/browser'
import { v } from "convex/values";
export const get = query({
args: {},
handler: async (ctx) => {
const auth = await ctx.auth.getUserIdentity();
console.log("Query abcv.get called, auth:", auth);
if (auth) {
console.log("Authenticated user ID:", auth.subject);
}
return await ctx.db.query("abcv").collect();
},
});
// import { query, mutation, action } from "./_generated/server";
// import { Kevisual } from '@kevisual/ai/browser'
// import { v } from "convex/values";
// export const get = query({
// args: {},
// handler: async (ctx) => {
// const auth = await ctx.auth.getUserIdentity();
// console.log("Query abcv.get called, auth:", auth);
// if (auth) {
// console.log("Authenticated user ID:", auth.subject);
// }
// return await ctx.db.query("abcv").collect();
// },
// });
export const chat = action({
args: { message: v.string() },
handler: async (ctx, { message }) => {
const kevisual = new Kevisual({
apiKey: process.env.KEVISUAL_NEW_API_KEY || "",
});
// export const chat = action({
// args: { message: v.string() },
// handler: async (ctx, { message }) => {
// const kevisual = new Kevisual({
// apiKey: process.env.KEVISUAL_NEW_API_KEY || "",
// });
const response = await kevisual.chat({
messages: [
{ role: "user", content: message }
]
})
return kevisual.responseText;
},
});
// const response = await kevisual.chat({
// messages: [
// { role: "user", content: message }
// ]
// })
// return kevisual.responseText;
// },
// });

View File

@@ -1,22 +1,22 @@
'use node';
// 'use node';
import { query, mutation, action } from "../_generated/server";
import { Kevisual } from '@kevisual/ai/browser'
import { v } from "convex/values";
import { Redis } from "ioredis";
const redisClient = new Redis({
host: process.env.REDIS_HOST,
password: process.env.REDIS_PASSWORD,
});
let time: any = null;
export const isConnected = action({
args: {},
handler: async (ctx) => {
const result = await redisClient.ping();
if (time === null) {
time = new Date();
}
console.log("Redis PING at", new Date().toISOString(), "since", time);
return result === "PONG";
},
});
// import { query, mutation, action } from "../_generated/server";
// import { Kevisual } from '@kevisual/ai/browser'
// import { v } from "convex/values";
// import { Redis } from "ioredis";
// const redisClient = new Redis({
// host: process.env.REDIS_HOST,
// password: process.env.REDIS_PASSWORD,
// });
// let time: any = null;
// export const isConnected = action({
// args: {},
// handler: async (ctx) => {
// const result = await redisClient.ping();
// if (time === null) {
// time = new Date();
// }
// console.log("Redis PING at", new Date().toISOString(), "since", time);
// return result === "PONG";
// },
// });

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

@@ -0,0 +1,8 @@
import { defineApp } from "convex/server";
import myComponent from "../component/convex.config.ts";
const app = defineApp();
// app.use(myComponent);
export default app;

View File

@@ -3,23 +3,23 @@ import { v } from "convex/values";
import { authTables } from "@convex-dev/auth/server";
// Define a messages table with an index.
export default defineSchema({
abcv: defineTable({
title: v.string(),
}),
xiong: defineTable({
name: v.string(),
title: v.string(),
}),
users: defineTable({
id: v.string(), // 外部系统的用户 ID
name: v.string(),
createdAt: v.string(),
lastLoginAt: v.optional(v.string()),
}).index("id", ["id"]),
sessions: defineTable({
userId: v.id("users"),
createdAt: v.string(),
expiresAt: v.optional(v.string()),
token: v.optional(v.string()),
}).index("token", ["token"]),
// abcv: defineTable({
// title: v.string(),
// }),
// xiong: defineTable({
// name: v.string(),
// title: v.string(),
// }),
// users: defineTable({
// id: v.string(), // 外部系统的用户 ID
// name: v.string(),
// createdAt: v.string(),
// lastLoginAt: v.optional(v.string()),
// }).index("id", ["id"]),
// sessions: defineTable({
// userId: v.id("users"),
// createdAt: v.string(),
// expiresAt: v.optional(v.string()),
// token: v.optional(v.string()),
// }).index("token", ["token"]),
});

9
convex/wrapper.ts Normal file
View File

@@ -0,0 +1,9 @@
// import { query } from "./_generated/server";
// import { components } from "./_generated/api";
// export const getTest2 = query({
// args: {},
// handler: async (ctx) => {
// return await ctx.runQuery(components.component.test2.get);
// },
// });

View File

@@ -1,8 +1,8 @@
import { query, mutation, action } from "./_generated/server";
// import { query, mutation, action } from "./_generated/server";
export const get = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query("xiong").collect();
},
});
// export const get = query({
// args: {},
// handler: async (ctx) => {
// return await ctx.db.query("xiong").collect();
// },
// });

View File

@@ -1,72 +1,10 @@
import { ConvexClient, AuthTokenFetcher } from "convex/browser";
import { ConvexClient } from "convex/browser";
import { api } from "./convex/_generated/api.js";
import * as jose from "jose";
const url = process.env["CONVEX_URL"]
const client = new ConvexClient(url!);
// 加载测试私钥
const keys = JSON.parse(await Bun.file("./jwt/privateKey.json").text());
const privateKey = await jose.importJWK(keys, "RS256");
// 使用主应用的公共 API
const test2List = await client.query(api.wrapper.getTest2, {});
// 生成 RS256 JWT
const payload = {
iss: "https://convex.kevisual.cn",
sub: "user:8fa2be73c2229e85",
aud: "convex-app",
exp: Math.floor(Date.now() / 1000) + 3600,
name: "Test User AA",
email: "test@example.com",
};
const token = await new jose.SignJWT(payload)
.setProtectedHeader({
"alg": "RS256",
"typ": "JWT",
"kid": "kid-key-1"
})
.setIssuedAt()
.sign(privateKey);
console.log("Generated RS256 token:", 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);
});
// console.log("Auth set up", auth);
await Bun.sleep(1000);
// const auth = client.getAuth();
// console.log("Client auth", auth);
const unsubscribe = client.onUpdate(api.abcv.get, {}, async (tasks) => {
console.log(tasks);
});
const list = await client.query(api.abcv.get, {});
console.log("Initial list:", list);
// for (let i = 0; i < list.length; i++) {
// const a = list[i];
// console.log(`Item ${i}:`, a.title);
// }
// const list = await client.action(api.abcv.chat, { message: "Hello, 1+1=?" });
// console.log("Chat response:", list);
const xiongList = await client.query(api.xiong.get, {});
console.log("Xiong list:", xiongList);
// const redisIsConnected = await client.action(api.actions.redis.isConnected, {});
// console.log("Redis isConnected:", redisIsConnected);
// const sign = await api.auth.signIn({ provider: "convex" })
// const sign = await httpClient.action("/auth", { method: "POST" });
// console.log("Sign in result:", sign);
const res = await fetch("http://convex.kevisual.cn:3211/auth", {
method: "POST",
});
const data = await res.json();
console.log("Custom endpoint response:", data);
await Bun.sleep(1000);
unsubscribe();
await client.close();
console.log("Test2 List:", test2List);

View File

@@ -4,7 +4,8 @@
"description": "",
"main": "index.js",
"scripts": {
"dev": "bunx convex dev"
"dev": "bunx convex dev",
"convex": "bunx convex dev"
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
@@ -26,4 +27,4 @@
"convex": "^1.31.6",
"jose": "^6.1.3"
}
}
}

72
src/test1.ts Normal file
View File

@@ -0,0 +1,72 @@
import { ConvexClient, AuthTokenFetcher } from "convex/browser";
import { api } from "../convex/_generated/api.js";
import * as jose from "jose";
const url = process.env["CONVEX_URL"]
const client = new ConvexClient(url!);
// 加载测试私钥
const keys = JSON.parse(await Bun.file("./jwt/privateKey.json").text());
const privateKey = await jose.importJWK(keys, "RS256");
// 生成 RS256 JWT
const payload = {
iss: "https://convex.kevisual.cn",
sub: "user:8fa2be73c2229e85",
aud: "convex-app",
exp: Math.floor(Date.now() / 1000) + 3600,
name: "Test User AA",
email: "test@example.com",
};
const token = await new jose.SignJWT(payload)
.setProtectedHeader({
"alg": "RS256",
"typ": "JWT",
"kid": "kid-key-1"
})
.setIssuedAt()
.sign(privateKey);
console.log("Generated RS256 token:", 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);
});
// console.log("Auth set up", auth);
await Bun.sleep(1000);
// const auth = client.getAuth();
// console.log("Client auth", auth);
const unsubscribe = client.onUpdate(api.abcv.get, {}, async (tasks) => {
console.log(tasks);
});
const list = await client.query(api.abcv.get, {});
console.log("Initial list:", list);
// for (let i = 0; i < list.length; i++) {
// const a = list[i];
// console.log(`Item ${i}:`, a.title);
// }
// const list = await client.action(api.abcv.chat, { message: "Hello, 1+1=?" });
// console.log("Chat response:", list);
const xiongList = await client.query(api.xiong.get, {});
console.log("Xiong list:", xiongList);
// const redisIsConnected = await client.action(api.actions.redis.isConnected, {});
// console.log("Redis isConnected:", redisIsConnected);
// const sign = await api.auth.signIn({ provider: "convex" })
// const sign = await httpClient.action("/auth", { method: "POST" });
// console.log("Sign in result:", sign);
const res = await fetch("http://convex.kevisual.cn:3211/auth", {
method: "POST",
});
const data = await res.json();
console.log("Custom endpoint response:", data);
await Bun.sleep(1000);
unsubscribe();
await client.close();