@12-apps/prisma 5.1.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/prisma",
3
- "version": "5.1.0",
3
+ "version": "6.0.0",
4
4
  "description": "Prisma host: the multi-file schema folder, the plugin migration seam, and the shared PrismaClient singleton with its audit / append-only extensions",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,7 +20,7 @@
20
20
  "lint": "pnpm run lint:files .",
21
21
  "lint:fix": "bash -c 'eslint \"${@:-.}\" --max-warnings 0 --fix' _",
22
22
  "typecheck": "tsc --noEmit",
23
- "prisma:generate": "node scripts/sync-lifecycle-schema.mjs && node scripts/sync-research-schema.mjs && node scripts/sync-shift-schema.mjs --check && node scripts/sync-jobs-schema.mjs --check && node scripts/sync-entitlements-schema.mjs --check && node scripts/sync-payments-schema.mjs --check && node scripts/sync-report-builder-schema.mjs --check && node scripts/sync-rbac-schema.mjs --check && node scripts/sync-notifications-schema.mjs --check && node scripts/sync-onboarding-schema.mjs --check && node scripts/sync-mcp-schema.mjs --check && node scripts/sync-realtime-schema.mjs --check && node scripts/sync-audit-schema.mjs --check && node scripts/sync-prisma-plugins.mjs --check && prisma generate",
23
+ "prisma:generate": "node scripts/sync-lifecycle-schema.mjs && node scripts/sync-research-schema.mjs && node scripts/sync-shift-schema.mjs --check && node scripts/sync-jobs-schema.mjs --check && node scripts/sync-entitlements-schema.mjs --check && node scripts/sync-payments-schema.mjs --check && node scripts/sync-auth-schema.mjs --check && node scripts/sync-report-builder-schema.mjs --check && node scripts/sync-rbac-schema.mjs --check && node scripts/sync-notifications-schema.mjs --check && node scripts/sync-onboarding-schema.mjs --check && node scripts/sync-mcp-schema.mjs --check && node scripts/sync-realtime-schema.mjs --check && node scripts/sync-audit-schema.mjs --check && node scripts/sync-prisma-plugins.mjs --check && prisma generate",
24
24
  "prisma:migrate": "prisma migrate dev",
25
25
  "prisma:push": "prisma db push",
26
26
  "prisma:studio": "prisma studio",
@@ -49,7 +49,9 @@
49
49
  "prisma:sync-realtime": "node scripts/sync-realtime-schema.mjs",
50
50
  "prisma:sync-realtime:check": "node scripts/sync-realtime-schema.mjs --check",
51
51
  "prisma:sync-audit": "node scripts/sync-audit-schema.mjs",
52
- "prisma:sync-audit:check": "node scripts/sync-audit-schema.mjs --check"
52
+ "prisma:sync-audit:check": "node scripts/sync-audit-schema.mjs --check",
53
+ "prisma:sync-auth": "node scripts/sync-auth-schema.mjs",
54
+ "prisma:sync-auth:check": "node scripts/sync-auth-schema.mjs --check"
53
55
  },
54
56
  "dependencies": {
55
57
  "@electric-sql/pglite": "0.2.17",
@@ -59,20 +61,21 @@
59
61
  "pglite-prisma-adapter": "0.7.2"
60
62
  },
61
63
  "devDependencies": {
62
- "@12-apps/audit": "^4.1.0",
64
+ "@12-apps/audit": "^5.0.1",
65
+ "@12-apps/auth": "^2.0.0",
63
66
  "@12-apps/entitlements": "^3.1.0",
64
- "@12-apps/entity-lifecycle": "^4.0.0",
67
+ "@12-apps/entity-lifecycle": "^4.2.0",
65
68
  "@12-apps/eslint-config": "^1.20.0",
66
- "@12-apps/jobs": "^4.1.0",
67
- "@12-apps/mcp": "^3.0.0",
68
- "@12-apps/notifications": "^4.1.0",
69
- "@12-apps/onboarding": "^2.0.0",
70
- "@12-apps/payments-backend": "^4.1.0",
69
+ "@12-apps/jobs": "^4.2.0",
70
+ "@12-apps/mcp": "^3.4.0",
71
+ "@12-apps/notifications": "^4.1.1",
72
+ "@12-apps/onboarding": "^2.0.1",
73
+ "@12-apps/payments-backend": "^4.11.0",
71
74
  "@12-apps/product-research": "^2.0.0",
72
- "@12-apps/rbac": "^4.0.0",
75
+ "@12-apps/rbac": "^4.0.2",
73
76
  "@12-apps/realtime": "^2.0.0",
74
- "@12-apps/report-builder": "^5.0.0",
75
- "@12-apps/shift": "^3.0.0",
77
+ "@12-apps/report-builder": "^5.0.2",
78
+ "@12-apps/shift": "^3.0.1",
76
79
  "@12-apps/typescript-config": "^1.20.0",
77
80
  "@types/node": "^22.10.6",
78
81
  "@vitest/coverage-v8": "^3.2.4",
@@ -5,7 +5,7 @@
5
5
  -- with no foreign key (that is what keeps the package host-agnostic), so a host
6
6
  -- that deletes its tenant row and then sweeps the remaining by-value tables by
7
7
  -- `client_id` could never remove a shift: the RAISE aborted the whole
8
- -- transaction. The origin host's demo-store reset does exactly that on EVERY deploy,
8
+ -- transaction. Future Pay's demo-store reset does exactly that on EVERY deploy,
9
9
  -- so a single shift row would have wedged the deploy step.
10
10
  --
11
11
  -- Deletability is host policy, not a package invariant. What the guard actually
@@ -18,13 +18,13 @@
18
18
  -- Authorization codes are deliberately absent: they are STATELESS signed blobs,
19
19
  -- so there is no table to create and nothing to sweep.
20
20
  --
21
- -- The columns, defaults, indexes and CHECK are the origin host's
21
+ -- The columns, defaults, indexes and CHECK are future-pay's
22
22
  -- `20260713120000_add_oauth_client_refresh`,
23
23
  -- `20260715180000_add_onboarding_state_mcp_connection` (the mcp_connections half
24
24
  -- — the onboarding half belongs to @12-apps/onboarding) and
25
25
  -- `20260720120000_add_mcp_connection_host` verbatim, minus the FK to `users`:
26
26
  -- this package cannot know the name of a host's user table, and a host that has
27
- -- one keeps its own constraint (the origin host's is ON DELETE CASCADE).
27
+ -- one keeps its own constraint (future-pay's is ON DELETE CASCADE).
28
28
  --
29
29
  -- EVERY statement is guarded (`IF NOT EXISTS`, and a conrelid-scoped DO block for
30
30
  -- the CHECK, which has no IF NOT EXISTS form). That is what makes adoption by a
@@ -35,7 +35,7 @@
35
35
  -- the difference bites exactly the host this file is written for: `CREATE TABLE IF
36
36
  -- NOT EXISTS` skips the whole table, columns included, so a host holding an OLDER
37
37
  -- shape of one of these tables silently keeps it. Each table below is therefore
38
- -- followed by a guarded `ADD COLUMN` for every column that reached the origin host in a
38
+ -- followed by a guarded `ADD COLUMN` for every column that reached future-pay in a
39
39
  -- LATER migration than its own CREATE. The full audit: `oauth_refresh_tokens
40
40
  -- .user_sub` (`20260713150000_add_oauth_refresh_user_sub`) and `mcp_connections
41
41
  -- .host` (`20260720120000_add_mcp_connection_host`). `oauth_clients` needs none —
@@ -112,10 +112,10 @@ CREATE INDEX IF NOT EXISTS "oauth_refresh_tokens_user_email_client_id_idx"
112
112
  -- `CREATE TABLE IF NOT EXISTS` skips the WHOLE table, so a host that already holds
113
113
  -- `oauth_refresh_tokens` in an OLDER SHAPE gets none of the columns declared above
114
114
  -- — statement-level guarding is not the same as column-level guarding. That is
115
- -- precisely how the origin host's own history ran: `user_sub` arrived in a SECOND
115
+ -- precisely how future-pay's own history ran: `user_sub` arrived in a SECOND
116
116
  -- migration (FUT-105, `20260713150000_add_oauth_refresh_user_sub`), so a host
117
117
  -- frozen before it would adopt this file, skip the CREATE, never get the column,
118
- -- and then fail on every refresh the package serves. Mirror the origin host's pair
118
+ -- and then fail on every refresh the package serves. Mirror future-pay's pair
119
119
  -- verbatim — guarded add with a backfill default to satisfy NOT NULL, then drop
120
120
  -- the default so the column matches the Prisma schema (`String`, no default).
121
121
  -- Both statements are no-ops on a fresh host and on a replay.
@@ -147,6 +147,6 @@ CREATE INDEX IF NOT EXISTS "mcp_connections_last_active_at_idx"
147
147
  ON "mcp_connections"("last_active_at");
148
148
 
149
149
  -- A host adopting this migration where `mcp_connections` predates the `host`
150
- -- column (the origin host added it in a later migration) gets it here; a fresh host
150
+ -- column (future-pay added it in a later migration) gets it here; a fresh host
151
151
  -- already has it from the CREATE above, so the guard makes both cases a no-op.
152
152
  ALTER TABLE "mcp_connections" ADD COLUMN IF NOT EXISTS "host" TEXT;
@@ -2,13 +2,13 @@
2
2
  -- the package and copied into a host's migrations folder by its
3
3
  -- plugin-migration sync.
4
4
  --
5
- -- The columns, defaults, indexes and the status CHECK are the origin host's
5
+ -- The columns, defaults, indexes and the status CHECK are future-pay's
6
6
  -- `20260715180000_add_onboarding_state_mcp_connection` verbatim, minus two
7
7
  -- things that are the HOST's vocabulary rather than the package's:
8
8
  --
9
9
  -- * the FKs to `users` / `clients` — this package cannot know the name of a
10
10
  -- host's user or tenant table. A host that has them keeps its own
11
- -- constraints (the origin host's are ON DELETE CASCADE) and they stay
11
+ -- constraints (future-pay's are ON DELETE CASCADE) and they stay
12
12
  -- compatible with everything the package writes;
13
13
  -- * the `mcp_connections` half of that migration, which belongs to
14
14
  -- @12-apps/mcp and ships in ITS folder.
@@ -16,7 +16,7 @@
16
16
  -- EVERY statement is guarded (`IF NOT EXISTS`, and a conrelid-scoped DO block
17
17
  -- for the CHECK, which has no IF NOT EXISTS form). That is what makes adoption
18
18
  -- by a host that ALREADY has the table a no-op instead of a failed deploy —
19
- -- the origin host applies this and nothing changes, no `prisma migrate resolve`
19
+ -- future-pay applies this and nothing changes, no `prisma migrate resolve`
20
20
  -- dance required. It is also what lets the PGlite provisioner replay it into an
21
21
  -- existing schema, which is how the harness and the integration suites run.
22
22
 
@@ -24,7 +24,7 @@
24
24
  --
25
25
  -- ============================ REPLAY SAFETY ================================
26
26
  -- Every statement is guarded, because the first adopters ALREADY HAVE these
27
- -- tables: the origin host created them by hand before the package existed, so this
27
+ -- tables: future-pay created them by hand before the package existed, so this
28
28
  -- migration must be a no-op there and correct on an empty database.
29
29
  --
30
30
  -- The guards are per COLUMN, not per table. `CREATE TABLE IF NOT EXISTS` alone
@@ -0,0 +1,58 @@
1
+ -- @12-apps/auth's own tables. Self-contained by design: every user reference is
2
+ -- an opaque `user_id` with NO foreign key into a host table, so this migration
3
+ -- applies to any schema regardless of what the host calls its users.
4
+
5
+ CREATE TABLE IF NOT EXISTS "auth_credentials" (
6
+ "user_id" TEXT NOT NULL,
7
+ "password_hash" TEXT,
8
+ "password_updated_at" TIMESTAMP(3),
9
+ "email_verified_at" TIMESTAMP(3),
10
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
11
+ "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
12
+
13
+ CONSTRAINT "auth_credentials_pkey" PRIMARY KEY ("user_id")
14
+ );
15
+
16
+ CREATE TABLE IF NOT EXISTS "auth_tokens" (
17
+ "id" TEXT NOT NULL,
18
+ "user_id" TEXT NOT NULL,
19
+ "purpose" TEXT NOT NULL,
20
+ "token_hash" TEXT NOT NULL,
21
+ "expires_at" TIMESTAMP(3) NOT NULL,
22
+ "consumed_at" TIMESTAMP(3),
23
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
24
+
25
+ CONSTRAINT "auth_tokens_pkey" PRIMARY KEY ("id")
26
+ );
27
+
28
+ -- The purposes, as a CHECK rather than a Prisma enum (house style). A token of
29
+ -- one purpose presented to the other endpoint must read as "no such token".
30
+ ALTER TABLE "auth_tokens"
31
+ ADD CONSTRAINT "auth_tokens_purpose_check"
32
+ CHECK ("purpose" IN ('EMAIL_VERIFICATION', 'PASSWORD_RESET'));
33
+
34
+ -- Unique because the hash IS the lookup key: a collision would mean two
35
+ -- accounts sharing one link.
36
+ CREATE UNIQUE INDEX IF NOT EXISTS "auth_tokens_token_hash_key" ON "auth_tokens"("token_hash");
37
+ CREATE INDEX IF NOT EXISTS "auth_tokens_user_id_purpose_idx" ON "auth_tokens"("user_id", "purpose");
38
+ CREATE INDEX IF NOT EXISTS "auth_tokens_expires_at_idx" ON "auth_tokens"("expires_at");
39
+
40
+ CREATE TABLE IF NOT EXISTS "auth_platform_settings" (
41
+ "key" TEXT NOT NULL,
42
+ "value" JSONB NOT NULL,
43
+ "updated_by" TEXT,
44
+ "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
45
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
46
+
47
+ CONSTRAINT "auth_platform_settings_pkey" PRIMARY KEY ("key")
48
+ );
49
+
50
+ -- The two switches, seeded to the posture this ships in: the method is OFF
51
+ -- until an operator turns it on, and verification is ON so that turning the
52
+ -- method on cannot accidentally open unverified registration. Both are the
53
+ -- conservative half of their own trade-off.
54
+ INSERT INTO "auth_platform_settings" ("key", "value", "updated_at")
55
+ VALUES
56
+ ('auth.email_password.enabled', 'false'::jsonb, CURRENT_TIMESTAMP),
57
+ ('auth.email_password.require_verification', 'true'::jsonb, CURRENT_TIMESTAMP)
58
+ ON CONFLICT ("key") DO NOTHING;
@@ -31,6 +31,7 @@
31
31
  "20260813120000_add_audit_log",
32
32
  "20260813120000_add_entity_lifecycle_tables",
33
33
  "20260813140000_add_notification_tables",
34
- "20260813180000_add_realtime_outbox"
34
+ "20260813180000_add_realtime_outbox",
35
+ "20260819230000_auth_email_password"
35
36
  ]
36
37
  }
@@ -0,0 +1,90 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // OWNED BY @12-apps/auth — the host's multi-file schema folder holds a COPY of
3
+ // this file, written by `pnpm --filter @12-apps/auth prisma:sync`. The
4
+ // credential persistence model lives here, in the package, never in the
5
+ // application.
6
+ //
7
+ // COPIED, never symlinked (the entity-lifecycle doctrine): `npm pack` silently
8
+ // drops symlinked entries, `turbo prune` dangles a link whose owner is not a
9
+ // declared dependency, and Prisma silently SKIPS a symlinked migration
10
+ // directory — a green deploy that applied no schema.
11
+ //
12
+ // Deliberately self-contained (the payments-backend doctrine, and the same rule
13
+ // `report-builder.prisma` states): every user reference is a `user_id` String
14
+ // column — NO foreign key into any host table — so these models work in a repo
15
+ // whose user table is called something else, or is in another database, or does
16
+ // not exist yet at migrate time.
17
+ //
18
+ // That constraint is why the three credential columns are a TABLE here rather
19
+ // than columns on the host's `users`, which is where they started. A package
20
+ // cannot add columns to a table it does not own, and a host should not have to
21
+ // hand-edit its own model to install a login flow.
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+
24
+ /// One account's password state, keyed by the host's user id as an OPAQUE
25
+ /// scalar. A row exists only once the address has a credential — an account
26
+ /// that has only ever signed in with Google has none, which is exactly the
27
+ /// "no password yet" state the security card reads.
28
+ model AuthCredential {
29
+ /// The host's user id. No relation by design: a stale id simply matches no
30
+ /// row, which is the same answer a deleted user should produce.
31
+ userId String @id @map("user_id")
32
+ /// `@12-apps/auth`'s self-describing scrypt string. Nullable because a row
33
+ /// may be created by verification before any password is set.
34
+ passwordHash String? @map("password_hash")
35
+ passwordUpdatedAt DateTime? @map("password_updated_at")
36
+ /// Answers "has this address been proven to belong to its owner", which is
37
+ /// not the same question as "does this account have a password".
38
+ emailVerifiedAt DateTime? @map("email_verified_at")
39
+ createdAt DateTime @default(now()) @map("created_at")
40
+ updatedAt DateTime @updatedAt @map("updated_at")
41
+
42
+ @@map("auth_credentials")
43
+ }
44
+
45
+ /// A single-use link: e-mail confirmation, or password reset.
46
+ ///
47
+ /// Only the HASH is stored. The raw token exists in the mail and nowhere else,
48
+ /// so a database read cannot mint a working link.
49
+ model AuthToken {
50
+ id String @id @default(uuid())
51
+ /// The host's user id, opaque. See the header — no FK.
52
+ userId String @map("user_id")
53
+ /// EMAIL_VERIFICATION | PASSWORD_RESET — a String + DB CHECK, the house style
54
+ /// used elsewhere rather than a Prisma enum. The two purposes never share a
55
+ /// namespace: a verification token presented to the reset endpoint is not a
56
+ /// token at all.
57
+ purpose String
58
+ /// SHA-256 of the raw token, lower-case hex. Unique because it IS the lookup
59
+ /// key, and a collision would mean two accounts sharing one link.
60
+ tokenHash String @unique @map("token_hash")
61
+ expiresAt DateTime @map("expires_at")
62
+ consumedAt DateTime? @map("consumed_at")
63
+ createdAt DateTime @default(now()) @map("created_at")
64
+
65
+ /// The sweep-on-password-change query, and the resend path's "does this user
66
+ /// already have one".
67
+ @@index([userId, purpose])
68
+ /// The retention job drops abandoned rows cheaply.
69
+ @@index([expiresAt])
70
+ @@map("auth_tokens")
71
+ }
72
+
73
+ /// Platform-wide switches, read on every sign-in attempt.
74
+ ///
75
+ /// Prefixed `auth_` rather than the bare `platform_settings` it started as: a
76
+ /// host may well own a settings table of its own, and a package must not claim
77
+ /// a name that generic. Deliberately NOT tenant-scoped — sign-in happens before
78
+ /// a tenant is known.
79
+ model AuthPlatformSetting {
80
+ key String @id
81
+ value Json
82
+ /// Who last changed it, by e-mail, for the audit trail the settings screen
83
+ /// shows. A plain string rather than a relation: the person may be an env
84
+ /// allowlist superadmin with no user row of their own.
85
+ updatedBy String? @map("updated_by")
86
+ updatedAt DateTime @updatedAt @map("updated_at")
87
+ createdAt DateTime @default(now()) @map("created_at")
88
+
89
+ @@map("auth_platform_settings")
90
+ }