@stacksjs/orm 0.72.16 → 0.72.18

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.
Files changed (102) hide show
  1. package/dist/index.d.ts +97 -97
  2. package/dist/index.js +7 -7
  3. package/dist/models/Activity.ts +95 -0
  4. package/dist/models/AnalyticsEvent.ts +70 -0
  5. package/dist/models/Automation.ts +23 -0
  6. package/dist/models/AutomationRun.ts +26 -0
  7. package/dist/models/Board.ts +104 -0
  8. package/dist/models/BoardColumn.ts +88 -0
  9. package/dist/models/Campaign.ts +321 -0
  10. package/dist/models/CampaignSend.ts +191 -0
  11. package/dist/models/CampaignVariant.ts +25 -0
  12. package/dist/models/Card.ts +118 -0
  13. package/dist/models/CardComment.ts +68 -0
  14. package/dist/models/Comment.ts +109 -0
  15. package/dist/models/CommunicationSuppression.ts +22 -0
  16. package/dist/models/ConsentEvent.ts +30 -0
  17. package/dist/models/Content/Author.ts +109 -0
  18. package/dist/models/Content/Menu.ts +56 -0
  19. package/dist/models/Content/MenuItem.ts +91 -0
  20. package/dist/models/Content/Page.ts +205 -0
  21. package/dist/models/Content/PageRevision.ts +86 -0
  22. package/dist/models/Content/Post.ts +230 -0
  23. package/dist/models/Content/Redirect.ts +80 -0
  24. package/dist/models/Deployment.ts +117 -0
  25. package/dist/models/EmailIdempotency.ts +64 -0
  26. package/dist/models/EmailList.ts +134 -0
  27. package/dist/models/EmailListSubscriber.ts +91 -0
  28. package/dist/models/EmailSuppression.ts +52 -0
  29. package/dist/models/EmailWebhookEvent.ts +51 -0
  30. package/dist/models/Error.ts +77 -0
  31. package/dist/models/FailedJob.ts +84 -0
  32. package/dist/models/Forms/Form.ts +91 -0
  33. package/dist/models/Forms/FormField.ts +127 -0
  34. package/dist/models/Forms/FormSubmission.ts +114 -0
  35. package/dist/models/Job.ts +69 -0
  36. package/dist/models/Label.ts +67 -0
  37. package/dist/models/Log.ts +134 -0
  38. package/dist/models/MagicLinkToken.ts +97 -0
  39. package/dist/models/MailPreference.ts +213 -0
  40. package/dist/models/Notification.ts +68 -0
  41. package/dist/models/NotificationDelivery.ts +102 -0
  42. package/dist/models/PaymentMethod.ts +98 -0
  43. package/dist/models/PaymentProduct.ts +91 -0
  44. package/dist/models/PaymentTransaction.ts +78 -0
  45. package/dist/models/QueryLog.ts +182 -0
  46. package/dist/models/README.md +31 -0
  47. package/dist/models/Release.ts +90 -0
  48. package/dist/models/Request.ts +133 -0
  49. package/dist/models/SenderDomain.ts +22 -0
  50. package/dist/models/Site.ts +112 -0
  51. package/dist/models/SiteDomain.ts +74 -0
  52. package/dist/models/SmsOptOut.ts +65 -0
  53. package/dist/models/SocialAccount.ts +70 -0
  54. package/dist/models/SocialPost.ts +144 -0
  55. package/dist/models/Subscriber.ts +89 -0
  56. package/dist/models/SubscriberEmail.ts +55 -0
  57. package/dist/models/Subscription.ts +153 -0
  58. package/dist/models/Tag.ts +105 -0
  59. package/dist/models/Team.ts +75 -0
  60. package/dist/models/TeamInvitation.ts +159 -0
  61. package/dist/models/TeamMember.ts +88 -0
  62. package/dist/models/UsageEvent.ts +24 -0
  63. package/dist/models/User.ts +142 -0
  64. package/dist/models/commerce/Auction.ts +173 -0
  65. package/dist/models/commerce/AuctionItem.ts +204 -0
  66. package/dist/models/commerce/Bid.ts +129 -0
  67. package/dist/models/commerce/Cart.ts +140 -0
  68. package/dist/models/commerce/CartItem.ts +138 -0
  69. package/dist/models/commerce/Category.ts +125 -0
  70. package/dist/models/commerce/Coupon.ts +186 -0
  71. package/dist/models/commerce/Customer.ts +132 -0
  72. package/dist/models/commerce/DeliveryRoute.ts +127 -0
  73. package/dist/models/commerce/DeliveryStop.ts +166 -0
  74. package/dist/models/commerce/DigitalDelivery.ts +106 -0
  75. package/dist/models/commerce/Driver.ts +168 -0
  76. package/dist/models/commerce/DriverPing.ts +103 -0
  77. package/dist/models/commerce/GiftCard.ts +195 -0
  78. package/dist/models/commerce/LicenseKey.ts +89 -0
  79. package/dist/models/commerce/LoyaltyPoint.ts +108 -0
  80. package/dist/models/commerce/LoyaltyReward.ts +129 -0
  81. package/dist/models/commerce/Manufacturer.ts +90 -0
  82. package/dist/models/commerce/Order.ts +198 -0
  83. package/dist/models/commerce/OrderIdempotency.ts +34 -0
  84. package/dist/models/commerce/OrderItem.ts +51 -0
  85. package/dist/models/commerce/Payment.ts +182 -0
  86. package/dist/models/commerce/Pledge.ts +112 -0
  87. package/dist/models/commerce/PrintDevice.ts +120 -0
  88. package/dist/models/commerce/Product.ts +153 -0
  89. package/dist/models/commerce/ProductUnit.ts +113 -0
  90. package/dist/models/commerce/ProductVariant.ts +111 -0
  91. package/dist/models/commerce/Receipt.ts +144 -0
  92. package/dist/models/commerce/Review.ts +171 -0
  93. package/dist/models/commerce/ShippingMethod.ts +102 -0
  94. package/dist/models/commerce/ShippingRate.ts +79 -0
  95. package/dist/models/commerce/ShippingZone.ts +116 -0
  96. package/dist/models/commerce/TaxRate.ts +167 -0
  97. package/dist/models/commerce/Transaction.ts +127 -0
  98. package/dist/models/commerce/WaitlistProduct.ts +151 -0
  99. package/dist/models/commerce/WaitlistRestaurant.ts +189 -0
  100. package/dist/models/realtime/Websocket.ts +78 -0
  101. package/dist/password-policy.ts +46 -0
  102. package/package.json +4 -4
@@ -0,0 +1,103 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ /**
5
+ * One position fix from a driver's device.
6
+ *
7
+ * The append-only history behind `drivers.latitude/longitude`. A tracking map
8
+ * needs the present position, but everything else about a delivery needs the
9
+ * series: drawing the path already travelled, replaying a disputed drop,
10
+ * measuring how long a stop actually took, and deriving an ETA from recent
11
+ * speed rather than straight-line distance.
12
+ *
13
+ * Deliberately not `useSearch` (indexing a row every few seconds is pointless
14
+ * and expensive) and not `useApi` (writes come through
15
+ * `recordDriverPing`, which does the fan-out, and reads are scoped to a
16
+ * route). `usePrunable` keeps the table from growing without bound.
17
+ */
18
+ export default defineModel({
19
+ name: 'DriverPing',
20
+ table: 'driver_pings',
21
+ primaryKey: 'id',
22
+ autoIncrement: true,
23
+
24
+ traits: {
25
+ useUuid: true,
26
+ useTimestamps: true,
27
+ useSeeder: { count: 0 },
28
+ },
29
+
30
+ belongsTo: ['Driver', 'DeliveryRoute'],
31
+
32
+ attributes: {
33
+ latitude: {
34
+ order: 1,
35
+ required: true,
36
+ fillable: true,
37
+ validation: {
38
+ rule: schema.number().required().min(-90).max(90),
39
+ message: {
40
+ min: 'Latitude must be between -90 and 90',
41
+ max: 'Latitude must be between -90 and 90',
42
+ },
43
+ },
44
+ factory: faker => faker.location.latitude(),
45
+ },
46
+
47
+ longitude: {
48
+ order: 2,
49
+ required: true,
50
+ fillable: true,
51
+ validation: {
52
+ rule: schema.number().required().min(-180).max(180),
53
+ message: {
54
+ min: 'Longitude must be between -180 and 180',
55
+ max: 'Longitude must be between -180 and 180',
56
+ },
57
+ },
58
+ factory: faker => faker.location.longitude(),
59
+ },
60
+
61
+ /** Degrees clockwise from true north. */
62
+ heading: {
63
+ order: 3,
64
+ fillable: true,
65
+ validation: { rule: schema.number().min(0).max(360) },
66
+ factory: faker => faker.number.int({ min: 0, max: 359 }),
67
+ },
68
+
69
+ /** Metres per second. */
70
+ speed: {
71
+ order: 4,
72
+ fillable: true,
73
+ default: 0,
74
+ validation: { rule: schema.number().min(0) },
75
+ factory: faker => faker.number.int({ min: 0, max: 30 }),
76
+ },
77
+
78
+ /**
79
+ * Reported horizontal accuracy in metres. Worth keeping: a fix with 800m
80
+ * of error should move the marker differently than one with 5m, and
81
+ * without this the map cannot tell them apart.
82
+ */
83
+ accuracy: {
84
+ order: 5,
85
+ fillable: true,
86
+ validation: { rule: schema.number().min(0) },
87
+ factory: faker => faker.number.int({ min: 3, max: 60 }),
88
+ },
89
+
90
+ /**
91
+ * When the device took the fix, which is not when the server received it.
92
+ * A driver through a tunnel sends five fixes at once on the far side, and
93
+ * ordering them by arrival draws a path that never happened.
94
+ */
95
+ recordedAt: {
96
+ order: 6,
97
+ required: true,
98
+ fillable: true,
99
+ validation: { rule: schema.timestamp().required() },
100
+ factory: () => new Date().toISOString(),
101
+ },
102
+ },
103
+ } as const)
@@ -0,0 +1,195 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'GiftCard',
6
+ table: 'gift_cards',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ traits: {
11
+ useUuid: true,
12
+ useTimestamps: true,
13
+ useSearch: {
14
+ displayable: ['id', 'code', 'initialBalance', 'currentBalance', 'status', 'expiryDate', 'isActive'],
15
+ searchable: ['code', 'recipientName', 'recipientEmail', 'purchaserId'],
16
+ sortable: ['createdAt', 'expiryDate', 'initialBalance', 'currentBalance'],
17
+ filterable: ['status', 'isDigital', 'isReloadable', 'isActive'],
18
+ },
19
+
20
+ useSeeder: {
21
+ count: 20,
22
+ },
23
+
24
+ useApi: {
25
+ uri: 'gift-cards',
26
+ middleware: ['auth'],
27
+ },
28
+
29
+ observe: true,
30
+ },
31
+
32
+ belongsTo: ['Customer'],
33
+ hasMany: ['Order'],
34
+
35
+ attributes: {
36
+ code: {
37
+ unique: true,
38
+ order: 1,
39
+ fillable: true,
40
+ validation: {
41
+ rule: schema.string().required().max(50),
42
+ message: {
43
+ max: 'Code must have a maximum of 50 characters',
44
+ },
45
+ },
46
+ factory: faker => faker.string.alphanumeric(12).toUpperCase(),
47
+ },
48
+
49
+ initialBalance: {
50
+ order: 2,
51
+ fillable: true,
52
+ validation: {
53
+ rule: schema.number().required().min(1),
54
+ },
55
+ factory: faker => faker.number.int({ min: 100, max: 2000 }),
56
+ },
57
+
58
+ currentBalance: {
59
+ order: 3,
60
+ fillable: true,
61
+ validation: {
62
+ rule: schema.number().required().min(0),
63
+ },
64
+ factory: () => 1,
65
+ },
66
+
67
+ currency: {
68
+ order: 4,
69
+ fillable: true,
70
+ default: 'USD',
71
+ validation: {
72
+ rule: schema.string().required().max(3),
73
+ },
74
+ factory: faker => faker.helpers.arrayElement(['USD', 'EUR', 'GBP', 'CAD', 'AUD']),
75
+ },
76
+
77
+ status: {
78
+ order: 5,
79
+ fillable: true,
80
+ validation: {
81
+ rule: schema.enum(['ACTIVE', 'USED', 'EXPIRED', 'DEACTIVATED']).required(),
82
+ },
83
+ factory: faker => faker.helpers.arrayElement(['ACTIVE', 'USED', 'EXPIRED', 'DEACTIVATED']),
84
+ },
85
+
86
+ purchaser_id: {
87
+ order: 6,
88
+ fillable: true,
89
+ validation: {
90
+ rule: schema.string(),
91
+ },
92
+ factory: faker => faker.string.uuid(),
93
+ },
94
+
95
+ recipient_email: {
96
+ order: 7,
97
+ fillable: true,
98
+ validation: {
99
+ rule: schema.string().email(),
100
+ },
101
+ factory: faker => faker.helpers.maybe(() => faker.internet.email(), { probability: 0.7 }),
102
+ },
103
+
104
+ recipientName: {
105
+ order: 8,
106
+ fillable: true,
107
+ validation: {
108
+ rule: schema.string(),
109
+ },
110
+ factory: faker => faker.helpers.maybe(() => faker.person.fullName(), { probability: 0.6 }),
111
+ },
112
+
113
+ personalMessage: {
114
+ order: 9,
115
+ fillable: true,
116
+ validation: {
117
+ rule: schema.string(),
118
+ },
119
+ factory: faker => faker.helpers.maybe(() => faker.lorem.sentence(), { probability: 0.4 }),
120
+ },
121
+
122
+ isDigital: {
123
+ default: false,
124
+ order: 10,
125
+ fillable: true,
126
+ validation: {
127
+ rule: schema.boolean(),
128
+ },
129
+ factory: faker => faker.datatype.boolean({ probability: 0.8 }), // 80% digital
130
+ },
131
+
132
+ isReloadable: {
133
+ default: false,
134
+ order: 11,
135
+ fillable: true,
136
+ validation: {
137
+ rule: schema.boolean(),
138
+ },
139
+ factory: faker => faker.datatype.boolean({ probability: 0.3 }), // 30% reloadable
140
+ },
141
+
142
+ isActive: {
143
+ order: 12,
144
+ fillable: true,
145
+ default: true,
146
+ validation: {
147
+ rule: schema.boolean(),
148
+ },
149
+ factory: faker => faker.datatype.boolean({ probability: 0.9 }), // 90% active
150
+ },
151
+
152
+ expiryDate: {
153
+ order: 13,
154
+ fillable: true,
155
+ validation: {
156
+ rule: schema.timestamp(),
157
+ message: {
158
+ timestamp: 'Expiry date must be a valid timestamp',
159
+ },
160
+ },
161
+ factory: (faker) => {
162
+ const date = faker.date.future()
163
+ return date.toISOString().slice(0, 19).replace('T', ' ')
164
+ },
165
+ },
166
+
167
+ lastUsedDate: {
168
+ order: 14,
169
+ fillable: true,
170
+ validation: {
171
+ rule: schema.timestamp(),
172
+ message: {
173
+ timestamp: 'Last used date must be a valid timestamp',
174
+ },
175
+ },
176
+ factory: (faker) => {
177
+ const date = faker.date.future()
178
+ return date.toISOString().slice(0, 19).replace('T', ' ')
179
+ },
180
+ },
181
+
182
+ templateId: {
183
+ order: 15,
184
+ fillable: true,
185
+ validation: {
186
+ rule: schema.string(),
187
+ },
188
+ factory: faker => faker.helpers.maybe(() => faker.string.uuid(), { probability: 0.5 }),
189
+ },
190
+ },
191
+
192
+ dashboard: {
193
+ highlight: true,
194
+ },
195
+ } as const)
@@ -0,0 +1,89 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'LicenseKey',
6
+ table: 'license_keys',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ traits: {
11
+ useUuid: true,
12
+ useTimestamps: true,
13
+ useSearch: {
14
+ displayable: ['id', 'key', 'template', 'expiryDate', 'status'],
15
+ searchable: ['key'],
16
+ sortable: ['template', 'expiryDate', 'status', 'createdAt', 'updatedAt'],
17
+ filterable: ['status', 'template'],
18
+ },
19
+
20
+ useSeeder: {
21
+ count: 10,
22
+ },
23
+
24
+ useApi: {
25
+ uri: 'license-keys',
26
+ middleware: ['auth'],
27
+ },
28
+
29
+ observe: true,
30
+ },
31
+
32
+ belongsTo: ['Customer', 'Product', 'Order'],
33
+
34
+ attributes: {
35
+ key: {
36
+ unique: true,
37
+ order: 1,
38
+ fillable: true,
39
+ validation: {
40
+ rule: schema.string().matches(/^[A-Z0-9]{4}(?:-[A-Z0-9]{4}){4}$/).required(),
41
+ message: {
42
+ matches: 'License key must be in the format XXXX-XXXX-XXXX-XXXX-XXXX',
43
+ },
44
+ },
45
+ factory: (faker) => {
46
+ const generateBlock = () => faker.string.alphanumeric({ length: 4, casing: 'upper' })
47
+ return `${generateBlock()}-${generateBlock()}-${generateBlock()}-${generateBlock()}-${generateBlock()}`
48
+ },
49
+ },
50
+
51
+ template: {
52
+ order: 2,
53
+ fillable: true,
54
+ validation: {
55
+ rule: schema.enum(['Standard License', 'Premium License', 'Enterprise License']).required(),
56
+ },
57
+ factory: faker => faker.helpers.arrayElement(['Standard License', 'Premium License', 'Enterprise License']),
58
+ },
59
+
60
+ expiryDate: {
61
+ order: 3,
62
+ fillable: true,
63
+ validation: {
64
+ rule: schema.timestamp().required(),
65
+ message: {
66
+ timestamp: 'Expiry date must be a valid timestamp',
67
+ },
68
+ },
69
+ factory: (faker) => {
70
+ const date = faker.date.future()
71
+ return date.toISOString().slice(0, 19).replace('T', ' ')
72
+ },
73
+ },
74
+
75
+ status: {
76
+ default: 'unassigned',
77
+ order: 4,
78
+ fillable: true,
79
+ validation: {
80
+ rule: schema.enum(['active', 'inactive', 'unassigned']),
81
+ },
82
+ factory: faker => faker.helpers.arrayElement(['active', 'inactive', 'unassigned']),
83
+ },
84
+ },
85
+
86
+ dashboard: {
87
+ highlight: true,
88
+ },
89
+ } as const)
@@ -0,0 +1,108 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'LoyaltyPoint',
6
+ table: 'loyalty_points',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ traits: {
11
+ useUuid: true,
12
+ useTimestamps: true,
13
+ useSearch: {
14
+ displayable: ['id', 'walletId', 'points', 'source', 'description', 'expiryDate', 'isUsed'],
15
+ searchable: ['description', 'source', 'sourceReferenceId'],
16
+ sortable: ['createdAt', 'points', 'expiryDate'],
17
+ filterable: ['walletId', 'source', 'isUsed'],
18
+ },
19
+
20
+ useSeeder: {
21
+ count: 50,
22
+ },
23
+
24
+ useApi: {
25
+ uri: 'loyalty-points',
26
+ middleware: ['auth'],
27
+ },
28
+
29
+ observe: true,
30
+ },
31
+
32
+ attributes: {
33
+ walletId: {
34
+ order: 1,
35
+ fillable: true,
36
+ validation: {
37
+ rule: schema.string().required(),
38
+ },
39
+ factory: faker => faker.string.uuid(),
40
+ },
41
+
42
+ points: {
43
+ order: 2,
44
+ fillable: true,
45
+ validation: {
46
+ rule: schema.number().required().min(1),
47
+ },
48
+ factory: faker => faker.number.int({ min: 1, max: 500 }),
49
+ },
50
+
51
+ source: {
52
+ order: 3,
53
+ fillable: true,
54
+ validation: {
55
+ rule: schema.string(),
56
+ },
57
+ factory: faker => faker.helpers.arrayElement(['ORDER', 'PROMOTION', 'REFERRAL', 'MANUAL']),
58
+ },
59
+
60
+ sourceReferenceId: {
61
+ order: 4,
62
+ fillable: true,
63
+ validation: {
64
+ rule: schema.string(),
65
+ },
66
+ factory: faker => faker.string.uuid(),
67
+ },
68
+
69
+ description: {
70
+ order: 5,
71
+ fillable: true,
72
+ validation: {
73
+ rule: schema.string(),
74
+ },
75
+ factory: faker => faker.helpers.arrayElement([
76
+ `Points earned from order #${faker.string.alphanumeric(8).toUpperCase()}`,
77
+ `Bonus points from promotion`,
78
+ `Referral bonus for inviting a friend`,
79
+ `Manual adjustment by administrator`,
80
+ ]),
81
+ },
82
+
83
+ expiryDate: {
84
+ order: 6,
85
+ fillable: true,
86
+ validation: {
87
+ rule: schema.timestamp(),
88
+ },
89
+ factory: (faker) => {
90
+ const date = faker.date.future()
91
+ return date.toISOString().slice(0, 19).replace('T', ' ')
92
+ },
93
+ },
94
+
95
+ isUsed: {
96
+ order: 7,
97
+ fillable: true,
98
+ validation: {
99
+ rule: schema.boolean(),
100
+ },
101
+ factory: faker => faker.datatype.boolean({ probability: 0.3 }), // 30% chance of being used
102
+ },
103
+ },
104
+
105
+ dashboard: {
106
+ highlight: true,
107
+ },
108
+ } as const)
@@ -0,0 +1,129 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'LoyaltyReward',
6
+ table: 'loyalty_rewards',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ traits: {
11
+ useUuid: true,
12
+ useTimestamps: true,
13
+ useSearch: {
14
+ displayable: ['id', 'name', 'pointsRequired', 'rewardType', 'isActive'],
15
+ searchable: ['name', 'description', 'rewardType'],
16
+ sortable: ['pointsRequired', 'createdAt', 'updatedAt'],
17
+ filterable: ['rewardType', 'isActive'],
18
+ },
19
+
20
+ useSeeder: {
21
+ count: 15,
22
+ },
23
+
24
+ useApi: {
25
+ // Public catalog: anyone may browse, only authenticated callers may
26
+ // write. Declared explicitly because the trait now defaults BOTH sides to
27
+ // `auth` — an undeclared read route is how a customer list leaks
28
+ // (stacksjs/stacks#2224). Behaviour here is unchanged.
29
+ middleware: { read: [], write: ['auth'] },
30
+ uri: 'loyalty-rewards',
31
+ },
32
+
33
+ observe: true,
34
+ },
35
+
36
+ belongsTo: ['Product'],
37
+
38
+ attributes: {
39
+ name: {
40
+ order: 1,
41
+ fillable: true,
42
+ validation: {
43
+ rule: schema.string().required(),
44
+ },
45
+ factory: faker => faker.helpers.arrayElement([
46
+ `10% discount on your order`,
47
+ `20% discount on your order`,
48
+ `Free product with purchase`,
49
+ `Priority delivery service`,
50
+ ]),
51
+ },
52
+
53
+ description: {
54
+ order: 2,
55
+ fillable: true,
56
+ validation: {
57
+ rule: schema.string().max(500),
58
+ },
59
+ factory: faker => faker.lorem.paragraph(),
60
+ },
61
+
62
+ pointsRequired: {
63
+ order: 3,
64
+ fillable: true,
65
+ validation: {
66
+ rule: schema.number().required().min(1),
67
+ },
68
+ factory: faker => faker.number.int({ min: 100, max: 5000 }),
69
+ },
70
+
71
+ rewardType: {
72
+ order: 4,
73
+ fillable: true,
74
+ validation: {
75
+ rule: schema.string().required(),
76
+ },
77
+ factory: faker => faker.helpers.arrayElement(['DISCOUNT', 'FREE_ITEM', 'PRIORITY_SERVICE']),
78
+ },
79
+
80
+ discountPercentage: {
81
+ order: 5,
82
+ fillable: true,
83
+ validation: {
84
+ rule: schema.number().min(0).max(100),
85
+ },
86
+ factory: faker => faker.helpers.maybe(() => faker.number.int({ min: 5, max: 50 }), { probability: 0.6 }),
87
+ },
88
+
89
+ freeProductId: {
90
+ order: 6,
91
+ fillable: true,
92
+ validation: {
93
+ rule: schema.string(),
94
+ },
95
+ factory: faker => faker.helpers.maybe(() => faker.string.uuid(), { probability: 0.4 }),
96
+ },
97
+
98
+ isActive: {
99
+ order: 7,
100
+ fillable: true,
101
+ validation: {
102
+ rule: schema.boolean(),
103
+ },
104
+ factory: faker => faker.datatype.boolean({ probability: 0.9 }), // 90% active
105
+ },
106
+
107
+ expiryDays: {
108
+ order: 8,
109
+ fillable: true,
110
+ validation: {
111
+ rule: schema.number().min(0),
112
+ },
113
+ factory: faker => faker.helpers.arrayElement([7, 14, 30, 60, 90]),
114
+ },
115
+
116
+ imageUrl: {
117
+ order: 9,
118
+ fillable: true,
119
+ validation: {
120
+ rule: schema.string().max(255),
121
+ },
122
+ factory: faker => faker.image.url().substring(0, 255),
123
+ },
124
+ },
125
+
126
+ dashboard: {
127
+ highlight: true,
128
+ },
129
+ } as const)
@@ -0,0 +1,90 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'Manufacturer',
6
+ table: 'manufacturers',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ traits: {
11
+ useUuid: true,
12
+ useTimestamps: true,
13
+ useSearch: {
14
+ displayable: ['id', 'manufacturer', 'description', 'country', 'featured'],
15
+ searchable: ['manufacturer', 'description', 'country'],
16
+ sortable: ['manufacturer', 'country', 'createdAt', 'updatedAt'],
17
+ filterable: ['country', 'featured'],
18
+ },
19
+
20
+ useSeeder: {
21
+ count: 30,
22
+ },
23
+
24
+ useApi: {
25
+ // Public catalog: anyone may browse, only authenticated callers may
26
+ // write. Declared explicitly because the trait now defaults BOTH sides to
27
+ // `auth` — an undeclared read route is how a customer list leaks
28
+ // (stacksjs/stacks#2224). Behaviour here is unchanged.
29
+ middleware: { read: [], write: ['auth'] },
30
+ uri: 'product-manufacturers',
31
+ },
32
+
33
+ observe: true,
34
+ },
35
+
36
+ hasMany: ['Product'],
37
+
38
+ attributes: {
39
+ manufacturer: {
40
+ unique: true,
41
+ order: 1,
42
+ fillable: true,
43
+ validation: {
44
+ rule: schema.string().required().max(100),
45
+ message: {
46
+ max: 'Manufacturer name must have a maximum of 100 characters',
47
+ },
48
+ },
49
+ factory: faker => `${faker.company.name()} ${faker.string.alphanumeric(6)}`,
50
+ },
51
+
52
+ description: {
53
+ order: 2,
54
+ fillable: true,
55
+ validation: {
56
+ rule: schema.string().max(2000),
57
+ message: {
58
+ max: 'Description must have a maximum of 2000 characters',
59
+ },
60
+ },
61
+ factory: faker => `${faker.company.catchPhrase()}. ${faker.company.buzzPhrase()}`,
62
+ },
63
+
64
+ country: {
65
+ order: 3,
66
+ fillable: true,
67
+ validation: {
68
+ rule: schema.string().required().max(100),
69
+ message: {
70
+ max: 'Country must have a maximum of 100 characters',
71
+ },
72
+ },
73
+ factory: faker => faker.location.country(),
74
+ },
75
+
76
+ featured: {
77
+ default: false,
78
+ order: 4,
79
+ fillable: true,
80
+ validation: {
81
+ rule: schema.boolean(),
82
+ },
83
+ factory: faker => faker.datatype.boolean({ probability: 0.2 }),
84
+ },
85
+ },
86
+
87
+ dashboard: {
88
+ highlight: true,
89
+ },
90
+ } as const)