@ak--47/dungeon-master 1.0.0 → 1.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.
Files changed (103) hide show
  1. package/README.md +7 -7
  2. package/dungeons/technical/ad-spend-schema.json +128 -0
  3. package/dungeons/technical/ad-spend.js +116 -0
  4. package/dungeons/technical/anonymous-users-schema.json +91 -0
  5. package/dungeons/technical/anonymous-users.js +102 -0
  6. package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
  7. package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +11 -4
  8. package/dungeons/technical/experiments-schema.json +197 -0
  9. package/dungeons/technical/experiments.js +168 -0
  10. package/dungeons/technical/foobar-schema.json +362 -0
  11. package/dungeons/{foobar.js → technical/foobar.js} +2 -2
  12. package/dungeons/technical/group-analytics-schema.json +149 -0
  13. package/dungeons/technical/group-analytics.js +151 -0
  14. package/dungeons/technical/mirror-strategies-schema.json +84 -0
  15. package/dungeons/technical/mirror-strategies.js +126 -0
  16. package/dungeons/technical/nested-objects-schema.json +145 -0
  17. package/dungeons/technical/nested-objects.js +207 -0
  18. package/dungeons/technical/retention-cadence-schema.json +37 -0
  19. package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +1 -1
  20. package/dungeons/technical/sanity-schema.json +185 -0
  21. package/dungeons/{sanity.js → technical/sanity.js} +3 -2
  22. package/dungeons/technical/scale-test-schema.json +70 -0
  23. package/dungeons/technical/scale-test.js +90 -0
  24. package/dungeons/technical/scd-schema.json +270 -0
  25. package/dungeons/{scd.js → technical/scd.js} +19 -10
  26. package/dungeons/technical/simple-schema.json +278 -0
  27. package/dungeons/{simple.js → technical/simple.js} +6 -6
  28. package/dungeons/technical/simplest-schema.json +635 -0
  29. package/dungeons/{simplest.js → technical/simplest.js} +6 -6
  30. package/dungeons/technical/text-generation-schema.json +1059 -0
  31. package/dungeons/{text-generation.js → technical/text-generation.js} +23 -9
  32. package/dungeons/vertical/community-schema.json +557 -0
  33. package/dungeons/vertical/community.js +788 -0
  34. package/dungeons/vertical/devtools-schema.json +580 -0
  35. package/dungeons/vertical/devtools.js +837 -0
  36. package/dungeons/vertical/ecommerce-schema.json +586 -0
  37. package/dungeons/{ecommerce.js → vertical/ecommerce.js} +34 -3
  38. package/dungeons/vertical/education-schema.json +697 -0
  39. package/dungeons/{education.js → vertical/education.js} +54 -36
  40. package/dungeons/vertical/fintech-schema.json +563 -0
  41. package/dungeons/{fintech.js → vertical/fintech.js} +91 -53
  42. package/dungeons/vertical/fitness-schema.json +508 -0
  43. package/dungeons/vertical/fitness.js +802 -0
  44. package/dungeons/vertical/food-delivery-schema.json +678 -0
  45. package/dungeons/{food.js → vertical/food-delivery.js} +51 -23
  46. package/dungeons/vertical/gaming-schema.json +394 -0
  47. package/dungeons/{gaming.js → vertical/gaming.js} +9 -7
  48. package/dungeons/vertical/healthcare-schema.json +527 -0
  49. package/dungeons/vertical/healthcare.js +769 -0
  50. package/dungeons/vertical/insurance-application-schema.json +466 -0
  51. package/dungeons/{insurance-application.js → vertical/insurance-application.js} +33 -29
  52. package/dungeons/vertical/logistics-schema.json +552 -0
  53. package/dungeons/vertical/logistics.js +793 -0
  54. package/dungeons/vertical/marketplace-schema.json +511 -0
  55. package/dungeons/vertical/marketplace.js +844 -0
  56. package/dungeons/vertical/media-schema.json +787 -0
  57. package/dungeons/{media.js → vertical/media.js} +94 -16
  58. package/dungeons/vertical/rpg-schema.json +654 -0
  59. package/dungeons/{rpg.js → vertical/rpg.js} +78 -57
  60. package/dungeons/vertical/sass-schema.json +653 -0
  61. package/dungeons/{sass.js → vertical/sass.js} +49 -23
  62. package/dungeons/vertical/social-schema.json +552 -0
  63. package/dungeons/{social.js → vertical/social.js} +60 -14
  64. package/dungeons/vertical/travel-schema.json +558 -0
  65. package/dungeons/vertical/travel.js +736 -0
  66. package/index.js +19 -15
  67. package/lib/core/config-validator.js +286 -1
  68. package/lib/core/context.js +7 -5
  69. package/lib/generators/adspend.js +3 -3
  70. package/lib/generators/events.js +141 -14
  71. package/lib/generators/funnels.js +67 -7
  72. package/lib/generators/text.js +2 -2
  73. package/lib/orchestrators/user-loop.js +441 -22
  74. package/lib/templates/soup-presets.js +2 -2
  75. package/lib/utils/utils.js +4 -5
  76. package/package.json +4 -3
  77. package/scripts/dungeon-to-json.mjs +163 -0
  78. package/scripts/extract-dungeon-schema.mjs +297 -0
  79. package/scripts/json-to-dungeon.mjs +200 -0
  80. package/scripts/run-dungeon.mjs +76 -0
  81. package/scripts/run-many.mjs +118 -0
  82. package/scripts/verify-runner.mjs +42 -0
  83. package/types.d.ts +313 -1
  84. package/dungeons/array-of-object-lookup-schema.json +0 -327
  85. package/dungeons/ecommerce-schema.json +0 -462
  86. package/dungeons/education-schema.json +0 -2409
  87. package/dungeons/fintech-schema.json +0 -14034
  88. package/dungeons/foobar-schema.json +0 -403
  89. package/dungeons/food-delivery-schema.json +0 -192
  90. package/dungeons/food-delivery.js +0 -602
  91. package/dungeons/food-schema.json +0 -1152
  92. package/dungeons/gaming-schema.json +0 -1270
  93. package/dungeons/insurance-application-schema.json +0 -204
  94. package/dungeons/media-schema.json +0 -906
  95. package/dungeons/retention-cadence-schema.json +0 -78
  96. package/dungeons/rpg-schema.json +0 -4526
  97. package/dungeons/sanity-schema.json +0 -255
  98. package/dungeons/sass-schema.json +0 -1291
  99. package/dungeons/scd-schema.json +0 -919
  100. package/dungeons/simple-schema.json +0 -608
  101. package/dungeons/simplest-schema.json +0 -1418
  102. package/dungeons/social-schema.json +0 -1118
  103. package/dungeons/text-generation-schema.json +0 -3096
@@ -0,0 +1,84 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "event": "page view",
5
+ "weight": 10,
6
+ "properties": {
7
+ "page": [
8
+ "/account",
9
+ "/",
10
+ "/help",
11
+ "/product",
12
+ "/pricing"
13
+ ]
14
+ }
15
+ },
16
+ {
17
+ "event": "checkout",
18
+ "weight": 3,
19
+ "properties": {
20
+ "amount": {
21
+ "$range": [
22
+ 44,
23
+ 154
24
+ ]
25
+ },
26
+ "currency": [
27
+ "USD",
28
+ "EUR",
29
+ "GBP"
30
+ ]
31
+ }
32
+ },
33
+ {
34
+ "event": "profile update",
35
+ "weight": 4,
36
+ "properties": {
37
+ "field": [
38
+ "name",
39
+ "email",
40
+ "avatar",
41
+ "address",
42
+ "phone"
43
+ ]
44
+ }
45
+ },
46
+ {
47
+ "event": "subscription change",
48
+ "weight": 2,
49
+ "properties": {
50
+ "plan": [
51
+ "free",
52
+ "starter",
53
+ "pro",
54
+ "enterprise"
55
+ ],
56
+ "action": [
57
+ "upgrade",
58
+ "downgrade",
59
+ "cancel",
60
+ "renew"
61
+ ]
62
+ }
63
+ },
64
+ {
65
+ "event": "login",
66
+ "weight": 8,
67
+ "properties": {
68
+ "method": [
69
+ "password",
70
+ "google",
71
+ "sso"
72
+ ]
73
+ }
74
+ }
75
+ ],
76
+ "userProps": {
77
+ "plan": [
78
+ "free",
79
+ "starter",
80
+ "pro",
81
+ "enterprise"
82
+ ]
83
+ }
84
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Mirror Strategies — tests all 4 mirror data strategies.
3
+ *
4
+ * Exercises: create, update, fill, delete mirror transformations.
5
+ * Each mirror prop targets a different event and uses a different
6
+ * strategy so the output can be diffed against the original data.
7
+ *
8
+ * - 500 users, 30K events, 30 days
9
+ * - 5 simple events, no funnels, no hooks
10
+ */
11
+
12
+ import Chance from 'chance';
13
+ let chance = new Chance();
14
+ import { weighNumRange, weighChoices } from "../../lib/utils/utils.js";
15
+
16
+ /** @type {import('../../types').Dungeon} */
17
+ const config = {
18
+ token: "",
19
+ seed: "mirror-strategies",
20
+ numDays: 30,
21
+ numEvents: 30_000,
22
+ numUsers: 500,
23
+ format: "json",
24
+ region: "US",
25
+ hasAnonIds: false,
26
+ hasSessionIds: false,
27
+ hasAdSpend: false,
28
+ hasLocation: false,
29
+ hasAndroidDevices: false,
30
+ hasIOSDevices: false,
31
+ hasDesktopDevices: false,
32
+ hasBrowser: false,
33
+ hasCampaigns: false,
34
+ isAnonymous: false,
35
+ alsoInferFunnels: false,
36
+ concurrency: 1,
37
+ batchSize: 2_500_000,
38
+ writeToDisk: false,
39
+
40
+ events: [
41
+ {
42
+ event: "page view",
43
+ weight: 10,
44
+ properties: {
45
+ page: ["/", "/help", "/account", "/pricing", "/product"],
46
+ }
47
+ },
48
+ {
49
+ event: "checkout",
50
+ weight: 3,
51
+ properties: {
52
+ amount: weighNumRange(5, 200, .25),
53
+ currency: ["USD", "EUR", "GBP"],
54
+ }
55
+ },
56
+ {
57
+ event: "profile update",
58
+ weight: 4,
59
+ properties: {
60
+ field: ["name", "email", "avatar", "address", "phone"],
61
+ }
62
+ },
63
+ {
64
+ event: "subscription change",
65
+ weight: 2,
66
+ properties: {
67
+ plan: ["free", "starter", "pro", "enterprise"],
68
+ action: ["upgrade", "downgrade", "cancel", "renew"],
69
+ }
70
+ },
71
+ {
72
+ event: "login",
73
+ weight: 8,
74
+ properties: {
75
+ method: ["password", "google", "sso"],
76
+ }
77
+ },
78
+ ],
79
+
80
+ funnels: [],
81
+
82
+ superProps: {},
83
+
84
+ userProps: {
85
+ plan: ["free", "starter", "pro", "enterprise"],
86
+ },
87
+
88
+ scdProps: {},
89
+
90
+ mirrorProps: {
91
+ // "update" — adds a status property to profile update events; keeps existing values
92
+ user_status: {
93
+ events: ["profile update"],
94
+ strategy: "update",
95
+ values: ["active", "inactive", "suspended", "pending"],
96
+ },
97
+ // "create" — always creates a new tier property on subscription change events
98
+ subscription_tier: {
99
+ events: ["subscription change"],
100
+ strategy: "create",
101
+ values: ["bronze", "silver", "gold", "platinum"],
102
+ },
103
+ // "fill" — fills login_source on login events only if older than 10 days
104
+ login_source: {
105
+ events: ["login"],
106
+ strategy: "fill",
107
+ values: ["web", "mobile", "api", "desktop"],
108
+ daysUnfilled: 10,
109
+ },
110
+ // "delete" — removes the amount property from checkout events in the mirror
111
+ amount: {
112
+ events: ["checkout"],
113
+ strategy: "delete",
114
+ },
115
+ },
116
+
117
+ groupKeys: [],
118
+ groupProps: {},
119
+ lookupTables: [],
120
+
121
+ hook: function (record, type, meta) {
122
+ return record;
123
+ }
124
+ };
125
+
126
+ export default config;
@@ -0,0 +1,145 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "event": "checkout",
5
+ "weight": 3,
6
+ "properties": {
7
+ "cart": {
8
+ "$type": "object[]",
9
+ "$keys": [
10
+ "product_id",
11
+ "name",
12
+ "price",
13
+ "quantity",
14
+ "category"
15
+ ]
16
+ },
17
+ "total_amount": {
18
+ "$range": [
19
+ 408,
20
+ 1563
21
+ ]
22
+ },
23
+ "currency": [
24
+ "USD",
25
+ "EUR",
26
+ "GBP",
27
+ "JPY",
28
+ "CAD"
29
+ ],
30
+ "session_id": {
31
+ "$type": "string"
32
+ },
33
+ "metadata": {
34
+ "$type": "object"
35
+ }
36
+ }
37
+ },
38
+ {
39
+ "event": "search performed",
40
+ "weight": 5,
41
+ "properties": {
42
+ "query": [
43
+ "laptop",
44
+ "shoes",
45
+ "headphones",
46
+ "jacket",
47
+ "phone case",
48
+ "coffee maker",
49
+ "backpack",
50
+ "monitor",
51
+ "keyboard",
52
+ "mouse"
53
+ ],
54
+ "results": {
55
+ "$type": "object[]",
56
+ "$keys": [
57
+ "result_id",
58
+ "title",
59
+ "score",
60
+ "sponsored"
61
+ ]
62
+ },
63
+ "result_count": {
64
+ "$range": [
65
+ 6,
66
+ 46
67
+ ]
68
+ },
69
+ "top_result_id": {
70
+ "$type": "string"
71
+ },
72
+ "session_id": {
73
+ "$type": "string"
74
+ },
75
+ "metadata": {
76
+ "$type": "object"
77
+ }
78
+ }
79
+ },
80
+ {
81
+ "event": "form submitted",
82
+ "weight": 4,
83
+ "properties": {
84
+ "form_name": [
85
+ "checkout",
86
+ "signup",
87
+ "contact",
88
+ "feedback",
89
+ "settings",
90
+ "profile"
91
+ ],
92
+ "fields": {
93
+ "$type": "object[]",
94
+ "$keys": [
95
+ "field_name",
96
+ "value",
97
+ "valid"
98
+ ]
99
+ },
100
+ "submission_valid": [
101
+ true,
102
+ false
103
+ ],
104
+ "session_id": {
105
+ "$type": "string"
106
+ },
107
+ "metadata": {
108
+ "$type": "object"
109
+ }
110
+ }
111
+ },
112
+ {
113
+ "event": "page view",
114
+ "weight": 8,
115
+ "properties": {
116
+ "page": [
117
+ "/",
118
+ "/products",
119
+ "/cart",
120
+ "/search",
121
+ "/account",
122
+ "/help",
123
+ "/about"
124
+ ],
125
+ "product_id": {
126
+ "$type": "string"
127
+ },
128
+ "session_id": {
129
+ "$type": "string"
130
+ },
131
+ "metadata": {
132
+ "$type": "object"
133
+ }
134
+ }
135
+ }
136
+ ],
137
+ "userProps": {
138
+ "plan": [
139
+ "free",
140
+ "basic",
141
+ "pro",
142
+ "enterprise"
143
+ ]
144
+ }
145
+ }
@@ -0,0 +1,207 @@
1
+ import Chance from 'chance';
2
+ let chance = new Chance();
3
+ import dayjs from "dayjs";
4
+ import utc from "dayjs/plugin/utc.js";
5
+ dayjs.extend(utc);
6
+ import { uid, comma } from 'ak-tools';
7
+ import { weighNumRange, date, integer, weighChoices } from "../../lib/utils/utils.js";
8
+
9
+ /**
10
+ * ═══════════════════════════════════════════════════════════════
11
+ * TECHNICAL TEST: Nested Objects & High Cardinality
12
+ * ═══════════════════════════════════════════════════════════════
13
+ *
14
+ * Tests Mixpanel's handling of:
15
+ * - Array-of-object event properties (cart items, search results, form fields)
16
+ * - Deeply nested object properties (metadata.browser.name, etc.)
17
+ * - High cardinality string properties (10K product IDs, unique session IDs)
18
+ *
19
+ * 500 users, 30K events, 30 days. No hooks.
20
+ */
21
+
22
+ const productCategories = ["electronics", "books", "clothing", "home", "garden", "toys", "sports", "automotive", "beauty", "health", "grocery", "jewelry", "shoes", "tools", "office"];
23
+ const browsers = ["Chrome", "Firefox", "Safari", "Edge", "Opera", "Brave"];
24
+ const browserVersions = ["120.0", "121.0", "119.0", "118.0", "117.0", "116.0", "115.0"];
25
+ const deviceTypes = ["desktop", "mobile", "tablet"];
26
+ const osList = ["Windows 11", "macOS 14", "iOS 17", "Android 14", "Linux", "ChromeOS"];
27
+ const cities = ["New York", "London", "Tokyo", "Berlin", "Sydney", "Toronto", "Paris", "Mumbai", "Seoul", "Mexico City", "Cairo", "Lagos", "Dubai", "Bangkok", "Istanbul"];
28
+ const countries = ["US", "UK", "JP", "DE", "AU", "CA", "FR", "IN", "KR", "MX", "EG", "NG", "AE", "TH", "TR"];
29
+
30
+ /** @type {import('../../types').Dungeon} */
31
+ const config = {
32
+ seed: "nested objects test",
33
+ name: "nested-objects",
34
+ numDays: 30,
35
+ numEvents: 30_000,
36
+ numUsers: 500,
37
+ format: 'json',
38
+ region: "US",
39
+ hasAnonIds: false,
40
+ hasSessionIds: false,
41
+ hasAdSpend: false,
42
+ hasLocation: false,
43
+ hasAndroidDevices: false,
44
+ hasIOSDevices: false,
45
+ hasDesktopDevices: true,
46
+ hasBrowser: false,
47
+ hasCampaigns: false,
48
+ isAnonymous: false,
49
+ alsoInferFunnels: false,
50
+ concurrency: 1,
51
+ batchSize: 500_000,
52
+ writeToDisk: false,
53
+
54
+ events: [
55
+ {
56
+ event: "checkout",
57
+ weight: 3,
58
+ properties: {
59
+ // Array of objects: cart items
60
+ cart: makeCartItems(),
61
+ total_amount: weighNumRange(10, 2000, .25),
62
+ currency: ["USD", "EUR", "GBP", "JPY", "CAD"],
63
+ // High cardinality: unique session per event
64
+ session_id: () => chance.guid(),
65
+ // Deeply nested metadata
66
+ metadata: makeMetadata(),
67
+ }
68
+ },
69
+ {
70
+ event: "search performed",
71
+ weight: 5,
72
+ properties: {
73
+ query: ["laptop", "shoes", "headphones", "jacket", "phone case", "coffee maker", "backpack", "monitor", "keyboard", "mouse"],
74
+ // Array of objects: search results
75
+ results: makeSearchResults(),
76
+ result_count: weighNumRange(0, 50),
77
+ // High cardinality product ID
78
+ top_result_id: () => `prod_${integer(1, 10000)}`,
79
+ session_id: () => chance.guid(),
80
+ metadata: makeMetadata(),
81
+ }
82
+ },
83
+ {
84
+ event: "form submitted",
85
+ weight: 4,
86
+ properties: {
87
+ form_name: ["checkout", "signup", "contact", "feedback", "settings", "profile"],
88
+ // Array of objects: form fields
89
+ fields: makeFormFields(),
90
+ submission_valid: [true, true, true, false],
91
+ session_id: () => chance.guid(),
92
+ metadata: makeMetadata(),
93
+ }
94
+ },
95
+ {
96
+ event: "page view",
97
+ weight: 8,
98
+ properties: {
99
+ page: ["/", "/products", "/cart", "/search", "/account", "/help", "/about"],
100
+ // High cardinality
101
+ product_id: () => `prod_${integer(1, 10000)}`,
102
+ session_id: () => chance.guid(),
103
+ metadata: makeMetadata(),
104
+ }
105
+ },
106
+ ],
107
+ funnels: [],
108
+ superProps: {},
109
+ userProps: {
110
+ plan: ["free", "basic", "pro", "enterprise"],
111
+ },
112
+ scdProps: {},
113
+ mirrorProps: {},
114
+ groupKeys: [],
115
+ groupProps: {},
116
+ lookupTables: [],
117
+ hook: function (record, type, meta) {
118
+ return record;
119
+ }
120
+ };
121
+
122
+ /**
123
+ * Generates an array-of-objects property for cart items.
124
+ * Each cart has 1-5 items with product_id, name, price, quantity, category.
125
+ */
126
+ function makeCartItems() {
127
+ return function () {
128
+ const items = [];
129
+ const count = integer(1, 5);
130
+ for (let i = 0; i < count; i++) {
131
+ items.push({
132
+ product_id: `prod_${integer(1, 10000)}`,
133
+ name: `${chance.pickone(["Premium", "Basic", "Ultra", "Eco", "Pro"])} ${chance.pickone(["Widget", "Gadget", "Device", "Tool", "Kit"])}`,
134
+ price: integer(5, 500),
135
+ quantity: integer(1, 4),
136
+ category: chance.pickone(productCategories),
137
+ });
138
+ }
139
+ return () => [items];
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Generates an array-of-objects property for search results.
145
+ * Each search has 1-5 results with result_id, title, score, sponsored.
146
+ */
147
+ function makeSearchResults() {
148
+ return function () {
149
+ const results = [];
150
+ const count = integer(1, 5);
151
+ for (let i = 0; i < count; i++) {
152
+ results.push({
153
+ result_id: `res_${integer(1, 50000)}`,
154
+ title: `${chance.pickone(["Best", "Top", "New", "Sale"])} ${chance.pickone(productCategories)} item`,
155
+ score: parseFloat((Math.random() * 100).toFixed(2)),
156
+ sponsored: chance.pickone([true, false, false, false]),
157
+ });
158
+ }
159
+ return () => [results];
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Generates an array-of-objects property for form fields.
165
+ * Each form has 2-6 fields with field_name, value, valid.
166
+ */
167
+ function makeFormFields() {
168
+ return function () {
169
+ const fieldNames = ["email", "name", "phone", "address", "city", "zip", "country", "card_number", "expiry", "cvv"];
170
+ const fields = [];
171
+ const count = integer(2, 6);
172
+ const picked = chance.pickset(fieldNames, count);
173
+ for (const name of picked) {
174
+ fields.push({
175
+ field_name: name,
176
+ value: name === "email" ? chance.email() : chance.word(),
177
+ valid: chance.pickone([true, true, true, false]),
178
+ });
179
+ }
180
+ return () => [fields];
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Generates a deeply nested metadata object:
186
+ * metadata: { browser: { name, version }, device: { type, os }, location: { city, country } }
187
+ */
188
+ function makeMetadata() {
189
+ return function () {
190
+ return {
191
+ browser: {
192
+ name: chance.pickone(browsers),
193
+ version: chance.pickone(browserVersions),
194
+ },
195
+ device: {
196
+ type: chance.pickone(deviceTypes),
197
+ os: chance.pickone(osList),
198
+ },
199
+ location: {
200
+ city: chance.pickone(cities),
201
+ country: chance.pickone(countries),
202
+ },
203
+ };
204
+ };
205
+ }
206
+
207
+ export default config;
@@ -0,0 +1,37 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "event": "first behavior",
5
+ "isFirstEvent": true
6
+ },
7
+ {
8
+ "event": "hourly behavior",
9
+ "weight": 40
10
+ },
11
+ {
12
+ "event": "daily behavior",
13
+ "weight": 30
14
+ },
15
+ {
16
+ "event": "weekly behavior",
17
+ "weight": 15
18
+ },
19
+ {
20
+ "event": "monthly behavior",
21
+ "weight": 10
22
+ },
23
+ {
24
+ "event": "placeholder",
25
+ "weight": 50
26
+ }
27
+ ],
28
+ "userProps": {
29
+ "favorite color": [
30
+ "red",
31
+ "blue",
32
+ "green",
33
+ "yellow",
34
+ "purple"
35
+ ]
36
+ }
37
+ }
@@ -2,7 +2,7 @@ const SEED = "kurby-retention";
2
2
  import dayjs from 'dayjs';
3
3
  import utc from 'dayjs/plugin/utc.js';
4
4
  dayjs.extend(utc);
5
- import * as u from "../lib/utils/utils.js";
5
+ import * as u from "../../lib/utils/utils.js";
6
6
  import * as v from 'ak-tools';
7
7
  const chance = u.initChance(SEED);
8
8