@withwiz/coupon 0.1.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 ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "packageManager": "pnpm@11.10.0",
3
+ "name": "@withwiz/coupon",
4
+ "version": "0.1.0",
5
+ "description": "Multi-tenant, concurrency-safe coupon domain core for Prisma + Node.js applications",
6
+ "license": "MIT",
7
+ "author": "withwiz",
8
+ "private": false,
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/greeun/withwiz-coupon.git"
12
+ },
13
+ "homepage": "https://github.com/greeun/withwiz-coupon#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/greeun/withwiz-coupon/issues"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "keywords": [
21
+ "coupon",
22
+ "discount",
23
+ "promotion",
24
+ "campaign",
25
+ "prisma",
26
+ "multi-tenant",
27
+ "concurrency"
28
+ ],
29
+ "sideEffects": false,
30
+ "type": "module",
31
+ "main": "./dist/index.cjs",
32
+ "module": "./dist/index.mjs",
33
+ "types": "./dist/index.d.ts",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.mjs",
38
+ "require": "./dist/index.cjs"
39
+ },
40
+ "./package.json": "./package.json"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "prisma/fragment.prisma",
45
+ "README.md",
46
+ "LICENSE"
47
+ ],
48
+ "engines": {
49
+ "node": ">=18.17.0"
50
+ },
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "typecheck": "tsc --noEmit",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest",
56
+ "test:coverage": "vitest run --coverage",
57
+ "test:concurrency:x3": "node scripts/check-concurrency-x3.mjs",
58
+ "check:isolation": "node scripts/check-isolation.mjs",
59
+ "prisma:generate": "prisma generate --schema=prisma/schema.prisma",
60
+ "db:push": "prisma db push --schema=prisma/schema.prisma --skip-generate --accept-data-loss",
61
+ "prepublishOnly": "pnpm run build"
62
+ },
63
+ "dependencies": {
64
+ "zod": "^3.23.0",
65
+ "nanoid": "^5.0.0",
66
+ "@paralleldrive/cuid2": "^2.2.2"
67
+ },
68
+ "peerDependencies": {
69
+ "@prisma/client": ">=5.0.0 <8.0.0",
70
+ "typescript": ">=5.0.0"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "typescript": {
74
+ "optional": true
75
+ }
76
+ },
77
+ "devDependencies": {
78
+ "@prisma/client": "^6.0.0",
79
+ "@types/node": "^20.12.0",
80
+ "@vitest/coverage-v8": "^1.6.0",
81
+ "prisma": "^6.0.0",
82
+ "tsup": "^8.0.0",
83
+ "typescript": "^5.4.0",
84
+ "vitest": "^1.6.0"
85
+ }
86
+ }
@@ -0,0 +1,122 @@
1
+ // ============================================================================
2
+ // @withwiz/coupon — schema fragment
3
+ //
4
+ // Copy-paste into your schema.prisma. Model names may be prefixed when they
5
+ // collide with your own schema (`Campaign` is the usual one) — declare the
6
+ // mapping through createCouponClient({ models, tables }). Fields, indexes and
7
+ // @@unique constraints must be kept as they are: the concurrency guarantees
8
+ // depend on them. See "Schema Setup" in README.md.
9
+ // ============================================================================
10
+
11
+ model Coupon {
12
+ id String @id @default(cuid())
13
+ tenantId String @db.VarChar(64)
14
+ campaignId String?
15
+ code String @db.VarChar(64)
16
+ status CouponStatus @default(ACTIVE)
17
+ discount Json // DiscountPolicy (PERCENT | FIXED)
18
+ startsAt DateTime?
19
+ endsAt DateTime?
20
+ maxRedemptions Int?
21
+ redeemedCount Int @default(0)
22
+ maxRedemptionsPerUser Int?
23
+ eligibility Json // EligibilityRules
24
+ metadata Json?
25
+ createdAt DateTime @default(now())
26
+ updatedAt DateTime @updatedAt
27
+
28
+ campaign Campaign? @relation(fields: [campaignId], references: [id], onDelete: SetNull)
29
+ issuances CouponIssuance[]
30
+ redemptions CouponRedemption[]
31
+
32
+ @@unique([tenantId, code])
33
+ @@index([tenantId, status, endsAt])
34
+ @@index([campaignId])
35
+ @@index([tenantId, createdAt])
36
+ }
37
+
38
+ model Campaign {
39
+ id String @id @default(cuid())
40
+ tenantId String @db.VarChar(64)
41
+ name String @db.VarChar(200)
42
+ status CampaignStatus @default(ACTIVE)
43
+ discount Json
44
+ eligibility Json
45
+ startsAt DateTime?
46
+ endsAt DateTime?
47
+ maxCoupons Int?
48
+ issuedCount Int @default(0)
49
+ metadata Json?
50
+ createdAt DateTime @default(now())
51
+ updatedAt DateTime @updatedAt
52
+
53
+ coupons Coupon[]
54
+
55
+ @@unique([tenantId, name])
56
+ @@index([tenantId, status])
57
+ @@index([tenantId, createdAt])
58
+ }
59
+
60
+ model CouponIssuance {
61
+ id String @id @default(cuid())
62
+ tenantId String @db.VarChar(64)
63
+ couponId String
64
+ issuedToUserId String?
65
+ issuedBy String?
66
+ issuedAt DateTime @default(now())
67
+ metadata Json?
68
+
69
+ coupon Coupon @relation(fields: [couponId], references: [id], onDelete: Cascade)
70
+
71
+ @@index([tenantId, couponId])
72
+ @@index([tenantId, issuedToUserId, issuedAt])
73
+ @@index([issuedAt])
74
+ }
75
+
76
+ model CouponRedemption {
77
+ id String @id @default(cuid())
78
+ tenantId String @db.VarChar(64)
79
+ couponId String
80
+ redeemedByUserId String?
81
+ orderRef String? @db.VarChar(128)
82
+ discountAmount Int
83
+ currency String @db.VarChar(3)
84
+ redeemedAt DateTime @default(now())
85
+ metadata Json?
86
+
87
+ coupon Coupon @relation(fields: [couponId], references: [id], onDelete: Cascade)
88
+
89
+ @@unique([couponId, redeemedByUserId, orderRef])
90
+ @@index([tenantId, couponId, redeemedAt])
91
+ @@index([tenantId, redeemedByUserId, redeemedAt])
92
+ @@index([redeemedAt])
93
+ }
94
+
95
+ model CouponAuditLog {
96
+ id String @id @default(cuid())
97
+ tenantId String @db.VarChar(64)
98
+ action String @db.VarChar(50)
99
+ resourceId String? @db.VarChar(64)
100
+ resourceType String? @db.VarChar(30)
101
+ actorId String?
102
+ details Json?
103
+ createdAt DateTime @default(now())
104
+
105
+ @@index([tenantId, createdAt])
106
+ @@index([tenantId, resourceType, resourceId])
107
+ @@index([action])
108
+ }
109
+
110
+ enum CouponStatus {
111
+ DRAFT
112
+ ACTIVE
113
+ PAUSED
114
+ EXPIRED
115
+ EXHAUSTED
116
+ ARCHIVED
117
+ }
118
+
119
+ enum CampaignStatus {
120
+ ACTIVE
121
+ CLOSED
122
+ }