@junejs/server 0.0.16 → 0.0.17
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/package.json +19 -4
- package/src/adapter.ts +61 -25
- package/src/d1.ts +1 -0
- package/src/db.ts +2 -0
- package/src/index.ts +1 -1
- package/src/mysql-driver.ts +68 -0
- package/src/postgres-driver.ts +67 -0
- package/src/sqlite-driver.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "June host adapters + dev server. Host-coupled; depends on the pure @junejs/core contract layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"typecheck": "tsc --noEmit"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@junejs/core": "0.0.
|
|
34
|
-
"@junejs/db": "0.0.
|
|
33
|
+
"@junejs/core": "0.0.17",
|
|
34
|
+
"@junejs/db": "0.0.17",
|
|
35
35
|
"marked": "^18.0.5",
|
|
36
36
|
"rolldown": "^1.1.0",
|
|
37
37
|
"postcss": "^8.4.0",
|
|
@@ -40,10 +40,25 @@
|
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"react": "^19.2.0",
|
|
43
|
-
"react-dom": "^19.2.0"
|
|
43
|
+
"react-dom": "^19.2.0",
|
|
44
|
+
"pg": "^8.0.0",
|
|
45
|
+
"mysql2": "^3.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"pg": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"mysql2": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
44
54
|
},
|
|
45
55
|
"repository": {
|
|
46
56
|
"type": "git",
|
|
47
57
|
"url": "https://github.com/junebuild/june"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/pg": "^8.20.0",
|
|
61
|
+
"mysql2": "^3.22.5",
|
|
62
|
+
"pg": "^8.21.0"
|
|
48
63
|
}
|
|
49
64
|
}
|
package/src/adapter.ts
CHANGED
|
@@ -136,26 +136,44 @@ export function workers(opts?: { name?: string; domain?: string }): JuneAdapter
|
|
|
136
136
|
|
|
137
137
|
// Vercel target — emits the Build Output API v3 tree (.vercel/output/). June's
|
|
138
138
|
// worker is already a Web-standard fetch handler, so this is packaging, not a
|
|
139
|
-
// re-bundle: the same portable graph
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
139
|
+
// re-bundle: the same portable graph (built with edge-light conditions, so
|
|
140
|
+
// react-dom resolves to server.edge.js) packaged for the platform.
|
|
141
|
+
//
|
|
142
|
+
// Default runtime is "node" — a Vercel Function on Fluid compute. Vercel
|
|
143
|
+
// deprecated the Edge Functions *product* and officially recommends the Node
|
|
144
|
+
// runtime for performance + reliability, and Fluid compute (in-function
|
|
145
|
+
// concurrency) closed the cold-start gap edge used to win on. Node also lifts
|
|
146
|
+
// edge's limits (no 1–4MB code cap, full API) and is the substrate a future
|
|
147
|
+
// HTTP/TCP db backend (Neon/Turso) needs. `vercel({ runtime: "edge" })` still
|
|
148
|
+
// emits an Edge Function for latency-critical global cases — the runtime is not
|
|
149
|
+
// removed, just discouraged. BOTH run the SAME bundle; only the .func packaging
|
|
150
|
+
// (.vc-config.json + the export shape) differs.
|
|
151
|
+
//
|
|
152
|
+
// No db yet (D1 is Cloudflare-only) — declaring one still fails fast; the Node
|
|
153
|
+
// runtime makes a real backend possible but it isn't wired here.
|
|
143
154
|
//
|
|
144
155
|
// Routing: only the hashed framework assets (/_june/*) are served statically by
|
|
145
156
|
// the CDN (their exact-path URLs match June's scheme); every page + projection
|
|
146
|
-
// (.md/.json/mcp/llms.txt) renders through the one
|
|
147
|
-
//
|
|
157
|
+
// (.md/.json/mcp/llms.txt) renders through the one function, so the dual-audience
|
|
158
|
+
// surface is identical to Workers.
|
|
148
159
|
const VERCEL_FUNCTION = "__june"; // → /__june, the catch-all SSR function
|
|
149
160
|
|
|
150
|
-
export function vercel(opts?: { regions?: string[] }): JuneAdapter {
|
|
161
|
+
export function vercel(opts?: { runtime?: "node" | "edge"; regions?: string[] }): JuneAdapter {
|
|
162
|
+
const isEdge = (opts?.runtime ?? "node") === "edge";
|
|
151
163
|
return {
|
|
152
164
|
name: "vercel",
|
|
153
|
-
capabilities: {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
capabilities: {
|
|
166
|
+
// Node on Fluid holds streamed/SSE connections; the edge variant keeps the
|
|
167
|
+
// conservative flag it shipped with.
|
|
168
|
+
runtime: isEdge ? "edge" : "node",
|
|
169
|
+
persistentConnections: !isEdge,
|
|
170
|
+
assets: "platform",
|
|
171
|
+
},
|
|
172
|
+
// The bundle is identical for both runtimes. NO "worker"/"browser": react-dom's
|
|
173
|
+
// ./server exports map "worker" → the BROWSER SSR build (server.browser.js),
|
|
174
|
+
// listed BEFORE "edge-light", so requesting "worker" pulls the wrong renderer.
|
|
175
|
+
// "edge-light" → server.edge.js, which uses Web Streams (renderToReadableStream)
|
|
176
|
+
// and runs on workerd AND Node alike. Mirrors workers()'s shape.
|
|
159
177
|
conditions: ["edge-light", "edge", "import", "default"],
|
|
160
178
|
|
|
161
179
|
validate({ plan }) {
|
|
@@ -170,13 +188,17 @@ export function vercel(opts?: { regions?: string[] }): JuneAdapter {
|
|
|
170
188
|
|
|
171
189
|
entry() {
|
|
172
190
|
// No withAssets: the CDN serves /_june/*; this function renders everything
|
|
173
|
-
// else. Resources arrive via process.env (Vercel injects env vars
|
|
174
|
-
//
|
|
191
|
+
// else. Resources arrive via process.env (Vercel injects env vars); there
|
|
192
|
+
// are no platform bindings. The Node runtime captures the `fetch` Web
|
|
193
|
+
// Standard export (an object with a fetch method); the Edge runtime takes a
|
|
194
|
+
// bare default function. Same pipeline call either way.
|
|
195
|
+
const env = `const __env = typeof process !== "undefined" && process.env ? process.env : {};\n`;
|
|
175
196
|
return {
|
|
176
197
|
imports: [],
|
|
177
198
|
wrap: (pipelineVar) =>
|
|
178
|
-
|
|
179
|
-
|
|
199
|
+
isEdge
|
|
200
|
+
? env + `export default (request) => ${pipelineVar}.fetch(request, __env);`
|
|
201
|
+
: env + `export default { fetch: (request) => ${pipelineVar}.fetch(request, __env) };`,
|
|
180
202
|
};
|
|
181
203
|
},
|
|
182
204
|
|
|
@@ -192,14 +214,28 @@ export function vercel(opts?: { regions?: string[] }): JuneAdapter {
|
|
|
192
214
|
// function ("referencing unsupported modules"). assets/ is handled below.
|
|
193
215
|
const jsFiles = (await readdir(outDir)).filter((f) => f.endsWith(".js"));
|
|
194
216
|
for (const f of jsFiles) await copyFile(join(outDir, f), join(fnDir, f));
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
217
|
+
|
|
218
|
+
const regions = opts?.regions ? { regions: opts.regions } : {};
|
|
219
|
+
const vcConfig = isEdge
|
|
220
|
+
? { runtime: "edge", entrypoint: "worker.js", ...regions }
|
|
221
|
+
: {
|
|
222
|
+
// A Vercel Function on Fluid compute. handler = the ESM bundle entry;
|
|
223
|
+
// launcherType Nodejs invokes its default `fetch` export.
|
|
224
|
+
// supportsResponseStreaming is REQUIRED or a streamed Response (our SSE
|
|
225
|
+
// live feed, streamed SSR) is buffered instead of flushed.
|
|
226
|
+
runtime: "nodejs22.x",
|
|
227
|
+
handler: "worker.js",
|
|
228
|
+
launcherType: "Nodejs",
|
|
229
|
+
supportsResponseStreaming: true,
|
|
230
|
+
...regions,
|
|
231
|
+
};
|
|
232
|
+
await writeFile(join(fnDir, ".vc-config.json"), JSON.stringify(vcConfig, null, 2) + "\n");
|
|
233
|
+
// The bundle is ESM (worker.js + ./chunk-*.js all use `export`/`import`).
|
|
234
|
+
// The Node runtime loads the .func as CommonJS by default, so mark the whole
|
|
235
|
+
// function dir as ESM. (The edge runtime always treats sources as ESM.)
|
|
236
|
+
if (!isEdge) {
|
|
237
|
+
await writeFile(join(fnDir, "package.json"), JSON.stringify({ type: "module" }, null, 2) + "\n");
|
|
238
|
+
}
|
|
203
239
|
|
|
204
240
|
// static: only the hashed framework assets under /_june/ (exact-path URLs)
|
|
205
241
|
if (hasAssets) {
|
package/src/d1.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface D1Database {
|
|
|
26
26
|
// already-bound D1Database.
|
|
27
27
|
export function d1(binding: D1Database): DbFactory {
|
|
28
28
|
const db: JuneDb = {
|
|
29
|
+
dialect: "sqlite", // D1 *is* sqlite — same SQL, same Juno compiler
|
|
29
30
|
async query<T>(sql: string, params: unknown[] = []) {
|
|
30
31
|
return (await binding.prepare(sql).bind(...params).all<T>()).results;
|
|
31
32
|
},
|
package/src/db.ts
CHANGED
|
@@ -16,6 +16,8 @@ import type { DbFactory } from "@junejs/core/resources";
|
|
|
16
16
|
import { host } from "./host";
|
|
17
17
|
|
|
18
18
|
export { d1, type D1Database } from "./d1";
|
|
19
|
+
export { postgres, pgJuneDb, type PgClientLike } from "./postgres-driver";
|
|
20
|
+
export { mysql, mysqlJuneDb, type MysqlConnLike } from "./mysql-driver";
|
|
19
21
|
|
|
20
22
|
// Local SQLite — the zero-config dev default. A persistent FILE, not :memory:,
|
|
21
23
|
// on purpose: `june dev` restarts the process on every save (the watch
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ export {
|
|
|
11
11
|
type AdapterEmitContext,
|
|
12
12
|
} from "./adapter";
|
|
13
13
|
export { host, type JuneHost, type JuneDb, type RunResult, type ServeHandle, type SpawnedModule } from "./host";
|
|
14
|
-
export { sqlite, d1, type D1Database } from "./db";
|
|
14
|
+
export { sqlite, d1, postgres, mysql, type D1Database } from "./db";
|
|
15
15
|
export { memoryKv, redisKv } from "./kv";
|
|
16
16
|
export { localBlob, r2, type R2Bucket } from "./blob";
|
|
17
17
|
export {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// MySQL `db` adapter — implements the @junejs/core JuneDb contract over `mysql2`.
|
|
2
|
+
// Tags itself `dialect: "mysql"` so Juno compiles backtick / `?` / ON DUPLICATE KEY
|
|
3
|
+
// SQL for it. `mysql2` is dynamically imported (a peer the app installs only when it
|
|
4
|
+
// declares `mysql()`). Test/dev grade: ONE connection per open(); pooling / env
|
|
5
|
+
// binding come later.
|
|
6
|
+
|
|
7
|
+
import type { DbFactory, JuneDb, RunResult } from "@junejs/core/resources";
|
|
8
|
+
|
|
9
|
+
// mysql2 returns [rows, fields]; a write yields a ResultSetHeader as the first element.
|
|
10
|
+
interface OkHeader {
|
|
11
|
+
affectedRows?: number;
|
|
12
|
+
insertId?: number;
|
|
13
|
+
}
|
|
14
|
+
// The slice of mysql2/promise's Connection we depend on — so mysqlJuneDb is fakeable.
|
|
15
|
+
export interface MysqlConnLike {
|
|
16
|
+
query(sql: string, params?: unknown[]): Promise<[unknown, unknown]>;
|
|
17
|
+
end(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Wrap a mysql2 connection as a JuneDb. Exported for offline unit tests.
|
|
21
|
+
export function mysqlJuneDb(conn: MysqlConnLike): JuneDb {
|
|
22
|
+
const self: JuneDb = {
|
|
23
|
+
dialect: "mysql",
|
|
24
|
+
async query<T>(sql: string, params: unknown[] = []) {
|
|
25
|
+
return (await conn.query(sql, params))[0] as T[];
|
|
26
|
+
},
|
|
27
|
+
async get<T>(sql: string, params: unknown[] = []) {
|
|
28
|
+
const rows = (await conn.query(sql, params))[0] as unknown[];
|
|
29
|
+
return (rows[0] ?? undefined) as T | undefined;
|
|
30
|
+
},
|
|
31
|
+
async run(sql: string, params: unknown[] = []): Promise<RunResult> {
|
|
32
|
+
const header = (await conn.query(sql, params))[0] as OkHeader;
|
|
33
|
+
return { changes: header.affectedRows ?? 0, lastInsertRowid: header.insertId ?? 0 };
|
|
34
|
+
},
|
|
35
|
+
async exec(sql: string) {
|
|
36
|
+
await conn.query(sql); // needs multipleStatements for a multi-statement script
|
|
37
|
+
},
|
|
38
|
+
async transaction<T>(fn: (tx: JuneDb) => Promise<T>) {
|
|
39
|
+
await conn.query("begin");
|
|
40
|
+
try {
|
|
41
|
+
const out = await fn(self);
|
|
42
|
+
await conn.query("commit");
|
|
43
|
+
return out;
|
|
44
|
+
} catch (e) {
|
|
45
|
+
await conn.query("rollback");
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
async close() {
|
|
50
|
+
await conn.end();
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
return self;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// `mysql({ url })` — multipleStatements is on so exec() can run a whole migration file.
|
|
57
|
+
export function mysql(opts: { url: string }): DbFactory {
|
|
58
|
+
return {
|
|
59
|
+
kind: "mysql",
|
|
60
|
+
open: async () => {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
|
+
const mod = (await import("mysql2/promise")) as any;
|
|
63
|
+
const create = (mod.default ?? mod).createConnection;
|
|
64
|
+
const conn = await create({ uri: opts.url, multipleStatements: true });
|
|
65
|
+
return mysqlJuneDb(conn);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Postgres `db` adapter — implements the @junejs/core JuneDb contract over
|
|
2
|
+
// node-postgres (`pg`). Tags itself `dialect: "postgres"` so Juno compiles `$n` /
|
|
3
|
+
// double-quoted SQL for it (see @junejs/juno's dialectFor). `pg` is dynamically
|
|
4
|
+
// imported, so it's a peer the app installs only when it declares `postgres()` — the
|
|
5
|
+
// host never hard-depends on it. Test/dev grade: ONE connection per open(); pooling /
|
|
6
|
+
// env binding / deploy adapters come in a later step.
|
|
7
|
+
|
|
8
|
+
import type { DbFactory, JuneDb, RunResult } from "@junejs/core/resources";
|
|
9
|
+
|
|
10
|
+
// The slice of `pg`'s Client we depend on — so pgJuneDb is unit-testable with a fake.
|
|
11
|
+
export interface PgClientLike {
|
|
12
|
+
query(sql: string, params?: unknown[]): Promise<{ rows: unknown[]; rowCount: number | null }>;
|
|
13
|
+
end(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Wrap a connected pg client as a JuneDb. Exported (not just the factory) so the
|
|
17
|
+
// mapping — rows, first-row, RunResult, transaction ordering — is testable offline.
|
|
18
|
+
export function pgJuneDb(client: PgClientLike): JuneDb {
|
|
19
|
+
const self: JuneDb = {
|
|
20
|
+
dialect: "postgres",
|
|
21
|
+
async query<T>(sql: string, params: unknown[] = []) {
|
|
22
|
+
return (await client.query(sql, params)).rows as T[];
|
|
23
|
+
},
|
|
24
|
+
async get<T>(sql: string, params: unknown[] = []) {
|
|
25
|
+
return ((await client.query(sql, params)).rows[0] ?? undefined) as T | undefined;
|
|
26
|
+
},
|
|
27
|
+
async run(sql: string, params: unknown[] = []): Promise<RunResult> {
|
|
28
|
+
const r = await client.query(sql, params);
|
|
29
|
+
// Postgres has no autoincrement rowid — callers use `RETURNING id`. Report the
|
|
30
|
+
// affected-row count; lastInsertRowid is 0 (not meaningful on PG).
|
|
31
|
+
return { changes: r.rowCount ?? 0, lastInsertRowid: 0 };
|
|
32
|
+
},
|
|
33
|
+
async exec(sql: string) {
|
|
34
|
+
await client.query(sql); // DDL / multi-statement via the simple-query protocol
|
|
35
|
+
},
|
|
36
|
+
async transaction<T>(fn: (tx: JuneDb) => Promise<T>) {
|
|
37
|
+
await client.query("begin");
|
|
38
|
+
try {
|
|
39
|
+
const out = await fn(self); // same connection
|
|
40
|
+
await client.query("commit");
|
|
41
|
+
return out;
|
|
42
|
+
} catch (e) {
|
|
43
|
+
await client.query("rollback");
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
async close() {
|
|
48
|
+
await client.end();
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
return self;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// `postgres({ url })` — declared in june.config.ts; opened by the host.
|
|
55
|
+
export function postgres(opts: { url: string }): DbFactory {
|
|
56
|
+
return {
|
|
57
|
+
kind: "postgres",
|
|
58
|
+
open: async () => {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
const mod = (await import("pg")) as any;
|
|
61
|
+
const Client = (mod.default ?? mod).Client;
|
|
62
|
+
const client = new Client({ connectionString: opts.url });
|
|
63
|
+
await client.connect();
|
|
64
|
+
return pgJuneDb(client);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|