create-nifra 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/agents.d.ts +22 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +259 -0
- package/dist/agents.js.map +1 -0
- package/dist/auth.d.ts +30 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +90 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +101 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +450 -0
- package/dist/cli.js.map +1 -0
- package/dist/db.d.ts +39 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +467 -0
- package/dist/db.js.map +1 -0
- package/package.json +50 -0
- package/template/README.md +24 -0
- package/template/gitignore +6 -0
- package/template/package.json +20 -0
- package/template/src/app.test.ts +8 -0
- package/template/src/app.ts +8 -0
- package/template/src/index.ts +5 -0
- package/template/tsconfig.json +16 -0
- package/template-isr/README.md +46 -0
- package/template-isr/backend.ts +12 -0
- package/template-isr/build.ts +23 -0
- package/template-isr/gitignore +7 -0
- package/template-isr/package.json +24 -0
- package/template-isr/routes/_404.tsx +10 -0
- package/template-isr/routes/_layout.tsx +18 -0
- package/template-isr/routes/index.tsx +34 -0
- package/template-isr/server.ts +56 -0
- package/template-isr/tsconfig.json +28 -0
- package/template-isr/worker.ts +52 -0
- package/template-isr/wrangler.toml +20 -0
- package/template-site/.dockerignore +14 -0
- package/template-site/Dockerfile +20 -0
- package/template-site/README.md +44 -0
- package/template-site/_worker.ts +17 -0
- package/template-site/backend.ts +12 -0
- package/template-site/build-bun.ts +27 -0
- package/template-site/build-deno.ts +25 -0
- package/template-site/build-node.ts +26 -0
- package/template-site/build-vercel.ts +45 -0
- package/template-site/build.ts +29 -0
- package/template-site/deno.json +7 -0
- package/template-site/framework.ts +6 -0
- package/template-site/gitignore +16 -0
- package/template-site/nifra.config.ts +9 -0
- package/template-site/package.json +37 -0
- package/template-site/routes/_404.tsx +20 -0
- package/template-site/routes/_layout.tsx +40 -0
- package/template-site/routes/index.tsx +53 -0
- package/template-site/server-bun.ts +35 -0
- package/template-site/server-deno.ts +38 -0
- package/template-site/server-node.ts +40 -0
- package/template-site/server-vercel.ts +17 -0
- package/template-site/tsconfig.json +37 -0
- package/template-site/wrangler.toml +4 -0
- package/template-site-preact/.dockerignore +14 -0
- package/template-site-preact/Dockerfile +20 -0
- package/template-site-preact/README.md +43 -0
- package/template-site-preact/_worker.ts +17 -0
- package/template-site-preact/backend.ts +12 -0
- package/template-site-preact/build-bun.ts +27 -0
- package/template-site-preact/build-deno.ts +25 -0
- package/template-site-preact/build-node.ts +26 -0
- package/template-site-preact/build-vercel.ts +45 -0
- package/template-site-preact/build.ts +29 -0
- package/template-site-preact/deno.json +7 -0
- package/template-site-preact/framework.ts +6 -0
- package/template-site-preact/gitignore +16 -0
- package/template-site-preact/nifra.config.ts +9 -0
- package/template-site-preact/package.json +34 -0
- package/template-site-preact/routes/_404.tsx +20 -0
- package/template-site-preact/routes/_layout.tsx +40 -0
- package/template-site-preact/routes/index.tsx +53 -0
- package/template-site-preact/server-bun.ts +35 -0
- package/template-site-preact/server-deno.ts +38 -0
- package/template-site-preact/server-node.ts +40 -0
- package/template-site-preact/server-vercel.ts +17 -0
- package/template-site-preact/tsconfig.json +38 -0
- package/template-site-preact/wrangler.toml +4 -0
- package/template-site-solid/.dockerignore +14 -0
- package/template-site-solid/Dockerfile +20 -0
- package/template-site-solid/README.md +43 -0
- package/template-site-solid/_worker.ts +17 -0
- package/template-site-solid/backend.ts +12 -0
- package/template-site-solid/build-bun.ts +33 -0
- package/template-site-solid/build-deno.ts +32 -0
- package/template-site-solid/build-node.ts +33 -0
- package/template-site-solid/build-vercel.ts +47 -0
- package/template-site-solid/build.ts +36 -0
- package/template-site-solid/deno.json +7 -0
- package/template-site-solid/framework.ts +6 -0
- package/template-site-solid/gitignore +16 -0
- package/template-site-solid/nifra.config.ts +14 -0
- package/template-site-solid/package.json +34 -0
- package/template-site-solid/routes/_404.tsx +20 -0
- package/template-site-solid/routes/_layout.tsx +39 -0
- package/template-site-solid/routes/index.tsx +53 -0
- package/template-site-solid/server-bun.ts +35 -0
- package/template-site-solid/server-deno.ts +38 -0
- package/template-site-solid/server-node.ts +40 -0
- package/template-site-solid/server-vercel.ts +17 -0
- package/template-site-solid/tsconfig.json +38 -0
- package/template-site-solid/wrangler.toml +4 -0
- package/template-site-svelte/.dockerignore +14 -0
- package/template-site-svelte/Dockerfile +20 -0
- package/template-site-svelte/README.md +45 -0
- package/template-site-svelte/_worker.ts +17 -0
- package/template-site-svelte/backend.ts +12 -0
- package/template-site-svelte/build-bun.ts +32 -0
- package/template-site-svelte/build-deno.ts +31 -0
- package/template-site-svelte/build-node.ts +32 -0
- package/template-site-svelte/build-vercel.ts +46 -0
- package/template-site-svelte/build.ts +35 -0
- package/template-site-svelte/deno.json +7 -0
- package/template-site-svelte/framework.ts +6 -0
- package/template-site-svelte/gitignore +16 -0
- package/template-site-svelte/nifra.config.ts +13 -0
- package/template-site-svelte/package.json +34 -0
- package/template-site-svelte/routes/_404.svelte +16 -0
- package/template-site-svelte/routes/_layout.svelte +35 -0
- package/template-site-svelte/routes/index.svelte +47 -0
- package/template-site-svelte/server-bun.ts +35 -0
- package/template-site-svelte/server-deno.ts +38 -0
- package/template-site-svelte/server-node.ts +40 -0
- package/template-site-svelte/server-vercel.ts +17 -0
- package/template-site-svelte/tsconfig.json +37 -0
- package/template-site-svelte/wrangler.toml +4 -0
- package/template-site-vue/.dockerignore +14 -0
- package/template-site-vue/Dockerfile +20 -0
- package/template-site-vue/README.md +45 -0
- package/template-site-vue/_worker.ts +17 -0
- package/template-site-vue/backend.ts +12 -0
- package/template-site-vue/build-bun.ts +37 -0
- package/template-site-vue/build-deno.ts +36 -0
- package/template-site-vue/build-node.ts +37 -0
- package/template-site-vue/build-vercel.ts +54 -0
- package/template-site-vue/build.ts +40 -0
- package/template-site-vue/deno.json +7 -0
- package/template-site-vue/framework.ts +6 -0
- package/template-site-vue/gitignore +16 -0
- package/template-site-vue/nifra.config.ts +19 -0
- package/template-site-vue/package.json +35 -0
- package/template-site-vue/routes/_404.vue +18 -0
- package/template-site-vue/routes/_layout.vue +40 -0
- package/template-site-vue/routes/index.vue +56 -0
- package/template-site-vue/server-bun.ts +35 -0
- package/template-site-vue/server-deno.ts +38 -0
- package/template-site-vue/server-node.ts +40 -0
- package/template-site-vue/server-vercel.ts +17 -0
- package/template-site-vue/tsconfig.json +37 -0
- package/template-site-vue/wrangler.toml +4 -0
package/dist/db.js
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--db <preset>` — wire a Drizzle data layer into a freshly scaffolded app, so a human (or a coding
|
|
3
|
+
* agent) starts from a correct, production-grade DB setup instead of inventing one. nifra ships NO DB
|
|
4
|
+
* abstraction (that belongs to Drizzle/libSQL, not a web framework) — this just bundles the existing
|
|
5
|
+
* tool, the Redwood-style "batteries available" move: a starter schema, a typed client, a migration
|
|
6
|
+
* config, scripts, and env, all rip-out-able.
|
|
7
|
+
*
|
|
8
|
+
* Three presets, by runtime fit:
|
|
9
|
+
* - drizzle-libsql — SQLite that runs EVERYWHERE incl. the edge (local file, or Turso). The default
|
|
10
|
+
* cross-runtime answer.
|
|
11
|
+
* - drizzle-postgres — Postgres (postgres.js) on Bun/Node/Deno.
|
|
12
|
+
* - drizzle-sqlite — Bun's built-in `bun:sqlite` (sync, local file). Bun-only.
|
|
13
|
+
*
|
|
14
|
+
* The starter `notes` table follows the production-grade DB defaults for its dialect; what a dialect
|
|
15
|
+
* can't express inline (CHECK constraints, RLS, uuidv7) is called out in a comment, per those rules.
|
|
16
|
+
*/
|
|
17
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
18
|
+
import { dirname, join } from "node:path";
|
|
19
|
+
export const DB_CHOICES = [
|
|
20
|
+
"drizzle-libsql",
|
|
21
|
+
"drizzle-postgres",
|
|
22
|
+
"drizzle-sqlite",
|
|
23
|
+
"prisma-postgres",
|
|
24
|
+
"prisma-sqlite",
|
|
25
|
+
"kysely-postgres",
|
|
26
|
+
];
|
|
27
|
+
const DRIZZLE_ORM = "^0.38.3";
|
|
28
|
+
const DRIZZLE_KIT = "^0.30.4";
|
|
29
|
+
const SCRIPTS = {
|
|
30
|
+
"db:generate": "drizzle-kit generate",
|
|
31
|
+
"db:migrate": "drizzle-kit migrate",
|
|
32
|
+
"db:studio": "drizzle-kit studio",
|
|
33
|
+
};
|
|
34
|
+
// --- starter schemas (one production-grade `notes` table per dialect) -------------------------------
|
|
35
|
+
const PG_SCHEMA = `import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
|
|
36
|
+
|
|
37
|
+
// Production-grade starter table. Postgres: UUID PK, TIMESTAMPTZ stamps, NOT NULL by default,
|
|
38
|
+
// soft-delete via deleted_at. Deferred to a migration (Drizzle can't express these inline; add them in
|
|
39
|
+
// the generated SQL): CHECK constraints (e.g. char_length(title) BETWEEN 1 AND 200), RLS for tenancy,
|
|
40
|
+
// and gen_uuidv7() for insert locality on hot tables (defaultRandom() = gen_random_uuid() for now).
|
|
41
|
+
export const notes = pgTable("notes", {
|
|
42
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
43
|
+
title: text("title").notNull(),
|
|
44
|
+
body: text("body").notNull().default(""),
|
|
45
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
46
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
47
|
+
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
export type Note = typeof notes.$inferSelect
|
|
51
|
+
export type NewNote = typeof notes.$inferInsert
|
|
52
|
+
`;
|
|
53
|
+
// SQLite has no UUID/TIMESTAMPTZ/RLS — so: text id (app-generated UUID), millisecond-epoch timestamps
|
|
54
|
+
// (timestamp_ms mode → JS Date), NOT NULL by default, soft-delete via deleted_at. Shared by the
|
|
55
|
+
// bun:sqlite and libSQL presets (same dialect). CHECK constraints are deferred to the migration.
|
|
56
|
+
const SQLITE_SCHEMA = `import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
|
|
57
|
+
|
|
58
|
+
// Production-grade starter table (SQLite dialect — see the comment in your DB defaults for what the
|
|
59
|
+
// dialect can't enforce inline: CHECK constraints go in the generated migration).
|
|
60
|
+
export const notes = sqliteTable("notes", {
|
|
61
|
+
id: text("id")
|
|
62
|
+
.primaryKey()
|
|
63
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
64
|
+
title: text("title").notNull(),
|
|
65
|
+
body: text("body").notNull().default(""),
|
|
66
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
67
|
+
.notNull()
|
|
68
|
+
.$defaultFn(() => new Date()),
|
|
69
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
70
|
+
.notNull()
|
|
71
|
+
.$defaultFn(() => new Date()),
|
|
72
|
+
deletedAt: integer("deleted_at", { mode: "timestamp_ms" }),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
export type Note = typeof notes.$inferSelect
|
|
76
|
+
export type NewNote = typeof notes.$inferInsert
|
|
77
|
+
`;
|
|
78
|
+
// --- typed clients (export \`db\` + re-export the schema) --------------------------------------------
|
|
79
|
+
// Each prints the same usage header so an agent sees how to wire it (decorate) and query it.
|
|
80
|
+
const WIRE_DOC = `// Wire it into your backend ONCE, then read \`c.db\` in handlers:
|
|
81
|
+
// import { server } from "@nifrajs/core"
|
|
82
|
+
// import { db, notes } from "./db"
|
|
83
|
+
// export const app = server().decorate("db", db)
|
|
84
|
+
// .get("/notes", async (c) => c.db.select().from(notes))
|
|
85
|
+
// Never top-level-import this into a routes/ page file (server-only) — reach it via c.db / ctx.api.`;
|
|
86
|
+
const PG_CLIENT = `import { drizzle } from "drizzle-orm/postgres-js"
|
|
87
|
+
import postgres from "postgres"
|
|
88
|
+
import * as schema from "./schema.ts"
|
|
89
|
+
|
|
90
|
+
${WIRE_DOC}
|
|
91
|
+
|
|
92
|
+
const url = process.env.DATABASE_URL
|
|
93
|
+
if (url === undefined || url === "") {
|
|
94
|
+
throw new Error("DATABASE_URL is not set — copy .env.example to .env and fill it in.")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// One pooled client per process (per instance in a multi-instance deploy — fine for Postgres).
|
|
98
|
+
export const db = drizzle(postgres(url), { schema })
|
|
99
|
+
export * from "./schema.ts"
|
|
100
|
+
`;
|
|
101
|
+
const SQLITE_CLIENT = `import { Database } from "bun:sqlite"
|
|
102
|
+
import { drizzle } from "drizzle-orm/bun-sqlite"
|
|
103
|
+
import * as schema from "./schema.ts"
|
|
104
|
+
|
|
105
|
+
${WIRE_DOC}
|
|
106
|
+
|
|
107
|
+
// Bun's built-in SQLite — sync + fast, local file. (Bun-only; for Node/Deno/edge use the libsql preset.)
|
|
108
|
+
export const db = drizzle(new Database(process.env.DATABASE_URL ?? "local.db"), { schema })
|
|
109
|
+
export * from "./schema.ts"
|
|
110
|
+
`;
|
|
111
|
+
const LIBSQL_CLIENT = `import { createClient } from "@libsql/client"
|
|
112
|
+
import { drizzle } from "drizzle-orm/libsql"
|
|
113
|
+
import * as schema from "./schema.ts"
|
|
114
|
+
|
|
115
|
+
${WIRE_DOC}
|
|
116
|
+
|
|
117
|
+
// libSQL runs everywhere — a local file (file:local.db), a remote Turso URL (libsql://…) with an auth
|
|
118
|
+
// token, or an embedded replica. The one SQLite client that also works on the edge (Cloudflare Workers).
|
|
119
|
+
const token = process.env.DATABASE_AUTH_TOKEN
|
|
120
|
+
export const db = drizzle(
|
|
121
|
+
createClient({
|
|
122
|
+
url: process.env.DATABASE_URL ?? "file:local.db",
|
|
123
|
+
...(token !== undefined && token !== "" ? { authToken: token } : {}),
|
|
124
|
+
}),
|
|
125
|
+
{ schema },
|
|
126
|
+
)
|
|
127
|
+
export * from "./schema.ts"
|
|
128
|
+
`;
|
|
129
|
+
// --- drizzle-kit config (per dialect) ---------------------------------------------------------------
|
|
130
|
+
const PG_CONFIG = `import { defineConfig } from "drizzle-kit"
|
|
131
|
+
|
|
132
|
+
export default defineConfig({
|
|
133
|
+
schema: "./db/schema.ts",
|
|
134
|
+
out: "./db/migrations",
|
|
135
|
+
dialect: "postgresql",
|
|
136
|
+
// biome-ignore lint/style/noNonNullAssertion: drizzle-kit reads this from .env (Bun auto-loads it).
|
|
137
|
+
dbCredentials: { url: process.env.DATABASE_URL! },
|
|
138
|
+
})
|
|
139
|
+
`;
|
|
140
|
+
const SQLITE_CONFIG = `import { defineConfig } from "drizzle-kit"
|
|
141
|
+
|
|
142
|
+
export default defineConfig({
|
|
143
|
+
schema: "./db/schema.ts",
|
|
144
|
+
out: "./db/migrations",
|
|
145
|
+
dialect: "sqlite",
|
|
146
|
+
dbCredentials: { url: process.env.DATABASE_URL ?? "local.db" },
|
|
147
|
+
})
|
|
148
|
+
`;
|
|
149
|
+
const LIBSQL_CONFIG = `import { defineConfig } from "drizzle-kit"
|
|
150
|
+
|
|
151
|
+
export default defineConfig({
|
|
152
|
+
schema: "./db/schema.ts",
|
|
153
|
+
out: "./db/migrations",
|
|
154
|
+
dialect: "turso",
|
|
155
|
+
dbCredentials: {
|
|
156
|
+
url: process.env.DATABASE_URL ?? "file:local.db",
|
|
157
|
+
authToken: process.env.DATABASE_AUTH_TOKEN,
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
`;
|
|
161
|
+
// --- .env examples ----------------------------------------------------------------------------------
|
|
162
|
+
const PG_ENV = `# Postgres connection string (postgres.js). Never commit the real .env.
|
|
163
|
+
DATABASE_URL="postgres://user:password@localhost:5432/app"
|
|
164
|
+
`;
|
|
165
|
+
const SQLITE_ENV = `# Path to the local SQLite file (Bun's bun:sqlite). Never commit the real .env.
|
|
166
|
+
DATABASE_URL="local.db"
|
|
167
|
+
`;
|
|
168
|
+
const LIBSQL_ENV = `# Local file by default; point at Turso for a hosted/edge DB. Never commit the real .env.
|
|
169
|
+
DATABASE_URL="file:local.db"
|
|
170
|
+
# For Turso (or any remote libSQL):
|
|
171
|
+
# DATABASE_URL="libsql://your-db.turso.io"
|
|
172
|
+
# DATABASE_AUTH_TOKEN="your-token"
|
|
173
|
+
`;
|
|
174
|
+
// --- Prisma presets ---------------------------------------------------------------------------------
|
|
175
|
+
// Prisma owns its schema DSL (prisma/schema.prisma), client codegen, and migration engine — so the
|
|
176
|
+
// scaffold is schema + a singleton client + the prisma scripts (no drizzle-kit equivalent needed).
|
|
177
|
+
const PRISMA_CLIENT_VERSION = "^6.5.0";
|
|
178
|
+
const PRISMA_SCRIPTS = {
|
|
179
|
+
"db:generate": "prisma generate",
|
|
180
|
+
"db:migrate": "prisma migrate dev",
|
|
181
|
+
"db:studio": "prisma studio",
|
|
182
|
+
};
|
|
183
|
+
const PRISMA_PG_SCHEMA = `datasource db {
|
|
184
|
+
provider = "postgresql"
|
|
185
|
+
url = env("DATABASE_URL")
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
generator client {
|
|
189
|
+
provider = "prisma-client-js"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Production-grade starter model. Postgres: UUID PK, TIMESTAMPTZ stamps (@db.Timestamptz), NOT NULL by
|
|
193
|
+
// default, soft-delete via deletedAt. Add in a migration what the DSL can't express inline: CHECK
|
|
194
|
+
// constraints (e.g. length caps on title), RLS for tenancy, and gen_uuidv7() for insert locality on hot
|
|
195
|
+
// tables (uuid() = gen_random_uuid() for now).
|
|
196
|
+
model Note {
|
|
197
|
+
id String @id @default(uuid()) @db.Uuid
|
|
198
|
+
title String
|
|
199
|
+
body String @default("")
|
|
200
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
|
201
|
+
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
|
|
202
|
+
deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6)
|
|
203
|
+
|
|
204
|
+
@@map("notes")
|
|
205
|
+
}
|
|
206
|
+
`;
|
|
207
|
+
const PRISMA_SQLITE_SCHEMA = `datasource db {
|
|
208
|
+
provider = "sqlite"
|
|
209
|
+
url = env("DATABASE_URL")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
generator client {
|
|
213
|
+
provider = "prisma-client-js"
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Production-grade starter model (SQLite dialect — no native UUID/TIMESTAMPTZ/RLS; Prisma stores the
|
|
217
|
+
// String id + DateTime stamps for you). NOT NULL by default, soft-delete via deletedAt. CHECK
|
|
218
|
+
// constraints are deferred to the generated migration SQL.
|
|
219
|
+
model Note {
|
|
220
|
+
id String @id @default(uuid())
|
|
221
|
+
title String
|
|
222
|
+
body String @default("")
|
|
223
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
224
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
225
|
+
deletedAt DateTime? @map("deleted_at")
|
|
226
|
+
|
|
227
|
+
@@map("notes")
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
const PRISMA_CLIENT = `import { PrismaClient } from "@prisma/client"
|
|
231
|
+
|
|
232
|
+
// Wire it into your backend ONCE, then read \`c.db\` in handlers:
|
|
233
|
+
// import { server } from "@nifrajs/core"
|
|
234
|
+
// import { db } from "./db"
|
|
235
|
+
// export const app = server().decorate("db", db)
|
|
236
|
+
// .get("/notes", async (c) => c.db.note.findMany({ where: { deletedAt: null } }))
|
|
237
|
+
// Never top-level-import this into a routes/ page file (server-only) — reach it via c.db / ctx.api.
|
|
238
|
+
|
|
239
|
+
// One PrismaClient per process. The globalThis guard stops dev hot-reload from opening a new connection
|
|
240
|
+
// pool on every reload (which exhausts the DB); production gets a single fresh instance.
|
|
241
|
+
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient }
|
|
242
|
+
export const db = globalForPrisma.prisma ?? new PrismaClient()
|
|
243
|
+
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db
|
|
244
|
+
`;
|
|
245
|
+
const PRISMA_PG_ENV = `# Postgres connection string (Prisma). Never commit the real .env.
|
|
246
|
+
DATABASE_URL="postgresql://user:password@localhost:5432/app"
|
|
247
|
+
`;
|
|
248
|
+
const PRISMA_SQLITE_ENV = `# Local SQLite file (Prisma). Never commit the real .env.
|
|
249
|
+
DATABASE_URL="file:./local.db"
|
|
250
|
+
`;
|
|
251
|
+
// --- Kysely preset ----------------------------------------------------------------------------------
|
|
252
|
+
// Kysely is a typed query builder, not an ORM: you describe the DB shape (db/schema.ts) and own the
|
|
253
|
+
// migrations. It ships no CLI, so the scaffold includes a Migrator runner (db/migrate.ts) + a starter
|
|
254
|
+
// migration. Postgres via the official PostgresDialect + node-postgres.
|
|
255
|
+
const KYSELY_VERSION = "^0.27.4";
|
|
256
|
+
const KYSELY_SCRIPTS = {
|
|
257
|
+
"db:migrate": "bun run db/migrate.ts",
|
|
258
|
+
};
|
|
259
|
+
const KYSELY_PG_SCHEMA = `import type { ColumnType, Generated } from "kysely"
|
|
260
|
+
|
|
261
|
+
// Kysely is types-only: describe the DB shape and keep it in sync with db/migrations (or generate it
|
|
262
|
+
// with \`kysely-codegen\`). There is no runtime schema object.
|
|
263
|
+
export interface NotesTable {
|
|
264
|
+
id: Generated<string> // uuid PK, DB-defaulted (gen_random_uuid())
|
|
265
|
+
title: string
|
|
266
|
+
body: Generated<string> // defaults to ''
|
|
267
|
+
// DB-managed stamps: never written by the app (never on insert/update), read back as Date.
|
|
268
|
+
created_at: ColumnType<Date, never, never>
|
|
269
|
+
updated_at: ColumnType<Date, never, Date>
|
|
270
|
+
deleted_at: Date | null // soft delete
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface DB {
|
|
274
|
+
notes: NotesTable
|
|
275
|
+
}
|
|
276
|
+
`;
|
|
277
|
+
const KYSELY_PG_CLIENT = `import { Kysely, PostgresDialect } from "kysely"
|
|
278
|
+
import { Pool } from "pg"
|
|
279
|
+
import type { DB } from "./schema.ts"
|
|
280
|
+
|
|
281
|
+
// Wire it into your backend ONCE, then read \`c.db\` in handlers:
|
|
282
|
+
// import { server } from "@nifrajs/core"
|
|
283
|
+
// import { db } from "./db"
|
|
284
|
+
// export const app = server().decorate("db", db)
|
|
285
|
+
// .get("/notes", async (c) =>
|
|
286
|
+
// c.db.selectFrom("notes").selectAll().where("deleted_at", "is", null).execute())
|
|
287
|
+
// Never top-level-import this into a routes/ page file (server-only) — reach it via c.db / ctx.api.
|
|
288
|
+
|
|
289
|
+
const url = process.env.DATABASE_URL
|
|
290
|
+
if (url === undefined || url === "") {
|
|
291
|
+
throw new Error("DATABASE_URL is not set — copy .env.example to .env and fill it in.")
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// One pooled client per process (per instance in a multi-instance deploy — fine for Postgres).
|
|
295
|
+
export const db = new Kysely<DB>({
|
|
296
|
+
dialect: new PostgresDialect({ pool: new Pool({ connectionString: url }) }),
|
|
297
|
+
})
|
|
298
|
+
export type { DB } from "./schema.ts"
|
|
299
|
+
`;
|
|
300
|
+
const KYSELY_MIGRATION = `import { type Kysely, sql } from "kysely"
|
|
301
|
+
|
|
302
|
+
// Production-grade starter table. Postgres: UUID PK (gen_random_uuid — swap to gen_uuidv7() for insert
|
|
303
|
+
// locality on hot tables once available), TIMESTAMPTZ stamps, NOT NULL by default, soft-delete via
|
|
304
|
+
// deleted_at. Add CHECK constraints (e.g. length caps) and RLS here per your DB defaults.
|
|
305
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
306
|
+
await db.schema
|
|
307
|
+
.createTable("notes")
|
|
308
|
+
.addColumn("id", "uuid", (c) => c.primaryKey().defaultTo(sql\`gen_random_uuid()\`))
|
|
309
|
+
.addColumn("title", "text", (c) => c.notNull())
|
|
310
|
+
.addColumn("body", "text", (c) => c.notNull().defaultTo(""))
|
|
311
|
+
.addColumn("created_at", sql\`timestamptz\`, (c) => c.notNull().defaultTo(sql\`now()\`))
|
|
312
|
+
.addColumn("updated_at", sql\`timestamptz\`, (c) => c.notNull().defaultTo(sql\`now()\`))
|
|
313
|
+
.addColumn("deleted_at", sql\`timestamptz\`)
|
|
314
|
+
.execute()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
318
|
+
await db.schema.dropTable("notes").execute()
|
|
319
|
+
}
|
|
320
|
+
`;
|
|
321
|
+
const KYSELY_MIGRATE_RUNNER = `import { promises as fs } from "node:fs"
|
|
322
|
+
import * as path from "node:path"
|
|
323
|
+
import { FileMigrationProvider, Migrator } from "kysely"
|
|
324
|
+
import { db } from "./index.ts"
|
|
325
|
+
|
|
326
|
+
// Kysely ships no migration CLI, so this is the runner: it applies every pending migration in
|
|
327
|
+
// db/migrations (in filename order, each in its own transaction). Run with \`bun run db:migrate\`.
|
|
328
|
+
const migrator = new Migrator({
|
|
329
|
+
db,
|
|
330
|
+
provider: new FileMigrationProvider({
|
|
331
|
+
fs,
|
|
332
|
+
path,
|
|
333
|
+
migrationFolder: path.join(import.meta.dir, "migrations"),
|
|
334
|
+
}),
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
const { error, results } = await migrator.migrateToLatest()
|
|
338
|
+
for (const r of results ?? []) {
|
|
339
|
+
console.log(r.status === "Success" ? \`✓ \${r.migrationName}\` : \`✗ \${r.migrationName}\`)
|
|
340
|
+
}
|
|
341
|
+
await db.destroy()
|
|
342
|
+
if (error !== undefined) {
|
|
343
|
+
console.error("migration failed:", error)
|
|
344
|
+
process.exit(1)
|
|
345
|
+
}
|
|
346
|
+
`;
|
|
347
|
+
export const DB_PRESETS = {
|
|
348
|
+
"drizzle-libsql": {
|
|
349
|
+
label: "Drizzle + libSQL",
|
|
350
|
+
orm: "drizzle",
|
|
351
|
+
dialect: "sqlite",
|
|
352
|
+
note: "libSQL runs on every runtime incl. the edge (local file or Turso).",
|
|
353
|
+
deps: { "drizzle-orm": DRIZZLE_ORM, "@libsql/client": "^0.14.0" },
|
|
354
|
+
devDeps: { "drizzle-kit": DRIZZLE_KIT },
|
|
355
|
+
scripts: SCRIPTS,
|
|
356
|
+
files: {
|
|
357
|
+
"db/schema.ts": SQLITE_SCHEMA,
|
|
358
|
+
"db/index.ts": LIBSQL_CLIENT,
|
|
359
|
+
"drizzle.config.ts": LIBSQL_CONFIG,
|
|
360
|
+
".env.example": LIBSQL_ENV,
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
"drizzle-postgres": {
|
|
364
|
+
label: "Drizzle + Postgres",
|
|
365
|
+
orm: "drizzle",
|
|
366
|
+
dialect: "postgres",
|
|
367
|
+
note: "Postgres (postgres.js) on Bun/Node/Deno — not the edge.",
|
|
368
|
+
deps: { "drizzle-orm": DRIZZLE_ORM, postgres: "^3.4.5" },
|
|
369
|
+
devDeps: { "drizzle-kit": DRIZZLE_KIT },
|
|
370
|
+
scripts: SCRIPTS,
|
|
371
|
+
files: {
|
|
372
|
+
"db/schema.ts": PG_SCHEMA,
|
|
373
|
+
"db/index.ts": PG_CLIENT,
|
|
374
|
+
"drizzle.config.ts": PG_CONFIG,
|
|
375
|
+
".env.example": PG_ENV,
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
"drizzle-sqlite": {
|
|
379
|
+
label: "Drizzle + SQLite (bun:sqlite)",
|
|
380
|
+
orm: "drizzle",
|
|
381
|
+
dialect: "sqlite",
|
|
382
|
+
note: "Bun's built-in SQLite — local file, Bun-only.",
|
|
383
|
+
deps: { "drizzle-orm": DRIZZLE_ORM },
|
|
384
|
+
devDeps: { "drizzle-kit": DRIZZLE_KIT },
|
|
385
|
+
scripts: SCRIPTS,
|
|
386
|
+
files: {
|
|
387
|
+
"db/schema.ts": SQLITE_SCHEMA,
|
|
388
|
+
"db/index.ts": SQLITE_CLIENT,
|
|
389
|
+
"drizzle.config.ts": SQLITE_CONFIG,
|
|
390
|
+
".env.example": SQLITE_ENV,
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
"prisma-postgres": {
|
|
394
|
+
label: "Prisma + Postgres",
|
|
395
|
+
orm: "prisma",
|
|
396
|
+
dialect: "postgres",
|
|
397
|
+
note: "Prisma ORM on Postgres (Bun/Node/Deno) — own schema DSL, migrate engine, and Studio.",
|
|
398
|
+
deps: { "@prisma/client": PRISMA_CLIENT_VERSION },
|
|
399
|
+
devDeps: { prisma: PRISMA_CLIENT_VERSION },
|
|
400
|
+
scripts: PRISMA_SCRIPTS,
|
|
401
|
+
files: {
|
|
402
|
+
"prisma/schema.prisma": PRISMA_PG_SCHEMA,
|
|
403
|
+
"db/index.ts": PRISMA_CLIENT,
|
|
404
|
+
".env.example": PRISMA_PG_ENV,
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
"prisma-sqlite": {
|
|
408
|
+
label: "Prisma + SQLite",
|
|
409
|
+
orm: "prisma",
|
|
410
|
+
dialect: "sqlite",
|
|
411
|
+
note: "Prisma ORM on a local SQLite file — fastest start; migrate to Postgres later by swapping the datasource.",
|
|
412
|
+
deps: { "@prisma/client": PRISMA_CLIENT_VERSION },
|
|
413
|
+
devDeps: { prisma: PRISMA_CLIENT_VERSION },
|
|
414
|
+
scripts: PRISMA_SCRIPTS,
|
|
415
|
+
files: {
|
|
416
|
+
"prisma/schema.prisma": PRISMA_SQLITE_SCHEMA,
|
|
417
|
+
"db/index.ts": PRISMA_CLIENT,
|
|
418
|
+
".env.example": PRISMA_SQLITE_ENV,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
"kysely-postgres": {
|
|
422
|
+
label: "Kysely + Postgres",
|
|
423
|
+
orm: "kysely",
|
|
424
|
+
dialect: "postgres",
|
|
425
|
+
note: "Kysely typed query builder on Postgres (node-postgres) — you own the schema types + migrations.",
|
|
426
|
+
deps: { kysely: KYSELY_VERSION, pg: "^8.13.1" },
|
|
427
|
+
devDeps: { "@types/pg": "^8.11.10" },
|
|
428
|
+
scripts: KYSELY_SCRIPTS,
|
|
429
|
+
files: {
|
|
430
|
+
"db/schema.ts": KYSELY_PG_SCHEMA,
|
|
431
|
+
"db/index.ts": KYSELY_PG_CLIENT,
|
|
432
|
+
"db/migrate.ts": KYSELY_MIGRATE_RUNNER,
|
|
433
|
+
"db/migrations/0001_create_notes.ts": KYSELY_MIGRATION,
|
|
434
|
+
".env.example": PG_ENV,
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
};
|
|
438
|
+
const GITIGNORE_BLOCK = `
|
|
439
|
+
# database (added by --db)
|
|
440
|
+
.env
|
|
441
|
+
local.db
|
|
442
|
+
*.db-shm
|
|
443
|
+
*.db-wal
|
|
444
|
+
db/migrations/meta
|
|
445
|
+
`;
|
|
446
|
+
/** Write a DB preset's files into `target` and ensure local DB artifacts + .env are gitignored. */
|
|
447
|
+
export async function writeDbFiles(target, choice) {
|
|
448
|
+
const preset = DB_PRESETS[choice];
|
|
449
|
+
for (const [rel, content] of Object.entries(preset.files)) {
|
|
450
|
+
const path = join(target, rel);
|
|
451
|
+
await mkdir(dirname(path), { recursive: true });
|
|
452
|
+
await writeFile(path, content);
|
|
453
|
+
}
|
|
454
|
+
// Append DB ignores once (the template's .gitignore already exists post-rename; never clobber it).
|
|
455
|
+
const gitignorePath = join(target, ".gitignore");
|
|
456
|
+
let current = "";
|
|
457
|
+
try {
|
|
458
|
+
current = await readFile(gitignorePath, "utf8");
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
// No .gitignore in this template — create one with just the DB block.
|
|
462
|
+
}
|
|
463
|
+
if (!current.includes("local.db")) {
|
|
464
|
+
await writeFile(gitignorePath, `${current}${GITIGNORE_BLOCK}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
//# sourceMappingURL=db.js.map
|
package/dist/db.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-nifra",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Scaffold a new nifra app — `bun create nifra <dir>`.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bun",
|
|
7
|
+
"typescript",
|
|
8
|
+
"nifra",
|
|
9
|
+
"web-framework",
|
|
10
|
+
"full-stack",
|
|
11
|
+
"edge",
|
|
12
|
+
"ssr",
|
|
13
|
+
"scaffold",
|
|
14
|
+
"create",
|
|
15
|
+
"starter",
|
|
16
|
+
"template",
|
|
17
|
+
"cli"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"bin": {
|
|
21
|
+
"create-nifra": "./dist/cli.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"template",
|
|
26
|
+
"template-site",
|
|
27
|
+
"template-site-vue",
|
|
28
|
+
"template-site-preact",
|
|
29
|
+
"template-site-solid",
|
|
30
|
+
"template-site-svelte",
|
|
31
|
+
"template-isr"
|
|
32
|
+
],
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -p tsconfig.build.json && bun run ../../scripts/fix-dts.ts dist"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/nifrajs/nifra.git",
|
|
44
|
+
"directory": "packages/create-nifra"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/nifrajs/nifra/issues"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/nifrajs/nifra#readme"
|
|
50
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# nifra-app
|
|
2
|
+
|
|
3
|
+
A starter [nifra](https://github.com/nifra) app, scaffolded with `create-nifra`.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bun install
|
|
7
|
+
bun run dev # watch + serve on http://localhost:3000
|
|
8
|
+
bun test # run the tests
|
|
9
|
+
bun run typecheck # type-check
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- `src/app.ts` — your routes (exported without `listen`, so tests drive it via `app.fetch`).
|
|
13
|
+
- `src/index.ts` — boots the server.
|
|
14
|
+
- `src/app.test.ts` — an example test.
|
|
15
|
+
|
|
16
|
+
Add a typed client from `typeof app`:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { client } from "@nifrajs/client"
|
|
20
|
+
import type { App } from "./src/app.ts"
|
|
21
|
+
|
|
22
|
+
const api = client<App>("http://localhost:3000")
|
|
23
|
+
const { data } = await api.users({ id: "42" }).get()
|
|
24
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nifra-app",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "bun --watch run src/index.ts",
|
|
8
|
+
"start": "bun run src/index.ts",
|
|
9
|
+
"test": "bun test",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@nifrajs/client": "^0.1.0",
|
|
14
|
+
"@nifrajs/core": "^0.1.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/bun": "latest",
|
|
18
|
+
"typescript": "^6.0.3"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { app } from "./app.ts"
|
|
3
|
+
|
|
4
|
+
test("GET /users/:id echoes the id", async () => {
|
|
5
|
+
const res = await app.fetch(new Request("http://localhost/users/42"))
|
|
6
|
+
expect(res.status).toBe(200)
|
|
7
|
+
expect(await res.json()).toEqual({ id: "42" })
|
|
8
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { server } from "@nifrajs/core"
|
|
2
|
+
|
|
3
|
+
/** Your app. Exported (without `listen`) so tests can drive it via `app.fetch`. */
|
|
4
|
+
export const app = server()
|
|
5
|
+
.get("/", () => ({ hello: "nifra" }))
|
|
6
|
+
.get("/users/:id", (c) => ({ id: c.params.id }))
|
|
7
|
+
|
|
8
|
+
export type App = typeof app
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ESNext"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noUncheckedIndexedAccess": true,
|
|
9
|
+
"verbatimModuleSyntax": true,
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"types": ["bun"],
|
|
13
|
+
"skipLibCheck": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# nifra + ISR
|
|
2
|
+
|
|
3
|
+
A nifra + React app with **Incremental Static Regeneration** — pages are cached and served
|
|
4
|
+
stale-while-revalidate, backed by **Workers KV** in production (so the cache and on-demand purges hold
|
|
5
|
+
across instances) and an in-memory store for local dev.
|
|
6
|
+
|
|
7
|
+
## Develop
|
|
8
|
+
|
|
9
|
+
On real workerd with a local KV (no Cloudflare account needed):
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bun install
|
|
13
|
+
bun run build # buildClient → public/assets, buildServer → dist-server/worker.js
|
|
14
|
+
bun run dev # bunx wrangler dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or a plain Bun server with an in-memory store:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
bun run build
|
|
21
|
+
bun run dev:bun # http://localhost:3000
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Watch the `x-nifra-isr` response header as you reload: `miss` (rendered + stored) → `hit` (fresh, from
|
|
25
|
+
cache) → `stale` (served instantly while it regenerates). The page shows a server-render counter that
|
|
26
|
+
holds across `hit`s and bumps on a `miss`/regeneration.
|
|
27
|
+
|
|
28
|
+
## How it works
|
|
29
|
+
|
|
30
|
+
- `withISR(app, { store, revalidate, now })` wraps the app's `fetch`. Only full-document `text/html`
|
|
31
|
+
`GET` `200`s are cached; assets, soft-nav data fetches, redirects, and errors pass through.
|
|
32
|
+
- A route sets its freshness with `export const revalidate = <seconds>` (see `routes/index.tsx`).
|
|
33
|
+
- `KVCacheStore(env.ISR_CACHE)` is the shared, durable store (production); `MemoryCacheStore` is dev.
|
|
34
|
+
- `revalidateEndpoint({ store, secret })` is the on-demand purge handler:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
curl -X POST 'http://localhost:3000/__velo/revalidate?path=/' -H 'x-nifra-revalidate-token: dev-secret'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Deploy
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
bunx wrangler kv namespace create ISR_CACHE # paste the id into wrangler.toml
|
|
44
|
+
bunx wrangler secret put REVALIDATE_SECRET # set a real purge secret
|
|
45
|
+
bun run build && bunx wrangler deploy
|
|
46
|
+
```
|