1
+ import { and , eq , sql } from "drizzle-orm" ;
2
+ import { type DrizzleD1Database , drizzle } from "drizzle-orm/d1" ;
1
3
import type { Context } from "hono" ;
2
- import type { Env } from "../types/env " ;
4
+ import * as schema from "../db/schema " ;
3
5
import { getStorageProvider } from "../storage/factory" ;
4
6
import type { StorageProvider } from "../storage/storage" ;
7
+ import type { Env } from "../types/env" ;
5
8
6
9
const METRICS_PREFIX = "metrics:" as const ;
7
10
@@ -26,22 +29,12 @@ export interface DeploymentMetrics {
26
29
}
27
30
28
31
export class MetricsManager {
29
- private readonly ctx : Context < Env > ;
30
- private readonly kv : KVNamespace ;
31
32
private readonly storage : StorageProvider ;
33
+ private readonly db : DrizzleD1Database < typeof schema > ;
32
34
33
- constructor ( ctx : Context < Env > ) {
34
- this . ctx = ctx ;
35
- this . kv = ctx . env . CODE_PUSH_KV ;
35
+ constructor ( private readonly ctx : Context < Env > ) {
36
36
this . storage = getStorageProvider ( ctx ) ;
37
- }
38
-
39
- private async increment ( key : string ) : Promise < void > {
40
- const currentValue = await this . kv . get ( key ) ;
41
- const newValue = (
42
- ( Number . parseInt ( currentValue ?? "0" , 10 ) || 0 ) + 1
43
- ) . toString ( ) ;
44
- await this . kv . put ( key , newValue ) ;
37
+ this . db = drizzle ( ctx . env . DB , { schema } ) ;
45
38
}
46
39
47
40
private getMetricKey (
@@ -56,6 +49,31 @@ export class MetricsManager {
56
49
return `${ METRICS_PREFIX } client:${ deploymentKey } :${ clientId } ` ;
57
50
}
58
51
52
+ private async increment (
53
+ deploymentKey : string ,
54
+ label : string ,
55
+ type : MetricType ,
56
+ ) : Promise < void > {
57
+ await this . db
58
+ . insert ( schema . metrics )
59
+ . values ( {
60
+ deploymentId : deploymentKey ,
61
+ label,
62
+ type,
63
+ count : 1 ,
64
+ } )
65
+ . onConflictDoUpdate ( {
66
+ target : [
67
+ schema . metrics . deploymentId ,
68
+ schema . metrics . label ,
69
+ schema . metrics . type ,
70
+ ] ,
71
+ set : {
72
+ count : sql `${ schema . metrics . count } + 1` ,
73
+ } ,
74
+ } ) ;
75
+ }
76
+
59
77
async recordDeploymentStatus (
60
78
deploymentKey : string ,
61
79
label : string ,
@@ -67,20 +85,33 @@ export class MetricsManager {
67
85
? MetricType . DEPLOYMENT_SUCCEEDED
68
86
: MetricType . DEPLOYMENT_FAILED ;
69
87
70
- await this . increment ( this . getMetricKey ( deploymentKey , label , type ) ) ;
88
+ await this . increment ( deploymentKey , label , type ) ;
71
89
72
90
if ( status === "DeploymentSucceeded" ) {
73
91
const clientKey = this . getClientKey ( deploymentKey , clientId ) ;
74
- await this . kv . put ( clientKey , label ) ;
75
- await this . increment (
76
- this . getMetricKey ( deploymentKey , label , MetricType . ACTIVE ) ,
77
- ) ;
92
+ await this . db
93
+ . insert ( schema . clientLabels )
94
+ . values ( {
95
+ deploymentId : deploymentKey ,
96
+ clientId,
97
+ label,
98
+ } )
99
+ . onConflictDoUpdate ( {
100
+ target : [
101
+ schema . clientLabels . clientId ,
102
+ schema . clientLabels . deploymentId ,
103
+ ] ,
104
+ set : {
105
+ label,
106
+ } ,
107
+ } ) ;
108
+ await this . increment ( deploymentKey , label , MetricType . ACTIVE ) ;
78
109
}
79
110
}
80
111
81
112
async recordDeployment (
82
113
deploymentKey : string ,
83
- appVersion : string ,
114
+ label : string ,
84
115
clientId : string ,
85
116
previousDeploymentKey ?: string ,
86
117
previousLabel ?: string ,
@@ -91,87 +122,84 @@ export class MetricsManager {
91
122
previousLabel ,
92
123
MetricType . ACTIVE ,
93
124
) ;
94
- const currentActive = Number . parseInt (
95
- ( await this . kv . get ( prevActiveKey ) ) ?? "0" ,
96
- 10 ,
97
- ) ;
98
- if ( currentActive > 0 ) {
99
- await this . kv . put ( prevActiveKey , ( currentActive - 1 ) . toString ( ) ) ;
100
- }
125
+
126
+ await this . db . run ( sql `
127
+ UPDATE metrics
128
+ SET count = count - 1
129
+ WHERE deploymentId = ${ previousDeploymentKey } AND label = ${ previousLabel } AND type = ${ MetricType . ACTIVE } AND count > 0
130
+ ` ) ;
101
131
}
102
132
103
133
const clientKey = this . getClientKey ( deploymentKey , clientId ) ;
104
- await this . kv . put ( clientKey , appVersion ) ;
105
- await this . increment (
106
- this . getMetricKey ( deploymentKey , appVersion , MetricType . ACTIVE ) ,
107
- ) ;
134
+ await this . db
135
+ . insert ( schema . clientLabels )
136
+ . values ( {
137
+ deploymentId : deploymentKey ,
138
+ clientId,
139
+ label,
140
+ } )
141
+ . onConflictDoUpdate ( {
142
+ target : [
143
+ schema . clientLabels . clientId ,
144
+ schema . clientLabels . deploymentId ,
145
+ ] ,
146
+ set : {
147
+ label,
148
+ } ,
149
+ } ) ;
150
+ await this . increment ( deploymentKey , label , MetricType . ACTIVE ) ;
108
151
}
109
152
110
153
async recordDownload (
111
154
deploymentKey : string ,
112
155
label : string ,
113
156
clientId : string ,
114
157
) : Promise < void > {
115
- await this . increment (
116
- this . getMetricKey ( deploymentKey , label , MetricType . DOWNLOADED ) ,
117
- ) ;
158
+ await this . increment ( deploymentKey , label , MetricType . DOWNLOADED ) ;
118
159
}
119
160
120
161
async getMetrics ( deploymentKey : string ) : Promise < DeploymentMetrics > {
121
- const list = await this . kv . list ( {
122
- prefix : ` ${ METRICS_PREFIX } ${ deploymentKey } :` ,
162
+ const results = await this . db . query . metrics . findMany ( {
163
+ where : eq ( schema . metrics . deploymentId , deploymentKey ) ,
123
164
} ) ;
165
+
124
166
const metrics : DeploymentMetrics = { } ;
125
167
126
- for ( const { name } of list . keys ) {
127
- const [ , , label , type ] = name . split ( ":" ) ;
128
- if ( ! metrics [ label ] ) {
129
- metrics [ label ] = {
168
+ for ( const result of results ) {
169
+ if ( ! metrics [ result . label ] ) {
170
+ metrics [ result . label ] = {
130
171
active : 0 ,
131
172
downloads : 0 ,
132
173
installed : 0 ,
133
174
failed : 0 ,
134
175
} ;
135
176
}
136
177
137
- const value = Number . parseInt ( ( await this . kv . get ( name ) ) ?? "0" , 10 ) ;
138
-
139
- switch ( type as MetricType ) {
178
+ switch ( result . type as MetricType ) {
140
179
case MetricType . ACTIVE :
141
- metrics [ label ] . active = value ;
180
+ metrics [ result . label ] . active = result . count ;
142
181
break ;
143
182
case MetricType . DOWNLOADED :
144
- metrics [ label ] . downloads = value ;
183
+ metrics [ result . label ] . downloads = result . count ;
145
184
break ;
146
185
case MetricType . DEPLOYMENT_SUCCEEDED :
147
- metrics [ label ] . installed = value ;
186
+ metrics [ result . label ] . installed = result . count ;
148
187
break ;
149
188
case MetricType . DEPLOYMENT_FAILED :
150
- metrics [ label ] . failed = value ;
189
+ metrics [ result . label ] . failed = result . count ;
151
190
break ;
152
191
}
153
192
}
154
193
155
194
return metrics ;
156
195
}
157
196
158
- async clearMetrics ( deploymentKey : string ) : Promise < void > {
159
- const list = await this . kv . list ( {
160
- prefix : `${ METRICS_PREFIX } ${ deploymentKey } :` ,
161
- } ) ;
162
-
163
- // Delete in batches of 100
164
- const keys = list . keys . map ( ( k ) => k . name ) ;
165
- for ( let i = 0 ; i < keys . length ; i += 100 ) {
166
- const batch = keys . slice ( i , i + 100 ) ;
167
- await Promise . all ( batch . map ( ( key ) => this . kv . delete ( key ) ) ) ;
168
- }
169
- }
170
-
171
- async getActiveDevices ( deploymentKey : string ) : Promise < number > {
172
- const list = await this . kv . list ( {
173
- prefix : `${ METRICS_PREFIX } client:${ deploymentKey } :` ,
174
- } ) ;
175
- return list . keys . length ;
197
+ async clearDeploymentMetrics ( deploymentKey : string ) : Promise < void > {
198
+ await this . db
199
+ . delete ( schema . metrics )
200
+ . where ( eq ( schema . metrics . deploymentId , deploymentKey ) ) ;
201
+ await this . db
202
+ . delete ( schema . clientLabels )
203
+ . where ( eq ( schema . clientLabels . deploymentId , deploymentKey ) ) ;
176
204
}
177
205
}
0 commit comments