Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed51a59c80 | |||
| bedeb05a67 | |||
| 58305d7a89 | |||
| 47e2021cf4 | |||
| 31404befee | |||
| a9db0ca9ac | |||
| 4bbb0c9fc5 | |||
| 03f79a3701 | |||
| 2b92c2ab6c | |||
| e9ad92de01 | |||
| bfe1a29c08 | |||
| 1ad43ce701 | |||
| 96dbb8c41d | |||
| 636fa713e1 | |||
| 6120b96c7c | |||
| eb92f99c4a | |||
| 587fd4ec95 | |||
| 6e2e46daf8 | |||
| 8cf72d926d | |||
| 8721f0b63c | |||
| 027e012a58 | |||
| b3db206588 | |||
| fc072d51f4 | |||
| 6538406db2 | |||
| e2eacbc9fe | |||
| e639cc82d1 | |||
| f2931d7be2 | |||
| d4a4ddce37 | |||
| bd384bdf5c | |||
| c92fb2539d | |||
| 411c42b2c4 | |||
| bf97849324 | |||
| 2a6242d3de | |||
| 7181d41b24 | |||
| 4e9c4c5e08 | |||
| 16c959434b | |||
| 23484dc90a | |||
| 766728865e | |||
| 6a81a52a50 | |||
| 5a4b9a98bd | |||
| f7f88156e1 | |||
| 8af5a49d14 | |||
| 403634eb96 | |||
| 152abfc4d5 | |||
| c8bbb12edb | |||
| ba95088653 | |||
| dd83f29736 | |||
| 185fce8e17 | |||
| 081379c189 | |||
| e01c12a316 |
@@ -0,0 +1,54 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
This repository (`groombook/api`) is part of the GroomBook application stack. The
|
||||||
|
authoritative process, quality bar, and safety rules live in the shared
|
||||||
|
[`groombook/org`](https://git.farh.net/groombook/org) skills repository. Read
|
||||||
|
those first; this file is only a pointer.
|
||||||
|
|
||||||
|
## Authoritative skills
|
||||||
|
|
||||||
|
- **SDLC (branching, PRs, phases, handoffs):**
|
||||||
|
[`groombook/org/skills/sdlc/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/sdlc/SKILL.md)
|
||||||
|
- **Coding standards (priority ordering, PR discipline, tests, no-hardcoded-values, CalVer):**
|
||||||
|
[`groombook/org/skills/coding-standards/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/coding-standards/SKILL.md)
|
||||||
|
- **Safety (no plaintext secrets, no direct `kubectl apply` to `groombook`, no self-merge, board approval for destructive actions):**
|
||||||
|
[`groombook/org/skills/safety/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/safety/SKILL.md)
|
||||||
|
|
||||||
|
For human contributors and humans reviewing agent work, see
|
||||||
|
[`CONTRIBUTING.md`](./CONTRIBUTING.md) in this repo for the phase-by-phase PR
|
||||||
|
flow and the `uat→main` merge-gate policy summary.
|
||||||
|
|
||||||
|
## Non-negotiable operational rules
|
||||||
|
|
||||||
|
These mirror the org skills; they are restated here so any agent landing in
|
||||||
|
this repo sees them without a cross-repo fetch.
|
||||||
|
|
||||||
|
- **All changes go through a PR.** Never push directly to `dev`, `uat`, or `main`.
|
||||||
|
- **Branch strategy:** `feature/<name>` → `dev` → `uat` → `main`. Engineers
|
||||||
|
always target `dev` first.
|
||||||
|
- **No self-merge contract.** The engineer who opened a PR clicks merge only
|
||||||
|
after the named reviewer (CI / QA / UAT / Security / CTO per phase)
|
||||||
|
approves. Issue-thread QA / UAT / security approvals do **not** clear the
|
||||||
|
Gitea `required_approvals` gate on `uat→main` — only a Gitea **Approve**
|
||||||
|
click from a member of the `approvals_whitelist_username` does. On this
|
||||||
|
repo that whitelist is `["gb_flea", "gb_dogfather"]` (engineer team).
|
||||||
|
Board-level accounts cannot give the Approve click by policy.
|
||||||
|
- **Always include `cc @cpfarhood`** at the bottom of every PR body for
|
||||||
|
board visibility (not as a reviewer).
|
||||||
|
- **Secrets in code are forbidden.** Use Bitnami Sealed Secrets; never commit
|
||||||
|
plaintext. See the `safety` skill.
|
||||||
|
- **Production (`groombook` namespace) is Flux-managed.** Never
|
||||||
|
`kubectl apply` directly. Infrastructure changes go through PRs in
|
||||||
|
`groombook/infra`.
|
||||||
|
|
||||||
|
## Local development
|
||||||
|
|
||||||
|
See the repo's own README, package scripts, and CI workflow. The
|
||||||
|
authoritative pipeline (Gitea Actions, image build, deploy hooks) is the
|
||||||
|
shared `groombook/infra` overlay; do not reimplement it here.
|
||||||
|
|
||||||
|
## When uncertain
|
||||||
|
|
||||||
|
If a task conflicts with the org skills, **the org skills win**. Open an
|
||||||
|
issue in `groombook/org` to propose a change rather than encoding a local
|
||||||
|
exception.
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
# Contributing to `groombook/api`
|
||||||
|
|
||||||
|
Thanks for contributing. This document is the human-facing companion to
|
||||||
|
[`AGENTS.md`](./AGENTS.md) and the authoritative
|
||||||
|
[`groombook/org`](https://git.farh.net/groombook/org) skills. The org skills
|
||||||
|
govern; this file is a quick-reference for the human/agent PR flow in this
|
||||||
|
repo.
|
||||||
|
|
||||||
|
## Branch strategy
|
||||||
|
|
||||||
|
Three long-lived branches; one PR per promotion step.
|
||||||
|
|
||||||
|
| Branch | Environment | Who merges | Prerequisites for merge |
|
||||||
|
|---------|-------------|-----------|-------------------------|
|
||||||
|
| `dev` | Dev | Engineer | CI passes |
|
||||||
|
| `uat` | UAT | Engineer | QA code review approval |
|
||||||
|
| `main` | Production | Engineer | UAT validation + CTO Gitea Approve when the `uat→main` merge-gate policy applies (see below) |
|
||||||
|
|
||||||
|
Engineers always target `dev` first. Feature branches: `<agent-name>/<short-description>`.
|
||||||
|
|
||||||
|
## Phase-by-phase PR flow
|
||||||
|
|
||||||
|
### Phase 1 — Dev
|
||||||
|
|
||||||
|
1. Branch from `dev`: `git checkout -b <name>/<short-description> origin/dev`.
|
||||||
|
2. Write code + tests. Run unit tests, type check, and lint locally (or rely on CI).
|
||||||
|
3. Open a PR against `dev`:
|
||||||
|
```bash
|
||||||
|
tea pr create --base dev --title "..." --body "..."
|
||||||
|
```
|
||||||
|
Include `cc @cpfarhood` at the bottom of the body for board visibility.
|
||||||
|
4. CI must pass. CI green → engineer self-merges.
|
||||||
|
5. CI builds and deploys to Dev automatically.
|
||||||
|
|
||||||
|
### Phase 2 — UAT promotion
|
||||||
|
|
||||||
|
1. Open a PR from `dev` to `uat`.
|
||||||
|
2. CI must pass.
|
||||||
|
3. **QA (Lint Roller)** reviews and approves on the Gitea PR.
|
||||||
|
4. QA approved → engineer self-merges.
|
||||||
|
5. CI builds and deploys to UAT automatically.
|
||||||
|
|
||||||
|
### Phase 3 — UAT regression + Security review
|
||||||
|
|
||||||
|
1. **UAT (Shedward Scissorhands)** runs full regression against UAT — every
|
||||||
|
feature, old and new, no exceptions.
|
||||||
|
2. **Security (Barkley Trimsworth)** reviews the changes.
|
||||||
|
3. Failures in either gate bounce back to Phase 1.
|
||||||
|
|
||||||
|
### Phase 4 — Production promotion (`uat → main`)
|
||||||
|
|
||||||
|
This is the gate the org PR
|
||||||
|
[`groombook/org#13`](https://git.farh.net/groombook/org/pulls/13) defines.
|
||||||
|
The full rule is in
|
||||||
|
[`groombook/org/skills/sdlc/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/sdlc/SKILL.md)
|
||||||
|
and
|
||||||
|
[`groombook/org/skills/coding-standards/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/coding-standards/SKILL.md);
|
||||||
|
the summary is below.
|
||||||
|
|
||||||
|
**The CTO Gitea Approve click is NOT the default gate.** Once the four
|
||||||
|
pre-gates (QA, UAT deploy, UAT regression, security) are green, the engineer
|
||||||
|
self-merges.
|
||||||
|
|
||||||
|
**A CTO Gitea Approve click IS required** only for PRs in one of three
|
||||||
|
categories:
|
||||||
|
|
||||||
|
1. **Novel auth / session paths** — login, OIDC, OOBE, session middleware,
|
||||||
|
token issuance, password reset, MFA, new auth provider integrations.
|
||||||
|
Routine auth-gated UI (button styling, error messages, form layout) is
|
||||||
|
**not** in this category.
|
||||||
|
2. **Infra / prod-affecting merges** — deploys, infra manifests, secrets,
|
||||||
|
GitOps overlays, CI/CD, `main` branch protection, production
|
||||||
|
routing/ingress, prod state mutations. All Phase 5 infra overlay PRs in
|
||||||
|
`groombook/infra` require CTO Gitea Approve without exception.
|
||||||
|
3. **Risk-flagged merges** — `risk:cto-approve` label, or explicit CTO/CEO
|
||||||
|
sign-off request in the PR or issue thread.
|
||||||
|
|
||||||
|
The engineer opens the `uat→main` PR, classifies it against the three
|
||||||
|
categories above, and adds `cc @cpfarhood`. If the PR is in scope, the CTO
|
||||||
|
clicks Approve; once approved (and the four pre-gates are green), the
|
||||||
|
engineer merges.
|
||||||
|
|
||||||
|
### Phase 5 — Production deployment
|
||||||
|
|
||||||
|
A separate PR in `groombook/infra` bumps the overlay image tag for prod.
|
||||||
|
Handed to QA (Lint Roller) for review, then self-merged by the engineer.
|
||||||
|
|
||||||
|
## The four pre-gates (uat→main)
|
||||||
|
|
||||||
|
A `uat→main` PR is mergeable when **all four** are green:
|
||||||
|
|
||||||
|
1. **QA code review** — done on the dev→uat promotion PR.
|
||||||
|
2. **UAT deploy** — the UAT image built from the uat tip is live in UAT.
|
||||||
|
3. **UAT regression** — Shedward's full-feature UAT pass is green (no
|
||||||
|
pre-existing defects, no new defects).
|
||||||
|
4. **Security review** — Barkley's security code review is green.
|
||||||
|
|
||||||
|
Issue-thread QA / UAT / security approvals do **not** clear the Gitea
|
||||||
|
`required_approvals` gate. Only a Gitea **Approve** click from a member of
|
||||||
|
the `approvals_whitelist_username` for `main` clears it. In this repo that
|
||||||
|
whitelist is the engineer team (`gb_flea`, `gb_dogfather`).
|
||||||
|
|
||||||
|
## Style, tests, and quality bar
|
||||||
|
|
||||||
|
See
|
||||||
|
[`groombook/org/skills/coding-standards/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/coding-standards/SKILL.md)
|
||||||
|
for the engineering priority ordering, test requirements, no-hardcoded-values
|
||||||
|
rules, CalVer versioning policy, and the `git.farh.net` container registry
|
||||||
|
policy.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
|
||||||
|
See
|
||||||
|
[`groombook/org/skills/safety/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/safety/SKILL.md)
|
||||||
|
for the non-negotiable rules: no plaintext secrets, no `kubectl apply` to
|
||||||
|
`groombook`, no self-merge, no direct `tofu` runs, board approval for
|
||||||
|
destructive actions, escalation protocol.
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"wait-for-db": "node ./scripts/wait-for-db.mjs",
|
"wait-for-db": "node ./scripts/wait-for-db.mjs",
|
||||||
"migrate": "node ./scripts/wait-for-db.mjs && drizzle-kit migrate",
|
"migrate": "node ./scripts/wait-for-db.mjs && drizzle-kit migrate",
|
||||||
"seed": "node ./scripts/wait-for-db.mjs && tsx src/seed.ts",
|
"seed": "node ./scripts/wait-for-db.mjs && tsx src/seed.ts",
|
||||||
"reset": "node ./scripts/wait-for-db.mjs && tsx src/reset.ts",
|
"reset": "node ./scripts/wait-for-db.mjs && tsx src/reset.ts && drizzle-kit migrate && tsx src/seed.ts",
|
||||||
"studio": "drizzle-kit studio",
|
"studio": "drizzle-kit studio",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
|
|||||||
+39
-114
@@ -1,52 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* reset.ts — Drop all application tables, re-run migrations, and re-seed.
|
* reset.ts — Drop all application tables and re-run migrations + seed.
|
||||||
*
|
*
|
||||||
* Intended for local development only. Never run against production.
|
* Intended for local development only. Never run against production.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* DATABASE_URL=postgres://... npx tsx packages/db/src/reset.ts
|
* DATABASE_URL=postgres://... npx tsx packages/db/src/reset.ts
|
||||||
*
|
|
||||||
* GRO-2139: the entire drop→migrate→seed chain runs inside a single
|
|
||||||
* Postgres advisory lock (SEED_ADVISORY_LOCK_KEY) so a concurrent
|
|
||||||
* `seed.ts` (e.g. the dev `seed-test-data-*` Job being recreated at
|
|
||||||
* the top of the hour) cannot interleave between `reset.ts` (DROP)
|
|
||||||
* and `seed.ts` (TRUNCATE+insert) and collide on `invoices_pkey`.
|
|
||||||
*
|
|
||||||
* Why this matters: `seed.ts` derives every primary key from a single
|
|
||||||
* shared Mulberry32 PRNG seeded with 42 (see `createPrng(42)` and
|
|
||||||
* `uuid()` in seed.ts). Two concurrent same-profile seeders therefore
|
|
||||||
* emit *identical* ids for the same logical row, and any moment
|
|
||||||
* between a concurrent `seed.ts` TRUNCATE and INSERT is exactly the
|
|
||||||
* window in which the second seeder's INSERT can hit a pkey already
|
|
||||||
* taken by the first. Pre-GRO-2123 this raced unconditionally;
|
|
||||||
* GRO-2123 added the advisory lock around `runSeedBody` but left
|
|
||||||
* `reset.ts` and `drizzle-kit migrate` outside the lock. This script
|
|
||||||
* now wraps the *whole* chain in the same lock: `withSeedAdvisoryLock`
|
|
||||||
* pins the lock to one reserved session and the DROP → migrate → seed
|
|
||||||
* work runs on the rest of the pool, so the lock guarantees mutual
|
|
||||||
* exclusion against any concurrent seeder for the entire chain.
|
|
||||||
*
|
|
||||||
* See: groombook/infra `apps/base/reset-cronjob.yaml` (CronJob) and
|
|
||||||
* `apps/base/seed-job.yaml` (one-shot Job) — both invoke the same
|
|
||||||
* `seed.ts` code path on the same database in `groombook-dev`.
|
|
||||||
*/
|
*/
|
||||||
import postgres from "postgres";
|
|
||||||
import { drizzle } from "drizzle-orm/postgres-js";
|
|
||||||
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
import { dirname, resolve } from "node:path";
|
|
||||||
import * as schema from "./schema.js";
|
|
||||||
import {
|
|
||||||
SEED_ADVISORY_LOCK_KEY,
|
|
||||||
withSeedAdvisoryLock,
|
|
||||||
getProfile,
|
|
||||||
runSeedBody,
|
|
||||||
profiles,
|
|
||||||
} from "./seed.js";
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
import postgres from "postgres";
|
||||||
const __dirname = dirname(__filename);
|
|
||||||
const MIGRATIONS_FOLDER = resolve(__dirname, "../migrations");
|
|
||||||
|
|
||||||
async function reset() {
|
async function reset() {
|
||||||
const url = process.env.DATABASE_URL;
|
const url = process.env.DATABASE_URL;
|
||||||
@@ -55,88 +16,52 @@ async function reset() {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (process.env.NODE_ENV === "production" && process.env.ALLOW_RESET !== "true") {
|
||||||
process.env.NODE_ENV === "production" &&
|
console.error("[FATAL] db:reset must not be run in production without ALLOW_RESET=true.");
|
||||||
process.env.ALLOW_RESET !== "true"
|
|
||||||
) {
|
|
||||||
console.error(
|
|
||||||
"[FATAL] db:reset must not be run in production without ALLOW_RESET=true.",
|
|
||||||
);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pool sizing is load-bearing here. `withSeedAdvisoryLock` does
|
const client = postgres(url, { max: 1 });
|
||||||
// `pool.reserve()` to pin the advisory lock to one dedicated session
|
|
||||||
// (a session-level lock released on a *different* pooled connection is
|
|
||||||
// a no-op), and the DROP / migrate / seed work then runs on the
|
|
||||||
// *remaining* pooled connections. The lock provides mutual exclusion
|
|
||||||
// across processes regardless of how many connections the work uses —
|
|
||||||
// it does NOT require the work to share the lock's session.
|
|
||||||
//
|
|
||||||
// Therefore `max` must be ≥ 2: 1 reserved for the lock + ≥1 free for
|
|
||||||
// the work. `max: 1` would let `reserve()` consume the only connection
|
|
||||||
// and every query inside the callback would block forever waiting for
|
|
||||||
// a connection that never frees (connection-starvation deadlock). We
|
|
||||||
// use `max: 6` to match `seed()`'s headroom (1 reserved + 5 work).
|
|
||||||
const client = postgres(url, { max: 6 });
|
|
||||||
const db = drizzle(client, { schema });
|
|
||||||
|
|
||||||
try {
|
console.log("Dropping all application tables...\n");
|
||||||
await withSeedAdvisoryLock(client, async () => {
|
|
||||||
console.log("Dropping all application tables...\n");
|
|
||||||
|
|
||||||
// Drop dependencies (tables) first
|
// Drop in dependency order (children before parents)
|
||||||
await client`
|
await client`
|
||||||
DO $$ DECLARE
|
DO $$ DECLARE
|
||||||
r RECORD;
|
r RECORD;
|
||||||
BEGIN
|
BEGIN
|
||||||
FOR r IN (
|
FOR r IN (
|
||||||
SELECT tablename FROM pg_tables
|
SELECT tablename FROM pg_tables
|
||||||
WHERE schemaname = 'public'
|
WHERE schemaname = 'public'
|
||||||
) LOOP
|
) LOOP
|
||||||
EXECUTE 'DROP TABLE IF EXISTS public.' || quote_ident(r.tablename) || ' CASCADE';
|
EXECUTE 'DROP TABLE IF EXISTS public.' || quote_ident(r.tablename) || ' CASCADE';
|
||||||
END LOOP;
|
END LOOP;
|
||||||
END $$;
|
END $$;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Drop custom enums
|
// Drop custom enums
|
||||||
await client`
|
await client`
|
||||||
DO $$ DECLARE
|
DO $$ DECLARE
|
||||||
r RECORD;
|
r RECORD;
|
||||||
BEGIN
|
BEGIN
|
||||||
FOR r IN (
|
FOR r IN (
|
||||||
SELECT typname FROM pg_type
|
SELECT typname FROM pg_type
|
||||||
WHERE typtype = 'e' AND typnamespace = (
|
WHERE typtype = 'e' AND typnamespace = (
|
||||||
SELECT oid FROM pg_namespace WHERE nspname = 'public'
|
SELECT oid FROM pg_namespace WHERE nspname = 'public'
|
||||||
)
|
)
|
||||||
) LOOP
|
) LOOP
|
||||||
EXECUTE 'DROP TYPE IF EXISTS ' || quote_ident(r.typname) || ' CASCADE';
|
EXECUTE 'DROP TYPE IF EXISTS ' || quote_ident(r.typname) || ' CASCADE';
|
||||||
END LOOP;
|
END LOOP;
|
||||||
END $$;
|
END $$;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Drop the drizzle migrations tracking table
|
// Drop the drizzle migrations tracking table
|
||||||
await client`DROP TABLE IF EXISTS drizzle.__drizzle_migrations CASCADE`;
|
await client`DROP TABLE IF EXISTS drizzle.__drizzle_migrations CASCADE`;
|
||||||
await client`DROP SCHEMA IF EXISTS drizzle CASCADE`;
|
await client`DROP SCHEMA IF EXISTS drizzle CASCADE`;
|
||||||
|
|
||||||
console.log("✓ All tables and enums dropped\n");
|
console.log("✓ All tables and enums dropped\n");
|
||||||
|
|
||||||
console.log("Running migrations...");
|
await client.end();
|
||||||
await migrate(db, { migrationsFolder: MIGRATIONS_FOLDER });
|
|
||||||
console.log("✓ Migrations applied\n");
|
|
||||||
|
|
||||||
console.log("Seeding database...");
|
|
||||||
const profile = getProfile();
|
|
||||||
const cfg = profiles[profile];
|
|
||||||
await runSeedBody(client, db, profile, cfg);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`\n✓ Reset complete (advisory lock key=0x${SEED_ADVISORY_LOCK_KEY.toString(16)})`,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
await client.end();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset().catch((err) => {
|
reset().catch((err) => {
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ import type { MedicalAlert } from "@groombook/types";
|
|||||||
|
|
||||||
// ── Seed profile configuration ─────────────────────────────────────────────
|
// ── Seed profile configuration ─────────────────────────────────────────────
|
||||||
|
|
||||||
export type SeedProfile = "dev" | "uat" | "demo";
|
type SeedProfile = "dev" | "uat" | "demo";
|
||||||
|
|
||||||
export interface ProfileConfig {
|
interface ProfileConfig {
|
||||||
staffCount: { manager: number; receptionist: number; groomer: number; bather: number };
|
staffCount: { manager: number; receptionist: number; groomer: number; bather: number };
|
||||||
clientCount: number;
|
clientCount: number;
|
||||||
appointmentsBackDays: number;
|
appointmentsBackDays: number;
|
||||||
@@ -35,7 +35,7 @@ export interface ProfileConfig {
|
|||||||
includeUatClients: boolean;
|
includeUatClients: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const profiles: Record<SeedProfile, ProfileConfig> = {
|
const profiles: Record<SeedProfile, ProfileConfig> = {
|
||||||
dev: {
|
dev: {
|
||||||
staffCount: { manager: 1, receptionist: 1, groomer: 2, bather: 0 },
|
staffCount: { manager: 1, receptionist: 1, groomer: 2, bather: 0 },
|
||||||
clientCount: 100,
|
clientCount: 100,
|
||||||
@@ -70,8 +70,6 @@ function getProfile(): SeedProfile {
|
|||||||
return "uat";
|
return "uat";
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getProfile };
|
|
||||||
|
|
||||||
// ── Deterministic PRNG (Mulberry32) ──────────────────────────────────────────
|
// ── Deterministic PRNG (Mulberry32) ──────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1402,7 +1400,7 @@ async function seedKnownUsers() {
|
|||||||
// from runbooks without ambiguity and binds to the single-argument
|
// from runbooks without ambiguity and binds to the single-argument
|
||||||
// `pg_advisory_lock(int)` form, which postgres-js serializes as a plain
|
// `pg_advisory_lock(int)` form, which postgres-js serializes as a plain
|
||||||
// number (no bigint type plumbing required).
|
// number (no bigint type plumbing required).
|
||||||
export const SEED_ADVISORY_LOCK_KEY = 0x47524f4f; // "GROO" in ASCII — arbitrary, stable
|
const SEED_ADVISORY_LOCK_KEY = 0x47524f4f; // "GROO" in ASCII — arbitrary, stable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reserve a dedicated connection from `pool`, take the seed advisory lock
|
* Reserve a dedicated connection from `pool`, take the seed advisory lock
|
||||||
@@ -1415,7 +1413,7 @@ export const SEED_ADVISORY_LOCK_KEY = 0x47524f4f; // "GROO" in ASCII — arbitra
|
|||||||
* for the lock and release it from the same reserved connection. The
|
* for the lock and release it from the same reserved connection. The
|
||||||
* seed work itself still runs on the pooled connections.
|
* seed work itself still runs on the pooled connections.
|
||||||
*/
|
*/
|
||||||
export async function withSeedAdvisoryLock<T>(
|
async function withSeedAdvisoryLock<T>(
|
||||||
pool: ReturnType<typeof postgres>,
|
pool: ReturnType<typeof postgres>,
|
||||||
fn: () => Promise<T>,
|
fn: () => Promise<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
@@ -1473,7 +1471,7 @@ async function seed() {
|
|||||||
await client.end();
|
await client.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runSeedBody(
|
async function runSeedBody(
|
||||||
client: ReturnType<typeof postgres>,
|
client: ReturnType<typeof postgres>,
|
||||||
db: ReturnType<typeof drizzle>,
|
db: ReturnType<typeof drizzle>,
|
||||||
profile: SeedProfile,
|
profile: SeedProfile,
|
||||||
|
|||||||
Reference in New Issue
Block a user