feat(server): Drizzle + PostgreSQL schema + Open Trivia DB seed #5
18 changed files with 950 additions and 26 deletions
|
|
@ -2,3 +2,5 @@ NODE_ENV=development
|
||||||
HOST=0.0.0.0
|
HOST=0.0.0.0
|
||||||
PORT=3001
|
PORT=3001
|
||||||
CORS_ORIGINS=http://localhost:5173
|
CORS_ORIGINS=http://localhost:5173
|
||||||
|
# Laisser vide pour jouer sans DB (banque de questions en dur).
|
||||||
|
DATABASE_URL=postgres://nerdware:nerdware@localhost:5432/nerdware
|
||||||
|
|
|
||||||
10
apps/server/drizzle.config.ts
Normal file
10
apps/server/drizzle.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from "drizzle-kit"
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
dialect: "postgresql",
|
||||||
|
schema: "./src/db/schema.ts",
|
||||||
|
out: "./drizzle",
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env.DATABASE_URL ?? "",
|
||||||
|
},
|
||||||
|
})
|
||||||
39
apps/server/drizzle/0000_loose_doctor_spectrum.sql
Normal file
39
apps/server/drizzle/0000_loose_doctor_spectrum.sql
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
CREATE TABLE "game_history" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"room_code" text NOT NULL,
|
||||||
|
"played_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"modes" jsonb,
|
||||||
|
"results" jsonb
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "quiz_category" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "quiz_category_name_unique" UNIQUE("name")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "quiz_played" (
|
||||||
|
"question_id" uuid NOT NULL,
|
||||||
|
"room_code" text NOT NULL,
|
||||||
|
"played_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "quiz_played_question_id_room_code_pk" PRIMARY KEY("question_id","room_code")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "quiz_question" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"category_id" uuid,
|
||||||
|
"format" text NOT NULL,
|
||||||
|
"prompt" text NOT NULL,
|
||||||
|
"difficulty" integer DEFAULT 1 NOT NULL,
|
||||||
|
"source" text NOT NULL,
|
||||||
|
"choices" jsonb,
|
||||||
|
"correct_index" integer,
|
||||||
|
"accepted_answers" jsonb,
|
||||||
|
"image_url" text,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
CONSTRAINT "quiz_question_prompt_unique" UNIQUE("prompt")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "quiz_played" ADD CONSTRAINT "quiz_played_question_id_quiz_question_id_fk" FOREIGN KEY ("question_id") REFERENCES "public"."quiz_question"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "quiz_question" ADD CONSTRAINT "quiz_question_category_id_quiz_category_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."quiz_category"("id") ON DELETE no action ON UPDATE no action;
|
||||||
263
apps/server/drizzle/meta/0000_snapshot.json
Normal file
263
apps/server/drizzle/meta/0000_snapshot.json
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
{
|
||||||
|
"id": "c68e2f59-3d1b-4fca-8443-17fe243e2919",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.game_history": {
|
||||||
|
"name": "game_history",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"room_code": {
|
||||||
|
"name": "room_code",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"played_at": {
|
||||||
|
"name": "played_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"modes": {
|
||||||
|
"name": "modes",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"results": {
|
||||||
|
"name": "results",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.quiz_category": {
|
||||||
|
"name": "quiz_category",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"quiz_category_name_unique": {
|
||||||
|
"name": "quiz_category_name_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.quiz_played": {
|
||||||
|
"name": "quiz_played",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"question_id": {
|
||||||
|
"name": "question_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"room_code": {
|
||||||
|
"name": "room_code",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"played_at": {
|
||||||
|
"name": "played_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"quiz_played_question_id_quiz_question_id_fk": {
|
||||||
|
"name": "quiz_played_question_id_quiz_question_id_fk",
|
||||||
|
"tableFrom": "quiz_played",
|
||||||
|
"tableTo": "quiz_question",
|
||||||
|
"columnsFrom": [
|
||||||
|
"question_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"quiz_played_question_id_room_code_pk": {
|
||||||
|
"name": "quiz_played_question_id_room_code_pk",
|
||||||
|
"columns": [
|
||||||
|
"question_id",
|
||||||
|
"room_code"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.quiz_question": {
|
||||||
|
"name": "quiz_question",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "gen_random_uuid()"
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"name": "category_id",
|
||||||
|
"type": "uuid",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"name": "format",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"prompt": {
|
||||||
|
"name": "prompt",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"difficulty": {
|
||||||
|
"name": "difficulty",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 1
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"name": "source",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"choices": {
|
||||||
|
"name": "choices",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"correct_index": {
|
||||||
|
"name": "correct_index",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"accepted_answers": {
|
||||||
|
"name": "accepted_answers",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"image_url": {
|
||||||
|
"name": "image_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"quiz_question_category_id_quiz_category_id_fk": {
|
||||||
|
"name": "quiz_question_category_id_quiz_category_id_fk",
|
||||||
|
"tableFrom": "quiz_question",
|
||||||
|
"tableTo": "quiz_category",
|
||||||
|
"columnsFrom": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"quiz_question_prompt_unique": {
|
||||||
|
"name": "quiz_question_prompt_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"prompt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
apps/server/drizzle/meta/_journal.json
Normal file
13
apps/server/drizzle/meta/_journal.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1781085893179,
|
||||||
|
"tag": "0000_loose_doctor_spectrum",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -10,18 +10,25 @@
|
||||||
"build": "bun build ./src/index.ts --target bun --outdir dist",
|
"build": "bun build ./src/index.ts --target bun --outdir dist",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"format": "prettier --write \"**/*.ts\"",
|
"format": "prettier --write \"**/*.ts\"",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit",
|
||||||
|
"db:generate": "drizzle-kit generate",
|
||||||
|
"db:migrate": "bun src/db/migrate.ts",
|
||||||
|
"db:seed": "bun src/db/seed.ts",
|
||||||
|
"db:studio": "drizzle-kit studio"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^11",
|
"@fastify/cors": "^11",
|
||||||
"@nerdware/shared": "workspace:*",
|
"@nerdware/shared": "workspace:*",
|
||||||
|
"drizzle-orm": "^0.45",
|
||||||
"fastify": "^5",
|
"fastify": "^5",
|
||||||
|
"postgres": "^3.4",
|
||||||
"socket.io": "^4.8.1"
|
"socket.io": "^4.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10",
|
"@eslint/js": "^10",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/node": "^24",
|
"@types/node": "^24",
|
||||||
|
"drizzle-kit": "^0.31",
|
||||||
"eslint": "^10",
|
"eslint": "^10",
|
||||||
"globals": "^17",
|
"globals": "^17",
|
||||||
"pino-pretty": "^13",
|
"pino-pretty": "^13",
|
||||||
|
|
|
||||||
21
apps/server/src/db/index.ts
Normal file
21
apps/server/src/db/index.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Client Drizzle/PostgreSQL. Optionnel : sans DATABASE_URL, `db` vaut null et
|
||||||
|
// le jeu retombe sur la banque de questions en dur (dev sans infra).
|
||||||
|
|
||||||
|
import { drizzle } from "drizzle-orm/postgres-js"
|
||||||
|
import postgres from "postgres"
|
||||||
|
import { env } from "../env"
|
||||||
|
import * as schema from "./schema"
|
||||||
|
|
||||||
|
export type Database = ReturnType<typeof drizzle<typeof schema>>
|
||||||
|
|
||||||
|
function createDb(): Database | null {
|
||||||
|
if (!env.databaseUrl) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const client = postgres(env.databaseUrl)
|
||||||
|
return drizzle(client, { schema })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const db = createDb()
|
||||||
|
export const hasDb = db !== null
|
||||||
|
export { schema }
|
||||||
18
apps/server/src/db/migrate.ts
Normal file
18
apps/server/src/db/migrate.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Applique les migrations générées (drizzle/*.sql) à la base.
|
||||||
|
// bun run db:migrate
|
||||||
|
import { drizzle } from "drizzle-orm/postgres-js"
|
||||||
|
import { migrate } from "drizzle-orm/postgres-js/migrator"
|
||||||
|
import postgres from "postgres"
|
||||||
|
import { env } from "../env"
|
||||||
|
|
||||||
|
if (!env.databaseUrl) {
|
||||||
|
console.error("DATABASE_URL manquant — rien à migrer.")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = postgres(env.databaseUrl, { max: 1 })
|
||||||
|
const db = drizzle(client)
|
||||||
|
|
||||||
|
await migrate(db, { migrationsFolder: "./drizzle" })
|
||||||
|
console.log("Migrations appliquées.")
|
||||||
|
await client.end()
|
||||||
71
apps/server/src/db/quiz-repo.ts
Normal file
71
apps/server/src/db/quiz-repo.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Accès aux questions de quiz en base. No-op si pas de DB (db === null).
|
||||||
|
|
||||||
|
import { and, eq, inArray, isNotNull, notInArray, sql } from "drizzle-orm"
|
||||||
|
import { db } from "./index"
|
||||||
|
import { quizCategory, quizPlayed, quizQuestion } from "./schema"
|
||||||
|
import type { QuizQuestion } from "../game/modes/quiz/questions"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pool de questions mcq/truefalse non encore jouées par cette room,
|
||||||
|
* mélangées et limitées. Tableau vide si pas de DB ou rien à servir.
|
||||||
|
*/
|
||||||
|
export async function loadQuizPool(
|
||||||
|
roomCode: string,
|
||||||
|
limit: number
|
||||||
|
): Promise<QuizQuestion[]> {
|
||||||
|
if (!db) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const alreadyPlayed = db
|
||||||
|
.select({ id: quizPlayed.questionId })
|
||||||
|
.from(quizPlayed)
|
||||||
|
.where(eq(quizPlayed.roomCode, roomCode))
|
||||||
|
|
||||||
|
const rows = await db
|
||||||
|
.select({
|
||||||
|
id: quizQuestion.id,
|
||||||
|
format: quizQuestion.format,
|
||||||
|
prompt: quizQuestion.prompt,
|
||||||
|
choices: quizQuestion.choices,
|
||||||
|
correctIndex: quizQuestion.correctIndex,
|
||||||
|
difficulty: quizQuestion.difficulty,
|
||||||
|
category: quizCategory.name,
|
||||||
|
})
|
||||||
|
.from(quizQuestion)
|
||||||
|
.leftJoin(quizCategory, eq(quizQuestion.categoryId, quizCategory.id))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
inArray(quizQuestion.format, ["mcq", "truefalse"]),
|
||||||
|
isNotNull(quizQuestion.correctIndex),
|
||||||
|
notInArray(quizQuestion.id, alreadyPlayed)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(sql`random()`)
|
||||||
|
.limit(limit)
|
||||||
|
|
||||||
|
return rows
|
||||||
|
.filter((r) => r.choices && r.correctIndex !== null)
|
||||||
|
.map((r) => ({
|
||||||
|
id: r.id,
|
||||||
|
format: r.format as QuizQuestion["format"],
|
||||||
|
prompt: r.prompt,
|
||||||
|
choices: r.choices as string[],
|
||||||
|
correctIndex: r.correctIndex as number,
|
||||||
|
category: r.category ?? "Quiz",
|
||||||
|
difficulty: r.difficulty,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Marque une question comme jouée par cette room (anti-répétition). */
|
||||||
|
export async function markQuestionPlayed(
|
||||||
|
roomCode: string,
|
||||||
|
questionId: string
|
||||||
|
): Promise<void> {
|
||||||
|
if (!db) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await db
|
||||||
|
.insert(quizPlayed)
|
||||||
|
.values({ roomCode, questionId })
|
||||||
|
.onConflictDoNothing()
|
||||||
|
}
|
||||||
72
apps/server/src/db/schema.ts
Normal file
72
apps/server/src/db/schema.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Schéma persistant (PostgreSQL via Drizzle). Ne stocke que le contenu quiz
|
||||||
|
// et l'historique — les rooms vivent en mémoire (voir ARCHITECTURE §3).
|
||||||
|
|
||||||
|
import { sql } from "drizzle-orm"
|
||||||
|
import {
|
||||||
|
integer,
|
||||||
|
jsonb,
|
||||||
|
pgTable,
|
||||||
|
primaryKey,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
uuid,
|
||||||
|
} from "drizzle-orm/pg-core"
|
||||||
|
|
||||||
|
export const quizCategory = pgTable("quiz_category", {
|
||||||
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
|
name: text("name").notNull().unique(),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`now()`),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const quizQuestion = pgTable("quiz_question", {
|
||||||
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
|
categoryId: uuid("category_id").references(() => quizCategory.id),
|
||||||
|
/** 'mcq' | 'truefalse' | 'free' | 'image_reveal' */
|
||||||
|
format: text("format").notNull(),
|
||||||
|
/** Unique → seed idempotent (onConflictDoNothing). */
|
||||||
|
prompt: text("prompt").notNull().unique(),
|
||||||
|
/** 1 (facile) .. 3 (difficile). */
|
||||||
|
difficulty: integer("difficulty").notNull().default(1),
|
||||||
|
/** 'opentdb' | 'manual' */
|
||||||
|
source: text("source").notNull(),
|
||||||
|
// Champs selon le format (NULL si non applicable) :
|
||||||
|
choices: jsonb("choices").$type<string[]>(),
|
||||||
|
correctIndex: integer("correct_index"),
|
||||||
|
acceptedAnswers: jsonb("accepted_answers").$type<string[]>(),
|
||||||
|
imageUrl: text("image_url"),
|
||||||
|
createdAt: timestamp("created_at", { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`now()`),
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Anti-répétition : ce qu'un groupe (room_code) a déjà joué. */
|
||||||
|
export const quizPlayed = pgTable(
|
||||||
|
"quiz_played",
|
||||||
|
{
|
||||||
|
questionId: uuid("question_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => quizQuestion.id),
|
||||||
|
roomCode: text("room_code").notNull(),
|
||||||
|
playedAt: timestamp("played_at", { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`now()`),
|
||||||
|
},
|
||||||
|
(t) => [primaryKey({ columns: [t.questionId, t.roomCode] })]
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Historique de parties (stats / portfolio). */
|
||||||
|
export const gameHistory = pgTable("game_history", {
|
||||||
|
id: uuid("id").primaryKey().defaultRandom(),
|
||||||
|
roomCode: text("room_code").notNull(),
|
||||||
|
playedAt: timestamp("played_at", { withTimezone: true })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`now()`),
|
||||||
|
modes: jsonb("modes").$type<string[]>(),
|
||||||
|
results: jsonb("results"),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type QuizQuestionRow = typeof quizQuestion.$inferSelect
|
||||||
|
export type NewQuizQuestion = typeof quizQuestion.$inferInsert
|
||||||
|
export type NewQuizCategory = typeof quizCategory.$inferInsert
|
||||||
193
apps/server/src/db/seed.ts
Normal file
193
apps/server/src/db/seed.ts
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
// Seed du contenu quiz. One-shot au déploiement : bun run db:seed
|
||||||
|
// - Open Trivia DB : catégories geek (mcq + truefalse), respect 1 req / 5 s.
|
||||||
|
// - Banque FR en dur : insérée comme source 'manual'.
|
||||||
|
// Idempotent : prompt unique → onConflictDoNothing.
|
||||||
|
|
||||||
|
import { eq } from "drizzle-orm"
|
||||||
|
import { drizzle } from "drizzle-orm/postgres-js"
|
||||||
|
import postgres from "postgres"
|
||||||
|
import { env } from "../env"
|
||||||
|
import {
|
||||||
|
quizCategory,
|
||||||
|
quizQuestion,
|
||||||
|
type NewQuizQuestion,
|
||||||
|
} from "./schema"
|
||||||
|
import { QUIZ_QUESTIONS } from "../game/modes/quiz/questions"
|
||||||
|
|
||||||
|
if (!env.databaseUrl) {
|
||||||
|
console.error("DATABASE_URL manquant — impossible de seeder.")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = postgres(env.databaseUrl, { max: 1 })
|
||||||
|
const db = drizzle(client, { schema: { quizCategory, quizQuestion } })
|
||||||
|
|
||||||
|
// Catégories Open Trivia DB pertinentes (id OpenTDB → nom affiché).
|
||||||
|
const OPENTDB_CATEGORIES = [
|
||||||
|
{ id: 15, name: "Jeux vidéo" },
|
||||||
|
{ id: 31, name: "Anime & Manga" },
|
||||||
|
{ id: 29, name: "Comics" },
|
||||||
|
{ id: 11, name: "Cinéma" },
|
||||||
|
{ id: 14, name: "Télévision" },
|
||||||
|
{ id: 18, name: "Informatique" },
|
||||||
|
]
|
||||||
|
const AMOUNT = 30
|
||||||
|
const RATE_LIMIT_MS = 5200 // OpenTDB : 1 requête / 5 s par IP
|
||||||
|
|
||||||
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
||||||
|
const decode = (s: string) => Buffer.from(s, "base64").toString("utf8")
|
||||||
|
const difficultyOf = (d: string) => (d === "hard" ? 3 : d === "medium" ? 2 : 1)
|
||||||
|
|
||||||
|
// Tous les champs string sont encodés en base64 (encode=base64).
|
||||||
|
interface OtdbQuestion {
|
||||||
|
type: string
|
||||||
|
difficulty: string
|
||||||
|
question: string
|
||||||
|
correct_answer: string
|
||||||
|
incorrect_answers: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function shuffle<T>(items: T[]): T[] {
|
||||||
|
const arr = [...items]
|
||||||
|
for (let i = arr.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1))
|
||||||
|
;[arr[i], arr[j]] = [arr[j], arr[i]]
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getToken(): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const res = await fetch("https://opentdb.com/api_token.php?command=request")
|
||||||
|
const json = (await res.json()) as { token?: string }
|
||||||
|
return json.token
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchBatch(
|
||||||
|
categoryId: number,
|
||||||
|
type: "multiple" | "boolean",
|
||||||
|
token: string | undefined
|
||||||
|
): Promise<OtdbQuestion[]> {
|
||||||
|
const url =
|
||||||
|
`https://opentdb.com/api.php?amount=${AMOUNT}&category=${categoryId}` +
|
||||||
|
`&type=${type}&encode=base64${token ? `&token=${token}` : ""}`
|
||||||
|
const res = await fetch(url)
|
||||||
|
const json = (await res.json()) as {
|
||||||
|
response_code: number
|
||||||
|
results: OtdbQuestion[]
|
||||||
|
}
|
||||||
|
if (json.response_code === 5) {
|
||||||
|
// Rate limit : on attend et on réessaie une fois.
|
||||||
|
await sleep(RATE_LIMIT_MS)
|
||||||
|
return fetchBatch(categoryId, type, token)
|
||||||
|
}
|
||||||
|
return json.response_code === 0 ? json.results : []
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Upsert une catégorie et renvoie son id. */
|
||||||
|
async function upsertCategory(name: string): Promise<string> {
|
||||||
|
await db.insert(quizCategory).values({ name }).onConflictDoNothing()
|
||||||
|
const [row] = await db
|
||||||
|
.select({ id: quizCategory.id })
|
||||||
|
.from(quizCategory)
|
||||||
|
.where(eq(quizCategory.name, name))
|
||||||
|
return row.id
|
||||||
|
}
|
||||||
|
|
||||||
|
function toQuestion(
|
||||||
|
q: OtdbQuestion,
|
||||||
|
categoryId: string
|
||||||
|
): NewQuizQuestion {
|
||||||
|
// Avec encode=base64, TOUS les champs sont encodés (type/difficulty inclus).
|
||||||
|
const prompt = decode(q.question)
|
||||||
|
const type = decode(q.type)
|
||||||
|
const difficulty = difficultyOf(decode(q.difficulty))
|
||||||
|
if (type === "boolean") {
|
||||||
|
const correct = decode(q.correct_answer) // "True" | "False"
|
||||||
|
return {
|
||||||
|
categoryId,
|
||||||
|
format: "truefalse",
|
||||||
|
prompt,
|
||||||
|
difficulty,
|
||||||
|
source: "opentdb",
|
||||||
|
choices: ["True", "False"],
|
||||||
|
correctIndex: correct === "True" ? 0 : 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const correct = decode(q.correct_answer)
|
||||||
|
const choices = shuffle([
|
||||||
|
correct,
|
||||||
|
...q.incorrect_answers.map(decode),
|
||||||
|
])
|
||||||
|
return {
|
||||||
|
categoryId,
|
||||||
|
format: "mcq",
|
||||||
|
prompt,
|
||||||
|
difficulty,
|
||||||
|
source: "opentdb",
|
||||||
|
choices,
|
||||||
|
correctIndex: choices.indexOf(correct),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function insertQuestions(rows: NewQuizQuestion[]): Promise<number> {
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const inserted = await db
|
||||||
|
.insert(quizQuestion)
|
||||||
|
.values(rows)
|
||||||
|
.onConflictDoNothing()
|
||||||
|
.returning({ id: quizQuestion.id })
|
||||||
|
return inserted.length
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedOpenTdb(): Promise<number> {
|
||||||
|
const token = await getToken()
|
||||||
|
let total = 0
|
||||||
|
let first = true
|
||||||
|
for (const cat of OPENTDB_CATEGORIES) {
|
||||||
|
const categoryId = await upsertCategory(cat.name)
|
||||||
|
for (const type of ["multiple", "boolean"] as const) {
|
||||||
|
if (!first) {
|
||||||
|
await sleep(RATE_LIMIT_MS)
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
const batch = await fetchBatch(cat.id, type, token)
|
||||||
|
const rows = batch.map((q) => toQuestion(q, categoryId))
|
||||||
|
const n = await insertQuestions(rows)
|
||||||
|
total += n
|
||||||
|
console.log(` ${cat.name} (${type}): +${n}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
async function seedManual(): Promise<number> {
|
||||||
|
let total = 0
|
||||||
|
for (const q of QUIZ_QUESTIONS) {
|
||||||
|
const categoryId = await upsertCategory(q.category)
|
||||||
|
total += await insertQuestions([
|
||||||
|
{
|
||||||
|
categoryId,
|
||||||
|
format: q.format,
|
||||||
|
prompt: q.prompt,
|
||||||
|
difficulty: q.difficulty,
|
||||||
|
source: "manual",
|
||||||
|
choices: q.choices,
|
||||||
|
correctIndex: q.correctIndex,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Seed Open Trivia DB (respect du 1 req / 5 s, patientez)…")
|
||||||
|
const otdb = await seedOpenTdb()
|
||||||
|
console.log("Seed banque FR (manual)…")
|
||||||
|
const manual = await seedManual()
|
||||||
|
console.log(`Terminé : ${otdb} questions OpenTDB + ${manual} manuelles.`)
|
||||||
|
await client.end()
|
||||||
|
|
@ -10,6 +10,8 @@ export const env = {
|
||||||
nodeEnv: process.env.NODE_ENV ?? "development",
|
nodeEnv: process.env.NODE_ENV ?? "development",
|
||||||
host: process.env.HOST ?? "0.0.0.0",
|
host: process.env.HOST ?? "0.0.0.0",
|
||||||
port: num(process.env.PORT, 3001),
|
port: num(process.env.PORT, 3001),
|
||||||
|
/** Connexion PostgreSQL. Vide → pas de DB, fallback banque en dur. */
|
||||||
|
databaseUrl: process.env.DATABASE_URL ?? "",
|
||||||
/** Origines autorisées pour CORS / Socket.IO (séparées par des virgules). */
|
/** Origines autorisées pour CORS / Socket.IO (séparées par des virgules). */
|
||||||
corsOrigins: (process.env.CORS_ORIGINS ?? "http://localhost:5173")
|
corsOrigins: (process.env.CORS_ORIGINS ?? "http://localhost:5173")
|
||||||
.split(",")
|
.split(",")
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,13 @@
|
||||||
// pour ses effets de bord (registerRound). Ajouter un mode = une ligne ici.
|
// pour ses effets de bord (registerRound). Ajouter un mode = une ligne ici.
|
||||||
|
|
||||||
import "./quiz"
|
import "./quiz"
|
||||||
|
|
||||||
|
import type { ServerRoom } from "../../rooms"
|
||||||
|
import { prepareQuizForRoom } from "./quiz/pool"
|
||||||
|
|
||||||
|
/** Précharge le contenu nécessaire avant de lancer la partie (ex: pool quiz). */
|
||||||
|
export async function prepareRoom(room: ServerRoom): Promise<void> {
|
||||||
|
if (room.settings.rounds.some((r) => r.type === "quiz")) {
|
||||||
|
await prepareQuizForRoom(room)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
61
apps/server/src/game/modes/quiz/pool.ts
Normal file
61
apps/server/src/game/modes/quiz/pool.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
// File d'attente de questions par room. Préchargée au lancement de la partie
|
||||||
|
// depuis la DB (si dispo), sinon depuis la banque en dur. Le QuizRound y pioche.
|
||||||
|
|
||||||
|
import { hasDb } from "../../../db"
|
||||||
|
import { loadQuizPool } from "../../../db/quiz-repo"
|
||||||
|
import type { ServerRoom } from "../../../rooms"
|
||||||
|
import { QUIZ_QUESTIONS, type QuizQuestion } from "./questions"
|
||||||
|
|
||||||
|
interface RoomPool {
|
||||||
|
queue: QuizQuestion[]
|
||||||
|
/** true si les questions viennent de la DB (→ marquer jouées). */
|
||||||
|
fromDb: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const poolByRoom = new WeakMap<ServerRoom, RoomPool>()
|
||||||
|
|
||||||
|
function shuffle<T>(items: T[]): T[] {
|
||||||
|
const arr = [...items]
|
||||||
|
for (let i = arr.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1))
|
||||||
|
;[arr[i], arr[j]] = [arr[j], arr[i]]
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Précharge les questions de la partie. À appeler avant de lancer le moteur. */
|
||||||
|
export async function prepareQuizForRoom(room: ServerRoom): Promise<void> {
|
||||||
|
const needed = room.settings.rounds.filter((r) => r.type === "quiz").length
|
||||||
|
let queue: QuizQuestion[] = []
|
||||||
|
let fromDb = false
|
||||||
|
|
||||||
|
if (hasDb) {
|
||||||
|
try {
|
||||||
|
queue = await loadQuizPool(room.code, Math.max(needed, 1))
|
||||||
|
fromDb = queue.length > 0
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[quiz] chargement DB échoué, fallback banque en dur", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queue.length === 0) {
|
||||||
|
queue = shuffle(QUIZ_QUESTIONS)
|
||||||
|
fromDb = false
|
||||||
|
}
|
||||||
|
|
||||||
|
poolByRoom.set(room, { queue, fromDb })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pioche la prochaine question. Fallback aléatoire si la file est vide. */
|
||||||
|
export function takeQuestion(room: ServerRoom): {
|
||||||
|
question: QuizQuestion
|
||||||
|
fromDb: boolean
|
||||||
|
} {
|
||||||
|
const pool = poolByRoom.get(room)
|
||||||
|
if (pool && pool.queue.length > 0) {
|
||||||
|
return { question: pool.queue.shift()!, fromDb: pool.fromDb }
|
||||||
|
}
|
||||||
|
const question =
|
||||||
|
QUIZ_QUESTIONS[Math.floor(Math.random() * QUIZ_QUESTIONS.length)]
|
||||||
|
return { question, fromDb: false }
|
||||||
|
}
|
||||||
|
|
@ -8,9 +8,12 @@ import type {
|
||||||
QuizRevealTruth,
|
QuizRevealTruth,
|
||||||
ScoreDelta,
|
ScoreDelta,
|
||||||
} from "@nerdware/shared"
|
} from "@nerdware/shared"
|
||||||
|
import { hasDb } from "../../../db"
|
||||||
|
import { markQuestionPlayed } from "../../../db/quiz-repo"
|
||||||
import type { GameRound, RoundContext, RoundStart } from "../../round"
|
import type { GameRound, RoundContext, RoundStart } from "../../round"
|
||||||
import type { ServerRoom } from "../../../rooms"
|
import type { ServerRoom } from "../../../rooms"
|
||||||
import { QUIZ_QUESTIONS, type QuizQuestion } from "./questions"
|
import type { QuizQuestion } from "./questions"
|
||||||
|
import { takeQuestion } from "./pool"
|
||||||
|
|
||||||
const BASE_POINTS = 100
|
const BASE_POINTS = 100
|
||||||
const SPEED_BONUS_MAX = 100
|
const SPEED_BONUS_MAX = 100
|
||||||
|
|
@ -22,26 +25,6 @@ interface QuizRoundData {
|
||||||
votedAt: Map<string, number>
|
votedAt: Map<string, number>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anti-répétition à l'échelle d'une partie : questions déjà jouées par room.
|
|
||||||
const playedByRoom = new WeakMap<ServerRoom, Set<string>>()
|
|
||||||
|
|
||||||
function pickQuestion(room: ServerRoom): QuizQuestion {
|
|
||||||
let played = playedByRoom.get(room)
|
|
||||||
if (!played) {
|
|
||||||
played = new Set()
|
|
||||||
playedByRoom.set(room, played)
|
|
||||||
}
|
|
||||||
let pool = QUIZ_QUESTIONS.filter((q) => !played.has(q.id))
|
|
||||||
if (pool.length === 0) {
|
|
||||||
// Banque épuisée sur cette partie : on autorise à nouveau les répétitions.
|
|
||||||
played.clear()
|
|
||||||
pool = QUIZ_QUESTIONS
|
|
||||||
}
|
|
||||||
const question = pool[Math.floor(Math.random() * pool.length)]
|
|
||||||
played.add(question.id)
|
|
||||||
return question
|
|
||||||
}
|
|
||||||
|
|
||||||
function isQuizAnswer(answer: Answer): answer is { choiceIndex: number } {
|
function isQuizAnswer(answer: Answer): answer is { choiceIndex: number } {
|
||||||
return (
|
return (
|
||||||
typeof (answer as { choiceIndex?: unknown }).choiceIndex === "number"
|
typeof (answer as { choiceIndex?: unknown }).choiceIndex === "number"
|
||||||
|
|
@ -54,7 +37,11 @@ export class QuizRound implements GameRound {
|
||||||
constructor(private readonly now: () => number = Date.now) {}
|
constructor(private readonly now: () => number = Date.now) {}
|
||||||
|
|
||||||
start(room: ServerRoom): RoundStart {
|
start(room: ServerRoom): RoundStart {
|
||||||
const question = pickQuestion(room)
|
const { question, fromDb } = takeQuestion(room)
|
||||||
|
// Anti-répétition cross-session : on enregistre la question jouée (DB only).
|
||||||
|
if (fromDb && hasDb) {
|
||||||
|
void markQuestionPlayed(room.code, question.id)
|
||||||
|
}
|
||||||
const payload: QuizQuestionPayload = {
|
const payload: QuizQuestionPayload = {
|
||||||
format: question.format,
|
format: question.format,
|
||||||
prompt: question.prompt,
|
prompt: question.prompt,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import type { IoServer } from "./socket"
|
||||||
import { RoomError, RoomManager, type ServerRoom } from "./rooms"
|
import { RoomError, RoomManager, type ServerRoom } from "./rooms"
|
||||||
import { GameEngine } from "./game/engine"
|
import { GameEngine } from "./game/engine"
|
||||||
import { hasRound } from "./game/registry"
|
import { hasRound } from "./game/registry"
|
||||||
|
import { prepareRoom } from "./game/modes"
|
||||||
|
|
||||||
type AppSocket = Socket<
|
type AppSocket = Socket<
|
||||||
ClientToServerEvents,
|
ClientToServerEvents,
|
||||||
|
|
@ -121,10 +122,15 @@ export function registerRoomHandlers(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ack({ ok: true, data: null })
|
ack({ ok: true, data: null })
|
||||||
|
// Précharge le contenu (pool quiz depuis la DB), puis lance la boucle.
|
||||||
|
// Fire-and-forget : la boucle de jeu broadcast son propre état.
|
||||||
|
void prepareRoom(room)
|
||||||
|
.then(() => {
|
||||||
const engine = new GameEngine(io, rooms, room)
|
const engine = new GameEngine(io, rooms, room)
|
||||||
room.game = engine
|
room.game = engine
|
||||||
// Fire-and-forget : la boucle de jeu broadcast son propre état.
|
return engine.run()
|
||||||
void engine.run().catch((err) => console.error("[game] run failed", err))
|
})
|
||||||
|
.catch((err) => console.error("[game] run failed", err))
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on("round:vote", (payload) => {
|
socket.on("round:vote", (payload) => {
|
||||||
|
|
|
||||||
125
bun.lock
125
bun.lock
|
|
@ -17,13 +17,16 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cors": "^11",
|
"@fastify/cors": "^11",
|
||||||
"@nerdware/shared": "workspace:*",
|
"@nerdware/shared": "workspace:*",
|
||||||
|
"drizzle-orm": "^0.45",
|
||||||
"fastify": "^5",
|
"fastify": "^5",
|
||||||
|
"postgres": "^3.4",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10",
|
"@eslint/js": "^10",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/node": "^24",
|
"@types/node": "^24",
|
||||||
|
"drizzle-kit": "^0.31",
|
||||||
"eslint": "^10",
|
"eslint": "^10",
|
||||||
"globals": "^17",
|
"globals": "^17",
|
||||||
"pino-pretty": "^13",
|
"pino-pretty": "^13",
|
||||||
|
|
@ -171,6 +174,8 @@
|
||||||
|
|
||||||
"@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.71.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "enquirer": "^2.4.1", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.4", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-KEUw/mGu+EDRhYWRTNGHIimVCs9NvMFaIXOGrHSXoCteKLE5EsJnmPjOPpYorjXVg/0xG0fbdVw720azw1z4ag=="],
|
"@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.71.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "enquirer": "^2.4.1", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.4", "which": "^4.0.0", "yocto-spinner": "^1.1.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-KEUw/mGu+EDRhYWRTNGHIimVCs9NvMFaIXOGrHSXoCteKLE5EsJnmPjOPpYorjXVg/0xG0fbdVw720azw1z4ag=="],
|
||||||
|
|
||||||
|
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
|
||||||
|
|
||||||
"@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="],
|
"@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="],
|
||||||
|
|
||||||
"@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
"@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
||||||
|
|
@ -179,6 +184,10 @@
|
||||||
|
|
||||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
|
||||||
|
|
||||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
|
||||||
|
|
||||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
|
||||||
|
|
@ -657,6 +666,8 @@
|
||||||
|
|
||||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||||
|
|
||||||
|
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||||
|
|
||||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||||
|
|
@ -747,6 +758,10 @@
|
||||||
|
|
||||||
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
|
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
|
||||||
|
|
||||||
|
"drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
|
||||||
|
|
||||||
|
"drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "prisma": "*", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "prisma", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="],
|
||||||
|
|
||||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||||
|
|
||||||
"eciesjs": ["eciesjs@0.4.18", "", { "dependencies": { "@ecies/ciphers": "^0.2.5", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ=="],
|
"eciesjs": ["eciesjs@0.4.18", "", { "dependencies": { "@ecies/ciphers": "^0.2.5", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ=="],
|
||||||
|
|
@ -907,6 +922,8 @@
|
||||||
|
|
||||||
"get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
|
"get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
|
||||||
|
|
||||||
|
"get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="],
|
||||||
|
|
||||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||||
|
|
||||||
"globals": ["globals@17.6.0", "", {}, "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA=="],
|
"globals": ["globals@17.6.0", "", {}, "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA=="],
|
||||||
|
|
@ -1169,6 +1186,8 @@
|
||||||
|
|
||||||
"postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
|
"postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
|
||||||
|
|
||||||
|
"postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="],
|
||||||
|
|
||||||
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
|
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
|
||||||
|
|
||||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||||
|
|
@ -1223,6 +1242,8 @@
|
||||||
|
|
||||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||||
|
|
||||||
|
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||||
|
|
||||||
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
|
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
|
||||||
|
|
||||||
"ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="],
|
"ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="],
|
||||||
|
|
@ -1293,6 +1314,8 @@
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||||
|
|
||||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||||
|
|
||||||
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||||
|
|
@ -1347,6 +1370,8 @@
|
||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"tsx": ["tsx@4.22.4", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="],
|
||||||
|
|
||||||
"turbo": ["turbo@2.9.16", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.16", "@turbo/darwin-arm64": "2.9.16", "@turbo/linux-64": "2.9.16", "@turbo/linux-arm64": "2.9.16", "@turbo/windows-64": "2.9.16", "@turbo/windows-arm64": "2.9.16" }, "bin": { "turbo": "bin/turbo" } }, "sha512-NqgRQy6j6dPYcdSdv0q1g9QsZg7SWg87RERM8otw/1AtKU2yTFVClOM7cbwKzOonZr/Ek1blTBucw64L9H0Bwg=="],
|
"turbo": ["turbo@2.9.16", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.16", "@turbo/darwin-arm64": "2.9.16", "@turbo/linux-64": "2.9.16", "@turbo/linux-arm64": "2.9.16", "@turbo/windows-64": "2.9.16", "@turbo/windows-arm64": "2.9.16" }, "bin": { "turbo": "bin/turbo" } }, "sha512-NqgRQy6j6dPYcdSdv0q1g9QsZg7SWg87RERM8otw/1AtKU2yTFVClOM7cbwKzOonZr/Ek1blTBucw64L9H0Bwg=="],
|
||||||
|
|
||||||
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
|
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
|
||||||
|
|
@ -1445,6 +1470,8 @@
|
||||||
|
|
||||||
"@dotenvx/dotenvx/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
"@dotenvx/dotenvx/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||||
|
|
||||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|
||||||
"@fastify/ajv-compiler/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
|
"@fastify/ajv-compiler/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
|
||||||
|
|
@ -1515,6 +1542,8 @@
|
||||||
|
|
||||||
"thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="],
|
"thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="],
|
||||||
|
|
||||||
|
"tsx/esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="],
|
||||||
|
|
||||||
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
||||||
|
|
||||||
"type-is/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
"type-is/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
||||||
|
|
@ -1539,6 +1568,50 @@
|
||||||
|
|
||||||
"@dotenvx/dotenvx/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
"@dotenvx/dotenvx/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||||
|
|
||||||
"@fastify/ajv-compiler/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
"@fastify/ajv-compiler/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
"@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
@ -1563,6 +1636,58 @@
|
||||||
|
|
||||||
"send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
"send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.0", "", { "os": "android", "cpu": "arm" }, "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.0", "", { "os": "android", "cpu": "x64" }, "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.0", "", { "os": "none", "cpu": "x64" }, "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA=="],
|
||||||
|
|
||||||
|
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="],
|
||||||
|
|
||||||
"type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
"type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||||
|
|
||||||
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
|
||||||
24
docker-compose.yml
Normal file
24
docker-compose.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Postgres de développement local pour NerdWare.
|
||||||
|
# docker compose up -d
|
||||||
|
# Puis depuis apps/server : bun run db:migrate && bun run db:seed
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
container_name: nerdware-postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: nerdware
|
||||||
|
POSTGRES_PASSWORD: nerdware
|
||||||
|
POSTGRES_DB: nerdware
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- nerdware-pgdata:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U nerdware -d nerdware"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
nerdware-pgdata:
|
||||||
Loading…
Add table
Reference in a new issue