@tscircuit/cli 0.0.116 → 0.0.119
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/server-tests.yml +31 -0
- package/.github/workflows/test.yml +4 -1
- package/README.md +1 -2
- package/bun.lockb +0 -0
- package/dev-server-api/bun.lockb +0 -0
- package/dev-server-api/package.json +2 -4
- package/dev-server-api/routes/api/db/download.ts +25 -0
- package/dev-server-api/routes/api/dev_package_examples/create.ts +15 -25
- package/dev-server-api/routes/api/dev_package_examples/get.ts +12 -21
- package/dev-server-api/routes/api/dev_package_examples/list.ts +17 -22
- package/dev-server-api/routes/api/dev_package_examples/update.ts +39 -37
- package/dev-server-api/routes/api/dev_server/reset.ts +6 -8
- package/dev-server-api/routes/api/export_files/create.ts +10 -13
- package/dev-server-api/routes/api/export_files/download.ts +10 -7
- package/dev-server-api/routes/api/export_requests/create.ts +11 -15
- package/dev-server-api/routes/api/export_requests/get.ts +24 -15
- package/dev-server-api/routes/api/export_requests/list.ts +9 -10
- package/dev-server-api/routes/api/export_requests/update.ts +18 -18
- package/dev-server-api/routes/api/package_info/create.ts +9 -11
- package/dev-server-api/routes/api/package_info/get.ts +5 -8
- package/dev-server-api/routes/index.ts +16 -0
- package/dev-server-api/server.ts +1 -1
- package/dev-server-api/src/db/get-db.ts +11 -102
- package/dev-server-api/src/db/schema.ts +65 -0
- package/dev-server-api/src/db/zod-level-db.ts +146 -0
- package/dev-server-api/src/middlewares/with-db.ts +7 -3
- package/dev-server-api/static-routes.ts +3 -1
- package/dev-server-api/tests/fixtures/get-test-server.ts +30 -0
- package/dev-server-api/tests/fixtures/start-server.ts +20 -0
- package/dev-server-api/tests/routes/dev_package_examples/create.test.ts +19 -0
- package/dev-server-api/tests/routes/dev_package_examples/get.test.ts +25 -0
- package/dev-server-api/tests/routes/dev_package_examples/list.test.ts +32 -0
- package/dev-server-api/tests/routes/dev_package_examples/update.test.ts +28 -0
- package/dev-server-api/tests/routes/export_files/create.test.ts +18 -0
- package/dev-server-api/tests/routes/export_files/download.test.ts +29 -0
- package/dev-server-api/tests/routes/export_requests/create.test.ts +24 -0
- package/dev-server-api/tests/routes/export_requests/get.test.ts +41 -0
- package/dev-server-api/tests/routes/export_requests/list.test.ts +35 -0
- package/dev-server-api/tests/routes/export_requests/update.test.ts +50 -0
- package/dev-server-api/tests/routes/health.test.ts +10 -0
- package/dist/cli.js +487 -368
- package/lib/cmd-fns/dev/index.ts +14 -11
- package/package.json +6 -8
- package/tests/open.test.ts +1 -2
- package/tests/soupify.test.ts +1 -1
- package/dev-server-api/src/lib/zod/export_file.ts +0 -8
- package/dev-server-api/src/lib/zod/export_package_info.ts +0 -5
- package/dev-server-api/src/lib/zod/export_request.ts +0 -21
- /package/{tests/assets/example-project → example-project}/README.md +0 -0
- /package/{tests/assets/example-project → example-project}/examples/basic-bug.tsx +0 -0
- /package/{tests/assets/example-project → example-project}/examples/basic-capacitor.tsx +0 -0
- /package/{tests/assets/example-project → example-project}/examples/basic-resistor.tsx +0 -0
- /package/{tests/assets/example-project → example-project}/index.ts +0 -0
- /package/{tests/assets/example-project → example-project}/package-lock.json +0 -0
- /package/{tests/assets/example-project → example-project}/package.json +0 -0
- /package/{tests/assets/example-project → example-project}/src/MyCircuit.tsx +0 -0
- /package/{tests/assets/example-project → example-project}/src/manual-edits.ts +0 -0
|
@@ -4,25 +4,23 @@ import { z } from "zod"
|
|
|
4
4
|
export default withWinterSpec({
|
|
5
5
|
methods: ["POST"],
|
|
6
6
|
jsonBody: z.object({
|
|
7
|
-
package_name: z.string()
|
|
7
|
+
package_name: z.string(),
|
|
8
8
|
}),
|
|
9
9
|
jsonResponse: z.object({
|
|
10
10
|
package_info: z.object({
|
|
11
|
-
name: z.string()
|
|
12
|
-
})
|
|
11
|
+
name: z.string(),
|
|
12
|
+
}),
|
|
13
13
|
}),
|
|
14
14
|
auth: "none",
|
|
15
15
|
})(async (req, ctx) => {
|
|
16
16
|
const package_name = req.jsonBody.package_name
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.returningAll()
|
|
23
|
-
.executeTakeFirstOrThrow()
|
|
17
|
+
|
|
18
|
+
const package_info = await ctx.db.put("package_info", {
|
|
19
|
+
package_info_id: 1,
|
|
20
|
+
name: package_name,
|
|
21
|
+
})
|
|
24
22
|
|
|
25
23
|
return ctx.json({
|
|
26
|
-
package_info
|
|
24
|
+
package_info,
|
|
27
25
|
})
|
|
28
26
|
})
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PackageInfoSchema } from "src/db/schema"
|
|
2
2
|
import { withWinterSpec } from "src/with-winter-spec"
|
|
3
3
|
import { z } from "zod"
|
|
4
4
|
|
|
5
5
|
export default withWinterSpec({
|
|
6
6
|
methods: ["GET"],
|
|
7
7
|
jsonResponse: z.object({
|
|
8
|
-
package_info:
|
|
8
|
+
package_info: PackageInfoSchema,
|
|
9
9
|
}),
|
|
10
10
|
auth: "none",
|
|
11
11
|
})(async (req, ctx) => {
|
|
12
|
-
const package_info = await ctx.db
|
|
13
|
-
.selectFrom("package_info")
|
|
14
|
-
.select("name")
|
|
15
|
-
.executeTakeFirstOrThrow()
|
|
12
|
+
const package_info = await ctx.db.get("package_info", 1)
|
|
16
13
|
|
|
17
|
-
return ctx.json({
|
|
18
|
-
})
|
|
14
|
+
return ctx.json({ package_info: package_info! })
|
|
15
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { withWinterSpec } from "../src/with-winter-spec"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
export default withWinterSpec({
|
|
5
|
+
methods: ["GET"],
|
|
6
|
+
auth: "none",
|
|
7
|
+
})(async (req, ctx) => {
|
|
8
|
+
return new Response(
|
|
9
|
+
`<html><body>This is the dev server API <a href="/api/db/download">view database</a></body></html>`,
|
|
10
|
+
{
|
|
11
|
+
headers: {
|
|
12
|
+
"content-type": "text/html",
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
)
|
|
16
|
+
})
|
package/dev-server-api/server.ts
CHANGED
|
@@ -6,7 +6,7 @@ const serverFetch = await createFetchHandlerFromDir(
|
|
|
6
6
|
join(import.meta.dir, "./routes")
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
console.log("starting dev-server-api on localhost:3021")
|
|
9
|
+
console.log("starting dev-server-api on http://localhost:3021")
|
|
10
10
|
Bun.serve({
|
|
11
11
|
fetch: (bunReq) => {
|
|
12
12
|
const req = new EdgeRuntimeRequest(bunReq.url, {
|
|
@@ -1,117 +1,26 @@
|
|
|
1
1
|
import { mkdirSync } from "fs"
|
|
2
|
-
import { Kysely, SqliteDialect, sql, type Generated } from "kysely"
|
|
3
2
|
import * as Path from "path"
|
|
4
|
-
import {
|
|
3
|
+
import { ZodLevelDatabase } from "./zod-level-db"
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
name: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface DevPackageExample {
|
|
11
|
-
dev_package_example_id: Generated<number>
|
|
12
|
-
tscircuit_soup: any
|
|
13
|
-
completed_edit_events: any
|
|
14
|
-
file_path: string
|
|
15
|
-
export_name: string
|
|
16
|
-
error: string | null
|
|
17
|
-
is_loading: 1 | 0
|
|
18
|
-
last_updated_at: string
|
|
19
|
-
soup_last_updated_at: string
|
|
20
|
-
edit_events_last_updated_at: string
|
|
21
|
-
edit_events_last_applied_at: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ExportRequest {
|
|
25
|
-
export_request_id: Generated<number>
|
|
26
|
-
example_file_path: string
|
|
27
|
-
export_parameters: string
|
|
28
|
-
export_name: string
|
|
29
|
-
is_complete: 1 | 0
|
|
30
|
-
has_error: 1 | 0
|
|
31
|
-
error?: string
|
|
32
|
-
created_at: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ExportFile {
|
|
36
|
-
export_file_id: Generated<number>
|
|
37
|
-
file_name: string
|
|
38
|
-
file_content: Buffer
|
|
39
|
-
export_request_id: number
|
|
40
|
-
created_at: string
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface KyselyDatabaseSchema {
|
|
44
|
-
dev_package_example: DevPackageExample
|
|
45
|
-
export_request: ExportRequest
|
|
46
|
-
export_file: ExportFile
|
|
47
|
-
package_info: PackageInfo
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export type DbClient = Kysely<KyselyDatabaseSchema>
|
|
51
|
-
|
|
52
|
-
// let globalDb: Database | undefined
|
|
53
|
-
|
|
54
|
-
let globalDb: Kysely<KyselyDatabaseSchema> | undefined
|
|
5
|
+
let globalDb: ZodLevelDatabase | undefined
|
|
55
6
|
|
|
56
7
|
export const getDbFilePath = () =>
|
|
57
|
-
process.env.TSCI_DEV_SERVER_DB ?? "./.tscircuit/
|
|
8
|
+
process.env.TSCI_DEV_SERVER_DB ?? "./.tscircuit/devdb"
|
|
58
9
|
|
|
59
|
-
export const getDb = async (): Promise<
|
|
60
|
-
if (globalDb)
|
|
61
|
-
|
|
62
|
-
const devServerDbPath = getDbFilePath()
|
|
63
|
-
|
|
64
|
-
mkdirSync(Path.dirname(devServerDbPath), { recursive: true })
|
|
65
|
-
|
|
66
|
-
// better-sqlite3 doesn't work in bun, so if we see we can use the bun
|
|
67
|
-
// alternative, attempt to use that instead
|
|
68
|
-
let dialect: any
|
|
69
|
-
|
|
70
|
-
if (typeof Bun !== "undefined") {
|
|
71
|
-
// console.log("Attempting to use bun-sqlite")
|
|
72
|
-
try {
|
|
73
|
-
const { BunSqliteDialect } = await import("kysely-bun-sqlite")
|
|
74
|
-
const { Database } = await import("bun:sqlite")
|
|
75
|
-
dialect = new BunSqliteDialect({
|
|
76
|
-
database: new Database(devServerDbPath, {
|
|
77
|
-
create: true,
|
|
78
|
-
}),
|
|
79
|
-
})
|
|
80
|
-
} catch (e) { }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!dialect) {
|
|
84
|
-
// console.log("Attempting to use better-sqlite3")
|
|
85
|
-
try {
|
|
86
|
-
const BetterSqlite3 = await import("better-sqlite3")
|
|
87
|
-
dialect = new SqliteDialect({
|
|
88
|
-
database: new BetterSqlite3.default(devServerDbPath),
|
|
89
|
-
})
|
|
90
|
-
} catch (e) { }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (!dialect) {
|
|
94
|
-
throw new Error("Was not able to load sqlite dialect")
|
|
10
|
+
export const getDb = async (): Promise<ZodLevelDatabase> => {
|
|
11
|
+
if (globalDb) {
|
|
12
|
+
return globalDb
|
|
95
13
|
}
|
|
96
14
|
|
|
97
|
-
const
|
|
98
|
-
dialect,
|
|
99
|
-
})
|
|
15
|
+
const devServerDbPath = getDbFilePath()
|
|
100
16
|
|
|
101
|
-
|
|
17
|
+
mkdirSync(devServerDbPath, { recursive: true })
|
|
102
18
|
|
|
103
|
-
const
|
|
104
|
-
SELECT name
|
|
105
|
-
FROM sqlite_master
|
|
106
|
-
WHERE type='table' AND name IN ('dev_package_example', 'export_request', 'export_file', 'package_info')
|
|
107
|
-
`.execute(db)
|
|
19
|
+
const db = new ZodLevelDatabase(devServerDbPath)
|
|
108
20
|
|
|
109
|
-
|
|
110
|
-
if (schemaExistsResult.rows.length < 4) {
|
|
111
|
-
await createSchema(db)
|
|
112
|
-
}
|
|
21
|
+
db.open()
|
|
113
22
|
|
|
114
23
|
globalDb = db
|
|
115
24
|
|
|
116
25
|
return db
|
|
117
|
-
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
// Helper function for nullable fields
|
|
4
|
+
const nullableText = () => z.string().nullable().default(null)
|
|
5
|
+
const id = () => z.any().pipe(z.number().int())
|
|
6
|
+
|
|
7
|
+
// PackageInfo schema
|
|
8
|
+
export const PackageInfoSchema = z.object({
|
|
9
|
+
package_info_id: id(),
|
|
10
|
+
name: z.string(),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
// DevPackageExample schema
|
|
14
|
+
export const DevPackageExampleSchema = z.object({
|
|
15
|
+
dev_package_example_id: id(),
|
|
16
|
+
file_path: z.string(),
|
|
17
|
+
export_name: nullableText(),
|
|
18
|
+
tscircuit_soup: z.any().nullable(), // Using any for JSON type
|
|
19
|
+
completed_edit_events: z.array(z.any()).default([]), // Using any for JSON type
|
|
20
|
+
error: nullableText(),
|
|
21
|
+
is_loading: z.boolean(),
|
|
22
|
+
soup_last_updated_at: nullableText(),
|
|
23
|
+
edit_events_last_updated_at: nullableText(),
|
|
24
|
+
edit_events_last_applied_at: nullableText(),
|
|
25
|
+
last_updated_at: nullableText(),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// ExportRequest schema
|
|
29
|
+
export const ExportRequestSchema = z.object({
|
|
30
|
+
export_request_id: id(),
|
|
31
|
+
example_file_path: nullableText(),
|
|
32
|
+
export_parameters: z.any().nullable(), // Using any for JSON type
|
|
33
|
+
export_name: nullableText(),
|
|
34
|
+
is_complete: z.boolean(),
|
|
35
|
+
has_error: z.boolean(),
|
|
36
|
+
error: nullableText(),
|
|
37
|
+
created_at: nullableText(),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// ExportFile schema
|
|
41
|
+
export const ExportFileSchema = z.object({
|
|
42
|
+
export_file_id: id(),
|
|
43
|
+
file_name: nullableText(),
|
|
44
|
+
file_content_base64: z.string().nullable(),
|
|
45
|
+
export_request_id: z.number().int().nullable(),
|
|
46
|
+
created_at: nullableText(),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Combined DBSchema
|
|
50
|
+
export const DBSchema = z.object({
|
|
51
|
+
package_info: PackageInfoSchema,
|
|
52
|
+
dev_package_example: DevPackageExampleSchema,
|
|
53
|
+
export_request: ExportRequestSchema,
|
|
54
|
+
export_file: ExportFileSchema,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// TypeScript type inference
|
|
58
|
+
export type DBSchemaType = z.infer<typeof DBSchema>
|
|
59
|
+
export type DBInputSchemaType = z.input<typeof DBSchema>
|
|
60
|
+
|
|
61
|
+
// You can also export individual types if needed
|
|
62
|
+
export type PackageInfo = z.infer<typeof PackageInfoSchema>
|
|
63
|
+
export type DevPackageExample = z.infer<typeof DevPackageExampleSchema>
|
|
64
|
+
export type ExportRequest = z.infer<typeof ExportRequestSchema>
|
|
65
|
+
export type ExportFile = z.infer<typeof ExportFileSchema>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Level } from "level"
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
import { DBSchema, type DBSchemaType, type DBInputSchemaType } from "./schema"
|
|
4
|
+
|
|
5
|
+
// Create a wrapper class for Level with Zod validation
|
|
6
|
+
export class ZodLevelDatabase {
|
|
7
|
+
private db: Level<string, any>
|
|
8
|
+
|
|
9
|
+
constructor(location: string) {
|
|
10
|
+
this.db = new Level(location)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async open() {
|
|
14
|
+
return this.db.open()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async close() {
|
|
18
|
+
return this.db.close()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async get<K extends keyof DBSchemaType>(
|
|
22
|
+
collection: K,
|
|
23
|
+
id: string | number
|
|
24
|
+
): Promise<DBSchemaType[K] | null> {
|
|
25
|
+
const key = `${collection}:${id}`
|
|
26
|
+
const data = await this.db.get(key)
|
|
27
|
+
return DBSchema.shape[collection].parse(JSON.parse(data)) as any
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async put<K extends keyof DBSchemaType>(
|
|
31
|
+
collection: K,
|
|
32
|
+
value: DBInputSchemaType[K]
|
|
33
|
+
): Promise<DBSchemaType[K]> {
|
|
34
|
+
const idkey = `${collection}_id`
|
|
35
|
+
const valueLoose: any = value
|
|
36
|
+
if (!valueLoose[idkey]) {
|
|
37
|
+
// generate an id using the "count" key
|
|
38
|
+
let count = await this.db
|
|
39
|
+
.get(`${collection}.count`, { valueEncoding: "json" })
|
|
40
|
+
.catch(() => 1)
|
|
41
|
+
;(value as any)[idkey] = count
|
|
42
|
+
await this.db.put(`${collection}.count`, count + 1)
|
|
43
|
+
}
|
|
44
|
+
const key = `${collection}:${valueLoose[idkey]}`
|
|
45
|
+
const validatedData = DBSchema.shape[collection].parse(value)
|
|
46
|
+
await this.db.put(key, JSON.stringify(validatedData))
|
|
47
|
+
return validatedData as DBSchemaType[K]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async del<K extends keyof DBSchemaType>(
|
|
51
|
+
collection: K,
|
|
52
|
+
id: string
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
const key = `${collection}:${id}`
|
|
55
|
+
await this.db.del(key)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async find<K extends keyof DBSchemaType>(
|
|
59
|
+
collection: K,
|
|
60
|
+
partialObject: Partial<DBSchemaType[K]>
|
|
61
|
+
): Promise<DBSchemaType[K] | null> {
|
|
62
|
+
const schema = DBSchema.shape[collection]
|
|
63
|
+
|
|
64
|
+
for await (const [key, value] of this.db.iterator({
|
|
65
|
+
gte: `${collection}:`,
|
|
66
|
+
lte: `${collection}:\uffff`,
|
|
67
|
+
})) {
|
|
68
|
+
try {
|
|
69
|
+
const parsedValue = schema.parse(JSON.parse(value))
|
|
70
|
+
if (this.matchesPartialObject(parsedValue, partialObject)) {
|
|
71
|
+
return parsedValue as any
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(`Error parsing value for key ${key}:`, error)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async findOrThrow<K extends keyof DBSchemaType>(
|
|
82
|
+
collection: K,
|
|
83
|
+
partialObject: Partial<DBSchemaType[K]>
|
|
84
|
+
): Promise<DBSchemaType[K]> {
|
|
85
|
+
const result = await this.find(collection, partialObject)
|
|
86
|
+
if (!result) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`No record in "${collection}" matches query ${JSON.stringify(
|
|
89
|
+
partialObject
|
|
90
|
+
)}`
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
return result
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private matchesPartialObject<T>(
|
|
97
|
+
fullObject: T,
|
|
98
|
+
partialObject: Partial<T>
|
|
99
|
+
): boolean {
|
|
100
|
+
for (const [key, value] of Object.entries(partialObject)) {
|
|
101
|
+
if (fullObject[key as keyof T] !== value) {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async dump(): Promise<DBSchemaType> {
|
|
109
|
+
// Serialize all data in the database
|
|
110
|
+
const dump: any = {}
|
|
111
|
+
for await (const [key, value] of this.db.iterator({})) {
|
|
112
|
+
const [collection, id] = key.split(":")
|
|
113
|
+
if (!dump[collection]) {
|
|
114
|
+
dump[collection] = {}
|
|
115
|
+
}
|
|
116
|
+
dump[collection][id] = JSON.parse(value)
|
|
117
|
+
}
|
|
118
|
+
return dump
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async list<K extends keyof DBSchemaType>(
|
|
122
|
+
collection: K
|
|
123
|
+
): Promise<DBSchemaType[K][]> {
|
|
124
|
+
const schema = DBSchema.shape[collection]
|
|
125
|
+
const results: DBSchemaType[K][] = []
|
|
126
|
+
|
|
127
|
+
for await (const [key, value] of this.db.iterator({
|
|
128
|
+
gte: `${collection}:`,
|
|
129
|
+
lte: `${collection}:\uffff`,
|
|
130
|
+
})) {
|
|
131
|
+
if (key.endsWith(".count")) continue
|
|
132
|
+
try {
|
|
133
|
+
const parsedValue = schema.parse(JSON.parse(value))
|
|
134
|
+
results.push(parsedValue as DBSchemaType[K])
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(`Error parsing value for key ${key}:`, error)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return results
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async clear() {
|
|
144
|
+
return this.db.clear()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import type { Middleware } from "winterspec"
|
|
2
|
-
import { getDb
|
|
2
|
+
import { getDb } from "../db/get-db"
|
|
3
|
+
import type { ZodLevelDatabase } from "src/db/zod-level-db"
|
|
3
4
|
|
|
4
5
|
export const withDb: Middleware<
|
|
5
6
|
{},
|
|
6
7
|
{
|
|
7
|
-
db:
|
|
8
|
+
db: ZodLevelDatabase
|
|
8
9
|
}
|
|
9
10
|
> = async (req, ctx, next) => {
|
|
10
11
|
if (!ctx.db) {
|
|
11
12
|
ctx.db = await getDb()
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
// await ctx.db.open()
|
|
15
|
+
const res = await next(req, ctx)
|
|
16
|
+
// await ctx.db.close()
|
|
17
|
+
return res
|
|
14
18
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// import { WinterSpecRouteMap } from "@winterspec/types"
|
|
3
3
|
|
|
4
4
|
const routeMap = {
|
|
5
|
+
"/api/db/download": (await import('routes/api/db/download.ts')).default,
|
|
5
6
|
"/api/dev_package_examples/create": (await import('routes/api/dev_package_examples/create.ts')).default,
|
|
6
7
|
"/api/dev_package_examples/get": (await import('routes/api/dev_package_examples/get.ts')).default,
|
|
7
8
|
"/api/dev_package_examples/list": (await import('routes/api/dev_package_examples/list.ts')).default,
|
|
@@ -16,7 +17,8 @@ const routeMap = {
|
|
|
16
17
|
"/api/health": (await import('routes/api/health.ts')).default,
|
|
17
18
|
"/api/package_info/create": (await import('routes/api/package_info/create.ts')).default,
|
|
18
19
|
"/api/package_info/get": (await import('routes/api/package_info/get.ts')).default,
|
|
19
|
-
"/health": (await import('routes/health.ts')).default
|
|
20
|
+
"/health": (await import('routes/health.ts')).default,
|
|
21
|
+
"/": (await import('routes/index.ts')).default
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export default routeMap
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { afterEach } from "bun:test"
|
|
2
|
+
import defaultAxios from "redaxios"
|
|
3
|
+
import { startServer } from "./start-server"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
|
|
6
|
+
interface TestFixture {
|
|
7
|
+
url: string
|
|
8
|
+
server: any
|
|
9
|
+
axios: typeof defaultAxios
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const getTestFixture = async (): Promise<TestFixture> => {
|
|
13
|
+
process.env.TSCI_DEV_SERVER_DB = tmpdir() + `/${Math.random()}` + "/devdb"
|
|
14
|
+
const port = 3001 + Math.floor(Math.random() * 999)
|
|
15
|
+
const server = startServer({ port })
|
|
16
|
+
const url = `http://localhost:${port}`
|
|
17
|
+
const axios = defaultAxios.create({
|
|
18
|
+
baseURL: url,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
server.stop()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
url,
|
|
27
|
+
server,
|
|
28
|
+
axios,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createFetchHandlerFromDir } from "winterspec/adapters/node"
|
|
2
|
+
import { Request as EdgeRuntimeRequest } from "@edge-runtime/primitives"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
|
|
5
|
+
const serverFetch = await createFetchHandlerFromDir(
|
|
6
|
+
join(import.meta.dir, "../../routes")
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
export const startServer = ({ port }: { port: number }) =>
|
|
10
|
+
Bun.serve({
|
|
11
|
+
fetch: (bunReq) => {
|
|
12
|
+
const req = new EdgeRuntimeRequest(bunReq.url, {
|
|
13
|
+
headers: bunReq.headers,
|
|
14
|
+
method: bunReq.method,
|
|
15
|
+
body: bunReq.body,
|
|
16
|
+
})
|
|
17
|
+
return serverFetch(req as any)
|
|
18
|
+
},
|
|
19
|
+
port,
|
|
20
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { it, expect } from "bun:test"
|
|
2
|
+
import { getTestFixture } from "tests/fixtures/get-test-server"
|
|
3
|
+
|
|
4
|
+
it("POST /api/dev_package_examples/create", async () => {
|
|
5
|
+
const { axios } = await getTestFixture()
|
|
6
|
+
|
|
7
|
+
const res = await axios
|
|
8
|
+
.post("/api/dev_package_examples/create", {
|
|
9
|
+
file_path: "examples/basic-resistor.tsx",
|
|
10
|
+
export_name: "default",
|
|
11
|
+
tscircuit_soup: [],
|
|
12
|
+
is_loading: true,
|
|
13
|
+
})
|
|
14
|
+
.then((r) => r.data)
|
|
15
|
+
|
|
16
|
+
expect(res.dev_package_example.file_path).toEqual(
|
|
17
|
+
"examples/basic-resistor.tsx"
|
|
18
|
+
)
|
|
19
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { it, expect } from "bun:test"
|
|
2
|
+
import { getTestFixture } from "tests/fixtures/get-test-server"
|
|
3
|
+
|
|
4
|
+
it("POST /api/dev_package_examples/create", async () => {
|
|
5
|
+
const { axios } = await getTestFixture()
|
|
6
|
+
|
|
7
|
+
await axios
|
|
8
|
+
.post("/api/dev_package_examples/create", {
|
|
9
|
+
file_path: "examples/basic-resistor.tsx",
|
|
10
|
+
export_name: "default",
|
|
11
|
+
tscircuit_soup: [],
|
|
12
|
+
is_loading: true,
|
|
13
|
+
})
|
|
14
|
+
.then((r) => r.data)
|
|
15
|
+
|
|
16
|
+
const res = await axios
|
|
17
|
+
.post("/api/dev_package_examples/get", {
|
|
18
|
+
dev_package_example_id: 1,
|
|
19
|
+
})
|
|
20
|
+
.then((r) => r.data)
|
|
21
|
+
|
|
22
|
+
expect(res.dev_package_example.file_path).toEqual(
|
|
23
|
+
"examples/basic-resistor.tsx"
|
|
24
|
+
)
|
|
25
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { it, expect } from "bun:test"
|
|
2
|
+
import { getTestFixture } from "tests/fixtures/get-test-server"
|
|
3
|
+
|
|
4
|
+
it("GET /api/dev_package_examples/list", async () => {
|
|
5
|
+
const { axios } = await getTestFixture()
|
|
6
|
+
|
|
7
|
+
// First, create a dev package example
|
|
8
|
+
await axios.post("/api/dev_package_examples/create", {
|
|
9
|
+
file_path: "examples/test-example.tsx",
|
|
10
|
+
export_name: "default",
|
|
11
|
+
tscircuit_soup: [],
|
|
12
|
+
is_loading: false,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// Then, list all dev package examples
|
|
16
|
+
const res = await axios
|
|
17
|
+
.post("/api/dev_package_examples/list")
|
|
18
|
+
.then((r) => r.data)
|
|
19
|
+
|
|
20
|
+
expect(res.dev_package_examples).toBeDefined()
|
|
21
|
+
expect(Array.isArray(res.dev_package_examples)).toBe(true)
|
|
22
|
+
expect(res.dev_package_examples.length).toBeGreaterThan(0)
|
|
23
|
+
|
|
24
|
+
const example = res.dev_package_examples.find(
|
|
25
|
+
(e: any) => e.file_path === "examples/test-example.tsx"
|
|
26
|
+
)
|
|
27
|
+
expect(example).toBeDefined()
|
|
28
|
+
expect(example.export_name).toBe("default")
|
|
29
|
+
expect(example.is_loading).toBe(false)
|
|
30
|
+
expect(example.dev_package_example_id).toBeDefined()
|
|
31
|
+
expect(example.last_updated_at).toBeDefined()
|
|
32
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { it, expect } from "bun:test"
|
|
2
|
+
import { getTestFixture } from "tests/fixtures/get-test-server"
|
|
3
|
+
|
|
4
|
+
it("POST /api/dev_package_examples/update", async () => {
|
|
5
|
+
const { axios } = await getTestFixture()
|
|
6
|
+
|
|
7
|
+
await axios
|
|
8
|
+
.post("/api/dev_package_examples/create", {
|
|
9
|
+
file_path: "examples/basic-resistor.tsx",
|
|
10
|
+
export_name: "default",
|
|
11
|
+
tscircuit_soup: [],
|
|
12
|
+
is_loading: true,
|
|
13
|
+
})
|
|
14
|
+
.then((r) => r.data)
|
|
15
|
+
|
|
16
|
+
const res = await axios
|
|
17
|
+
.post("/api/dev_package_examples/update", {
|
|
18
|
+
dev_package_example_id: 1,
|
|
19
|
+
completed_edit_events: [],
|
|
20
|
+
edit_events_last_applied_at: "2023-01-01T00:00:00.000Z",
|
|
21
|
+
})
|
|
22
|
+
.then((r) => r.data)
|
|
23
|
+
|
|
24
|
+
expect(res.dev_package_example.completed_edit_events).toEqual([])
|
|
25
|
+
expect(res.dev_package_example.edit_events_last_applied_at).toEqual(
|
|
26
|
+
"2023-01-01T00:00:00.000Z"
|
|
27
|
+
)
|
|
28
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { it, expect } from "bun:test"
|
|
2
|
+
import { getTestFixture } from "tests/fixtures/get-test-server"
|
|
3
|
+
|
|
4
|
+
it("POST /api/export_files/create", async () => {
|
|
5
|
+
const { axios } = await getTestFixture()
|
|
6
|
+
|
|
7
|
+
const res = await axios
|
|
8
|
+
.post("/api/export_files/create", {
|
|
9
|
+
export_request_id: 1,
|
|
10
|
+
file_name: "test.png",
|
|
11
|
+
file_content_base64:
|
|
12
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
|
|
13
|
+
})
|
|
14
|
+
.then((r) => r.data)
|
|
15
|
+
|
|
16
|
+
expect(res.export_file.export_request_id).toEqual(1)
|
|
17
|
+
expect(res.export_file.file_name).toEqual("test.png")
|
|
18
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { it, expect } from "bun:test"
|
|
2
|
+
import { getTestFixture } from "tests/fixtures/get-test-server"
|
|
3
|
+
|
|
4
|
+
it("GET /api/export_files/download", async () => {
|
|
5
|
+
const { axios } = await getTestFixture()
|
|
6
|
+
|
|
7
|
+
const exampleBase64 = Buffer.from("example").toString("base64")
|
|
8
|
+
|
|
9
|
+
const res = await axios
|
|
10
|
+
.post("/api/export_files/create", {
|
|
11
|
+
export_request_id: 1,
|
|
12
|
+
file_name: "test.png",
|
|
13
|
+
file_content_base64: exampleBase64,
|
|
14
|
+
})
|
|
15
|
+
.then((r) => r.data)
|
|
16
|
+
|
|
17
|
+
const downloadRes = await axios
|
|
18
|
+
.get(
|
|
19
|
+
`/api/export_files/download?export_file_id=${res.export_file.export_file_id}`
|
|
20
|
+
)
|
|
21
|
+
.then((r) => r.data)
|
|
22
|
+
|
|
23
|
+
// Convert downloadRes to base64 string
|
|
24
|
+
const downloadResBase64 = Buffer.from(downloadRes, "binary").toString(
|
|
25
|
+
"base64"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
expect(downloadResBase64).toEqual(exampleBase64)
|
|
29
|
+
})
|