@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 +16 -14
- package/prisma/migrations/20260826170000_locale_preferences/migration.sql +29 -0
- package/prisma/migrations/20260830120000_discount_weekly_schedule/migration.sql +40 -0
- package/prisma/plugin-migrations.json +3 -1
- package/prisma/schema/discounts.prisma +23 -0
- package/prisma/schema/i18n.prisma +49 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/prisma",
|
|
3
|
-
"version": "6.
|
|
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.
|
|
70
|
-
"@12-apps/auth": "^2.
|
|
71
|
-
"@12-apps/discounts": "^1.
|
|
72
|
-
"@12-apps/entitlements": "^3.
|
|
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.
|
|
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.
|
|
78
|
-
"@12-apps/notifications": "^4.
|
|
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.
|
|
81
|
-
"@12-apps/product-research": "^2.
|
|
82
|
-
"@12-apps/rbac": "^4.
|
|
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
|
+
}
|