@zodic/shared 0.0.182 → 0.0.184

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.
@@ -6,6 +6,7 @@ export class ConceptNameDO {
6
6
  names: { 'en-us': string[]; 'pt-br': string[] };
7
7
  count: number;
8
8
  timestamps: number[];
9
+ lastIndex: number; // ✅ Tracks last processed index
9
10
 
10
11
  static TOTAL_NAMES = 1728; // Maximum total names to be generated
11
12
 
@@ -15,12 +16,15 @@ export class ConceptNameDO {
15
16
  this.names = { 'en-us': [], 'pt-br': [] };
16
17
  this.count = 0;
17
18
  this.timestamps = [];
19
+ this.lastIndex = 0; // ✅ Initialize lastIndex
18
20
  }
19
21
 
20
22
  async fetch(request: Request): Promise<Response> {
21
23
  const url = new URL(request.url);
22
24
  const method = request.method;
23
25
 
26
+ console.log(`📥 DO Received Request: ${method} ${url.pathname}`);
27
+
24
28
  if (method === 'GET' && url.pathname === '/names') {
25
29
  return new Response(JSON.stringify(await this.getNames()), {
26
30
  headers: { 'Content-Type': 'application/json' },
@@ -33,17 +37,30 @@ export class ConceptNameDO {
33
37
  });
34
38
  }
35
39
 
40
+ if (method === 'GET' && url.pathname === '/last-index') {
41
+ return new Response(JSON.stringify(await this.getLastIndex()), {
42
+ headers: { 'Content-Type': 'application/json' },
43
+ });
44
+ }
45
+
36
46
  if (method === 'POST' && url.pathname === '/add-name') {
37
- const { language, name } = (await request.json()) as {
47
+ const { language, name, index } = (await request.json()) as {
38
48
  language: 'en-us' | 'pt-br';
39
49
  name: string;
50
+ index: number;
40
51
  };
41
52
 
42
53
  if (!['en-us', 'pt-br'].includes(language)) {
43
54
  return new Response('Invalid language', { status: 400 });
44
55
  }
45
56
 
46
- await this.addName(language, name);
57
+ await this.addName(language, name, index);
58
+ return new Response('OK', { status: 200 });
59
+ }
60
+
61
+ if (method === 'POST' && url.pathname === '/set-progress') {
62
+ const { lastIndex } = (await request.json()) as { lastIndex: number };
63
+ await this.setLastIndex(lastIndex);
47
64
  return new Response('OK', { status: 200 });
48
65
  }
49
66
 
@@ -63,25 +80,19 @@ export class ConceptNameDO {
63
80
  async getProgress(): Promise<{
64
81
  count: number;
65
82
  total: number;
83
+ lastIndex: number;
66
84
  progress: string;
67
85
  ratePerMinute: number;
68
86
  estimatedTimeMinutes: number | 'N/A';
69
87
  }> {
70
- if (this.count === 0) {
71
- this.count = (await this.state.storage.get<number>('count')) || 0;
72
- }
73
- if (!Array.isArray(this.timestamps) || this.timestamps.length === 0) {
74
- this.timestamps =
75
- (await this.state.storage.get<number[]>('timestamps')) || [];
76
- }
88
+ await this.loadCount();
89
+ await this.loadTimestamps();
90
+ await this.loadLastIndex(); // ✅ Ensure latest lastIndex is fetched
77
91
 
78
92
  const now = Date.now();
79
93
  const oneMinuteAgo = now - 60 * 1000;
80
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
-
95
+ const recentTimestamps = this.timestamps.filter((ts) => ts >= oneMinuteAgo);
85
96
  const ratePerMinute = recentTimestamps.length;
86
97
 
87
98
  const remainingNames = ConceptNameDO.TOTAL_NAMES - this.count;
@@ -91,6 +102,7 @@ export class ConceptNameDO {
91
102
  return {
92
103
  count: this.count,
93
104
  total: ConceptNameDO.TOTAL_NAMES,
105
+ lastIndex: this.lastIndex, // ✅ Include lastIndex in response
94
106
  progress:
95
107
  ((this.count / ConceptNameDO.TOTAL_NAMES) * 100).toFixed(2) + '%',
96
108
  ratePerMinute,
@@ -98,50 +110,72 @@ export class ConceptNameDO {
98
110
  };
99
111
  }
100
112
 
101
- async addName(language: 'en-us' | 'pt-br', name: string): Promise<void> {
102
- await this.getNames(); // Ensure latest name list is loaded
103
- await this.loadCount(); // ✅ Load latest count before modifying
113
+ async getLastIndex(): Promise<number> {
114
+ await this.loadLastIndex(); // Fetch lastIndex from storage
115
+ return this.lastIndex;
116
+ }
117
+
118
+ async addName(
119
+ language: 'en-us' | 'pt-br',
120
+ name: string,
121
+ index: number
122
+ ): Promise<void> {
123
+ await this.getNames();
124
+ await this.loadCount();
125
+ await this.loadLastIndex(); // ✅ Ensure lastIndex is loaded before modifying
104
126
 
105
127
  if (!this.names[language].includes(name)) {
106
128
  this.names[language].push(name);
107
- this.count += 1; // ✅ Increment count correctly
129
+ this.count += 1;
108
130
 
109
131
  this.recordTimestamp();
110
132
 
111
- // **Store all updated values in a single put() call**
133
+ // 🔥 **Ensure lastIndex is updated**
134
+ if (index > this.lastIndex) {
135
+ this.lastIndex = index;
136
+ }
137
+
138
+ // 🔥 **Store all updated values in a single put() call**
112
139
  await this.state.storage.put({
113
140
  names: this.names,
114
- count: this.count, // ✅ Store updated count properly
141
+ count: this.count,
115
142
  timestamps: this.timestamps,
143
+ lastIndex: this.lastIndex, // ✅ Store the last processed index
116
144
  });
117
145
 
118
146
  console.log(
119
- `✅ Added name "${name}" to ${language}, count is now ${this.count}`
147
+ `✅ Added name "${name}" to ${language}, count: ${this.count}, lastIndex: ${this.lastIndex}`
120
148
  );
121
149
  }
122
150
  }
123
151
 
152
+ async setLastIndex(lastIndex: number): Promise<void> {
153
+ this.lastIndex = lastIndex;
154
+ await this.state.storage.put('lastIndex', this.lastIndex);
155
+ console.log(`📌 Updated lastIndex to ${this.lastIndex}`);
156
+ }
157
+
124
158
  async loadCount(): Promise<void> {
125
- const storedCount = await this.state.storage.get<number>('count');
126
- if (typeof storedCount === 'number') {
127
- this.count = storedCount; // ✅ Ensure we always load the latest count
159
+ this.count = (await this.state.storage.get<number>('count')) || 0;
160
+ }
161
+
162
+ async loadLastIndex(): Promise<void> {
163
+ const storedIndex = await this.state.storage.get<number>('lastIndex');
164
+ if (typeof storedIndex === 'number') {
165
+ this.lastIndex = storedIndex;
128
166
  } else {
129
- this.count = 0; // ✅ Ensure count is initialized properly
167
+ this.lastIndex = 0; // ✅ Ensure it starts from 0
130
168
  }
131
169
  }
132
170
 
171
+ async loadTimestamps(): Promise<void> {
172
+ this.timestamps =
173
+ (await this.state.storage.get<number[]>('timestamps')) || [];
174
+ }
175
+
133
176
  recordTimestamp(): void {
134
177
  const now = Date.now();
135
-
136
- // ✅ Ensure timestamps is always an array before modifying
137
- if (!Array.isArray(this.timestamps)) {
138
- this.timestamps = [];
139
- }
140
-
141
- // ✅ Keep only the last 60 timestamps
142
- if (this.timestamps.length >= 60) {
143
- this.timestamps.splice(0, this.timestamps.length - 59);
144
- }
178
+ this.timestamps = this.timestamps.filter((ts) => now - ts < 60 * 60 * 1000);
145
179
  this.timestamps.push(now);
146
180
  }
147
181
  }
@@ -1,12 +1,16 @@
1
1
  import { inject, injectable } from 'inversify';
2
2
  import pMap from 'p-map';
3
3
  import { zodiacSignCombinations } from '../../data/zodiacSignCombinations';
4
- import { Concept, ConceptPhase } from '../../types';
4
+ import { Concept, ConceptPhase, ConceptProgress } from '../../types';
5
+ import { AppContext } from '../base';
5
6
  import { ConceptService } from '../services/ConceptService';
6
7
 
7
8
  @injectable()
8
9
  export class ConceptWorkflow {
9
- constructor(@inject(ConceptService) private conceptService: ConceptService) {}
10
+ constructor(
11
+ @inject(AppContext) private context: AppContext,
12
+ @inject(ConceptService) private conceptService: ConceptService
13
+ ) {}
10
14
 
11
15
  async processSingle(
12
16
  conceptSlug: Concept,
@@ -66,34 +70,79 @@ export class ConceptWorkflow {
66
70
  conceptSlug: Concept,
67
71
  combinations: string[],
68
72
  phase: ConceptPhase,
69
- override?: boolean
73
+ override: boolean = false
70
74
  ) {
71
75
  console.log(
72
76
  `🚀 Processing batch for concept: ${conceptSlug}, Phase: ${phase}, Total Combinations: ${combinations.length}`
73
77
  );
74
78
 
75
- const concurrency = 20; // Adjust based on response speed
79
+ const concurrency = 5;
76
80
  const failedCombinations: string[] = [];
77
81
 
82
+ // ✅ Get Durable Object stub for progress tracking
83
+ const id = this.context.env.CONCEPT_NAMES_DO.idFromName(conceptSlug);
84
+ const stub = this.context.env.CONCEPT_NAMES_DO.get(id);
85
+
86
+ // ✅ Try to load last checkpoint from DO
87
+ let lastIndex = 0;
88
+ try {
89
+ const progressRes = await stub.fetch(`https://internal/progress`);
90
+ if (progressRes.ok) {
91
+ const progressData = (await progressRes.json()) as ConceptProgress;
92
+ lastIndex = progressData.lastIndex || 0;
93
+ console.log(`🔄 Resuming from index ${lastIndex}`);
94
+ } else {
95
+ console.log(`📌 No previous checkpoint found, starting from scratch.`);
96
+ }
97
+ } catch (error) {
98
+ console.error(`❌ Error fetching progress from DO:`, error);
99
+ }
100
+
101
+ // ✅ Filter unprocessed combinations
102
+ const remainingCombinations = combinations.slice(lastIndex);
103
+ console.log(
104
+ `⏳ Processing ${remainingCombinations.length} remaining combinations...`
105
+ );
106
+
78
107
  await pMap(
79
- combinations,
80
- async (combination) => {
108
+ remainingCombinations,
109
+ async (combination, index) => {
110
+ const currentIndex = lastIndex + index;
81
111
  try {
112
+ console.log(
113
+ `🛠️ Processing combination ${currentIndex}/${combinations.length}: ${combination}`
114
+ );
82
115
  await this.processSingle(conceptSlug, combination, phase, override);
116
+
117
+ // ✅ Save progress every 10 iterations in DO
118
+ if (currentIndex % 10 === 0) {
119
+ await stub.fetch(`https://internal/set-progress`, {
120
+ method: 'POST',
121
+ body: JSON.stringify({ lastIndex: currentIndex }),
122
+ headers: { 'Content-Type': 'application/json' },
123
+ });
124
+ console.log(`✅ Checkpoint saved at index ${currentIndex}`);
125
+ }
83
126
  } catch (error) {
84
127
  console.error(`❌ Error processing ${combination}:`, error);
85
- failedCombinations.push(combination); // Store failed ones
128
+ failedCombinations.push(combination);
86
129
  }
87
130
  },
88
131
  { concurrency }
89
132
  );
90
133
 
134
+ console.log(
135
+ `✅ Batch processing completed for ${conceptSlug}, Phase: ${phase}`
136
+ );
137
+
138
+ // ✅ Retry failed combinations after all batches
91
139
  if (failedCombinations.length > 0) {
92
140
  console.warn(`⚠️ Retrying failed combinations:`, failedCombinations);
93
- await this.processBatch(conceptSlug, failedCombinations, phase, override); // Retry
141
+ await this.processBatch(conceptSlug, failedCombinations, phase, override);
94
142
  }
143
+
95
144
  console.log(
96
- `✅ Batch processing completed for concept: ${conceptSlug}, Phase: ${phase}`
145
+ `🎉 All combinations successfully processed for ${conceptSlug}!`
97
146
  );
98
147
  }
99
148
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.182",
3
+ "version": "0.0.184",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -402,6 +402,7 @@ export type AstroKVData = {
402
402
  export type ConceptProgress = {
403
403
  count: number;
404
404
  total: number;
405
+ lastIndex: number;
405
406
  progress: string; // Percentage as string (e.g., "75.32%")
406
407
  ratePerMinute: number;
407
408
  estimatedTimeMinutes: number | 'N/A';