@zodic/shared 0.0.177 → 0.0.179
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,39 @@
|
|
|
1
|
-
import { DurableObjectState } from
|
|
2
|
-
import { BackendBindings, ConceptProgress } from "../../types";
|
|
1
|
+
import { DurableObjectState } from '@cloudflare/workers-types';
|
|
3
2
|
|
|
4
3
|
export class ConceptNameDO {
|
|
5
4
|
state: DurableObjectState;
|
|
6
5
|
env: any;
|
|
7
|
-
names:
|
|
8
|
-
|
|
9
|
-
timestamps:
|
|
6
|
+
names: { 'en-us': string[]; 'pt-br': string[] };
|
|
7
|
+
count: number;
|
|
8
|
+
timestamps: number[];
|
|
10
9
|
|
|
11
|
-
static TOTAL_NAMES = 1728; //
|
|
10
|
+
static TOTAL_NAMES = 1728; // Maximum total names to be generated
|
|
12
11
|
|
|
13
|
-
constructor(state: DurableObjectState, env:
|
|
12
|
+
constructor(state: DurableObjectState, env: any) {
|
|
14
13
|
this.state = state;
|
|
15
14
|
this.env = env;
|
|
16
|
-
this.names = {};
|
|
17
|
-
this.
|
|
18
|
-
this.timestamps =
|
|
15
|
+
this.names = { 'en-us': [], 'pt-br': [] };
|
|
16
|
+
this.count = 0;
|
|
17
|
+
this.timestamps = [];
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
async fetch(request: Request): Promise<Response> {
|
|
22
21
|
const url = new URL(request.url);
|
|
23
22
|
const method = request.method;
|
|
24
|
-
const conceptSlug = url.pathname.split('/')[2]; // Extract conceptSlug from URL
|
|
25
23
|
|
|
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)), {
|
|
24
|
+
if (method === 'GET' && url.pathname === '/names') {
|
|
25
|
+
return new Response(JSON.stringify(await this.getNames()), {
|
|
32
26
|
headers: { 'Content-Type': 'application/json' },
|
|
33
27
|
});
|
|
34
28
|
}
|
|
35
29
|
|
|
36
|
-
if (method === 'GET' && url.pathname
|
|
37
|
-
return new Response(JSON.stringify(await this.
|
|
30
|
+
if (method === 'GET' && url.pathname === '/progress') {
|
|
31
|
+
return new Response(JSON.stringify(await this.getProgress()), {
|
|
38
32
|
headers: { 'Content-Type': 'application/json' },
|
|
39
33
|
});
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
if (method === 'POST' && url.pathname
|
|
36
|
+
if (method === 'POST' && url.pathname === '/add-name') {
|
|
43
37
|
const { language, name } = (await request.json()) as {
|
|
44
38
|
language: 'en-us' | 'pt-br';
|
|
45
39
|
name: string;
|
|
@@ -49,87 +43,90 @@ export class ConceptNameDO {
|
|
|
49
43
|
return new Response('Invalid language', { status: 400 });
|
|
50
44
|
}
|
|
51
45
|
|
|
52
|
-
await this.addName(
|
|
46
|
+
await this.addName(language, name);
|
|
53
47
|
return new Response('OK', { status: 200 });
|
|
54
48
|
}
|
|
55
49
|
|
|
56
50
|
return new Response('Not Found', { status: 404 });
|
|
57
51
|
}
|
|
58
52
|
|
|
59
|
-
async getNames(
|
|
60
|
-
if (!this.names[
|
|
61
|
-
this.names
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
};
|
|
53
|
+
async getNames(): Promise<{ 'en-us': string[]; 'pt-br': string[] }> {
|
|
54
|
+
if (!this.names['en-us'].length && !this.names['pt-br'].length) {
|
|
55
|
+
this.names = (await this.state.storage.get('names')) || {
|
|
56
|
+
'en-us': [],
|
|
57
|
+
'pt-br': [],
|
|
58
|
+
};
|
|
66
59
|
}
|
|
67
|
-
return this.names
|
|
60
|
+
return this.names;
|
|
68
61
|
}
|
|
69
62
|
|
|
70
|
-
async
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
async getProgress(): Promise<{
|
|
64
|
+
count: number;
|
|
65
|
+
total: number;
|
|
66
|
+
progress: string;
|
|
67
|
+
ratePerMinute: number;
|
|
68
|
+
estimatedTimeMinutes: number | 'N/A';
|
|
69
|
+
}> {
|
|
70
|
+
if (this.count === 0) {
|
|
71
|
+
this.count = (await this.state.storage.get<number>('count')) || 0;
|
|
73
72
|
}
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async getTimestamps(conceptSlug: string): Promise<number[]> {
|
|
78
|
-
if (!this.timestamps[conceptSlug]) {
|
|
73
|
+
if (!Array.isArray(this.timestamps) || this.timestamps.length === 0) {
|
|
79
74
|
this.timestamps =
|
|
80
|
-
(await this.state.storage.get<
|
|
75
|
+
(await this.state.storage.get<number[]>('timestamps')) || [];
|
|
81
76
|
}
|
|
82
|
-
return this.timestamps[conceptSlug] || [];
|
|
83
|
-
}
|
|
84
77
|
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
if (!names[language].includes(name)) {
|
|
88
|
-
names[language].push(name);
|
|
89
|
-
this.state.storage.put(`names:${conceptSlug}`, names); // ✅ No need to await
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const oneMinuteAgo = now - 60 * 1000;
|
|
90
80
|
|
|
91
|
-
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
const recentTimestamps = Array.isArray(this.timestamps)
|
|
82
|
+
? this.timestamps.filter((ts) => ts >= oneMinuteAgo)
|
|
83
|
+
: []; // ✅ Ensure it's an array before calling filter
|
|
84
|
+
|
|
85
|
+
const ratePerMinute = recentTimestamps.length;
|
|
95
86
|
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
87
|
+
const remainingNames = ConceptNameDO.TOTAL_NAMES - this.count;
|
|
88
|
+
const estimatedTimeMinutes =
|
|
89
|
+
ratePerMinute > 0 ? Math.round(remainingNames / ratePerMinute) : 'N/A';
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
count: this.count,
|
|
93
|
+
total: ConceptNameDO.TOTAL_NAMES,
|
|
94
|
+
progress:
|
|
95
|
+
((this.count / ConceptNameDO.TOTAL_NAMES) * 100).toFixed(2) + '%',
|
|
96
|
+
ratePerMinute,
|
|
97
|
+
estimatedTimeMinutes,
|
|
98
|
+
};
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
async
|
|
103
|
-
|
|
104
|
-
const timestamps = await this.getTimestamps(conceptSlug);
|
|
101
|
+
async addName(language: 'en-us' | 'pt-br', name: string): Promise<void> {
|
|
102
|
+
await this.getNames(); // Ensure latest state
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
if (!this.names[language].includes(name)) {
|
|
105
|
+
this.names[language].push(name);
|
|
106
|
+
this.count++;
|
|
107
|
+
this.recordTimestamp();
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
// ✅ **Batch storage writes** instead of multiple separate `put()` calls
|
|
110
|
+
await this.state.storage.put({
|
|
111
|
+
names: this.names,
|
|
112
|
+
count: this.count,
|
|
113
|
+
timestamps: this.timestamps,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
recordTimestamp(): void {
|
|
115
119
|
const now = Date.now();
|
|
116
|
-
const oneMinuteAgo = now - 60 * 1000;
|
|
117
|
-
|
|
118
|
-
const count = await this.getCount(conceptSlug);
|
|
119
|
-
const timestamps = await this.getTimestamps(conceptSlug);
|
|
120
|
-
const recentTimestamps = timestamps.filter((ts) => ts >= oneMinuteAgo);
|
|
121
|
-
const ratePerMinute = recentTimestamps.length;
|
|
122
120
|
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
// ✅ Ensure timestamps is always an array before modifying
|
|
122
|
+
if (!Array.isArray(this.timestamps)) {
|
|
123
|
+
this.timestamps = [];
|
|
124
|
+
}
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
ratePerMinute,
|
|
132
|
-
estimatedTimeMinutes: estimatedTimeMinutes ? Math.round(estimatedTimeMinutes) : 'N/A',
|
|
133
|
-
};
|
|
126
|
+
// ✅ Keep only the last 60 timestamps
|
|
127
|
+
if (this.timestamps.length >= 60) {
|
|
128
|
+
this.timestamps.splice(0, this.timestamps.length - 59);
|
|
129
|
+
}
|
|
130
|
+
this.timestamps.push(now);
|
|
134
131
|
}
|
|
135
|
-
}
|
|
132
|
+
}
|
|
@@ -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
|
+
};
|