digital-products 2.1.3 → 2.3.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 (122) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +2 -0
  3. package/dist/api.js +7 -7
  4. package/dist/api.js.map +1 -1
  5. package/dist/app.js +6 -6
  6. package/dist/app.js.map +1 -1
  7. package/dist/client.d.ts +157 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +69 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/content.js +7 -7
  12. package/dist/content.js.map +1 -1
  13. package/dist/data.d.ts.map +1 -1
  14. package/dist/data.js +6 -6
  15. package/dist/data.js.map +1 -1
  16. package/dist/dataset.js +5 -5
  17. package/dist/dataset.js.map +1 -1
  18. package/dist/index.d.ts +92 -13
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +139 -15
  21. package/dist/index.js.map +1 -1
  22. package/dist/mcp.d.ts +1 -1
  23. package/dist/mcp.d.ts.map +1 -1
  24. package/dist/mcp.js +17 -10
  25. package/dist/mcp.js.map +1 -1
  26. package/dist/product.js +2 -2
  27. package/dist/product.js.map +1 -1
  28. package/dist/sdk.d.ts.map +1 -1
  29. package/dist/sdk.js +52 -16
  30. package/dist/sdk.js.map +1 -1
  31. package/dist/site.d.ts.map +1 -1
  32. package/dist/site.js +12 -8
  33. package/dist/site.js.map +1 -1
  34. package/dist/types.d.ts +830 -12
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/types.js +495 -2
  37. package/dist/types.js.map +1 -1
  38. package/dist/worker.d.ts +205 -0
  39. package/dist/worker.d.ts.map +1 -0
  40. package/dist/worker.js +356 -0
  41. package/dist/worker.js.map +1 -0
  42. package/package.json +29 -13
  43. package/src/api.ts +7 -7
  44. package/src/app.ts +6 -6
  45. package/src/client.ts +192 -0
  46. package/src/content.ts +7 -7
  47. package/src/data.ts +12 -7
  48. package/src/dataset.ts +5 -5
  49. package/src/index.ts +151 -15
  50. package/src/mcp.ts +18 -11
  51. package/src/product.ts +2 -2
  52. package/src/sdk.ts +54 -15
  53. package/src/site.ts +12 -8
  54. package/src/types.ts +821 -12
  55. package/src/worker.ts +525 -0
  56. package/test/product.test.ts +53 -198
  57. package/test/unified-types.test.ts +589 -0
  58. package/test/worker.test.ts +912 -0
  59. package/vitest.config.ts +42 -0
  60. package/wrangler.jsonc +36 -0
  61. package/.turbo/turbo-build.log +0 -5
  62. package/LICENSE +0 -21
  63. package/dist/features/define.d.ts +0 -63
  64. package/dist/features/define.d.ts.map +0 -1
  65. package/dist/features/define.js +0 -72
  66. package/dist/features/define.js.map +0 -1
  67. package/dist/features/flags.d.ts +0 -98
  68. package/dist/features/flags.d.ts.map +0 -1
  69. package/dist/features/flags.js +0 -145
  70. package/dist/features/flags.js.map +0 -1
  71. package/dist/features/toggles.d.ts +0 -75
  72. package/dist/features/toggles.d.ts.map +0 -1
  73. package/dist/features/toggles.js +0 -107
  74. package/dist/features/toggles.js.map +0 -1
  75. package/dist/tiers/define.d.ts +0 -63
  76. package/dist/tiers/define.d.ts.map +0 -1
  77. package/dist/tiers/define.js +0 -78
  78. package/dist/tiers/define.js.map +0 -1
  79. package/dist/tiers/entitlements.d.ts +0 -94
  80. package/dist/tiers/entitlements.d.ts.map +0 -1
  81. package/dist/tiers/entitlements.js +0 -94
  82. package/dist/tiers/entitlements.js.map +0 -1
  83. package/src/api.js +0 -128
  84. package/src/app.js +0 -106
  85. package/src/content.js +0 -77
  86. package/src/data.js +0 -106
  87. package/src/dataset.js +0 -49
  88. package/src/entities/ai.js +0 -858
  89. package/src/entities/content.js +0 -783
  90. package/src/entities/index.js +0 -88
  91. package/src/entities/interfaces.js +0 -929
  92. package/src/entities/lifecycle.js +0 -803
  93. package/src/entities/products.js +0 -797
  94. package/src/entities/web.js +0 -657
  95. package/src/features/define.ts +0 -130
  96. package/src/features/flags.ts +0 -247
  97. package/src/features/toggles.ts +0 -189
  98. package/src/index.js +0 -35
  99. package/src/mcp.js +0 -139
  100. package/src/pricing/billing.ts +0 -386
  101. package/src/pricing/plans.ts +0 -214
  102. package/src/product.js +0 -53
  103. package/src/registry.js +0 -31
  104. package/src/sdk.js +0 -127
  105. package/src/site.js +0 -112
  106. package/src/tiers/define.ts +0 -137
  107. package/src/tiers/entitlements.ts +0 -201
  108. package/src/types.js +0 -4
  109. package/test/analytics/events.test.ts +0 -319
  110. package/test/analytics/experiments.test.ts +0 -327
  111. package/test/features/define.test.ts +0 -187
  112. package/test/features/flags.test.ts +0 -259
  113. package/test/features/toggles.test.ts +0 -178
  114. package/test/lifecycle/stages.test.ts +0 -233
  115. package/test/lifecycle/transitions.test.ts +0 -207
  116. package/test/onboarding/flows.test.ts +0 -307
  117. package/test/pricing/billing.test.ts +0 -287
  118. package/test/pricing/plans.test.ts +0 -307
  119. package/test/roadmap/milestones.test.ts +0 -231
  120. package/test/roadmap/priorities.test.ts +0 -239
  121. package/test/tiers/define.test.ts +0 -192
  122. package/test/tiers/entitlements.test.ts +0 -220
@@ -1,207 +0,0 @@
1
- /**
2
- * Tests for Lifecycle Transitions
3
- * Phase 1: RED - These tests should FAIL initially
4
- */
5
-
6
- import { describe, it, expect, beforeEach } from 'vitest'
7
- import {
8
- TransitionRule,
9
- createTransitionRule,
10
- TransitionChecker,
11
- createTransitionChecker,
12
- TransitionResult,
13
- } from '../../src/lifecycle/transitions.js'
14
-
15
- describe('Lifecycle Transitions', () => {
16
- describe('TransitionRule', () => {
17
- it('creates a basic rule', () => {
18
- const rule = createTransitionRule({
19
- from: 'beta',
20
- to: 'ga',
21
- name: 'Beta to GA',
22
- })
23
-
24
- expect(rule.from).toBe('beta')
25
- expect(rule.to).toBe('ga')
26
- })
27
-
28
- it('supports preconditions', () => {
29
- const rule = createTransitionRule({
30
- from: 'beta',
31
- to: 'ga',
32
- name: 'GA Readiness',
33
- preconditions: [
34
- { name: 'tests-pass', check: () => true },
35
- { name: 'docs-complete', check: () => true },
36
- ],
37
- })
38
-
39
- expect(rule.preconditions).toHaveLength(2)
40
- })
41
-
42
- it('supports required approvals', () => {
43
- const rule = createTransitionRule({
44
- from: 'ga',
45
- to: 'deprecated',
46
- name: 'Deprecation',
47
- requiredApprovals: ['product-lead', 'eng-lead'],
48
- })
49
-
50
- expect(rule.requiredApprovals).toContain('product-lead')
51
- })
52
-
53
- it('supports notifications', () => {
54
- const rule = createTransitionRule({
55
- from: 'deprecated',
56
- to: 'sunset',
57
- name: 'Sunset',
58
- notifications: [
59
- { channel: 'email', recipients: ['users'] },
60
- { channel: 'slack', recipients: ['#product'] },
61
- ],
62
- })
63
-
64
- expect(rule.notifications).toHaveLength(2)
65
- })
66
- })
67
-
68
- describe('TransitionChecker', () => {
69
- let checker: TransitionChecker
70
-
71
- beforeEach(() => {
72
- checker = createTransitionChecker()
73
- })
74
-
75
- it('validates allowed transition', () => {
76
- checker.addRule(
77
- createTransitionRule({
78
- from: 'beta',
79
- to: 'ga',
80
- name: 'Beta to GA',
81
- })
82
- )
83
-
84
- const result = checker.check('beta', 'ga')
85
- expect(result.allowed).toBe(true)
86
- })
87
-
88
- it('rejects disallowed transition', () => {
89
- checker.addRule(
90
- createTransitionRule({
91
- from: 'beta',
92
- to: 'ga',
93
- name: 'Beta to GA',
94
- })
95
- )
96
-
97
- const result = checker.check('ga', 'beta')
98
- expect(result.allowed).toBe(false)
99
- expect(result.reason).toContain('No rule defined')
100
- })
101
-
102
- it('checks preconditions', async () => {
103
- let testsPass = false
104
-
105
- checker.addRule(
106
- createTransitionRule({
107
- from: 'beta',
108
- to: 'ga',
109
- name: 'Beta to GA',
110
- preconditions: [{ name: 'tests-pass', check: () => testsPass }],
111
- })
112
- )
113
-
114
- let result = await checker.checkWithPreconditions('beta', 'ga')
115
- expect(result.allowed).toBe(false)
116
- expect(result.failedPreconditions).toContain('tests-pass')
117
-
118
- testsPass = true
119
- result = await checker.checkWithPreconditions('beta', 'ga')
120
- expect(result.allowed).toBe(true)
121
- })
122
-
123
- it('checks async preconditions', async () => {
124
- checker.addRule(
125
- createTransitionRule({
126
- from: 'beta',
127
- to: 'ga',
128
- name: 'Beta to GA',
129
- preconditions: [
130
- {
131
- name: 'api-check',
132
- check: async () => {
133
- // Simulate API check
134
- return true
135
- },
136
- },
137
- ],
138
- })
139
- )
140
-
141
- const result = await checker.checkWithPreconditions('beta', 'ga')
142
- expect(result.allowed).toBe(true)
143
- })
144
-
145
- it('checks required approvals', () => {
146
- checker.addRule(
147
- createTransitionRule({
148
- from: 'ga',
149
- to: 'deprecated',
150
- name: 'Deprecation',
151
- requiredApprovals: ['product-lead', 'eng-lead'],
152
- })
153
- )
154
-
155
- const result = checker.checkApprovals('ga', 'deprecated', {
156
- approvals: ['product-lead'],
157
- })
158
-
159
- expect(result.allowed).toBe(false)
160
- expect(result.missingApprovals).toContain('eng-lead')
161
- })
162
-
163
- it('passes with all approvals', () => {
164
- checker.addRule(
165
- createTransitionRule({
166
- from: 'ga',
167
- to: 'deprecated',
168
- name: 'Deprecation',
169
- requiredApprovals: ['product-lead', 'eng-lead'],
170
- })
171
- )
172
-
173
- const result = checker.checkApprovals('ga', 'deprecated', {
174
- approvals: ['product-lead', 'eng-lead'],
175
- })
176
-
177
- expect(result.allowed).toBe(true)
178
- })
179
-
180
- it('lists available transitions', () => {
181
- checker.addRule(createTransitionRule({ from: 'alpha', to: 'beta', name: 'To Beta' }))
182
- checker.addRule(createTransitionRule({ from: 'beta', to: 'ga', name: 'To GA' }))
183
- checker.addRule(createTransitionRule({ from: 'beta', to: 'alpha', name: 'Rollback' }))
184
-
185
- const available = checker.getAvailableTransitions('beta')
186
- expect(available).toContain('ga')
187
- expect(available).toContain('alpha')
188
- })
189
-
190
- it('validates transition path', () => {
191
- checker.addRule(createTransitionRule({ from: 'alpha', to: 'beta', name: 'To Beta' }))
192
- checker.addRule(createTransitionRule({ from: 'beta', to: 'ga', name: 'To GA' }))
193
-
194
- const path = checker.validatePath(['alpha', 'beta', 'ga'])
195
- expect(path.valid).toBe(true)
196
- })
197
-
198
- it('rejects invalid transition path', () => {
199
- checker.addRule(createTransitionRule({ from: 'alpha', to: 'beta', name: 'To Beta' }))
200
- checker.addRule(createTransitionRule({ from: 'beta', to: 'ga', name: 'To GA' }))
201
-
202
- const path = checker.validatePath(['alpha', 'ga']) // Missing beta
203
- expect(path.valid).toBe(false)
204
- expect(path.invalidAt).toBe(1)
205
- })
206
- })
207
- })
@@ -1,307 +0,0 @@
1
- /**
2
- * Tests for Onboarding Flows
3
- * Phase 1: RED - These tests should FAIL initially
4
- */
5
-
6
- import { describe, it, expect, beforeEach } from 'vitest'
7
- import {
8
- OnboardingFlow,
9
- createOnboardingFlow,
10
- OnboardingStep,
11
- OnboardingManager,
12
- createOnboardingManager,
13
- UserOnboardingState,
14
- } from '../../src/onboarding/flows.js'
15
-
16
- describe('Onboarding Flows', () => {
17
- describe('OnboardingStep', () => {
18
- it('defines a step with required fields', () => {
19
- const step: OnboardingStep = {
20
- id: 'welcome',
21
- name: 'Welcome',
22
- description: 'Welcome to the product',
23
- order: 0,
24
- }
25
-
26
- expect(step.id).toBe('welcome')
27
- expect(step.order).toBe(0)
28
- })
29
-
30
- it('supports completion criteria', () => {
31
- const step: OnboardingStep = {
32
- id: 'profile-setup',
33
- name: 'Set Up Profile',
34
- order: 1,
35
- completionCriteria: {
36
- event: 'profile.completed',
37
- requiredFields: ['name', 'avatar'],
38
- },
39
- }
40
-
41
- expect(step.completionCriteria?.event).toBe('profile.completed')
42
- })
43
-
44
- it('supports skip conditions', () => {
45
- const step: OnboardingStep = {
46
- id: 'connect-integration',
47
- name: 'Connect Integration',
48
- order: 2,
49
- skippable: true,
50
- skipCondition: {
51
- attribute: 'hasIntegrations',
52
- value: true,
53
- },
54
- }
55
-
56
- expect(step.skippable).toBe(true)
57
- })
58
-
59
- it('supports custom content', () => {
60
- const step: OnboardingStep = {
61
- id: 'tutorial',
62
- name: 'Tutorial',
63
- order: 3,
64
- content: {
65
- type: 'video',
66
- url: 'https://example.com/tutorial.mp4',
67
- duration: 120,
68
- },
69
- }
70
-
71
- expect(step.content?.type).toBe('video')
72
- })
73
- })
74
-
75
- describe('OnboardingFlow', () => {
76
- it('creates a flow with steps', () => {
77
- const flow = createOnboardingFlow({
78
- id: 'new-user',
79
- name: 'New User Onboarding',
80
- steps: [
81
- { id: 'welcome', name: 'Welcome', order: 0 },
82
- { id: 'setup', name: 'Setup', order: 1 },
83
- { id: 'first-task', name: 'First Task', order: 2 },
84
- ],
85
- })
86
-
87
- expect(flow.id).toBe('new-user')
88
- expect(flow.steps).toHaveLength(3)
89
- })
90
-
91
- it('orders steps correctly', () => {
92
- const flow = createOnboardingFlow({
93
- id: 'ordered',
94
- name: 'Ordered Flow',
95
- steps: [
96
- { id: 'c', name: 'C', order: 2 },
97
- { id: 'a', name: 'A', order: 0 },
98
- { id: 'b', name: 'B', order: 1 },
99
- ],
100
- })
101
-
102
- expect(flow.steps[0].id).toBe('a')
103
- expect(flow.steps[1].id).toBe('b')
104
- expect(flow.steps[2].id).toBe('c')
105
- })
106
-
107
- it('supports conditional steps', () => {
108
- const flow = createOnboardingFlow({
109
- id: 'conditional',
110
- name: 'Conditional Flow',
111
- steps: [
112
- { id: 'base', name: 'Base', order: 0 },
113
- {
114
- id: 'enterprise-only',
115
- name: 'Enterprise Setup',
116
- order: 1,
117
- condition: { attribute: 'tier', operator: 'equals', value: 'enterprise' },
118
- },
119
- ],
120
- })
121
-
122
- expect(flow.steps[1].condition).toBeDefined()
123
- })
124
-
125
- it('supports tier-specific flows', () => {
126
- const flow = createOnboardingFlow({
127
- id: 'pro-onboarding',
128
- name: 'Pro User Onboarding',
129
- targetTiers: ['pro', 'enterprise'],
130
- steps: [
131
- { id: 'advanced-features', name: 'Advanced Features', order: 0 },
132
- ],
133
- })
134
-
135
- expect(flow.targetTiers).toContain('pro')
136
- })
137
-
138
- it('calculates estimated duration', () => {
139
- const flow = createOnboardingFlow({
140
- id: 'timed',
141
- name: 'Timed Flow',
142
- steps: [
143
- { id: 's1', name: 'S1', order: 0, estimatedMinutes: 5 },
144
- { id: 's2', name: 'S2', order: 1, estimatedMinutes: 10 },
145
- { id: 's3', name: 'S3', order: 2, estimatedMinutes: 3 },
146
- ],
147
- })
148
-
149
- expect(flow.totalEstimatedMinutes).toBe(18)
150
- })
151
- })
152
-
153
- describe('OnboardingManager', () => {
154
- let manager: OnboardingManager
155
-
156
- beforeEach(() => {
157
- manager = createOnboardingManager()
158
-
159
- manager.registerFlow(
160
- createOnboardingFlow({
161
- id: 'default',
162
- name: 'Default Onboarding',
163
- steps: [
164
- { id: 'welcome', name: 'Welcome', order: 0 },
165
- { id: 'setup', name: 'Setup', order: 1 },
166
- { id: 'complete', name: 'Complete', order: 2 },
167
- ],
168
- })
169
- )
170
- })
171
-
172
- it('starts onboarding for user', () => {
173
- const state = manager.startOnboarding('user-123', 'default')
174
-
175
- expect(state.userId).toBe('user-123')
176
- expect(state.flowId).toBe('default')
177
- expect(state.currentStepId).toBe('welcome')
178
- expect(state.completedSteps).toHaveLength(0)
179
- })
180
-
181
- it('advances to next step', () => {
182
- manager.startOnboarding('user-456', 'default')
183
- const state = manager.completeStep('user-456', 'welcome')
184
-
185
- expect(state.currentStepId).toBe('setup')
186
- expect(state.completedSteps).toContain('welcome')
187
- })
188
-
189
- it('completes onboarding', () => {
190
- manager.startOnboarding('user-789', 'default')
191
- manager.completeStep('user-789', 'welcome')
192
- manager.completeStep('user-789', 'setup')
193
- const state = manager.completeStep('user-789', 'complete')
194
-
195
- expect(state.status).toBe('completed')
196
- expect(state.completedAt).toBeDefined()
197
- })
198
-
199
- it('calculates progress', () => {
200
- manager.startOnboarding('user-progress', 'default')
201
- expect(manager.getProgress('user-progress')).toBe(0)
202
-
203
- manager.completeStep('user-progress', 'welcome')
204
- expect(manager.getProgress('user-progress')).toBeCloseTo(33, 0)
205
-
206
- manager.completeStep('user-progress', 'setup')
207
- expect(manager.getProgress('user-progress')).toBeCloseTo(67, 0)
208
- })
209
-
210
- it('skips steps', () => {
211
- manager.registerFlow(
212
- createOnboardingFlow({
213
- id: 'skippable',
214
- name: 'Skippable Flow',
215
- steps: [
216
- { id: 's1', name: 'S1', order: 0 },
217
- { id: 's2', name: 'S2', order: 1, skippable: true },
218
- { id: 's3', name: 'S3', order: 2 },
219
- ],
220
- })
221
- )
222
-
223
- manager.startOnboarding('skip-user', 'skippable')
224
- manager.completeStep('skip-user', 's1')
225
- const state = manager.skipStep('skip-user', 's2')
226
-
227
- expect(state.currentStepId).toBe('s3')
228
- expect(state.skippedSteps).toContain('s2')
229
- })
230
-
231
- it('prevents skipping non-skippable steps', () => {
232
- manager.startOnboarding('no-skip-user', 'default')
233
-
234
- expect(() => manager.skipStep('no-skip-user', 'welcome')).toThrow(
235
- 'Step is not skippable'
236
- )
237
- })
238
-
239
- it('resumes onboarding', () => {
240
- manager.startOnboarding('resume-user', 'default')
241
- manager.completeStep('resume-user', 'welcome')
242
-
243
- // Simulate session end and resume
244
- const state = manager.getState('resume-user')
245
- expect(state?.currentStepId).toBe('setup')
246
- })
247
-
248
- it('tracks time spent per step', () => {
249
- manager.startOnboarding('time-user', 'default')
250
-
251
- // Simulate spending time on step
252
- manager.startStep('time-user', 'welcome')
253
- // ... time passes ...
254
- manager.completeStep('time-user', 'welcome')
255
-
256
- const state = manager.getState('time-user')
257
- expect(state?.stepTimes?.welcome).toBeDefined()
258
- })
259
-
260
- it('supports multiple flows per user', () => {
261
- manager.registerFlow(
262
- createOnboardingFlow({
263
- id: 'advanced',
264
- name: 'Advanced Features',
265
- steps: [{ id: 'advanced-1', name: 'Advanced 1', order: 0 }],
266
- })
267
- )
268
-
269
- manager.startOnboarding('multi-flow-user', 'default')
270
- manager.startOnboarding('multi-flow-user', 'advanced')
271
-
272
- const flows = manager.getActiveFlows('multi-flow-user')
273
- expect(flows).toHaveLength(2)
274
- })
275
-
276
- it('emits events on step completion', () => {
277
- const events: Array<{ event: string; stepId: string }> = []
278
- manager.on('step.completed', (e) => events.push(e))
279
-
280
- manager.startOnboarding('event-user', 'default')
281
- manager.completeStep('event-user', 'welcome')
282
-
283
- expect(events).toHaveLength(1)
284
- expect(events[0].stepId).toBe('welcome')
285
- })
286
-
287
- it('gets onboarding analytics', () => {
288
- // Complete flow for some users
289
- for (let i = 0; i < 5; i++) {
290
- manager.startOnboarding(`user-${i}`, 'default')
291
- manager.completeStep(`user-${i}`, 'welcome')
292
- if (i < 3) {
293
- manager.completeStep(`user-${i}`, 'setup')
294
- }
295
- if (i < 2) {
296
- manager.completeStep(`user-${i}`, 'complete')
297
- }
298
- }
299
-
300
- const analytics = manager.getAnalytics('default')
301
- expect(analytics.started).toBe(5)
302
- expect(analytics.completed).toBe(2)
303
- expect(analytics.dropoffByStep.welcome).toBe(0)
304
- expect(analytics.dropoffByStep.setup).toBe(1)
305
- })
306
- })
307
- })