@zodic/shared 0.0.177 → 0.0.178
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.
|
@@ -1,45 +1,40 @@
|
|
|
1
|
-
import { DurableObjectState } from
|
|
2
|
-
import { BackendBindings, ConceptProgress } from
|
|
1
|
+
import { DurableObjectState } from '@cloudflare/workers-types';
|
|
2
|
+
import { BackendBindings, ConceptProgress } from '../../types';
|
|
3
3
|
|
|
4
4
|
export class ConceptNameDO {
|
|
5
5
|
state: DurableObjectState;
|
|
6
|
-
env:
|
|
7
|
-
names:
|
|
8
|
-
|
|
9
|
-
timestamps:
|
|
6
|
+
env: BackendBindings;
|
|
7
|
+
names: { 'en-us': string[]; 'pt-br': string[] };
|
|
8
|
+
count: number;
|
|
9
|
+
timestamps: number[];
|
|
10
10
|
|
|
11
|
-
static TOTAL_NAMES = 1728; //
|
|
11
|
+
static TOTAL_NAMES = 1728; // Maximum total names to be generated
|
|
12
12
|
|
|
13
13
|
constructor(state: DurableObjectState, env: BackendBindings) {
|
|
14
14
|
this.state = state;
|
|
15
15
|
this.env = env;
|
|
16
|
-
this.names = {};
|
|
17
|
-
this.
|
|
18
|
-
this.timestamps =
|
|
16
|
+
this.names = { 'en-us': [], 'pt-br': [] };
|
|
17
|
+
this.count = 0;
|
|
18
|
+
this.timestamps = [];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
async fetch(request: Request): Promise<Response> {
|
|
22
22
|
const url = new URL(request.url);
|
|
23
23
|
const method = request.method;
|
|
24
|
-
const conceptSlug = url.pathname.split('/')[2]; // Extract conceptSlug from URL
|
|
25
24
|
|
|
26
|
-
if (
|
|
27
|
-
return new Response(
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (method === 'GET' && url.pathname.endsWith('/names')) {
|
|
31
|
-
return new Response(JSON.stringify(await this.getNames(conceptSlug)), {
|
|
25
|
+
if (method === 'GET' && url.pathname === '/names') {
|
|
26
|
+
return new Response(JSON.stringify(await this.getNames()), {
|
|
32
27
|
headers: { 'Content-Type': 'application/json' },
|
|
33
28
|
});
|
|
34
29
|
}
|
|
35
30
|
|
|
36
|
-
if (method === 'GET' && url.pathname
|
|
37
|
-
return new Response(JSON.stringify(await this.
|
|
31
|
+
if (method === 'GET' && url.pathname === '/progress') {
|
|
32
|
+
return new Response(JSON.stringify(await this.getProgress()), {
|
|
38
33
|
headers: { 'Content-Type': 'application/json' },
|
|
39
34
|
});
|
|
40
35
|
}
|
|
41
36
|
|
|
42
|
-
if (method === 'POST' && url.pathname
|
|
37
|
+
if (method === 'POST' && url.pathname === '/add-name') {
|
|
43
38
|
const { language, name } = (await request.json()) as {
|
|
44
39
|
language: 'en-us' | 'pt-br';
|
|
45
40
|
name: string;
|
|
@@ -49,87 +44,76 @@ export class ConceptNameDO {
|
|
|
49
44
|
return new Response('Invalid language', { status: 400 });
|
|
50
45
|
}
|
|
51
46
|
|
|
52
|
-
await this.addName(
|
|
47
|
+
await this.addName(language, name);
|
|
53
48
|
return new Response('OK', { status: 200 });
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
return new Response('Not Found', { status: 404 });
|
|
57
52
|
}
|
|
58
53
|
|
|
59
|
-
async getNames(
|
|
60
|
-
if (!this.names[
|
|
61
|
-
this.names
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
};
|
|
54
|
+
async getNames(): Promise<{ 'en-us': string[]; 'pt-br': string[] }> {
|
|
55
|
+
if (!this.names['en-us'].length && !this.names['pt-br'].length) {
|
|
56
|
+
this.names = (await this.state.storage.get('names')) || {
|
|
57
|
+
'en-us': [],
|
|
58
|
+
'pt-br': [],
|
|
59
|
+
};
|
|
66
60
|
}
|
|
67
|
-
return this.names
|
|
61
|
+
return this.names;
|
|
68
62
|
}
|
|
69
63
|
|
|
70
|
-
async
|
|
71
|
-
if (this.
|
|
72
|
-
this.
|
|
64
|
+
async getProgress(): Promise<ConceptProgress> {
|
|
65
|
+
if (this.count === 0) {
|
|
66
|
+
this.count = (await this.state.storage.get<number>('count')) || 0;
|
|
73
67
|
}
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async getTimestamps(conceptSlug: string): Promise<number[]> {
|
|
78
|
-
if (!this.timestamps[conceptSlug]) {
|
|
68
|
+
if (this.timestamps.length === 0) {
|
|
79
69
|
this.timestamps =
|
|
80
|
-
(await this.state.storage.get<
|
|
81
|
-
}
|
|
82
|
-
return this.timestamps[conceptSlug] || [];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async addName(conceptSlug: string, language: 'en-us' | 'pt-br', name: string): Promise<void> {
|
|
86
|
-
const names = await this.getNames(conceptSlug);
|
|
87
|
-
if (!names[language].includes(name)) {
|
|
88
|
-
names[language].push(name);
|
|
89
|
-
this.state.storage.put(`names:${conceptSlug}`, names); // ✅ No need to await
|
|
90
|
-
|
|
91
|
-
this.incrementCount(conceptSlug);
|
|
92
|
-
this.recordTimestamp(conceptSlug);
|
|
70
|
+
(await this.state.storage.get<number[]>('timestamps')) || [];
|
|
93
71
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async incrementCount(conceptSlug: string): Promise<void> {
|
|
97
|
-
const count = (await this.getCount(conceptSlug)) + 1;
|
|
98
|
-
this.counts[conceptSlug] = count;
|
|
99
|
-
this.state.storage.put('counts', this.counts); // ✅ No need to await
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async recordTimestamp(conceptSlug: string): Promise<void> {
|
|
103
|
-
const now = Date.now();
|
|
104
|
-
const timestamps = await this.getTimestamps(conceptSlug);
|
|
105
72
|
|
|
106
|
-
// Keep only last 60 minutes
|
|
107
|
-
const filteredTimestamps = timestamps.filter((ts) => now - ts < 60 * 60 * 1000);
|
|
108
|
-
filteredTimestamps.push(now);
|
|
109
|
-
|
|
110
|
-
this.timestamps[conceptSlug] = filteredTimestamps;
|
|
111
|
-
this.state.storage.put('timestamps', this.timestamps); // ✅ No need to await
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async calculateProgress(conceptSlug: string): Promise<ConceptProgress> {
|
|
115
73
|
const now = Date.now();
|
|
116
74
|
const oneMinuteAgo = now - 60 * 1000;
|
|
117
75
|
|
|
118
|
-
const
|
|
119
|
-
const timestamps = await this.getTimestamps(conceptSlug);
|
|
120
|
-
const recentTimestamps = timestamps.filter((ts) => ts >= oneMinuteAgo);
|
|
76
|
+
const recentTimestamps = this.timestamps.filter((ts) => ts >= oneMinuteAgo);
|
|
121
77
|
const ratePerMinute = recentTimestamps.length;
|
|
122
78
|
|
|
123
|
-
const remainingNames = ConceptNameDO.TOTAL_NAMES - count;
|
|
124
|
-
const estimatedTimeMinutes =
|
|
79
|
+
const remainingNames = ConceptNameDO.TOTAL_NAMES - this.count;
|
|
80
|
+
const estimatedTimeMinutes =
|
|
81
|
+
ratePerMinute > 0 ? Math.round(remainingNames / ratePerMinute) : 'N/A';
|
|
125
82
|
|
|
126
83
|
return {
|
|
127
|
-
|
|
128
|
-
count,
|
|
84
|
+
count: this.count,
|
|
129
85
|
total: ConceptNameDO.TOTAL_NAMES,
|
|
130
|
-
progress:
|
|
86
|
+
progress:
|
|
87
|
+
((this.count / ConceptNameDO.TOTAL_NAMES) * 100).toFixed(2) + '%',
|
|
131
88
|
ratePerMinute,
|
|
132
|
-
estimatedTimeMinutes
|
|
89
|
+
estimatedTimeMinutes,
|
|
133
90
|
};
|
|
134
91
|
}
|
|
135
|
-
|
|
92
|
+
|
|
93
|
+
async addName(language: 'en-us' | 'pt-br', name: string): Promise<void> {
|
|
94
|
+
await this.getNames(); // Ensure latest state
|
|
95
|
+
|
|
96
|
+
if (!this.names[language].includes(name)) {
|
|
97
|
+
this.names[language].push(name);
|
|
98
|
+
this.count++;
|
|
99
|
+
this.recordTimestamp();
|
|
100
|
+
|
|
101
|
+
// ✅ **Batch storage writes** instead of multiple separate `put()` calls
|
|
102
|
+
await this.state.storage.put({
|
|
103
|
+
names: this.names,
|
|
104
|
+
count: this.count,
|
|
105
|
+
timestamps: this.timestamps,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
recordTimestamp(): void {
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
|
|
113
|
+
// Keep only the last 60 timestamps
|
|
114
|
+
if (this.timestamps.length >= 60) {
|
|
115
|
+
this.timestamps.splice(0, this.timestamps.length - 59);
|
|
116
|
+
}
|
|
117
|
+
this.timestamps.push(now);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -52,7 +52,7 @@ export class ConceptService {
|
|
|
52
52
|
let allNamesEN: string[] = [];
|
|
53
53
|
let allNamesPT: string[] = [];
|
|
54
54
|
const response = await stub.fetch(
|
|
55
|
-
`https://internal
|
|
55
|
+
`https://internal/names`
|
|
56
56
|
);
|
|
57
57
|
if (response.ok) {
|
|
58
58
|
const data = (await response.json()) as {
|
|
@@ -73,7 +73,7 @@ export class ConceptService {
|
|
|
73
73
|
// 🔥 If names already exist in KV but not in DO, add them
|
|
74
74
|
if (existingEN.name && !allNamesEN.includes(existingEN.name)) {
|
|
75
75
|
console.log(`⚡ Backfilling existing name to DO: ${existingEN.name}`);
|
|
76
|
-
await stub.fetch(`https://internal
|
|
76
|
+
await stub.fetch(`https://internal/add-name`, {
|
|
77
77
|
method: 'POST',
|
|
78
78
|
body: JSON.stringify({ language: 'en-us', name: existingEN.name }),
|
|
79
79
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -81,7 +81,7 @@ export class ConceptService {
|
|
|
81
81
|
}
|
|
82
82
|
if (existingPT.name && !allNamesPT.includes(existingPT.name)) {
|
|
83
83
|
console.log(`⚡ Backfilling existing name to DO: ${existingPT.name}`);
|
|
84
|
-
await stub.fetch(`https://internal
|
|
84
|
+
await stub.fetch(`https://internal/add-name`, {
|
|
85
85
|
method: 'POST',
|
|
86
86
|
body: JSON.stringify({ language: 'pt-br', name: existingPT.name }),
|
|
87
87
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -129,13 +129,13 @@ export class ConceptService {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// ✅ **Immediately update Durable Object with the new name**
|
|
132
|
-
await stub.fetch(`https://internal
|
|
132
|
+
await stub.fetch(`https://internal/add-name`, {
|
|
133
133
|
method: 'POST',
|
|
134
134
|
body: JSON.stringify({ language: 'en-us', name: nameEN }),
|
|
135
135
|
headers: { 'Content-Type': 'application/json' },
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
-
await stub.fetch(`https://internal
|
|
138
|
+
await stub.fetch(`https://internal/add-name`, {
|
|
139
139
|
method: 'POST',
|
|
140
140
|
body: JSON.stringify({ language: 'pt-br', name: namePT }),
|
|
141
141
|
headers: { 'Content-Type': 'application/json' },
|
package/package.json
CHANGED
package/types/scopes/generic.ts
CHANGED
|
@@ -400,10 +400,9 @@ export type AstroKVData = {
|
|
|
400
400
|
};
|
|
401
401
|
|
|
402
402
|
export type ConceptProgress = {
|
|
403
|
-
concept: string;
|
|
404
403
|
count: number;
|
|
405
404
|
total: number;
|
|
406
405
|
progress: string; // Percentage as string (e.g., "75.32%")
|
|
407
406
|
ratePerMinute: number;
|
|
408
407
|
estimatedTimeMinutes: number | 'N/A';
|
|
409
|
-
};
|
|
408
|
+
};
|