From 8f63c10dfd3b0e23881c8dce110fe5a510c878c9 Mon Sep 17 00:00:00 2001 From: Robel Estifanos Date: Wed, 12 Aug 2026 14:20:34 -0400 Subject: [PATCH] feat(component): register Stream V6 storage Pin the commit-specific @convex-dev/stream preview and Convex 1.43 floor, then add dormant text stream tables and a typed maintenance runner. Existing PTS storage and public behavior remain unchanged so later producer and reader layers can adopt V6 incrementally without migrating legacy data. --- package-lock.json | 57 ++++++++++++++++++--------- package.json | 7 ++-- src/component/_generated/api.ts | 2 + src/component/_generated/component.ts | 9 +++++ src/component/schema.ts | 2 + src/component/streamMaintenance.ts | 10 +++++ src/component/streams.test.ts | 17 ++++++++ src/component/streams.ts | 7 ++++ 8 files changed, 89 insertions(+), 22 deletions(-) create mode 100644 src/component/streamMaintenance.ts create mode 100644 src/component/streams.test.ts create mode 100644 src/component/streams.ts diff --git a/package-lock.json b/package-lock.json index 520083e..608650e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.3.3", "license": "Apache-2.0", "dependencies": { + "@convex-dev/stream": "https://pkg.pr.new/get-convex/stream/@convex-dev/stream@d253e4f", "convex-helpers": "^0.1.114" }, "devDependencies": { @@ -23,8 +24,8 @@ "@vitejs/plugin-react": "6.0.1", "chokidar-cli": "3.0.0", "clsx": "2.1.1", - "convex": "1.35.1", - "convex-test": "0.0.48", + "convex": "1.43.0", + "convex-test": "0.0.55", "eslint": "9.39.4", "eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-react-refresh": "0.5.2", @@ -43,7 +44,7 @@ "vitest": "4.1.4" }, "peerDependencies": { - "convex": "^1.32.0", + "convex": "^1.43.0", "react": "~18.3.1 || ^19.0.0", "react-dom": "~18.3.1 || ^19.0.0" } @@ -341,6 +342,24 @@ "convex": "^1.34.1" } }, + "node_modules/@convex-dev/stream": { + "version": "0.0.1", + "resolved": "https://pkg.pr.new/get-convex/stream/@convex-dev/stream@d253e4f", + "integrity": "sha512-8w/HlDuzwgt/xrJgvrOn6XWq9PWPL3XczCzZYdLWB35r9t4/6qtnRExPsHyOrTrMyk3tOJH7gfRNymGn3JxsiQ==", + "license": "Apache-2.0", + "dependencies": { + "convex-helpers": "0.1.119" + }, + "peerDependencies": { + "convex": "^1.43.0", + "react": "^18.3.1 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/@edge-runtime/primitives": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-6.0.0.tgz", @@ -2660,14 +2679,14 @@ "license": "MIT" }, "node_modules/convex": { - "version": "1.35.1", - "resolved": "https://registry.npmjs.org/convex/-/convex-1.35.1.tgz", - "integrity": "sha512-g23KrTjBiXqRHzWIN0PVFagKjrmFxWUaOSiBsAWPTpXX2rXl0L1F4PR0YpAcMJEzMgfZR9AGymJvLTM+KA6lsQ==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/convex/-/convex-1.43.0.tgz", + "integrity": "sha512-huZWEUZQYxIRG104o22ZI99HkviSEVltwezZRDi+pQDPrQbc5EoCPa4Y7pJDqUBQw9dc/iEEXj2/rLZg1j7Dww==", "license": "Apache-2.0", "dependencies": { "esbuild": "0.27.0", "prettier": "^3.0.0", - "ws": "8.18.0" + "ws": "8.21.0" }, "bin": { "convex": "bin/main.js" @@ -2679,7 +2698,7 @@ "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", - "@clerk/react": "^6.0.0", + "@clerk/react": "^6.4.3", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "peerDependenciesMeta": { @@ -2698,9 +2717,9 @@ } }, "node_modules/convex-helpers": { - "version": "0.1.114", - "resolved": "https://registry.npmjs.org/convex-helpers/-/convex-helpers-0.1.114.tgz", - "integrity": "sha512-elEdh+gG6BDv2dWIWVvBeJPbHnDQS5+WexUuwlGVJXz1EbMkXz/UIQwFIfLMZIXUwW6ot4JYf/1JJKNStrE6lg==", + "version": "0.1.119", + "resolved": "https://registry.npmjs.org/convex-helpers/-/convex-helpers-0.1.119.tgz", + "integrity": "sha512-fGNK9KAlBLk8Un729ZXBqD9S5jy910nxSrH6PloIL/Gl6TalFJvjN8+xCCwahlox8Ft+bb54SSg9MbcrsQb98w==", "license": "Apache-2.0", "bin": { "convex-helpers": "bin.cjs" @@ -2710,7 +2729,7 @@ "convex": "^1.32.0", "hono": "^4.0.5", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", - "typescript": "^5.5", + "typescript": "^5.5 || ^6.0.0", "zod": "^3.25.0 || ^4.0.0" }, "peerDependenciesMeta": { @@ -2732,13 +2751,13 @@ } }, "node_modules/convex-test": { - "version": "0.0.48", - "resolved": "https://registry.npmjs.org/convex-test/-/convex-test-0.0.48.tgz", - "integrity": "sha512-ewAkXwNJE0TpHAfHJt38NR6ZsArqdzd4HUy9BxegKENvupYwWCVJezFfkIJoYaZThqPABEfmiChvG8KCqCTm5w==", + "version": "0.0.55", + "resolved": "https://registry.npmjs.org/convex-test/-/convex-test-0.0.55.tgz", + "integrity": "sha512-ablJ6vR3WUpyTaYrrRQf8yxInGrhHyqBEQsCFzloda3G0MDEu2RMKNGUkvlBXYWPiiEP0UIKPS7gZuLbqGnvQw==", "dev": true, "license": "Apache-2.0", "peerDependencies": { - "convex": "^1.32.0" + "convex": "^1.43.0" } }, "node_modules/convex/node_modules/@esbuild/aix-ppc64": { @@ -6594,9 +6613,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 2403b7f..b7127f8 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ } }, "peerDependencies": { - "convex": "^1.32.0", + "convex": "^1.43.0", "react": "~18.3.1 || ^19.0.0", "react-dom": "~18.3.1 || ^19.0.0" }, @@ -77,8 +77,8 @@ "@vitejs/plugin-react": "6.0.1", "chokidar-cli": "3.0.0", "clsx": "2.1.1", - "convex": "1.35.1", - "convex-test": "0.0.48", + "convex": "1.43.0", + "convex-test": "0.0.55", "eslint": "9.39.4", "eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-react-refresh": "0.5.2", @@ -99,6 +99,7 @@ "types": "./dist/client/index.d.ts", "module": "./dist/client/index.js", "dependencies": { + "@convex-dev/stream": "https://pkg.pr.new/get-convex/stream/@convex-dev/stream@d253e4f", "convex-helpers": "^0.1.114" } } diff --git a/src/component/_generated/api.ts b/src/component/_generated/api.ts index 7aa5270..022b328 100644 --- a/src/component/_generated/api.ts +++ b/src/component/_generated/api.ts @@ -10,6 +10,7 @@ import type * as crons from "../crons.js"; import type * as lib from "../lib.js"; +import type * as streamMaintenance from "../streamMaintenance.js"; import type { ApiFromModules, @@ -21,6 +22,7 @@ import { anyApi, componentsGeneric } from "convex/server"; const fullApi: ApiFromModules<{ crons: typeof crons; lib: typeof lib; + streamMaintenance: typeof streamMaintenance; }> = anyApi as any; /** diff --git a/src/component/_generated/component.ts b/src/component/_generated/component.ts index 97320a2..3eb969f 100644 --- a/src/component/_generated/component.ts +++ b/src/component/_generated/component.ts @@ -67,4 +67,13 @@ export type ComponentApi = Name >; }; + streamMaintenance: { + run: FunctionReference< + "mutation", + "internal", + { streamId?: string; sweep?: boolean }, + { isDone: boolean }, + Name + >; + }; }; diff --git a/src/component/schema.ts b/src/component/schema.ts index d7435fc..9d88a10 100644 --- a/src/component/schema.ts +++ b/src/component/schema.ts @@ -1,5 +1,6 @@ import { defineSchema, defineTable } from "convex/server"; import { v, type Infer } from "convex/values"; +import { textStreams } from "./streams.js"; export const streamStatusValidator = v.union( v.literal("pending"), @@ -18,4 +19,5 @@ export default defineSchema({ streamId: v.id("streams"), text: v.string(), }).index("byStream", ["streamId"]), + ...textStreams.tables(), }); diff --git a/src/component/streamMaintenance.ts b/src/component/streamMaintenance.ts new file mode 100644 index 0000000..db24954 --- /dev/null +++ b/src/component/streamMaintenance.ts @@ -0,0 +1,10 @@ +import { internal } from "./_generated/api.js"; +import { internalMutation } from "./_generated/server.js"; +import { textStreams } from "./streams.js"; + +export const run = internalMutation({ + args: textStreams.args.run, + returns: textStreams.returns.run, + handler: async (ctx, args): Promise<{ isDone: boolean }> => + textStreams.run(ctx, args, { run: internal.streamMaintenance.run }), +}); diff --git a/src/component/streams.test.ts b/src/component/streams.test.ts new file mode 100644 index 0000000..208ad9a --- /dev/null +++ b/src/component/streams.test.ts @@ -0,0 +1,17 @@ +/// + +import { expect, it } from "vitest"; + +import schema from "./schema.js"; +import { textStreams } from "./streams.js"; + +it("registers dormant V6 text stream tables in the component schema", () => { + expect(Object.keys(textStreams.tables()).sort()).toEqual([ + "textStreams", + "textStreamsEvents", + ]); + expect(schema.tables).toMatchObject({ + textStreams: expect.anything(), + textStreamsEvents: expect.anything(), + }); +}); diff --git a/src/component/streams.ts b/src/component/streams.ts new file mode 100644 index 0000000..20f4521 --- /dev/null +++ b/src/component/streams.ts @@ -0,0 +1,7 @@ +import { defineStream } from "@convex-dev/stream/server"; +import { v } from "convex/values"; + +export const textStreams = defineStream("textStreams", { + event: v.string(), + eventFields: {}, +});