digital-products 2.1.3 → 2.4.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/.turbo/turbo-build.log +4 -5
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +2 -0
  4. package/dist/api.js +7 -7
  5. package/dist/api.js.map +1 -1
  6. package/dist/app.js +6 -6
  7. package/dist/app.js.map +1 -1
  8. package/dist/client.d.ts +157 -0
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +69 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/content.js +7 -7
  13. package/dist/content.js.map +1 -1
  14. package/dist/data.d.ts.map +1 -1
  15. package/dist/data.js +6 -6
  16. package/dist/data.js.map +1 -1
  17. package/dist/dataset.js +5 -5
  18. package/dist/dataset.js.map +1 -1
  19. package/dist/index.d.ts +92 -13
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +139 -15
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp.d.ts +1 -1
  24. package/dist/mcp.d.ts.map +1 -1
  25. package/dist/mcp.js +17 -10
  26. package/dist/mcp.js.map +1 -1
  27. package/dist/product.js +2 -2
  28. package/dist/product.js.map +1 -1
  29. package/dist/sdk.d.ts.map +1 -1
  30. package/dist/sdk.js +52 -16
  31. package/dist/sdk.js.map +1 -1
  32. package/dist/site.d.ts.map +1 -1
  33. package/dist/site.js +12 -8
  34. package/dist/site.js.map +1 -1
  35. package/dist/types.d.ts +830 -12
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/types.js +495 -2
  38. package/dist/types.js.map +1 -1
  39. package/dist/worker.d.ts +205 -0
  40. package/dist/worker.d.ts.map +1 -0
  41. package/dist/worker.js +356 -0
  42. package/dist/worker.js.map +1 -0
  43. package/package.json +29 -13
  44. package/src/api.ts +7 -7
  45. package/src/app.ts +6 -6
  46. package/src/client.ts +192 -0
  47. package/src/content.ts +7 -7
  48. package/src/data.ts +12 -7
  49. package/src/dataset.ts +5 -5
  50. package/src/index.ts +151 -15
  51. package/src/mcp.ts +18 -11
  52. package/src/product.ts +2 -2
  53. package/src/sdk.ts +54 -15
  54. package/src/site.ts +12 -8
  55. package/src/types.ts +821 -12
  56. package/src/worker.ts +525 -0
  57. package/test/product.test.ts +53 -198
  58. package/test/unified-types.test.ts +589 -0
  59. package/test/worker.test.ts +912 -0
  60. package/vitest.config.ts +42 -0
  61. package/wrangler.jsonc +36 -0
  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,201 +0,0 @@
1
- /**
2
- * Entitlements
3
- * Feature and resource entitlement management
4
- */
5
-
6
- /**
7
- * Entitlement types
8
- */
9
- export type EntitlementType = 'boolean' | 'limit' | 'quota' | 'feature'
10
-
11
- /**
12
- * Quota period
13
- */
14
- export type QuotaPeriod = 'daily' | 'weekly' | 'monthly' | 'yearly'
15
-
16
- /**
17
- * Entitlement definition
18
- */
19
- export interface EntitlementDefinition {
20
- /** Unique identifier */
21
- id: string
22
- /** Human-readable name */
23
- name: string
24
- /** Entitlement type */
25
- type: EntitlementType
26
- /** Description */
27
- description?: string
28
- /** Limit value (for limit type) */
29
- limit?: number
30
- /** Unit (for limit type) */
31
- unit?: string
32
- /** Quota value (for quota type) */
33
- quota?: number
34
- /** Period (for quota type) */
35
- period?: QuotaPeriod
36
- /** Feature ID (for feature type) */
37
- featureId?: string
38
- /** Tiers that have this entitlement */
39
- tiers?: string[]
40
- /** Tier-specific limits */
41
- tierLimits?: Record<string, number>
42
- /** Tier-specific quotas */
43
- tierQuotas?: Record<string, number>
44
- }
45
-
46
- /**
47
- * Create an entitlement
48
- */
49
- export function Entitlement(config: EntitlementDefinition): EntitlementDefinition {
50
- return {
51
- id: config.id,
52
- name: config.name,
53
- type: config.type,
54
- description: config.description,
55
- limit: config.limit,
56
- unit: config.unit,
57
- quota: config.quota,
58
- period: config.period,
59
- featureId: config.featureId,
60
- tiers: config.tiers,
61
- tierLimits: config.tierLimits,
62
- tierQuotas: config.tierQuotas,
63
- }
64
- }
65
-
66
- /**
67
- * Entitlement check request
68
- */
69
- export interface EntitlementCheck {
70
- entitlementId: string
71
- tier: string
72
- currentUsage?: number
73
- requestedUsage?: number
74
- }
75
-
76
- /**
77
- * Quota check result
78
- */
79
- export interface QuotaCheckResult {
80
- allowed: boolean
81
- remaining: number
82
- limit: number
83
- }
84
-
85
- /**
86
- * Entitlement comparison result
87
- */
88
- export interface EntitlementComparisonResult {
89
- added: string[]
90
- removed: string[]
91
- unchanged: string[]
92
- }
93
-
94
- /**
95
- * Entitlement manager interface
96
- */
97
- export interface EntitlementManager {
98
- /** Register an entitlement */
99
- register(entitlement: EntitlementDefinition): void
100
- /** Get an entitlement by ID */
101
- get(id: string): EntitlementDefinition | undefined
102
- /** Check if tier has entitlement */
103
- hasEntitlement(entitlementId: string, tier: string): boolean
104
- /** Get limit for tier */
105
- getLimit(entitlementId: string, tier: string): number | undefined
106
- /** Check quota */
107
- checkQuota(check: EntitlementCheck): QuotaCheckResult
108
- /** Get all entitlements for a tier */
109
- getEntitlementsForTier(tier: string): EntitlementDefinition[]
110
- /** Compare entitlements between tiers */
111
- compareEntitlements(tierA: string, tierB: string): EntitlementComparisonResult
112
- }
113
-
114
- /**
115
- * Create an entitlement manager
116
- */
117
- export function createEntitlementManager(): EntitlementManager {
118
- const entitlements = new Map<string, EntitlementDefinition>()
119
-
120
- return {
121
- register(entitlement: EntitlementDefinition): void {
122
- entitlements.set(entitlement.id, entitlement)
123
- },
124
-
125
- get(id: string): EntitlementDefinition | undefined {
126
- return entitlements.get(id)
127
- },
128
-
129
- hasEntitlement(entitlementId: string, tier: string): boolean {
130
- const entitlement = entitlements.get(entitlementId)
131
- if (!entitlement) return false
132
-
133
- return entitlement.tiers?.includes(tier) ?? false
134
- },
135
-
136
- getLimit(entitlementId: string, tier: string): number | undefined {
137
- const entitlement = entitlements.get(entitlementId)
138
- if (!entitlement) return undefined
139
-
140
- return entitlement.tierLimits?.[tier] ?? entitlement.limit
141
- },
142
-
143
- checkQuota(check: EntitlementCheck): QuotaCheckResult {
144
- const entitlement = entitlements.get(check.entitlementId)
145
- if (!entitlement) {
146
- return { allowed: false, remaining: 0, limit: 0 }
147
- }
148
-
149
- const limit = entitlement.tierQuotas?.[check.tier] ?? entitlement.quota ?? 0
150
- const currentUsage = check.currentUsage ?? 0
151
- const requestedUsage = check.requestedUsage ?? 0
152
-
153
- // Unlimited (-1)
154
- if (limit === -1) {
155
- return { allowed: true, remaining: Infinity, limit: -1 }
156
- }
157
-
158
- const remaining = Math.max(0, limit - currentUsage)
159
- const allowed = currentUsage + requestedUsage <= limit
160
-
161
- return { allowed, remaining: remaining - (allowed ? requestedUsage : 0), limit }
162
- },
163
-
164
- getEntitlementsForTier(tier: string): EntitlementDefinition[] {
165
- return Array.from(entitlements.values()).filter((e) => e.tiers?.includes(tier))
166
- },
167
-
168
- compareEntitlements(tierA: string, tierB: string): EntitlementComparisonResult {
169
- const tierAEnts = new Set(
170
- Array.from(entitlements.values())
171
- .filter((e) => e.tiers?.includes(tierA))
172
- .map((e) => e.id)
173
- )
174
- const tierBEnts = new Set(
175
- Array.from(entitlements.values())
176
- .filter((e) => e.tiers?.includes(tierB))
177
- .map((e) => e.id)
178
- )
179
-
180
- const added: string[] = []
181
- const removed: string[] = []
182
- const unchanged: string[] = []
183
-
184
- for (const id of tierBEnts) {
185
- if (tierAEnts.has(id)) {
186
- unchanged.push(id)
187
- } else {
188
- added.push(id)
189
- }
190
- }
191
-
192
- for (const id of tierAEnts) {
193
- if (!tierBEnts.has(id)) {
194
- removed.push(id)
195
- }
196
- }
197
-
198
- return { added, removed, unchanged }
199
- },
200
- }
201
- }
package/src/types.js DELETED
@@ -1,4 +0,0 @@
1
- /**
2
- * Core types for digital products
3
- */
4
- export {};
@@ -1,319 +0,0 @@
1
- /**
2
- * Tests for Analytics Events
3
- * Phase 1: RED - These tests should FAIL initially
4
- */
5
-
6
- import { describe, it, expect, beforeEach } from 'vitest'
7
- import {
8
- ProductEvent,
9
- createProductEvent,
10
- EventSchema,
11
- createEventSchema,
12
- EventEmitter,
13
- createEventEmitter,
14
- EventTracker,
15
- createEventTracker,
16
- } from '../../src/analytics/events.js'
17
-
18
- describe('Analytics Events', () => {
19
- describe('ProductEvent', () => {
20
- it('creates a basic event', () => {
21
- const event = createProductEvent({
22
- name: 'page.view',
23
- properties: {
24
- path: '/dashboard',
25
- title: 'Dashboard',
26
- },
27
- })
28
-
29
- expect(event.name).toBe('page.view')
30
- expect(event.properties.path).toBe('/dashboard')
31
- expect(event.timestamp).toBeDefined()
32
- })
33
-
34
- it('auto-generates timestamp', () => {
35
- const before = Date.now()
36
- const event = createProductEvent({
37
- name: 'test.event',
38
- })
39
- const after = Date.now()
40
-
41
- expect(event.timestamp.getTime()).toBeGreaterThanOrEqual(before)
42
- expect(event.timestamp.getTime()).toBeLessThanOrEqual(after)
43
- })
44
-
45
- it('supports custom timestamp', () => {
46
- const customTime = new Date('2024-01-15T10:00:00Z')
47
- const event = createProductEvent({
48
- name: 'test.event',
49
- timestamp: customTime,
50
- })
51
-
52
- expect(event.timestamp).toEqual(customTime)
53
- })
54
-
55
- it('supports user context', () => {
56
- const event = createProductEvent({
57
- name: 'button.click',
58
- userId: 'user-123',
59
- sessionId: 'session-456',
60
- properties: { button: 'submit' },
61
- })
62
-
63
- expect(event.userId).toBe('user-123')
64
- expect(event.sessionId).toBe('session-456')
65
- })
66
-
67
- it('supports product context', () => {
68
- const event = createProductEvent({
69
- name: 'feature.used',
70
- productId: 'my-product',
71
- version: '2.0.0',
72
- tier: 'pro',
73
- properties: { feature: 'analytics' },
74
- })
75
-
76
- expect(event.productId).toBe('my-product')
77
- expect(event.version).toBe('2.0.0')
78
- expect(event.tier).toBe('pro')
79
- })
80
- })
81
-
82
- describe('EventSchema', () => {
83
- it('defines required properties', () => {
84
- const schema = createEventSchema({
85
- name: 'signup.completed',
86
- properties: {
87
- email: { type: 'string', required: true },
88
- plan: { type: 'string', required: true },
89
- source: { type: 'string', required: false },
90
- },
91
- })
92
-
93
- expect(schema.properties.email.required).toBe(true)
94
- })
95
-
96
- it('validates event against schema', () => {
97
- const schema = createEventSchema({
98
- name: 'purchase.completed',
99
- properties: {
100
- amount: { type: 'number', required: true },
101
- currency: { type: 'string', required: true },
102
- },
103
- })
104
-
105
- const validEvent = createProductEvent({
106
- name: 'purchase.completed',
107
- properties: { amount: 99.99, currency: 'USD' },
108
- })
109
-
110
- const invalidEvent = createProductEvent({
111
- name: 'purchase.completed',
112
- properties: { amount: 99.99 }, // Missing currency
113
- })
114
-
115
- expect(schema.validate(validEvent)).toEqual({ valid: true })
116
- expect(schema.validate(invalidEvent)).toEqual({
117
- valid: false,
118
- errors: ['Missing required property: currency'],
119
- })
120
- })
121
-
122
- it('validates property types', () => {
123
- const schema = createEventSchema({
124
- name: 'test.event',
125
- properties: {
126
- count: { type: 'number', required: true },
127
- },
128
- })
129
-
130
- const invalidEvent = createProductEvent({
131
- name: 'test.event',
132
- properties: { count: 'not-a-number' },
133
- })
134
-
135
- expect(schema.validate(invalidEvent)).toEqual({
136
- valid: false,
137
- errors: ['Property count should be number, got string'],
138
- })
139
- })
140
-
141
- it('supports enum values', () => {
142
- const schema = createEventSchema({
143
- name: 'plan.selected',
144
- properties: {
145
- plan: {
146
- type: 'string',
147
- required: true,
148
- enum: ['free', 'pro', 'enterprise'],
149
- },
150
- },
151
- })
152
-
153
- const valid = createProductEvent({
154
- name: 'plan.selected',
155
- properties: { plan: 'pro' },
156
- })
157
-
158
- const invalid = createProductEvent({
159
- name: 'plan.selected',
160
- properties: { plan: 'invalid-plan' },
161
- })
162
-
163
- expect(schema.validate(valid).valid).toBe(true)
164
- expect(schema.validate(invalid).valid).toBe(false)
165
- })
166
- })
167
-
168
- describe('EventEmitter', () => {
169
- let emitter: EventEmitter
170
-
171
- beforeEach(() => {
172
- emitter = createEventEmitter()
173
- })
174
-
175
- it('emits events', () => {
176
- const events: ProductEvent[] = []
177
- emitter.on('page.view', (e) => events.push(e))
178
-
179
- emitter.emit(
180
- createProductEvent({
181
- name: 'page.view',
182
- properties: { path: '/' },
183
- })
184
- )
185
-
186
- expect(events).toHaveLength(1)
187
- })
188
-
189
- it('supports wildcard listeners', () => {
190
- const events: ProductEvent[] = []
191
- emitter.on('*', (e) => events.push(e))
192
-
193
- emitter.emit(createProductEvent({ name: 'event.one' }))
194
- emitter.emit(createProductEvent({ name: 'event.two' }))
195
-
196
- expect(events).toHaveLength(2)
197
- })
198
-
199
- it('supports pattern matching', () => {
200
- const events: ProductEvent[] = []
201
- emitter.on('user.*', (e) => events.push(e))
202
-
203
- emitter.emit(createProductEvent({ name: 'user.signup' }))
204
- emitter.emit(createProductEvent({ name: 'user.login' }))
205
- emitter.emit(createProductEvent({ name: 'page.view' }))
206
-
207
- expect(events).toHaveLength(2)
208
- })
209
-
210
- it('removes listeners', () => {
211
- const events: ProductEvent[] = []
212
- const handler = (e: ProductEvent) => events.push(e)
213
-
214
- emitter.on('test', handler)
215
- emitter.emit(createProductEvent({ name: 'test' }))
216
- expect(events).toHaveLength(1)
217
-
218
- emitter.off('test', handler)
219
- emitter.emit(createProductEvent({ name: 'test' }))
220
- expect(events).toHaveLength(1)
221
- })
222
-
223
- it('supports once listeners', () => {
224
- const events: ProductEvent[] = []
225
- emitter.once('once-event', (e) => events.push(e))
226
-
227
- emitter.emit(createProductEvent({ name: 'once-event' }))
228
- emitter.emit(createProductEvent({ name: 'once-event' }))
229
-
230
- expect(events).toHaveLength(1)
231
- })
232
- })
233
-
234
- describe('EventTracker', () => {
235
- let tracker: EventTracker
236
-
237
- beforeEach(() => {
238
- tracker = createEventTracker({
239
- productId: 'test-product',
240
- version: '1.0.0',
241
- })
242
- })
243
-
244
- it('tracks events with product context', () => {
245
- const tracked = tracker.track('button.click', { button: 'submit' })
246
-
247
- expect(tracked.productId).toBe('test-product')
248
- expect(tracked.version).toBe('1.0.0')
249
- })
250
-
251
- it('batches events', async () => {
252
- const sentBatches: ProductEvent[][] = []
253
-
254
- tracker = createEventTracker({
255
- productId: 'batch-test',
256
- batchSize: 3,
257
- onFlush: (events) => sentBatches.push(events),
258
- })
259
-
260
- tracker.track('event1')
261
- tracker.track('event2')
262
- expect(sentBatches).toHaveLength(0)
263
-
264
- tracker.track('event3')
265
- expect(sentBatches).toHaveLength(1)
266
- expect(sentBatches[0]).toHaveLength(3)
267
- })
268
-
269
- it('flushes on interval', async () => {
270
- const sentBatches: ProductEvent[][] = []
271
-
272
- tracker = createEventTracker({
273
- productId: 'interval-test',
274
- flushInterval: 50,
275
- onFlush: (events) => sentBatches.push(events),
276
- })
277
-
278
- tracker.track('event1')
279
- await new Promise((r) => setTimeout(r, 100))
280
-
281
- expect(sentBatches).toHaveLength(1)
282
- })
283
-
284
- it('enriches events with global context', () => {
285
- tracker.setContext({
286
- userId: 'global-user',
287
- tier: 'enterprise',
288
- })
289
-
290
- const event = tracker.track('test.event')
291
- expect(event.userId).toBe('global-user')
292
- expect(event.tier).toBe('enterprise')
293
- })
294
-
295
- it('validates events against schemas', () => {
296
- tracker.registerSchema(
297
- createEventSchema({
298
- name: 'validated.event',
299
- properties: {
300
- required_prop: { type: 'string', required: true },
301
- },
302
- })
303
- )
304
-
305
- expect(() => tracker.track('validated.event', {})).toThrow(
306
- 'Event validation failed'
307
- )
308
- })
309
-
310
- it('provides analytics helpers', () => {
311
- tracker.pageView('/dashboard', { title: 'Dashboard' })
312
- tracker.identify('user-123', { email: 'user@example.com' })
313
- tracker.track('feature.used', { feature: 'analytics' })
314
-
315
- const events = tracker.getBuffer()
316
- expect(events).toHaveLength(3)
317
- })
318
- })
319
- })