@zodic/shared 0.0.176 → 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,14 +1,21 @@
1
1
  import { DurableObjectState } from '@cloudflare/workers-types';
2
+ import { BackendBindings, ConceptProgress } from '../../types';
2
3
 
3
4
  export class ConceptNameDO {
4
5
  state: DurableObjectState;
5
- env: any;
6
+ env: BackendBindings;
6
7
  names: { 'en-us': string[]; 'pt-br': string[] };
8
+ count: number;
9
+ timestamps: number[];
7
10
 
8
- constructor(state: DurableObjectState, env: any) {
11
+ static TOTAL_NAMES = 1728; // Maximum total names to be generated
12
+
13
+ constructor(state: DurableObjectState, env: BackendBindings) {
9
14
  this.state = state;
10
15
  this.env = env;
11
16
  this.names = { 'en-us': [], 'pt-br': [] };
17
+ this.count = 0;
18
+ this.timestamps = [];
12
19
  }
13
20
 
14
21
  async fetch(request: Request): Promise<Response> {
@@ -16,8 +23,13 @@ export class ConceptNameDO {
16
23
  const method = request.method;
17
24
 
18
25
  if (method === 'GET' && url.pathname === '/names') {
19
- await this.loadNames();
20
- return new Response(JSON.stringify(this.names), {
26
+ return new Response(JSON.stringify(await this.getNames()), {
27
+ headers: { 'Content-Type': 'application/json' },
28
+ });
29
+ }
30
+
31
+ if (method === 'GET' && url.pathname === '/progress') {
32
+ return new Response(JSON.stringify(await this.getProgress()), {
21
33
  headers: { 'Content-Type': 'application/json' },
22
34
  });
23
35
  }
@@ -28,7 +40,6 @@ export class ConceptNameDO {
28
40
  name: string;
29
41
  };
30
42
 
31
- // ✅ Normalize language codes to match `generateBasicInfo`
32
43
  if (!['en-us', 'pt-br'].includes(language)) {
33
44
  return new Response('Invalid language', { status: 400 });
34
45
  }
@@ -40,20 +51,69 @@ export class ConceptNameDO {
40
51
  return new Response('Not Found', { status: 404 });
41
52
  }
42
53
 
43
- async loadNames(): Promise<void> {
44
- const stored = await this.state.storage.get<{
45
- 'en-us': string[];
46
- 'pt-br': string[];
47
- }>('names');
48
- if (stored) {
49
- this.names = stored;
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
+ };
50
60
  }
61
+ return this.names;
62
+ }
63
+
64
+ async getProgress(): Promise<ConceptProgress> {
65
+ if (this.count === 0) {
66
+ this.count = (await this.state.storage.get<number>('count')) || 0;
67
+ }
68
+ if (this.timestamps.length === 0) {
69
+ this.timestamps =
70
+ (await this.state.storage.get<number[]>('timestamps')) || [];
71
+ }
72
+
73
+ const now = Date.now();
74
+ const oneMinuteAgo = now - 60 * 1000;
75
+
76
+ const recentTimestamps = this.timestamps.filter((ts) => ts >= oneMinuteAgo);
77
+ const ratePerMinute = recentTimestamps.length;
78
+
79
+ const remainingNames = ConceptNameDO.TOTAL_NAMES - this.count;
80
+ const estimatedTimeMinutes =
81
+ ratePerMinute > 0 ? Math.round(remainingNames / ratePerMinute) : 'N/A';
82
+
83
+ return {
84
+ count: this.count,
85
+ total: ConceptNameDO.TOTAL_NAMES,
86
+ progress:
87
+ ((this.count / ConceptNameDO.TOTAL_NAMES) * 100).toFixed(2) + '%',
88
+ ratePerMinute,
89
+ estimatedTimeMinutes,
90
+ };
51
91
  }
52
92
 
53
93
  async addName(language: 'en-us' | 'pt-br', name: string): Promise<void> {
94
+ await this.getNames(); // Ensure latest state
95
+
54
96
  if (!this.names[language].includes(name)) {
55
97
  this.names[language].push(name);
56
- await this.state.storage.put('names', this.names);
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);
57
116
  }
117
+ this.timestamps.push(now);
58
118
  }
59
119
  }
@@ -2,7 +2,7 @@ import { and, eq } from 'drizzle-orm';
2
2
  import { inject, injectable } from 'inversify';
3
3
  import 'reflect-metadata';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
- import { ConceptNameDO, schema } from '../..';
5
+ import { schema } from '../..';
6
6
  import { Concept, ControlNetConfig, Languages } from '../../types';
7
7
  import {
8
8
  KVConcept,
@@ -51,7 +51,9 @@ export class ConceptService {
51
51
  // ✅ Fetch the latest name list from Durable Object
52
52
  let allNamesEN: string[] = [];
53
53
  let allNamesPT: string[] = [];
54
- const response = await stub.fetch('https://internal/names');
54
+ const response = await stub.fetch(
55
+ `https://internal/names`
56
+ );
55
57
  if (response.ok) {
56
58
  const data = (await response.json()) as {
57
59
  'en-us': string[];
@@ -60,7 +62,7 @@ export class ConceptService {
60
62
  allNamesEN = data['en-us'] || [];
61
63
  allNamesPT = data['pt-br'] || [];
62
64
  } else {
63
- console.log('!-- Error fetching names from Durable Object');
65
+ console.log(`!-- Error fetching names from DO for ${conceptSlug}`);
64
66
  console.log('!-- Response:', await response.text());
65
67
  }
66
68
 
@@ -71,7 +73,7 @@ export class ConceptService {
71
73
  // 🔥 If names already exist in KV but not in DO, add them
72
74
  if (existingEN.name && !allNamesEN.includes(existingEN.name)) {
73
75
  console.log(`⚡ Backfilling existing name to DO: ${existingEN.name}`);
74
- await stub.fetch('https://internal/add-name', {
76
+ await stub.fetch(`https://internal/add-name`, {
75
77
  method: 'POST',
76
78
  body: JSON.stringify({ language: 'en-us', name: existingEN.name }),
77
79
  headers: { 'Content-Type': 'application/json' },
@@ -79,7 +81,7 @@ export class ConceptService {
79
81
  }
80
82
  if (existingPT.name && !allNamesPT.includes(existingPT.name)) {
81
83
  console.log(`⚡ Backfilling existing name to DO: ${existingPT.name}`);
82
- await stub.fetch('https://internal/add-name', {
84
+ await stub.fetch(`https://internal/add-name`, {
83
85
  method: 'POST',
84
86
  body: JSON.stringify({ language: 'pt-br', name: existingPT.name }),
85
87
  headers: { 'Content-Type': 'application/json' },
@@ -127,13 +129,13 @@ export class ConceptService {
127
129
  }
128
130
 
129
131
  // ✅ **Immediately update Durable Object with the new name**
130
- await stub.fetch('https://internal/add-name', {
132
+ await stub.fetch(`https://internal/add-name`, {
131
133
  method: 'POST',
132
134
  body: JSON.stringify({ language: 'en-us', name: nameEN }),
133
135
  headers: { 'Content-Type': 'application/json' },
134
136
  });
135
137
 
136
- await stub.fetch('https://internal/add-name', {
138
+ await stub.fetch(`https://internal/add-name`, {
137
139
  method: 'POST',
138
140
  body: JSON.stringify({ language: 'pt-br', name: namePT }),
139
141
  headers: { 'Content-Type': 'application/json' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.176",
3
+ "version": "0.0.178",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -398,3 +398,11 @@ export type AstroKVData = {
398
398
  diff: number;
399
399
  }[];
400
400
  };
401
+
402
+ export type ConceptProgress = {
403
+ count: number;
404
+ total: number;
405
+ progress: string; // Percentage as string (e.g., "75.32%")
406
+ ratePerMinute: number;
407
+ estimatedTimeMinutes: number | 'N/A';
408
+ };