@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,91 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ /**
5
+ * Pivot row between Subscriber and EmailList.
6
+ *
7
+ * One subscriber can be on many lists; the subscribers.email unique
8
+ * constraint stays intact because membership lives here. Each pivot
9
+ * row carries its own UUID, which doubles as the per-list unsubscribe
10
+ * token — that lets a recipient drop "Weekly Digest" via one link
11
+ * without dropping every other list (or transactional mail).
12
+ */
13
+ export default defineModel({
14
+ name: 'EmailListSubscriber',
15
+ table: 'email_list_subscribers',
16
+ primaryKey: 'id',
17
+ autoIncrement: true,
18
+ belongsTo: ['EmailList', 'Subscriber'],
19
+
20
+ traits: {
21
+ useUuid: true,
22
+ useTimestamps: true,
23
+ useApi: {
24
+ uri: 'email-list-subscribers',
25
+ routes: ['index', 'show'],
26
+ // The pivot leaks list-membership PII (which addresses are on
27
+ // which list, signup source, status). Auth-gate it. Public
28
+ // unsubscribe goes through the dedicated token route, not this
29
+ // CRUD surface.
30
+ middleware: ['auth'],
31
+ },
32
+ },
33
+
34
+ attributes: {
35
+ emailListId: {
36
+ required: true,
37
+ fillable: true,
38
+ validation: {
39
+ rule: schema.number(),
40
+ },
41
+ factory: faker => faker.number.int({ min: 1, max: 8 }),
42
+ },
43
+
44
+ subscriberId: {
45
+ required: true,
46
+ fillable: true,
47
+ validation: {
48
+ rule: schema.number(),
49
+ },
50
+ factory: faker => faker.number.int({ min: 1, max: 100 }),
51
+ },
52
+
53
+ status: {
54
+ required: true,
55
+ fillable: true,
56
+ default: 'subscribed',
57
+ validation: {
58
+ rule: schema.enum(['subscribed', 'unsubscribed', 'pending', 'bounced']),
59
+ },
60
+ factory: faker => faker.helpers.arrayElement(['subscribed', 'subscribed', 'subscribed', 'unsubscribed']),
61
+ },
62
+
63
+ source: {
64
+ required: false,
65
+ fillable: true,
66
+ default: 'api',
67
+ validation: {
68
+ rule: schema.string().max(100),
69
+ },
70
+ factory: faker => faker.helpers.arrayElement(['homepage', 'blog', 'landing-page', 'api', 'import']),
71
+ },
72
+
73
+ subscribedAt: {
74
+ required: false,
75
+ fillable: true,
76
+ validation: {
77
+ rule: schema.timestamp(),
78
+ },
79
+ factory: faker => faker.date.recent({ days: 90 }).toISOString().slice(0, 19).replace('T', ' '),
80
+ },
81
+
82
+ unsubscribedAt: {
83
+ required: false,
84
+ fillable: true,
85
+ validation: {
86
+ rule: schema.timestamp(),
87
+ },
88
+ factory: () => null,
89
+ },
90
+ },
91
+ } as const)
@@ -0,0 +1,52 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'EmailSuppression',
6
+ table: 'email_suppressions',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ indexes: [
11
+ {
12
+ name: 'email_suppressions_email_type_unique',
13
+ columns: ['email', 'type'],
14
+ unique: true,
15
+ },
16
+ ],
17
+
18
+ traits: {
19
+ useTimestamps: true,
20
+ useApi: {
21
+ uri: 'email-suppressions',
22
+ routes: ['index', 'show', 'destroy'],
23
+ middleware: ['auth'],
24
+ },
25
+ },
26
+
27
+ attributes: {
28
+ email: {
29
+ required: true,
30
+ fillable: true,
31
+ validation: {
32
+ rule: schema.string().email().required().max(320),
33
+ },
34
+ },
35
+ type: {
36
+ required: true,
37
+ fillable: true,
38
+ validation: {
39
+ rule: schema.enum(['bounce', 'complaint', 'unsubscribe', 'manual']),
40
+ },
41
+ },
42
+ reason: {
43
+ required: false,
44
+ fillable: true,
45
+ validation: {
46
+ rule: schema.string().max(2000),
47
+ },
48
+ },
49
+ },
50
+
51
+ dashboard: { enabled: false },
52
+ } as const)
@@ -0,0 +1,51 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'EmailWebhookEvent',
6
+ table: 'email_webhook_events',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ indexes: [
11
+ {
12
+ name: 'email_webhook_events_provider_event_unique',
13
+ columns: ['provider', 'event_id'],
14
+ unique: true,
15
+ },
16
+ ],
17
+
18
+ traits: {
19
+ useTimestamps: true,
20
+ useApi: {
21
+ uri: 'email-webhook-events',
22
+ routes: ['index', 'show', 'destroy'],
23
+ middleware: ['auth'],
24
+ },
25
+ },
26
+
27
+ attributes: {
28
+ provider: {
29
+ required: true,
30
+ fillable: true,
31
+ validation: {
32
+ rule: schema.enum(['mailgun', 'postmark', 'ses', 'sendgrid']),
33
+ },
34
+ },
35
+ eventId: {
36
+ required: true,
37
+ hidden: true,
38
+ validation: {
39
+ rule: schema.string().required().max(512),
40
+ },
41
+ },
42
+ processedAt: {
43
+ required: true,
44
+ validation: {
45
+ rule: schema.timestamp().required(),
46
+ },
47
+ },
48
+ },
49
+
50
+ dashboard: { enabled: false },
51
+ } as const)
@@ -0,0 +1,77 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'Error',
6
+ table: 'errors',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ indexes: [
11
+ {
12
+ name: 'errors_created_at_index',
13
+ columns: ['created_at'],
14
+ },
15
+ ],
16
+
17
+ traits: {
18
+ useTimestamps: true,
19
+ },
20
+
21
+ attributes: {
22
+ type: {
23
+ fillable: true,
24
+ validation: {
25
+ rule: schema.string().required().max(255),
26
+ message: {
27
+ max: 'type must have a maximum of 255 characters',
28
+ },
29
+ },
30
+ factory: faker => faker.system.fileType(),
31
+ },
32
+
33
+ message: {
34
+ fillable: true,
35
+ validation: {
36
+ rule: schema.string().required(),
37
+ message: {
38
+ string: 'message must be a string',
39
+ },
40
+ },
41
+ factory: faker => faker.lorem.sentence(),
42
+ },
43
+
44
+ stack: {
45
+ fillable: true,
46
+ validation: {
47
+ rule: schema.string(),
48
+ message: {
49
+ string: 'stack must be a text',
50
+ },
51
+ },
52
+ factory: faker => faker.system.filePath(),
53
+ },
54
+
55
+ status: {
56
+ fillable: true,
57
+ validation: {
58
+ rule: schema.number(),
59
+ message: {
60
+ string: 'status must be a number',
61
+ },
62
+ },
63
+ factory: faker => faker.number.int({ min: 400, max: 500 }),
64
+ },
65
+
66
+ additional_info: {
67
+ fillable: true,
68
+ validation: {
69
+ rule: schema.string(),
70
+ message: {
71
+ string: 'additional_info must be a text',
72
+ },
73
+ },
74
+ factory: faker => faker.lorem.paragraph(),
75
+ },
76
+ },
77
+ } as const)
@@ -0,0 +1,84 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ export default defineModel({
5
+ name: 'FailedJob',
6
+ table: 'failed_jobs',
7
+ primaryKey: 'id',
8
+ autoIncrement: true,
9
+
10
+ traits: {
11
+ useUuid: true,
12
+ useTimestamps: true,
13
+ },
14
+
15
+ attributes: {
16
+ connection: {
17
+ fillable: true,
18
+ validation: {
19
+ rule: schema.string().required().max(100),
20
+ message: {
21
+ max: 'Connection must have a maximum of 100 characters',
22
+ string: 'Connection must be a string',
23
+ },
24
+ },
25
+ factory: () => 'default',
26
+ },
27
+
28
+ queue: {
29
+ fillable: true,
30
+ validation: {
31
+ rule: schema.string().required().max(255),
32
+ message: {
33
+ max: 'Queue must have a maximum of 255 characters',
34
+ },
35
+ },
36
+ factory: () => 'default',
37
+ },
38
+
39
+ payload: {
40
+ fillable: true,
41
+ validation: {
42
+ rule: schema.string().required(),
43
+ },
44
+ factory: faker => faker.lorem.sentence(),
45
+ },
46
+
47
+ exception: {
48
+ fillable: true,
49
+ validation: {
50
+ rule: schema.string().required(),
51
+ },
52
+ factory: faker => faker.lorem.sentence(),
53
+ },
54
+
55
+ attempts: {
56
+ fillable: true,
57
+ validation: {
58
+ rule: schema.number().min(0),
59
+ },
60
+ },
61
+
62
+ maxAttempts: {
63
+ fillable: true,
64
+ validation: {
65
+ rule: schema.number().min(1),
66
+ },
67
+ },
68
+
69
+ durationMs: {
70
+ fillable: true,
71
+ validation: {
72
+ rule: schema.number().min(0),
73
+ },
74
+ },
75
+
76
+ failed_at: {
77
+ fillable: true,
78
+ validation: {
79
+ rule: schema.date(),
80
+ },
81
+ factory: () => '2024-12-23 13:32:19',
82
+ },
83
+ },
84
+ } as const)
@@ -0,0 +1,91 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ /**
5
+ * A user-defined form: inquiry, permission slip, event registration,
6
+ * donation. Fields are FormField rows; submissions are FormSubmission rows.
7
+ *
8
+ * The `useApi` surface is the ADMIN builder surface. The public render and
9
+ * submit endpoints live in `@stacksjs/forms` routes, keyed by uuid, and only
10
+ * ever serve `status: 'active'` forms.
11
+ */
12
+ export default defineModel({
13
+ name: 'Form',
14
+ table: 'forms',
15
+ primaryKey: 'id',
16
+ autoIncrement: true,
17
+
18
+ indexes: [
19
+ {
20
+ name: 'forms_site_handle_unique',
21
+ columns: ['site_id', 'handle'],
22
+ unique: true,
23
+ },
24
+ ],
25
+
26
+ traits: {
27
+ useUuid: true,
28
+ useTimestamps: true,
29
+ useApi: {
30
+ middleware: ['auth'],
31
+ uri: 'forms',
32
+ routes: ['index', 'store', 'show', 'update', 'destroy'],
33
+ },
34
+ useSearch: {
35
+ displayable: ['id', 'name', 'handle', 'status'],
36
+ searchable: ['name', 'handle'],
37
+ filterable: ['status'],
38
+ },
39
+ },
40
+
41
+ belongsTo: ['Site'],
42
+ hasMany: ['FormField', 'FormSubmission'],
43
+
44
+ attributes: {
45
+ name: {
46
+ required: true,
47
+ order: 1,
48
+ fillable: true,
49
+ validation: {
50
+ rule: schema.string().min(2).max(255),
51
+ },
52
+ factory: faker => `${faker.lorem.words(2)} form`,
53
+ },
54
+
55
+ handle: {
56
+ required: true,
57
+ order: 2,
58
+ fillable: true,
59
+ validation: {
60
+ rule: schema.string().min(2).max(64).matches(/^[a-z0-9-]+$/),
61
+ },
62
+ factory: faker => faker.lorem.slug(),
63
+ },
64
+
65
+ status: {
66
+ required: false,
67
+ order: 3,
68
+ fillable: true,
69
+ default: 'draft',
70
+ validation: {
71
+ rule: schema.enum(['draft', 'active', 'closed'] as const),
72
+ },
73
+ factory: () => 'active',
74
+ },
75
+
76
+ /**
77
+ * JSON settings: { submitLabel?, confirmation: { type: 'message'|'redirect',
78
+ * value }, notifyEmails: string[], emailField?, nameField?,
79
+ * payment?: { mode: 'fixed'|'user_amount'|'field_sum', amountCents?, currency? } }
80
+ */
81
+ settings: {
82
+ required: false,
83
+ order: 4,
84
+ fillable: true,
85
+ validation: {
86
+ rule: schema.json(),
87
+ },
88
+ factory: () => JSON.stringify({}),
89
+ },
90
+ },
91
+ } as const)
@@ -0,0 +1,127 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ /**
5
+ * One field of a Form. `name` is the machine key submissions store values
6
+ * under; `conditions` is a show/hide rule set evaluated client-side for UX
7
+ * and re-evaluated server-side for correctness (a hidden required field is
8
+ * not required, and values for hidden fields are discarded).
9
+ */
10
+ export default defineModel({
11
+ name: 'FormField',
12
+ table: 'form_fields',
13
+ primaryKey: 'id',
14
+ autoIncrement: true,
15
+
16
+ indexes: [
17
+ {
18
+ name: 'form_fields_form_name_unique',
19
+ columns: ['form_id', 'name'],
20
+ unique: true,
21
+ },
22
+ ],
23
+
24
+ traits: {
25
+ useTimestamps: true,
26
+ useApi: {
27
+ middleware: ['auth'],
28
+ uri: 'form-fields',
29
+ routes: ['index', 'store', 'show', 'update', 'destroy'],
30
+ },
31
+ },
32
+
33
+ belongsTo: ['Form'],
34
+
35
+ attributes: {
36
+ name: {
37
+ required: true,
38
+ order: 1,
39
+ fillable: true,
40
+ validation: {
41
+ rule: schema.string().min(1).max(64).matches(/^[a-z0-9_]+$/),
42
+ },
43
+ factory: faker => faker.lorem.word(),
44
+ },
45
+
46
+ label: {
47
+ required: true,
48
+ order: 2,
49
+ fillable: true,
50
+ validation: {
51
+ rule: schema.string().min(1).max(255),
52
+ },
53
+ factory: faker => faker.lorem.words(2),
54
+ },
55
+
56
+ type: {
57
+ required: true,
58
+ order: 3,
59
+ fillable: true,
60
+ validation: {
61
+ rule: schema.enum(['text', 'textarea', 'email', 'phone', 'select', 'checkbox', 'radio', 'date', 'file', 'currency', 'section_break'] as const),
62
+ },
63
+ factory: faker => faker.helpers.arrayElement(['text', 'email', 'select']),
64
+ },
65
+
66
+ required: {
67
+ required: false,
68
+ order: 4,
69
+ fillable: true,
70
+ default: false,
71
+ validation: {
72
+ rule: schema.boolean(),
73
+ },
74
+ factory: faker => faker.datatype.boolean(),
75
+ },
76
+
77
+ position: {
78
+ required: false,
79
+ order: 5,
80
+ fillable: true,
81
+ default: 0,
82
+ validation: {
83
+ rule: schema.number().min(0),
84
+ },
85
+ factory: faker => faker.number.int({ min: 0, max: 20 }),
86
+ },
87
+
88
+ width: {
89
+ required: false,
90
+ order: 6,
91
+ fillable: true,
92
+ default: 'full',
93
+ validation: {
94
+ rule: schema.enum(['full', 'half'] as const),
95
+ },
96
+ factory: () => 'full',
97
+ },
98
+
99
+ /**
100
+ * Per-type options JSON: { placeholder?, choices?: {label,value}[],
101
+ * min?, max?, accept?: string[], maxSizeMb?, amountCents? }
102
+ */
103
+ options: {
104
+ required: false,
105
+ order: 7,
106
+ fillable: true,
107
+ validation: {
108
+ rule: schema.json(),
109
+ },
110
+ factory: () => JSON.stringify({}),
111
+ },
112
+
113
+ /**
114
+ * Visibility rules JSON: { action: 'show'|'hide', logic: 'all'|'any',
115
+ * rules: [{ field, op, value? }] }
116
+ */
117
+ conditions: {
118
+ required: false,
119
+ order: 8,
120
+ fillable: true,
121
+ validation: {
122
+ rule: schema.json(),
123
+ },
124
+ factory: () => null,
125
+ },
126
+ },
127
+ } as const)
@@ -0,0 +1,114 @@
1
+ import { defineModel } from '@stacksjs/orm'
2
+ import { schema } from '@stacksjs/validation'
3
+
4
+ /**
5
+ * One submission of a Form. `values` is the validated field map; `email` and
6
+ * `name` are extracted into typed columns at submit time (from the fields the
7
+ * form's settings name, defaulting to the first email field) so lookups,
8
+ * dedupe and confirmation addressing never need JSON extraction SQL.
9
+ *
10
+ * No `useApi`: public writes and admin reads both go through the
11
+ * `@stacksjs/forms` routes, which own spam guards, payment states and
12
+ * site scoping.
13
+ */
14
+ export default defineModel({
15
+ name: 'FormSubmission',
16
+ table: 'form_submissions',
17
+ primaryKey: 'id',
18
+ autoIncrement: true,
19
+
20
+ traits: {
21
+ useUuid: true,
22
+ useTimestamps: true,
23
+ },
24
+
25
+ belongsTo: ['Form', 'Site'],
26
+
27
+ attributes: {
28
+ /** Validated { [fieldName]: value } JSON. Named data, not values - values is a SQL keyword. */
29
+ data: {
30
+ required: true,
31
+ order: 1,
32
+ fillable: true,
33
+ validation: {
34
+ rule: schema.json(),
35
+ },
36
+ factory: () => JSON.stringify({}),
37
+ },
38
+
39
+ email: {
40
+ required: false,
41
+ order: 2,
42
+ fillable: true,
43
+ validation: {
44
+ rule: schema.string().email().max(255),
45
+ },
46
+ factory: faker => faker.internet.email(),
47
+ },
48
+
49
+ name: {
50
+ required: false,
51
+ order: 3,
52
+ fillable: true,
53
+ validation: {
54
+ rule: schema.string().max(255),
55
+ },
56
+ factory: faker => faker.person.fullName(),
57
+ },
58
+
59
+ status: {
60
+ required: false,
61
+ order: 4,
62
+ fillable: true,
63
+ default: 'complete',
64
+ validation: {
65
+ rule: schema.enum(['pending_payment', 'complete', 'spam'] as const),
66
+ },
67
+ factory: () => 'complete',
68
+ },
69
+
70
+ /** Server-computed. Never trusted from the client. */
71
+ amountCents: {
72
+ required: false,
73
+ order: 5,
74
+ fillable: true,
75
+ validation: {
76
+ rule: schema.number().min(0),
77
+ },
78
+ factory: () => null,
79
+ },
80
+
81
+ paymentIntentId: {
82
+ required: false,
83
+ order: 6,
84
+ fillable: true,
85
+ validation: {
86
+ rule: schema.string().max(255),
87
+ },
88
+ factory: () => null,
89
+ },
90
+
91
+ ip: {
92
+ required: false,
93
+ order: 7,
94
+ fillable: true,
95
+ validation: {
96
+ rule: schema.string().max(64),
97
+ },
98
+ factory: () => null,
99
+ },
100
+
101
+ submittedAt: {
102
+ required: false,
103
+ order: 8,
104
+ fillable: true,
105
+ validation: {
106
+ rule: schema.timestamp(),
107
+ },
108
+ factory: (faker) => {
109
+ const date = faker.date.recent()
110
+ return date.toISOString().slice(0, 19).replace('T', ' ')
111
+ },
112
+ },
113
+ },
114
+ } as const)