@12-apps/prisma 6.4.0 → 6.6.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": "6.4.0",
3
+ "version": "6.6.0",
4
4
  "sideEffects": false,
5
5
  "description": "Prisma host: the multi-file schema folder, the plugin migration seam, and the shared PrismaClient singleton with its audit / append-only extensions",
6
6
  "main": "dist/index.js",
@@ -13,7 +13,7 @@
13
13
  }
14
14
  },
15
15
  "scripts": {
16
- "build": "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-feature-flags-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-discounts-schema.mjs --check && node scripts/sync-prisma-plugins.mjs --check && prisma generate && tsc",
16
+ "build": "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-feature-flags-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-i18n-schema.mjs --check && node scripts/sync-discounts-schema.mjs --check && node scripts/sync-prisma-plugins.mjs --check && prisma generate && tsc",
17
17
  "clean": "rm -rf dist node_modules coverage",
18
18
  "test": "node ../../scripts/vitest-with-teardown.mjs run",
19
19
  "test:watch": "vitest watch",
@@ -21,7 +21,7 @@
21
21
  "lint": "pnpm run lint:files .",
22
22
  "lint:fix": "bash -c 'eslint \"${@:-.}\" --max-warnings 0 --fix' _",
23
23
  "typecheck": "tsc --noEmit",
24
- "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-feature-flags-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-discounts-schema.mjs --check && node scripts/sync-prisma-plugins.mjs --check && prisma generate",
24
+ "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-feature-flags-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-i18n-schema.mjs --check && node scripts/sync-discounts-schema.mjs --check && node scripts/sync-prisma-plugins.mjs --check && prisma generate",
25
25
  "prisma:migrate": "prisma migrate dev",
26
26
  "prisma:push": "prisma db push",
27
27
  "prisma:studio": "prisma studio",
@@ -56,7 +56,8 @@
56
56
  "prisma:sync-auth": "node scripts/sync-auth-schema.mjs",
57
57
  "prisma:sync-auth:check": "node scripts/sync-auth-schema.mjs --check",
58
58
  "prisma:sync-discounts": "node scripts/sync-discounts-schema.mjs",
59
- "prisma:sync-discounts:check": "node scripts/sync-discounts-schema.mjs --check"
59
+ "prisma:sync-discounts:check": "node scripts/sync-discounts-schema.mjs --check",
60
+ "prisma:sync-i18n": "node scripts/sync-i18n-schema.mjs"
60
61
  },
61
62
  "dependencies": {
62
63
  "@electric-sql/pglite": "0.2.17",
@@ -66,20 +67,21 @@
66
67
  "pglite-prisma-adapter": "0.7.2"
67
68
  },
68
69
  "devDependencies": {
69
- "@12-apps/audit": "^5.4.0",
70
- "@12-apps/auth": "^2.14.0",
71
- "@12-apps/discounts": "^1.11.0",
72
- "@12-apps/entitlements": "^3.9.0",
70
+ "@12-apps/audit": "^5.7.1",
71
+ "@12-apps/auth": "^2.19.0",
72
+ "@12-apps/discounts": "^1.13.0",
73
+ "@12-apps/entitlements": "^3.10.0",
73
74
  "@12-apps/entity-lifecycle": "^4.13.0",
74
75
  "@12-apps/eslint-config": "^1.22.0",
75
- "@12-apps/feature-flags": "^2.4.0",
76
+ "@12-apps/feature-flags": "^2.4.1",
77
+ "@12-apps/i18n": "^1.3.0",
76
78
  "@12-apps/jobs": "^4.5.0",
77
- "@12-apps/mcp": "^3.14.0",
78
- "@12-apps/notifications": "^4.6.0",
79
+ "@12-apps/mcp": "^3.16.0",
80
+ "@12-apps/notifications": "^4.9.0",
79
81
  "@12-apps/onboarding": "^2.7.0",
80
- "@12-apps/payments-backend": "^4.21.0",
81
- "@12-apps/product-research": "^2.8.0",
82
- "@12-apps/rbac": "^4.10.0",
82
+ "@12-apps/payments-backend": "^4.26.0",
83
+ "@12-apps/product-research": "^2.10.0",
84
+ "@12-apps/rbac": "^4.14.0",
83
85
  "@12-apps/realtime": "^2.6.0",
84
86
  "@12-apps/report-builder": "^5.14.0",
85
87
  "@12-apps/shift": "^3.4.0",
@@ -0,0 +1,29 @@
1
+ -- Which language a person reads, as a table this package owns.
2
+ --
3
+ -- The copy resolvers shipped first and nothing could answer them: a
4
+ -- notification blueprint and the four auth mails both ask for the recipient's
5
+ -- tag at the moment they render, and the only place a host kept a language was
6
+ -- the browser's localStorage plus a cookie. That is THIS DEVICE's preference —
7
+ -- it does not follow the person to their phone, and it is not in the room at
8
+ -- all when a background job writes to them. So every notice resolved to the
9
+ -- default: the mechanism was complete and the fact was missing.
10
+ --
11
+ -- A TABLE and not a column on the host's users, for the reason auth.prisma
12
+ -- already states about its three credential columns: a package cannot add a
13
+ -- column to a table it does not own, and `user_id` carries NO foreign key, so
14
+ -- this applies to a repo whose user table is named something else, lives in
15
+ -- another database, or does not exist yet at migrate time.
16
+ --
17
+ -- A row EXISTS only once somebody has chosen. There is no default and no
18
+ -- backfill: absence is how "has not chosen" is said, and it is a different fact
19
+ -- from "reads pt-BR". Collapsing them would make a guess indistinguishable from
20
+ -- a choice — and since a reader's own setting outranks the tenant's, that guess
21
+ -- would then beat a store language nobody had a chance to lose to.
22
+ CREATE TABLE "locale_preferences" (
23
+ "user_id" TEXT NOT NULL,
24
+ "locale" TEXT NOT NULL,
25
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
26
+ "updated_at" TIMESTAMP(3) NOT NULL,
27
+
28
+ CONSTRAINT "locale_preferences_pkey" PRIMARY KEY ("user_id")
29
+ );
@@ -0,0 +1,40 @@
1
+ -- @12-apps/discounts: the WEEKLY schedule (FUT-996).
2
+ --
3
+ -- "Toda sexta, das 16:00 às 20:00" — a recurring window INSIDE the campaign
4
+ -- `[starts_at, ends_at)`, which until now was the only scheduling a rule had.
5
+ --
6
+ -- ── PURELY ADDITIVE, AND THAT IS THE POINT ────────────────────────────────
7
+ -- One nullable column, no backfill, no CHECK to widen, nothing dropped. NULL
8
+ -- means "always, within the campaign window", which is exactly what every
9
+ -- existing row already means — so this migration cannot change the price of
10
+ -- anything, and the release carrying it needs none of the expand/contract
11
+ -- ceremony a destructive DDL would (see the host's CLAUDE.md on why a
12
+ -- `DROP COLUMN` takes production down for the length of a healthcheck).
13
+ --
14
+ -- Replay-safe like its predecessor: `IF NOT EXISTS` throughout.
15
+ --
16
+ -- ── WHY JSON RATHER THAN A FOURTH TABLE ───────────────────────────────────
17
+ -- The recurrence is not SQL-queryable in any useful way. The admin grid's
18
+ -- vigência filter already punts on comparing two nullable timestamps against
19
+ -- `now()` and rides as a separate parameter the store resolves, so a schedule
20
+ -- table would buy queryability that nothing asks for — while adding a third
21
+ -- `include` to `loadDiscountRules`, which is on the storefront's hottest read
22
+ -- (a whole menu page's badge preview walks it once per request).
23
+ --
24
+ -- The trade is stated plainly: the database cannot check this column's SHAPE.
25
+ -- `assertSchedule` in `src/server/validate.ts` is the only gate in front of it,
26
+ -- and the evaluator is deliberately tolerant of a row that got past it — a
27
+ -- malformed window covers NOTHING rather than everything, so the failure mode
28
+ -- of a bad blob is a promotion that does not fire, never a cart priced at zero.
29
+ --
30
+ -- Shape, for a reader with only the database in front of them:
31
+ --
32
+ -- {"windows":[{"days":[4],"from":"16:00","to":"20:00"}]}
33
+ --
34
+ -- * `days` — Monday-first, 0..6. NOT `Date#getDay()`'s Sunday-first axis.
35
+ -- * `from`/`to` — "HH:MM", 24-hour, in the STORE's timezone, half-open
36
+ -- [from, to) exactly as [starts_at, ends_at) is.
37
+ -- * `to` < `from` means the window runs past midnight (a bar shutting at
38
+ -- 02:00), which is the most common happy hour there is.
39
+
40
+ ALTER TABLE "discounts" ADD COLUMN IF NOT EXISTS "schedule" JSONB;
@@ -36,6 +36,8 @@
36
36
  "20260820120000_add_user_feature_grants",
37
37
  "20260821120000_shift_kind_host_vocabulary",
38
38
  "20260821140000_discounts_package_owned",
39
- "20260824120000_payments_pending_verification"
39
+ "20260824120000_payments_pending_verification",
40
+ "20260826170000_locale_preferences",
41
+ "20260830120000_discount_weekly_schedule"
40
42
  ]
41
43
  }
@@ -79,6 +79,29 @@ model Discount {
79
79
  // either side = open-ended.
80
80
  startsAt DateTime? @map("starts_at")
81
81
  endsAt DateTime? @map("ends_at")
82
+ // The WEEKLY schedule INSIDE that window (FUT-996) — "toda sexta, das 16:00
83
+ // às 20:00", "segunda e terça à tarde". NULL = always, within
84
+ // [starts_at, ends_at), which is what every row predating the feature means.
85
+ //
86
+ // {"windows":[{"days":[4],"from":"16:00","to":"20:00"}]}
87
+ //
88
+ // `days` is Monday-first 0..6 — the axis every hours surface uses, and NOT
89
+ // `Date#getDay()`'s Sunday-first one. `from`/`to` are "HH:MM" in the STORE's
90
+ // timezone, half-open [from, to) exactly as the campaign window is, and a
91
+ // `to` earlier than `from` runs past midnight rather than being invalid.
92
+ //
93
+ // JSON rather than a fourth table because the recurrence is not usefully
94
+ // SQL-queryable (the grid's vigência filter already cannot express the
95
+ // campaign window either), and a fourth table would put a third `include` on
96
+ // the storefront's hottest read. The cost is that the DATABASE cannot check
97
+ // this shape: `assertSchedule` is the only gate, and the evaluator treats a
98
+ // malformed window as covering NOTHING, so a bad blob is a promotion that
99
+ // does not fire rather than a cart priced wrong.
100
+ //
101
+ // A recurring window is a WALL-CLOCK fact, so it is only meaningful against
102
+ // the store's own timezone. The engine never reads one: a host resolves the
103
+ // instant into a weekday and a minute-of-day and passes those in.
104
+ schedule Json? @map("schedule")
82
105
  // Minimum PRE-DISCOUNT cart subtotal, in cents. Always compared against the
83
106
  // untouched subtotal, never a running total — which is what makes the result
84
107
  // independent of the order the discounts are applied in.
@@ -0,0 +1,49 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // OWNED BY @12-apps/i18n — the host's multi-file schema folder holds a COPY of
3
+ // this file, written by the partials assembler from this package's `wiring.db`
4
+ // declaration. Never edit the copy.
5
+ //
6
+ // COPIED, never symlinked (the entity-lifecycle doctrine): `npm pack` silently
7
+ // drops symlinked entries, `turbo prune` dangles a link whose owner is not a
8
+ // declared dependency, and Prisma silently SKIPS a symlinked migration
9
+ // directory — a green deploy that applied no schema.
10
+ //
11
+ // Self-contained, the same rule `auth.prisma` states: the user reference is a
12
+ // `user_id` String column with NO foreign key into any host table, so this
13
+ // model works in a repo whose user table is called something else, is in
14
+ // another database, or does not exist yet at migrate time.
15
+ //
16
+ // That constraint is why this is a TABLE rather than a `locale` column on the
17
+ // host's `users` — which is exactly where it started, and where the first
18
+ // implementation put it. A package cannot add a column to a table it does not
19
+ // own, and a host should not have to hand-edit its own model to install a
20
+ // language preference. The auth package learned this with the three credential
21
+ // columns; this is the same lesson, not a new one.
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+
24
+ /// Which language one person reads, keyed by the host's user id as an OPAQUE
25
+ /// scalar.
26
+ ///
27
+ /// A row EXISTS only once somebody has chosen. That absence is the design, not
28
+ /// an omission: "has not chosen" and "reads pt-BR" are different facts, and a
29
+ /// row-per-user with a default would collapse them — which matters because a
30
+ /// reader's own setting outranks the tenant's, so a default written here would
31
+ /// beat a store language nobody had a chance to lose to.
32
+ ///
33
+ /// Storing it at all is what lets a notice follow its READER. A browser's
34
+ /// `localStorage` and cookie answer for a screen, where the reader is present;
35
+ /// they answer nothing for a notification stored as rendered text, or a mail
36
+ /// sent by a background job, because neither has that browser in the room.
37
+ model LocalePreference {
38
+ /// The host's user id. No relation by design: a stale id simply matches no
39
+ /// row, which is the same answer a deleted user should produce.
40
+ userId String @id @map("user_id")
41
+ /// A canonical tag from this package's own list, validated on the way in AND
42
+ /// on the way out — a value written by an older release or a hand-run SQL fix
43
+ /// must not become the one thing in the system that never passed `matchLocale`.
44
+ locale String
45
+ createdAt DateTime @default(now()) @map("created_at")
46
+ updatedAt DateTime @updatedAt @map("updated_at")
47
+
48
+ @@map("locale_preferences")
49
+ }