@stacksjs/ts-analytics 0.1.6
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.
- package/CHANGELOG.md +68 -0
- package/LICENSE.md +21 -0
- package/README.md +361 -0
- package/bin/cli.ts +169 -0
- package/dist/Analytics.d.ts +558 -0
- package/dist/api.d.ts +109 -0
- package/dist/batching.d.ts +93 -0
- package/dist/chunk-2mx7fq49.js +4 -0
- package/dist/chunk-3z29508k.js +204 -0
- package/dist/chunk-deephkz6.js +56 -0
- package/dist/chunk-j261vgyp.js +305 -0
- package/dist/chunk-xga17tz7.js +207 -0
- package/dist/config.d.ts +132 -0
- package/dist/dashboard/components/index.d.ts +80 -0
- package/dist/dashboard/composables/useAnalytics.d.ts +99 -0
- package/dist/dashboard/index.d.ts +110 -0
- package/dist/dashboard/types/index.d.ts +144 -0
- package/dist/dashboard/utils/index.d.ts +81 -0
- package/dist/dynamodb.d.ts +86 -0
- package/dist/funnels.d.ts +104 -0
- package/dist/geolocation.d.ts +96 -0
- package/dist/index.d.ts +371 -0
- package/dist/index.js +10211 -0
- package/dist/infrastructure/cdk.d.ts +60 -0
- package/dist/infrastructure/cloudformation.d.ts +44 -0
- package/dist/infrastructure/index.d.ts +45 -0
- package/dist/infrastructure/setup.d.ts +133 -0
- package/dist/integrations/cloudflare.d.ts +76 -0
- package/dist/integrations/hono.d.ts +60 -0
- package/dist/integrations/index.d.ts +24 -0
- package/dist/integrations/nuxt.d.ts +7 -0
- package/dist/integrations/nuxt.js +42 -0
- package/dist/integrations/runtime/use-ts-analytics.d.ts +18 -0
- package/dist/integrations/runtime/use-ts-analytics.js +17 -0
- package/dist/integrations/stx.d.ts +88 -0
- package/dist/integrations/stx.js +18 -0
- package/dist/lib/crypto-random.d.ts +4 -0
- package/dist/lib/salt.d.ts +16 -0
- package/dist/local.d.ts +56 -0
- package/dist/model-connector.d.ts +145 -0
- package/dist/models/AggregatedStats.d.ts +9 -0
- package/dist/models/CampaignStats.d.ts +9 -0
- package/dist/models/Conversion.d.ts +11 -0
- package/dist/models/CustomEvent.d.ts +11 -0
- package/dist/models/DeviceStats.d.ts +9 -0
- package/dist/models/EventStats.d.ts +9 -0
- package/dist/models/GeoStats.d.ts +9 -0
- package/dist/models/Goal.d.ts +9 -0
- package/dist/models/GoalStats.d.ts +9 -0
- package/dist/models/PageStats.d.ts +11 -0
- package/dist/models/PageView.d.ts +13 -0
- package/dist/models/RealtimeStats.d.ts +9 -0
- package/dist/models/ReferrerStats.d.ts +9 -0
- package/dist/models/Session.d.ts +11 -0
- package/dist/models/Site.d.ts +11 -0
- package/dist/models/index.d.ts +28 -0
- package/dist/models/types.d.ts +60 -0
- package/dist/sqs-buffering.d.ts +243 -0
- package/dist/stacks-integration.d.ts +159 -0
- package/dist/tracking-script.d.ts +71 -0
- package/dist/tracking.d.ts +17 -0
- package/dist/tracking.js +26 -0
- package/dist/types.d.ts +595 -0
- package/dist/utils/geolocation.d.ts +111 -0
- package/dist/utils/user-agent.d.ts +17 -0
- package/dist/version.d.ts +7 -0
- package/package.json +119 -0
- package/src/Analytics.ts +3349 -0
- package/src/api.ts +1286 -0
- package/src/assets/crosswind.css +2220 -0
- package/src/batching.ts +452 -0
- package/src/components/dashboard/index.ts +18 -0
- package/src/config.ts +456 -0
- package/src/dashboard/Dashboard.stx +1517 -0
- package/src/dashboard/components/AlertCard.stx +177 -0
- package/src/dashboard/components/AnalyticsDashboard.stx +354 -0
- package/src/dashboard/components/AnimatedNumber.stx +86 -0
- package/src/dashboard/components/BarChart.stx +220 -0
- package/src/dashboard/components/BrowserBreakdown.stx +98 -0
- package/src/dashboard/components/BrowsersTable.stx +125 -0
- package/src/dashboard/components/CampaignBreakdown.stx +163 -0
- package/src/dashboard/components/CampaignTable.stx +238 -0
- package/src/dashboard/components/CountryList.stx +101 -0
- package/src/dashboard/components/DataTable.stx +226 -0
- package/src/dashboard/components/DateRangePicker.stx +77 -0
- package/src/dashboard/components/DeviceBreakdown.stx +94 -0
- package/src/dashboard/components/DevicesTable.stx +163 -0
- package/src/dashboard/components/DonutChart.stories.ts +55 -0
- package/src/dashboard/components/DonutChart.stx +157 -0
- package/src/dashboard/components/EmptyState.stx +90 -0
- package/src/dashboard/components/EngagementMetrics.stx +183 -0
- package/src/dashboard/components/EventsSection.stx +192 -0
- package/src/dashboard/components/FilterBar.stx +104 -0
- package/src/dashboard/components/FullAnalyticsDashboard.stx +455 -0
- package/src/dashboard/components/FunnelChart.stx +142 -0
- package/src/dashboard/components/GeoTable.stx +337 -0
- package/src/dashboard/components/GoalsPanel.stx +109 -0
- package/src/dashboard/components/Header.stx +39 -0
- package/src/dashboard/components/HeatmapChart.stx +140 -0
- package/src/dashboard/components/LiveActivityFeed.stx +172 -0
- package/src/dashboard/components/MetricComparison.stx +106 -0
- package/src/dashboard/components/MiniStats.stx +96 -0
- package/src/dashboard/components/OSBreakdown.stx +124 -0
- package/src/dashboard/components/PageDetailCard.stx +102 -0
- package/src/dashboard/components/PagesTable.stx +127 -0
- package/src/dashboard/components/ProgressRing.stx +89 -0
- package/src/dashboard/components/RealtimeCounter.stories.ts +45 -0
- package/src/dashboard/components/RealtimeCounter.stx +46 -0
- package/src/dashboard/components/ReferrersTable.stx +180 -0
- package/src/dashboard/components/SparklineChart.stx +160 -0
- package/src/dashboard/components/StatCard.stories.ts +58 -0
- package/src/dashboard/components/StatCard.stx +90 -0
- package/src/dashboard/components/SummaryStats.stx +81 -0
- package/src/dashboard/components/TabNav.stx +66 -0
- package/src/dashboard/components/ThemeSwitcher.stx +124 -0
- package/src/dashboard/components/TimeSeriesChart.stx +106 -0
- package/src/dashboard/components/TopList.stories.ts +58 -0
- package/src/dashboard/components/TopList.stx +74 -0
- package/src/dashboard/components/TrendIndicator.stx +84 -0
- package/src/dashboard/components/heatmap/ClickHeatmap.stx +264 -0
- package/src/dashboard/components/heatmap/ElementClickList.stx +125 -0
- package/src/dashboard/components/heatmap/HeatmapControls.stx +125 -0
- package/src/dashboard/components/heatmap/HeatmapLegend.stx +69 -0
- package/src/dashboard/components/heatmap/PageHeatmap.stx +264 -0
- package/src/dashboard/components/heatmap/ScrollHeatmap.stx +127 -0
- package/src/dashboard/components/heatmap/index.ts +12 -0
- package/src/dashboard/components/index.ts +86 -0
- package/src/dashboard/composables/useAnalytics.ts +465 -0
- package/src/dashboard/demo/DemoApp.stx +370 -0
- package/src/dashboard/demo/index.ts +8 -0
- package/src/dashboard/demo/mockData.ts +234 -0
- package/src/dashboard/index.ts +117 -0
- package/src/dashboard/stx-shim.d.ts +4 -0
- package/src/dashboard/types/index.ts +203 -0
- package/src/dashboard/utils/index.ts +426 -0
- package/src/dynamodb.ts +344 -0
- package/src/funnels.ts +534 -0
- package/src/geolocation.ts +515 -0
- package/src/handlers/alerts.ts +328 -0
- package/src/handlers/annotations.ts +128 -0
- package/src/handlers/api-keys.ts +240 -0
- package/src/handlers/auth.ts +1020 -0
- package/src/handlers/authz.ts +137 -0
- package/src/handlers/collect.ts +724 -0
- package/src/handlers/data.ts +625 -0
- package/src/handlers/experiments.ts +138 -0
- package/src/handlers/funnels.ts +216 -0
- package/src/handlers/goals.ts +218 -0
- package/src/handlers/heatmaps.ts +272 -0
- package/src/handlers/index.ts +56 -0
- package/src/handlers/lib/read-cache.ts +63 -0
- package/src/handlers/misc.ts +486 -0
- package/src/handlers/oauth.ts +233 -0
- package/src/handlers/performance.ts +388 -0
- package/src/handlers/sessions.ts +413 -0
- package/src/handlers/sharing.ts +198 -0
- package/src/handlers/stats.ts +1368 -0
- package/src/handlers/team.ts +161 -0
- package/src/handlers/uptime.ts +283 -0
- package/src/handlers/views.ts +390 -0
- package/src/handlers/webhooks.ts +226 -0
- package/src/heatmap/index.ts +32 -0
- package/src/heatmap/tracking-script.ts +452 -0
- package/src/heatmap/types.ts +79 -0
- package/src/index.ts +387 -0
- package/src/infrastructure/cdk.ts +496 -0
- package/src/infrastructure/cloudformation.ts +595 -0
- package/src/infrastructure/index.ts +48 -0
- package/src/infrastructure/setup.ts +611 -0
- package/src/integrations/cloudflare.ts +732 -0
- package/src/integrations/hono.ts +589 -0
- package/src/integrations/index.ts +27 -0
- package/src/integrations/nuxt.ts +78 -0
- package/src/integrations/runtime/use-ts-analytics.ts +32 -0
- package/src/integrations/stx.ts +138 -0
- package/src/jobs/index.ts +127 -0
- package/src/lib/crypto-random.ts +41 -0
- package/src/lib/ddb-errors.ts +20 -0
- package/src/lib/dynamodb.ts +216 -0
- package/src/lib/email.ts +38 -0
- package/src/lib/ga-import.ts +471 -0
- package/src/lib/ga4-api.ts +244 -0
- package/src/lib/goals.ts +205 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/ingest-counters.ts +123 -0
- package/src/lib/log.ts +36 -0
- package/src/lib/membership.ts +98 -0
- package/src/lib/plans.ts +90 -0
- package/src/lib/quota.ts +46 -0
- package/src/lib/rate-limit.ts +27 -0
- package/src/lib/rollups.ts +472 -0
- package/src/lib/salt.ts +97 -0
- package/src/lib/scheduler.ts +97 -0
- package/src/lib/significance.ts +66 -0
- package/src/lib/site-retention.ts +56 -0
- package/src/local.ts +360 -0
- package/src/model-connector.ts +616 -0
- package/src/models/AggregatedStats.ts +162 -0
- package/src/models/CampaignStats.ts +141 -0
- package/src/models/Conversion.ts +135 -0
- package/src/models/CustomEvent.ts +123 -0
- package/src/models/DeviceStats.ts +105 -0
- package/src/models/EventStats.ts +108 -0
- package/src/models/GeoStats.ts +112 -0
- package/src/models/Goal.ts +105 -0
- package/src/models/GoalStats.ts +106 -0
- package/src/models/PageStats.ts +152 -0
- package/src/models/PageView.ts +277 -0
- package/src/models/RealtimeStats.ts +96 -0
- package/src/models/ReferrerStats.ts +115 -0
- package/src/models/Session.ts +235 -0
- package/src/models/Site.ts +115 -0
- package/src/models/index.ts +50 -0
- package/src/models/orm/index.ts +2043 -0
- package/src/models/types.ts +71 -0
- package/src/router.ts +521 -0
- package/src/sqs-buffering.ts +806 -0
- package/src/stacks-integration.ts +527 -0
- package/src/tracking-script.ts +944 -0
- package/src/tracking.ts +27 -0
- package/src/types/analytics.ts +219 -0
- package/src/types/api.ts +75 -0
- package/src/types/bun-router.d.ts +15 -0
- package/src/types/dashboard.ts +62 -0
- package/src/types/index.ts +66 -0
- package/src/types/stx.d.ts +27 -0
- package/src/types/window.d.ts +101 -0
- package/src/types.ts +911 -0
- package/src/utils/cache.ts +148 -0
- package/src/utils/date.ts +118 -0
- package/src/utils/filters.ts +71 -0
- package/src/utils/geolocation.ts +323 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/response.ts +180 -0
- package/src/utils/timezone-country.ts +242 -0
- package/src/utils/user-agent.ts +110 -0
- package/src/version.ts +7 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics Table Setup & Migration Scripts
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for creating, updating, and migrating
|
|
5
|
+
* the analytics DynamoDB table.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AnalyticsTableProps } from './cdk'
|
|
9
|
+
import { generateCreateTableInput } from './cdk'
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Types
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
export interface SetupConfig extends AnalyticsTableProps {
|
|
16
|
+
/** AWS region */
|
|
17
|
+
region?: string
|
|
18
|
+
/** DynamoDB endpoint (for local development) */
|
|
19
|
+
endpoint?: string
|
|
20
|
+
/** Wait for table to be active */
|
|
21
|
+
waitForActive?: boolean
|
|
22
|
+
/** Timeout for waiting (ms) */
|
|
23
|
+
waitTimeout?: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SetupResult {
|
|
27
|
+
success: boolean
|
|
28
|
+
tableName: string
|
|
29
|
+
tableArn?: string
|
|
30
|
+
error?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MigrationStep {
|
|
34
|
+
name: string
|
|
35
|
+
description: string
|
|
36
|
+
execute: (client: DynamoDBClientLike) => Promise<void>
|
|
37
|
+
rollback?: (client: DynamoDBClientLike) => Promise<void>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface MigrationResult {
|
|
41
|
+
success: boolean
|
|
42
|
+
stepsCompleted: string[]
|
|
43
|
+
error?: string
|
|
44
|
+
failedStep?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Client Interface (for dependency injection)
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Minimal DynamoDB client interface
|
|
53
|
+
* Allows using either AWS SDK v2 or v3
|
|
54
|
+
*/
|
|
55
|
+
export interface DynamoDBClientLike {
|
|
56
|
+
send: (command: unknown) => Promise<unknown>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* CreateTable command interface
|
|
61
|
+
*/
|
|
62
|
+
export interface CreateTableCommandLike {
|
|
63
|
+
input: Record<string, unknown>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* DescribeTable command interface
|
|
68
|
+
*/
|
|
69
|
+
export interface DescribeTableCommandLike {
|
|
70
|
+
input: { TableName: string }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* UpdateTimeToLive command interface
|
|
75
|
+
*/
|
|
76
|
+
export interface UpdateTimeToLiveCommandLike {
|
|
77
|
+
input: {
|
|
78
|
+
TableName: string
|
|
79
|
+
TimeToLiveSpecification: {
|
|
80
|
+
AttributeName: string
|
|
81
|
+
Enabled: boolean
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// Setup Functions
|
|
88
|
+
// ============================================================================
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create the analytics table
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* import { DynamoDBClient, CreateTableCommand } from '@aws-sdk/client-dynamodb'
|
|
96
|
+
* import { createAnalyticsTable } from 'dynamodb-tooling/analytics/infrastructure'
|
|
97
|
+
*
|
|
98
|
+
* const client = new DynamoDBClient({ region: 'us-east-1' })
|
|
99
|
+
*
|
|
100
|
+
* const result = await createAnalyticsTable(client, {
|
|
101
|
+
* tableName: 'my-analytics',
|
|
102
|
+
* billingMode: 'PAY_PER_REQUEST',
|
|
103
|
+
* })
|
|
104
|
+
*
|
|
105
|
+
* console.log(`Created table: ${result.tableName}`)
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export async function createAnalyticsTable(
|
|
109
|
+
client: DynamoDBClientLike,
|
|
110
|
+
config: SetupConfig | undefined = {},
|
|
111
|
+
commandFactories: {
|
|
112
|
+
CreateTableCommand: new (input: Record<string, unknown>) => unknown
|
|
113
|
+
DescribeTableCommand: new (input: { TableName: string }) => unknown
|
|
114
|
+
},
|
|
115
|
+
): Promise<SetupResult> {
|
|
116
|
+
const { CreateTableCommand, DescribeTableCommand } = commandFactories
|
|
117
|
+
const tableName = config.tableName || 'analytics'
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Check if table exists
|
|
121
|
+
try {
|
|
122
|
+
await client.send(new DescribeTableCommand({ TableName: tableName }))
|
|
123
|
+
return {
|
|
124
|
+
success: true,
|
|
125
|
+
tableName,
|
|
126
|
+
error: 'Table already exists',
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
// Table doesn't exist, continue with creation
|
|
131
|
+
if ((err as { name?: string }).name !== 'ResourceNotFoundException') {
|
|
132
|
+
throw err
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Create table
|
|
137
|
+
const createInput = generateCreateTableInput(config)
|
|
138
|
+
await client.send(new CreateTableCommand(createInput))
|
|
139
|
+
|
|
140
|
+
// Wait for table to be active
|
|
141
|
+
if (config.waitForActive !== false) {
|
|
142
|
+
const timeout = config.waitTimeout || 60000
|
|
143
|
+
const startTime = Date.now()
|
|
144
|
+
|
|
145
|
+
while (Date.now() - startTime < timeout) {
|
|
146
|
+
const describeResult = await client.send(
|
|
147
|
+
new DescribeTableCommand({ TableName: tableName }),
|
|
148
|
+
) as { Table?: { TableStatus?: string, TableArn?: string } }
|
|
149
|
+
|
|
150
|
+
if (describeResult.Table?.TableStatus === 'ACTIVE') {
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
tableName,
|
|
154
|
+
tableArn: describeResult.Table.TableArn,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Wait 1 second before checking again
|
|
159
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
success: false,
|
|
164
|
+
tableName,
|
|
165
|
+
error: 'Timeout waiting for table to become active',
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
success: true,
|
|
171
|
+
tableName,
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
tableName,
|
|
178
|
+
error: err instanceof Error ? err.message : String(err),
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Enable TTL on the analytics table
|
|
185
|
+
*/
|
|
186
|
+
export async function enableTtl(
|
|
187
|
+
client: DynamoDBClientLike,
|
|
188
|
+
tableName: string,
|
|
189
|
+
attributeName: string,
|
|
190
|
+
commandFactory: {
|
|
191
|
+
UpdateTimeToLiveCommand: new (input: {
|
|
192
|
+
TableName: string
|
|
193
|
+
TimeToLiveSpecification: { AttributeName: string, Enabled: boolean }
|
|
194
|
+
}) => unknown
|
|
195
|
+
},
|
|
196
|
+
): Promise<SetupResult> {
|
|
197
|
+
const { UpdateTimeToLiveCommand } = commandFactory
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
await client.send(
|
|
201
|
+
new UpdateTimeToLiveCommand({
|
|
202
|
+
TableName: tableName,
|
|
203
|
+
TimeToLiveSpecification: {
|
|
204
|
+
AttributeName: attributeName,
|
|
205
|
+
Enabled: true,
|
|
206
|
+
},
|
|
207
|
+
}),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
success: true,
|
|
212
|
+
tableName,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
tableName,
|
|
219
|
+
error: err instanceof Error ? err.message : String(err),
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Check if table exists and is active
|
|
226
|
+
*/
|
|
227
|
+
// eslint-disable-next-line pickier/no-unused-vars
|
|
228
|
+
export async function checkTableStatus(
|
|
229
|
+
client: DynamoDBClientLike,
|
|
230
|
+
tableName: string,
|
|
231
|
+
commandFactory: {
|
|
232
|
+
DescribeTableCommand: new (input: { TableName: string }) => unknown
|
|
233
|
+
},
|
|
234
|
+
): Promise<{
|
|
235
|
+
exists: boolean
|
|
236
|
+
status?: string
|
|
237
|
+
itemCount?: number
|
|
238
|
+
sizeBytes?: number
|
|
239
|
+
gsiCount?: number
|
|
240
|
+
}> {
|
|
241
|
+
const { DescribeTableCommand } = commandFactory
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const result = await client.send(
|
|
245
|
+
new DescribeTableCommand({ TableName: tableName }),
|
|
246
|
+
) as {
|
|
247
|
+
Table?: {
|
|
248
|
+
TableStatus?: string
|
|
249
|
+
ItemCount?: number
|
|
250
|
+
TableSizeBytes?: number
|
|
251
|
+
GlobalSecondaryIndexes?: unknown[]
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
exists: true,
|
|
257
|
+
status: result.Table?.TableStatus,
|
|
258
|
+
itemCount: result.Table?.ItemCount,
|
|
259
|
+
sizeBytes: result.Table?.TableSizeBytes,
|
|
260
|
+
gsiCount: result.Table?.GlobalSecondaryIndexes?.length,
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
if ((err as { name?: string }).name === 'ResourceNotFoundException') {
|
|
265
|
+
return { exists: false }
|
|
266
|
+
}
|
|
267
|
+
throw err
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ============================================================================
|
|
272
|
+
// Migration Functions
|
|
273
|
+
// ============================================================================
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Run analytics migrations
|
|
277
|
+
*/
|
|
278
|
+
export async function runMigrations(
|
|
279
|
+
client: DynamoDBClientLike,
|
|
280
|
+
_tableName: string,
|
|
281
|
+
migrations: MigrationStep[],
|
|
282
|
+
): Promise<MigrationResult> {
|
|
283
|
+
const stepsCompleted: string[] = []
|
|
284
|
+
|
|
285
|
+
for (const migration of migrations) {
|
|
286
|
+
try {
|
|
287
|
+
await migration.execute(client)
|
|
288
|
+
stepsCompleted.push(migration.name)
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
// Attempt rollback of completed steps
|
|
292
|
+
for (const completedName of stepsCompleted.reverse()) {
|
|
293
|
+
const completedMigration = migrations.find(m => m.name === completedName)
|
|
294
|
+
if (completedMigration?.rollback) {
|
|
295
|
+
try {
|
|
296
|
+
await completedMigration.rollback(client)
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// Ignore rollback errors
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
success: false,
|
|
306
|
+
stepsCompleted,
|
|
307
|
+
error: err instanceof Error ? err.message : String(err),
|
|
308
|
+
failedStep: migration.name,
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
success: true,
|
|
315
|
+
stepsCompleted,
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// Pre-defined Migrations
|
|
321
|
+
// ============================================================================
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Migration: Add GSI for time-based queries
|
|
325
|
+
*/
|
|
326
|
+
export function createTimeBasedGsiMigration(
|
|
327
|
+
tableName: string,
|
|
328
|
+
commandFactory: {
|
|
329
|
+
UpdateTableCommand: new (input: Record<string, unknown>) => unknown
|
|
330
|
+
},
|
|
331
|
+
): MigrationStep {
|
|
332
|
+
const { UpdateTableCommand } = commandFactory
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
name: 'add-time-gsi',
|
|
336
|
+
description: 'Add GSI for time-based queries (gsi3pk/gsi3sk)',
|
|
337
|
+
execute: async (client) => {
|
|
338
|
+
await client.send(
|
|
339
|
+
new UpdateTableCommand({
|
|
340
|
+
TableName: tableName,
|
|
341
|
+
AttributeDefinitions: [
|
|
342
|
+
{ AttributeName: 'gsi3pk', AttributeType: 'S' },
|
|
343
|
+
{ AttributeName: 'gsi3sk', AttributeType: 'S' },
|
|
344
|
+
],
|
|
345
|
+
GlobalSecondaryIndexUpdates: [
|
|
346
|
+
{
|
|
347
|
+
Create: {
|
|
348
|
+
IndexName: 'GSI3',
|
|
349
|
+
KeySchema: [
|
|
350
|
+
{ AttributeName: 'gsi3pk', KeyType: 'HASH' },
|
|
351
|
+
{ AttributeName: 'gsi3sk', KeyType: 'RANGE' },
|
|
352
|
+
],
|
|
353
|
+
Projection: { ProjectionType: 'ALL' },
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
}),
|
|
358
|
+
)
|
|
359
|
+
},
|
|
360
|
+
rollback: async (client) => {
|
|
361
|
+
await client.send(
|
|
362
|
+
new UpdateTableCommand({
|
|
363
|
+
TableName: tableName,
|
|
364
|
+
GlobalSecondaryIndexUpdates: [
|
|
365
|
+
{
|
|
366
|
+
Delete: { IndexName: 'GSI3' },
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
}),
|
|
370
|
+
)
|
|
371
|
+
},
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Migration: Enable Point-in-Time Recovery
|
|
377
|
+
*/
|
|
378
|
+
export function createPitrMigration(
|
|
379
|
+
tableName: string,
|
|
380
|
+
commandFactory: {
|
|
381
|
+
UpdateContinuousBackupsCommand: new (input: Record<string, unknown>) => unknown
|
|
382
|
+
},
|
|
383
|
+
): MigrationStep {
|
|
384
|
+
const { UpdateContinuousBackupsCommand } = commandFactory
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
name: 'enable-pitr',
|
|
388
|
+
description: 'Enable Point-in-Time Recovery',
|
|
389
|
+
execute: async (client) => {
|
|
390
|
+
await client.send(
|
|
391
|
+
new UpdateContinuousBackupsCommand({
|
|
392
|
+
TableName: tableName,
|
|
393
|
+
PointInTimeRecoverySpecification: {
|
|
394
|
+
PointInTimeRecoveryEnabled: true,
|
|
395
|
+
},
|
|
396
|
+
}),
|
|
397
|
+
)
|
|
398
|
+
},
|
|
399
|
+
rollback: async (client) => {
|
|
400
|
+
await client.send(
|
|
401
|
+
new UpdateContinuousBackupsCommand({
|
|
402
|
+
TableName: tableName,
|
|
403
|
+
PointInTimeRecoverySpecification: {
|
|
404
|
+
PointInTimeRecoveryEnabled: false,
|
|
405
|
+
},
|
|
406
|
+
}),
|
|
407
|
+
)
|
|
408
|
+
},
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Migration: Enable DynamoDB Streams
|
|
414
|
+
*/
|
|
415
|
+
export function createStreamsMigration(
|
|
416
|
+
tableName: string,
|
|
417
|
+
viewType: 'KEYS_ONLY' | 'NEW_IMAGE' | 'OLD_IMAGE' | 'NEW_AND_OLD_IMAGES',
|
|
418
|
+
commandFactory: {
|
|
419
|
+
UpdateTableCommand: new (input: Record<string, unknown>) => unknown
|
|
420
|
+
},
|
|
421
|
+
): MigrationStep {
|
|
422
|
+
const { UpdateTableCommand } = commandFactory
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
name: 'enable-streams',
|
|
426
|
+
description: `Enable DynamoDB Streams (${viewType})`,
|
|
427
|
+
execute: async (client) => {
|
|
428
|
+
await client.send(
|
|
429
|
+
new UpdateTableCommand({
|
|
430
|
+
TableName: tableName,
|
|
431
|
+
StreamSpecification: {
|
|
432
|
+
StreamEnabled: true,
|
|
433
|
+
StreamViewType: viewType,
|
|
434
|
+
},
|
|
435
|
+
}),
|
|
436
|
+
)
|
|
437
|
+
},
|
|
438
|
+
rollback: async (client) => {
|
|
439
|
+
await client.send(
|
|
440
|
+
new UpdateTableCommand({
|
|
441
|
+
TableName: tableName,
|
|
442
|
+
StreamSpecification: {
|
|
443
|
+
StreamEnabled: false,
|
|
444
|
+
},
|
|
445
|
+
}),
|
|
446
|
+
)
|
|
447
|
+
},
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ============================================================================
|
|
452
|
+
// CLI Helper
|
|
453
|
+
// ============================================================================
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Print setup instructions for manual deployment
|
|
457
|
+
*/
|
|
458
|
+
export function printSetupInstructions(config: SetupConfig = {}): void {
|
|
459
|
+
const tableName = config.tableName || 'analytics'
|
|
460
|
+
|
|
461
|
+
console.log(`
|
|
462
|
+
# Analytics DynamoDB Table Setup
|
|
463
|
+
|
|
464
|
+
## Option 1: AWS CLI
|
|
465
|
+
|
|
466
|
+
\`\`\`bash
|
|
467
|
+
aws dynamodb create-table \\
|
|
468
|
+
--table-name ${tableName} \\
|
|
469
|
+
--attribute-definitions \\
|
|
470
|
+
AttributeName=pk,AttributeType=S \\
|
|
471
|
+
AttributeName=sk,AttributeType=S \\
|
|
472
|
+
AttributeName=gsi1pk,AttributeType=S \\
|
|
473
|
+
AttributeName=gsi1sk,AttributeType=S \\
|
|
474
|
+
AttributeName=gsi2pk,AttributeType=S \\
|
|
475
|
+
AttributeName=gsi2sk,AttributeType=S \\
|
|
476
|
+
--key-schema \\
|
|
477
|
+
AttributeName=pk,KeyType=HASH \\
|
|
478
|
+
AttributeName=sk,KeyType=RANGE \\
|
|
479
|
+
--billing-mode PAY_PER_REQUEST \\
|
|
480
|
+
--global-secondary-indexes \\
|
|
481
|
+
'[
|
|
482
|
+
{
|
|
483
|
+
"IndexName": "GSI1",
|
|
484
|
+
"KeySchema": [
|
|
485
|
+
{"AttributeName": "gsi1pk", "KeyType": "HASH"},
|
|
486
|
+
{"AttributeName": "gsi1sk", "KeyType": "RANGE"}
|
|
487
|
+
],
|
|
488
|
+
"Projection": {"ProjectionType": "ALL"}
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"IndexName": "GSI2",
|
|
492
|
+
"KeySchema": [
|
|
493
|
+
{"AttributeName": "gsi2pk", "KeyType": "HASH"},
|
|
494
|
+
{"AttributeName": "gsi2sk", "KeyType": "RANGE"}
|
|
495
|
+
],
|
|
496
|
+
"Projection": {"ProjectionType": "ALL"}
|
|
497
|
+
}
|
|
498
|
+
]'
|
|
499
|
+
|
|
500
|
+
# Enable TTL
|
|
501
|
+
aws dynamodb update-time-to-live \\
|
|
502
|
+
--table-name ${tableName} \\
|
|
503
|
+
--time-to-live-specification AttributeName=ttl,Enabled=true
|
|
504
|
+
|
|
505
|
+
# Enable PITR
|
|
506
|
+
aws dynamodb update-continuous-backups \\
|
|
507
|
+
--table-name ${tableName} \\
|
|
508
|
+
--point-in-time-recovery-specification PointInTimeRecoveryEnabled=true
|
|
509
|
+
\`\`\`
|
|
510
|
+
|
|
511
|
+
## Option 2: CloudFormation
|
|
512
|
+
|
|
513
|
+
\`\`\`bash
|
|
514
|
+
# Generate template
|
|
515
|
+
npx dynamodb-tooling analytics:cfn > analytics.yaml
|
|
516
|
+
|
|
517
|
+
# Deploy
|
|
518
|
+
aws cloudformation deploy \\
|
|
519
|
+
--template-file analytics.yaml \\
|
|
520
|
+
--stack-name ${tableName}-stack
|
|
521
|
+
\`\`\`
|
|
522
|
+
|
|
523
|
+
## Option 3: CDK
|
|
524
|
+
|
|
525
|
+
\`\`\`bash
|
|
526
|
+
# Generate CDK code
|
|
527
|
+
npx dynamodb-tooling analytics:cdk > lib/analytics-stack.ts
|
|
528
|
+
|
|
529
|
+
# Deploy
|
|
530
|
+
cdk deploy AnalyticsStack
|
|
531
|
+
\`\`\`
|
|
532
|
+
|
|
533
|
+
## Option 4: SAM
|
|
534
|
+
|
|
535
|
+
\`\`\`bash
|
|
536
|
+
# Generate SAM template
|
|
537
|
+
npx dynamodb-tooling analytics:sam > template.yaml
|
|
538
|
+
|
|
539
|
+
# Deploy
|
|
540
|
+
sam build && sam deploy --guided
|
|
541
|
+
\`\`\`
|
|
542
|
+
`)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Generate AWS CLI commands for table creation
|
|
547
|
+
*/
|
|
548
|
+
export function generateAwsCliCommands(config: SetupConfig = {}): string {
|
|
549
|
+
const tableName = config.tableName || 'analytics'
|
|
550
|
+
const region = config.region || 'us-east-1'
|
|
551
|
+
|
|
552
|
+
return `#!/bin/bash
|
|
553
|
+
set -e
|
|
554
|
+
|
|
555
|
+
TABLE_NAME="${tableName}"
|
|
556
|
+
REGION="${region}"
|
|
557
|
+
|
|
558
|
+
echo "Creating analytics table: $TABLE_NAME"
|
|
559
|
+
|
|
560
|
+
# Create table
|
|
561
|
+
aws dynamodb create-table \\
|
|
562
|
+
--table-name $TABLE_NAME \\
|
|
563
|
+
--region $REGION \\
|
|
564
|
+
--attribute-definitions \\
|
|
565
|
+
AttributeName=pk,AttributeType=S \\
|
|
566
|
+
AttributeName=sk,AttributeType=S \\
|
|
567
|
+
AttributeName=gsi1pk,AttributeType=S \\
|
|
568
|
+
AttributeName=gsi1sk,AttributeType=S \\
|
|
569
|
+
AttributeName=gsi2pk,AttributeType=S \\
|
|
570
|
+
AttributeName=gsi2sk,AttributeType=S \\
|
|
571
|
+
--key-schema \\
|
|
572
|
+
AttributeName=pk,KeyType=HASH \\
|
|
573
|
+
AttributeName=sk,KeyType=RANGE \\
|
|
574
|
+
--billing-mode PAY_PER_REQUEST \\
|
|
575
|
+
--global-secondary-indexes '[
|
|
576
|
+
{
|
|
577
|
+
"IndexName": "GSI1",
|
|
578
|
+
"KeySchema": [
|
|
579
|
+
{"AttributeName": "gsi1pk", "KeyType": "HASH"},
|
|
580
|
+
{"AttributeName": "gsi1sk", "KeyType": "RANGE"}
|
|
581
|
+
],
|
|
582
|
+
"Projection": {"ProjectionType": "ALL"}
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
"IndexName": "GSI2",
|
|
586
|
+
"KeySchema": [
|
|
587
|
+
{"AttributeName": "gsi2pk", "KeyType": "HASH"},
|
|
588
|
+
{"AttributeName": "gsi2sk", "KeyType": "RANGE"}
|
|
589
|
+
],
|
|
590
|
+
"Projection": {"ProjectionType": "ALL"}
|
|
591
|
+
}
|
|
592
|
+
]'
|
|
593
|
+
|
|
594
|
+
echo "Waiting for table to be active..."
|
|
595
|
+
aws dynamodb wait table-exists --table-name $TABLE_NAME --region $REGION
|
|
596
|
+
|
|
597
|
+
echo "Enabling TTL..."
|
|
598
|
+
aws dynamodb update-time-to-live \\
|
|
599
|
+
--table-name $TABLE_NAME \\
|
|
600
|
+
--region $REGION \\
|
|
601
|
+
--time-to-live-specification AttributeName=ttl,Enabled=true
|
|
602
|
+
|
|
603
|
+
echo "Enabling Point-in-Time Recovery..."
|
|
604
|
+
aws dynamodb update-continuous-backups \\
|
|
605
|
+
--table-name $TABLE_NAME \\
|
|
606
|
+
--region $REGION \\
|
|
607
|
+
--point-in-time-recovery-specification PointInTimeRecoveryEnabled=true
|
|
608
|
+
|
|
609
|
+
echo "Done! Table $TABLE_NAME is ready."
|
|
610
|
+
`
|
|
611
|
+
}
|