Skip to content

Commit 97a8d8c

Browse files
committed
chore: add examples
1 parent 65fb6cb commit 97a8d8c

File tree

9 files changed

+219
-2
lines changed

9 files changed

+219
-2
lines changed

src/add-ons.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,29 @@ import { existsSync, readdirSync, statSync } from 'node:fs'
33
import { resolve } from 'node:path'
44
import { fileURLToPath } from 'node:url'
55

6+
type BooleanVariable = {
7+
name: string
8+
default: boolean
9+
description: string
10+
type: 'boolean'
11+
}
12+
13+
type NumberVariable = {
14+
name: string
15+
default: number
16+
description: string
17+
type: 'number'
18+
}
19+
20+
type StringVariable = {
21+
name: string
22+
default: string
23+
description: string
24+
type: 'string'
25+
}
26+
27+
export type Variable = BooleanVariable | NumberVariable | StringVariable
28+
629
export type AddOn = {
730
id: string
831
type: 'add-on' | 'example'
@@ -44,6 +67,7 @@ export type AddOn = {
4467
shadcnComponents?: Array<string>
4568
warning?: string
4669
dependsOn?: Array<string>
70+
variables?: Array<Variable>
4771
}
4872

4973
function isDirectory(path: string): boolean {

src/options.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from './package-manager.js'
1515
import { CODE_ROUTER, FILE_ROUTER } from './constants.js'
1616
import { finalizeAddOns, getAllAddOns } from './add-ons.js'
17+
import type { Variable } from './add-ons.js'
1718

1819
import type { CliOptions, Options } from './types.js'
1920

@@ -35,10 +36,51 @@ export function normalizeOptions(
3536
git: !!cliOptions.git,
3637
addOns: !!cliOptions.addOns,
3738
chosenAddOns: [],
39+
variableValues: {},
3840
}
3941
}
4042
}
4143

44+
async function collectVariables(
45+
variables: Array<Variable>,
46+
): Promise<Record<string, string | number | boolean>> {
47+
const responses: Record<string, string | number | boolean> = {}
48+
for (const variable of variables) {
49+
if (variable.type === 'string') {
50+
const response = await text({
51+
message: variable.description,
52+
initialValue: variable.default,
53+
})
54+
if (isCancel(response)) {
55+
cancel('Operation cancelled.')
56+
process.exit(0)
57+
}
58+
responses[variable.name] = response
59+
} else if (variable.type === 'number') {
60+
const response = await text({
61+
message: variable.description,
62+
initialValue: variable.default.toString(),
63+
})
64+
if (isCancel(response)) {
65+
cancel('Operation cancelled.')
66+
process.exit(0)
67+
}
68+
responses[variable.name] = Number(response)
69+
} else {
70+
const response = await confirm({
71+
message: variable.description,
72+
initialValue: variable.default === true,
73+
})
74+
if (isCancel(response)) {
75+
cancel('Operation cancelled.')
76+
process.exit(0)
77+
}
78+
responses[variable.name] = response
79+
}
80+
}
81+
return responses
82+
}
83+
4284
export async function promptForOptions(
4385
cliOptions: CliOptions,
4486
): Promise<Required<Options>> {
@@ -194,6 +236,15 @@ export async function promptForOptions(
194236
options.chosenAddOns = []
195237
}
196238

239+
// Collect variables
240+
const variables: Array<Variable> = []
241+
for (const addOn of options.chosenAddOns) {
242+
for (const variable of addOn.variables ?? []) {
243+
variables.push(variable)
244+
}
245+
}
246+
options.variableValues = await collectVariables(variables)
247+
197248
// Git selection
198249
if (cliOptions.git === undefined) {
199250
const git = await confirm({

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface Options {
1111
addOns: boolean
1212
chosenAddOns: Array<AddOn>
1313
git: boolean
14+
variableValues: Record<string, string | number | boolean>
1415
}
1516

1617
export interface CliOptions {
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
This document serves as some special instructions when working with Convex.
2+
3+
# Schemas
4+
5+
When designing the schema please see this page on built in System fields and data types available: https://docs.convex.dev/database/types
6+
7+
Here are some specifics that are often mishandled:
8+
9+
## v (https://docs.convex.dev/api/modules/values#v)
10+
11+
The validator builder.
12+
13+
This builder allows you to build validators for Convex values.
14+
15+
Validators can be used in schema definitions and as input validators for Convex functions.
16+
17+
Type declaration
18+
Name Type
19+
id <TableName>(tableName: TableName) => VId<GenericId<TableName>, "required">
20+
null () => VNull<null, "required">
21+
number () => VFloat64<number, "required">
22+
float64 () => VFloat64<number, "required">
23+
bigint () => VInt64<bigint, "required">
24+
int64 () => VInt64<bigint, "required">
25+
boolean () => VBoolean<boolean, "required">
26+
string () => VString<string, "required">
27+
bytes () => VBytes<ArrayBuffer, "required">
28+
literal <T>(literal: T) => VLiteral<T, "required">
29+
array <T>(element: T) => VArray<T["type"][], T, "required">
30+
object <T>(fields: T) => VObject<Expand<{ [Property in string | number | symbol]?: Exclude<Infer<T[Property]>, undefined> } & { [Property in string | number | symbol]: Infer<T[Property]> }>, T, "required", { [Property in string | number | symbol]: Property | `${Property & string}.${T[Property]["fieldPaths"]}` }[keyof T] & string>
31+
record <Key, Value>(keys: Key, values: Value) => VRecord<Record<Infer<Key>, Value["type"]>, Key, Value, "required", string>
32+
union <T>(...members: T) => VUnion<T[number]["type"], T, "required", T[number]["fieldPaths"]>
33+
any () => VAny<any, "required", string>
34+
optional <T>(value: T) => VOptional<T>
35+
36+
## System fields (https://docs.convex.dev/database/types#system-fields)
37+
38+
Every document in Convex has two automatically-generated system fields:
39+
40+
_id: The document ID of the document.
41+
_creationTime: The time this document was created, in milliseconds since the Unix epoch.
42+
43+
You do not need to add indices as these are added automatically.
44+
45+
## Example Schema
46+
47+
This is an example of a well crafted schema.
48+
49+
```ts
50+
import { defineSchema, defineTable } from "convex/server";
51+
import { v } from "convex/values";
52+
53+
export default defineSchema(
54+
{
55+
users: defineTable({
56+
name: v.string(),
57+
}),
58+
59+
sessions: defineTable({
60+
userId: v.id("users"),
61+
sessionId: v.string(),
62+
}).index("sessionId", ["sessionId"]),
63+
64+
threads: defineTable({
65+
uuid: v.string(),
66+
summary: v.optional(v.string()),
67+
summarizer: v.optional(v.id("_scheduled_functions")),
68+
}).index("uuid", ["uuid"]),
69+
70+
messages: defineTable({
71+
message: v.string(),
72+
threadId: v.id("threads"),
73+
author: v.union(
74+
v.object({
75+
role: v.literal("system"),
76+
}),
77+
v.object({
78+
role: v.literal("assistant"),
79+
context: v.array(v.id("messages")),
80+
model: v.optional(v.string()),
81+
}),
82+
v.object({
83+
role: v.literal("user"),
84+
userId: v.id("users"),
85+
}),
86+
),
87+
})
88+
.index("threadId", ["threadId"]),
89+
},
90+
);
91+
```
92+
93+
Sourced from: https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/convex-cursorrules-prompt-file/.cursorrules
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
We use Sentry for watching for errors in our deployed application, as well as for instrumentation of our application.
2+
3+
## Error collection
4+
5+
Error collection is automatic and configured in `src/router.tsx`.
6+
7+
## Instrumentation
8+
9+
We want our server functions intstrumented. So if you see a function name like `createServerFn`, you can instrument it with Sentry. You'll need to import `Sentry`:
10+
11+
```tsx
12+
import * as Sentry from '@sentry/browser'
13+
```
14+
15+
And then wrap the implementation of the server function with `Sentry.startSpan`, liks so:
16+
17+
```tsx
18+
Sentry.startSpan({ name: 'Requesting all the pokemon' }, async () => {
19+
// Some lengthy operation here
20+
await fetch('https://api.pokemon.com/data/')
21+
})
22+
```
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { defineConfig } from '@tanstack/start/config'
2+
import viteTsConfigPaths from 'vite-tsconfig-paths'
23

34
export default defineConfig({
45
tsr: {
56
appDirectory: 'src',
67
},
8+
vite: {
9+
plugins: [
10+
// this is the plugin that enables path aliases
11+
viteTsConfigPaths({
12+
projects: ['./tsconfig.json'],
13+
}),
14+
],
15+
},
716
})

templates/add-on/start/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"@tailwindcss/postcss": "^4.0.7",
99
"@tanstack/start": "^1.106.0",
1010
"postcss": "^8.5.2",
11-
"vinxi": "^0.5.3"
11+
"vinxi": "^0.5.3",
12+
"vite-tsconfig-paths": "^5.1.4"
1213
}
1314
}

templates/example/ai-chat/assets/src/routes/example.ai-chat.tsx renamed to templates/example/ai-chat/assets/src/routes/example.ai-chat.tsx.ejs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const chat = createServerFn({ method: 'POST' })
2727
},
2828
body: JSON.stringify({
2929
model: 'gpt-3.5-turbo',
30+
system: `<%= variables.ai_chat_system %>`,
3031
messages: data,
3132
}),
3233
}).then((response) => {
@@ -59,6 +60,7 @@ function AIChat() {
5960

6061
return (
6162
<div className="flex flex-col py-10 px-10 text-2xl">
63+
<h1><%= variables.ai_chat_title %></h1>
6264
{messages.map((m) => (
6365
<div key={m.id} className="whitespace-pre-wrap">
6466
{m.role === 'user' ? 'User: ' : 'AI: '}

templates/example/ai-chat/info.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,19 @@
99
"name": "AI Chat"
1010
}
1111
],
12-
"dependsOn": ["start"]
12+
"dependsOn": ["start", "shadcn"],
13+
"variables": [
14+
{
15+
"name": "ai_chat_title",
16+
"default": "Your Amazing AI Chat",
17+
"description": "The title of the AI chat.",
18+
"type": "string"
19+
},
20+
{
21+
"name": "ai_chat_system",
22+
"default": "You are a helpful assistant.",
23+
"description": "The system prompt of the AI chat.",
24+
"type": "string"
25+
}
26+
]
1327
}

0 commit comments

Comments
 (0)