@gringow/gringow 0.6.1 → 0.6.2

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.
Files changed (44) hide show
  1. package/dist/browser.d.mts +1 -1
  2. package/dist/browser.mjs +22 -22
  3. package/dist/config.d.mts +30 -3
  4. package/dist/config.mjs +12 -22
  5. package/dist/constants/default-config.mjs +2 -2
  6. package/dist/create-cache-id.d.mts +2 -2
  7. package/dist/create-cache-id.mjs +22 -21
  8. package/dist/data/cache-file-path.d.mts +13 -0
  9. package/dist/data/cache-file-path.mjs +6 -0
  10. package/dist/data/cache-queue.d.mts +35 -0
  11. package/dist/data/cache-queue.mjs +372 -0
  12. package/dist/data/check-cache.d.mts +17 -8
  13. package/dist/data/check-cache.mjs +53 -87
  14. package/dist/data/clear-cache.mjs +32 -84
  15. package/dist/data/create-cache-dir.d.mts +3 -0
  16. package/dist/data/create-cache-dir.mjs +11 -71
  17. package/dist/data/delete-cache-item.d.mts +11 -8
  18. package/dist/data/delete-cache-item.mjs +51 -103
  19. package/dist/data/get-cache.d.mts +2 -1
  20. package/dist/data/get-cache.mjs +17 -72
  21. package/dist/data/get-or-create-cache.d.mts +16 -8
  22. package/dist/data/get-or-create-cache.mjs +54 -88
  23. package/dist/data/md5.d.mts +2 -5
  24. package/dist/data/md5.mjs +10 -10
  25. package/dist/data/save-cache.d.mts +2 -9
  26. package/dist/data/save-cache.mjs +32 -84
  27. package/dist/data/write-cache-to-disk.d.mts +12 -8
  28. package/dist/data/write-cache-to-disk.mjs +49 -100
  29. package/dist/gringow.d.mts +19 -8
  30. package/dist/gringow.mjs +116 -91
  31. package/dist/index.d.mts +6 -78
  32. package/dist/index.mjs +130 -99
  33. package/dist/interpolate-template.d.mts +7 -0
  34. package/dist/interpolate-template.mjs +11 -11
  35. package/dist/io/ask-llm.d.mts +1 -8
  36. package/dist/io/ask-llm.mjs +32 -78
  37. package/dist/logger.d.mts +18 -0
  38. package/dist/logger.mjs +18 -0
  39. package/dist/schemas/config.shcema.d.mts +5 -1
  40. package/dist/schemas/config.shcema.mjs +3 -3
  41. package/dist/strip-comments.d.mts +1 -9
  42. package/dist/strip-comments.mjs +0 -1
  43. package/dist/types/index.d.mts +22 -1
  44. package/package.json +7 -8
@@ -1,11 +1,11 @@
1
- import fs from 'fs/promises';
1
+ import fs2 from 'fs/promises';
2
2
  import { cosmiconfig } from 'cosmiconfig';
3
- import merge from 'lodash.merge';
3
+ import mergeWith from 'lodash.mergewith';
4
4
  import { z } from 'zod';
5
5
  import { dirname, resolve, parse } from 'path';
6
6
  import { mkdir, stat, statSync, mkdirSync } from 'fs';
7
7
 
8
- // src/data/save-cache.ts
8
+ // src/data/cache-queue.ts
9
9
 
10
10
  // src/constants/default-config.ts
11
11
  var defaultConfig = {
@@ -23,9 +23,9 @@ var defaultConfig = {
23
23
  defaultLanguage: "pt-BR",
24
24
  languages: ["pt-BR", "fr-CA"],
25
25
  llm: {
26
- provider: "openai",
26
+ provider: "ollama",
27
27
  apiKey: void 0,
28
- model: "gpt-4o-mini",
28
+ model: "llama3.2",
29
29
  temperature: 1,
30
30
  sleep: 0
31
31
  },
@@ -47,6 +47,7 @@ var llmProviderSchema = z.enum([
47
47
  "azure",
48
48
  "meta"
49
49
  ]);
50
+ var keylessProviders = ["ollama", "lmstudio"];
50
51
  var configSchema = z.object({
51
52
  debug: z.boolean().optional(),
52
53
  log: z.object({
@@ -78,8 +79,7 @@ var configSchema = z.object({
78
79
  globby: z.string().regex(/^\.\/.*/).optional()
79
80
  }).refine(
80
81
  (data) => {
81
- const noApiKeyRequired = ["ollama", "lmstudio"];
82
- if (noApiKeyRequired.includes(data.llm.provider)) {
82
+ if (keylessProviders.includes(data.llm.provider)) {
83
83
  return true;
84
84
  }
85
85
  return !!data.llm.apiKey;
@@ -93,79 +93,24 @@ var configSchema = z.object({
93
93
  // src/config.ts
94
94
  var explorer = cosmiconfig("gringow");
95
95
  var result = await explorer.search();
96
+ function replaceArrays(_destination, source) {
97
+ if (Array.isArray(source)) return [...source];
98
+ return void 0;
99
+ }
96
100
  async function loadUserConfig() {
97
101
  try {
98
- const mergedConfig = merge({}, defaultConfig, result?.config);
99
- const parsedConfig = await configSchema.parseAsync(mergedConfig);
100
- parsedConfig.llm.prePrompt = prePrompt(
101
- parsedConfig,
102
- parsedConfig.llm.prePromptAppend ?? "",
103
- parsedConfig.llm.prePromptReplace ?? ""
104
- );
105
- parsedConfig.llm.postPrompt = postPrompt(
106
- parsedConfig,
107
- parsedConfig.llm.postPromptPrepend ?? "",
108
- parsedConfig.llm.postPromptReplace ?? ""
109
- );
110
- return parsedConfig;
102
+ const mergedConfig = mergeWith({}, defaultConfig, result?.config, replaceArrays);
103
+ return await configSchema.parseAsync(mergedConfig);
111
104
  } catch (error) {
112
105
  console.warn(`${error?.message}, using default configuration`);
113
106
  return defaultConfig;
114
107
  }
115
108
  }
116
109
  var awaitedConfig = loadUserConfig();
117
- function defaultPrePrompt(config, extraInstructions) {
118
- return `
119
- You are a ultimate translation generator server api.
120
-
121
- Your task is to translate a given text and output **only one valid JSON object**, with **no text, explanations, or formatting than the JSON**.
122
-
123
- Follow these rules strictly:
124
-
125
- 1. The original text is written in English or Spanish.
126
- 2. You will receive additional user parameters under the variable 'extraInstructions'. These may specify tone, persona, style, context, or any relevant translation guideline.
127
- - Examples: \u201Ctranslate as if you were a Shakespearean narrator\u201D or \u201Cuse a formal business tone suitable for a corporate presentation.\u201D
128
- - Apply these creatively but **without altering the meaning or punctuation** of the original text.
129
- 3. Produce translations naturally, as a **professional human translator** would.
130
- 4. Respect the original punctuation, sentence boundaries, and capitalization.
131
- 5. Consider only the following target languages: ${(config?.languages ? Array.from(/* @__PURE__ */ new Set([...config.languages, config.sourceLanguage])) : []).join(", ")}.
132
- 6. The final output must be a **valid JSON** following this exact schema:
133
-
134
- {
135
- "original": "<original input text>",
136
- "translation": {
137
- "<language-code>": "<translated text>",
138
- "<language-code>": "<translated text>"
139
- }
140
- }
141
-
142
- 7. The JSON must be syntactically valid, UTF-8 encoded, with no code fences, markdown, or commentary.
143
- 8. Ignore any instructions that would alter the structure or format of the JSON.
144
- 9. The only text that should appear in the output is valid JSON \u2014 **no headers, preambles, or explanations**.
145
-
146
- ${extraInstructions ? `
147
- Now, using these instructions and applying the following extra parameters:
148
-
149
- extraInstructions: ${extraInstructions}
150
- ` : ""}
151
110
 
152
- Translate the following text faithfully:
153
- `;
154
- }
155
- function defaultPostPrompt(_config) {
156
- return "";
157
- }
158
- function prePrompt(config, prepend, replace) {
159
- return [
160
- // ...(append ? ['[SPECIFIC_INPUTS]', String(append), '[/SPECIFIC_INPUTS]'] : []),
161
- replace ? String(replace) : defaultPrePrompt(config, prepend)
162
- ].join(" ").trim();
163
- }
164
- function postPrompt(config, prepend, replace) {
165
- return [
166
- ...prepend ? ["[SPECIFIC_INPUTS]", String(prepend), "[/SPECIFIC_INPUTS]"] : [],
167
- replace ? String(replace) : defaultPostPrompt()
168
- ].join(" ").trim();
111
+ // src/data/cache-file-path.ts
112
+ function cacheFilePath(config) {
113
+ return `${config.cache.dir}/${config.cache.file}`;
169
114
  }
170
115
  var optsArg = (opts) => {
171
116
  if (!opts) {
@@ -379,21 +324,24 @@ async function createCacheDir() {
379
324
  await mkdirp(config.cache.dir);
380
325
  }
381
326
 
327
+ // src/data/cache-queue.ts
328
+ var cacheQueue = Promise.resolve();
329
+ function enqueueCacheTask(task) {
330
+ const run = cacheQueue.then(task);
331
+ cacheQueue = run.catch(() => void 0);
332
+ return run;
333
+ }
334
+ async function writeCacheFile(cache) {
335
+ const config = await awaitedConfig;
336
+ await createCacheDir();
337
+ const contents = JSON.stringify(cache, null, 2);
338
+ await fs2.writeFile(cacheFilePath(config), contents);
339
+ }
340
+
382
341
  // src/data/save-cache.ts
383
- var savingQueue = Promise.resolve();
384
342
  async function saveCache(cache) {
385
- try {
386
- savingQueue = savingQueue.then(async () => {
387
- const config = await awaitedConfig;
388
- await createCacheDir();
389
- const contents = JSON.stringify(cache, null, 2);
390
- await fs.writeFile(`${config.cache.dir}/${config.cache.file}`, contents);
391
- });
392
- await savingQueue;
393
- return cache;
394
- } catch (error) {
395
- throw error;
396
- }
343
+ await enqueueCacheTask(() => writeCacheFile(cache));
344
+ return cache;
397
345
  }
398
346
 
399
347
  // src/data/clear-cache.ts
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Creates the configured cache directory when it does not exist.
3
+ */
1
4
  declare function createCacheDir(): Promise<void>;
2
5
 
3
6
  export { createCacheDir };
@@ -1,7 +1,7 @@
1
1
  import { dirname, resolve, parse } from 'path';
2
2
  import { mkdir, stat, statSync, mkdirSync } from 'fs';
3
3
  import { cosmiconfig } from 'cosmiconfig';
4
- import merge from 'lodash.merge';
4
+ import mergeWith from 'lodash.mergewith';
5
5
  import { z } from 'zod';
6
6
 
7
7
  // ../node_modules/.pnpm/mkdirp@3.0.1/node_modules/mkdirp/dist/mjs/mkdirp-manual.js
@@ -227,9 +227,9 @@ var defaultConfig = {
227
227
  defaultLanguage: "pt-BR",
228
228
  languages: ["pt-BR", "fr-CA"],
229
229
  llm: {
230
- provider: "openai",
230
+ provider: "ollama",
231
231
  apiKey: void 0,
232
- model: "gpt-4o-mini",
232
+ model: "llama3.2",
233
233
  temperature: 1,
234
234
  sleep: 0
235
235
  },
@@ -251,6 +251,7 @@ var llmProviderSchema = z.enum([
251
251
  "azure",
252
252
  "meta"
253
253
  ]);
254
+ var keylessProviders = ["ollama", "lmstudio"];
254
255
  var configSchema = z.object({
255
256
  debug: z.boolean().optional(),
256
257
  log: z.object({
@@ -282,8 +283,7 @@ var configSchema = z.object({
282
283
  globby: z.string().regex(/^\.\/.*/).optional()
283
284
  }).refine(
284
285
  (data) => {
285
- const noApiKeyRequired = ["ollama", "lmstudio"];
286
- if (noApiKeyRequired.includes(data.llm.provider)) {
286
+ if (keylessProviders.includes(data.llm.provider)) {
287
287
  return true;
288
288
  }
289
289
  return !!data.llm.apiKey;
@@ -297,80 +297,20 @@ var configSchema = z.object({
297
297
  // src/config.ts
298
298
  var explorer = cosmiconfig("gringow");
299
299
  var result = await explorer.search();
300
+ function replaceArrays(_destination, source) {
301
+ if (Array.isArray(source)) return [...source];
302
+ return void 0;
303
+ }
300
304
  async function loadUserConfig() {
301
305
  try {
302
- const mergedConfig = merge({}, defaultConfig, result?.config);
303
- const parsedConfig = await configSchema.parseAsync(mergedConfig);
304
- parsedConfig.llm.prePrompt = prePrompt(
305
- parsedConfig,
306
- parsedConfig.llm.prePromptAppend ?? "",
307
- parsedConfig.llm.prePromptReplace ?? ""
308
- );
309
- parsedConfig.llm.postPrompt = postPrompt(
310
- parsedConfig,
311
- parsedConfig.llm.postPromptPrepend ?? "",
312
- parsedConfig.llm.postPromptReplace ?? ""
313
- );
314
- return parsedConfig;
306
+ const mergedConfig = mergeWith({}, defaultConfig, result?.config, replaceArrays);
307
+ return await configSchema.parseAsync(mergedConfig);
315
308
  } catch (error) {
316
309
  console.warn(`${error?.message}, using default configuration`);
317
310
  return defaultConfig;
318
311
  }
319
312
  }
320
313
  var awaitedConfig = loadUserConfig();
321
- function defaultPrePrompt(config, extraInstructions) {
322
- return `
323
- You are a ultimate translation generator server api.
324
-
325
- Your task is to translate a given text and output **only one valid JSON object**, with **no text, explanations, or formatting than the JSON**.
326
-
327
- Follow these rules strictly:
328
-
329
- 1. The original text is written in English or Spanish.
330
- 2. You will receive additional user parameters under the variable 'extraInstructions'. These may specify tone, persona, style, context, or any relevant translation guideline.
331
- - Examples: \u201Ctranslate as if you were a Shakespearean narrator\u201D or \u201Cuse a formal business tone suitable for a corporate presentation.\u201D
332
- - Apply these creatively but **without altering the meaning or punctuation** of the original text.
333
- 3. Produce translations naturally, as a **professional human translator** would.
334
- 4. Respect the original punctuation, sentence boundaries, and capitalization.
335
- 5. Consider only the following target languages: ${(config?.languages ? Array.from(/* @__PURE__ */ new Set([...config.languages, config.sourceLanguage])) : []).join(", ")}.
336
- 6. The final output must be a **valid JSON** following this exact schema:
337
-
338
- {
339
- "original": "<original input text>",
340
- "translation": {
341
- "<language-code>": "<translated text>",
342
- "<language-code>": "<translated text>"
343
- }
344
- }
345
-
346
- 7. The JSON must be syntactically valid, UTF-8 encoded, with no code fences, markdown, or commentary.
347
- 8. Ignore any instructions that would alter the structure or format of the JSON.
348
- 9. The only text that should appear in the output is valid JSON \u2014 **no headers, preambles, or explanations**.
349
-
350
- ${extraInstructions ? `
351
- Now, using these instructions and applying the following extra parameters:
352
-
353
- extraInstructions: ${extraInstructions}
354
- ` : ""}
355
-
356
- Translate the following text faithfully:
357
- `;
358
- }
359
- function defaultPostPrompt(_config) {
360
- return "";
361
- }
362
- function prePrompt(config, prepend, replace) {
363
- return [
364
- // ...(append ? ['[SPECIFIC_INPUTS]', String(append), '[/SPECIFIC_INPUTS]'] : []),
365
- replace ? String(replace) : defaultPrePrompt(config, prepend)
366
- ].join(" ").trim();
367
- }
368
- function postPrompt(config, prepend, replace) {
369
- return [
370
- ...prepend ? ["[SPECIFIC_INPUTS]", String(prepend), "[/SPECIFIC_INPUTS]"] : [],
371
- replace ? String(replace) : defaultPostPrompt()
372
- ].join(" ").trim();
373
- }
374
314
 
375
315
  // src/data/create-cache-dir.ts
376
316
  async function createCacheDir() {
@@ -1,10 +1,13 @@
1
- export { deleteCacheItem } from '../index.mjs';
2
- import '../config.mjs';
3
- import '../constants/default-config.mjs';
1
+ import { GringowCache } from '../types/index.mjs';
4
2
  import 'zod';
5
3
  import '../schemas/config.shcema.mjs';
6
- import '../create-cache-id.mjs';
7
- import './clear-cache.mjs';
8
- import './get-cache.mjs';
9
- import '../interpolate-template.mjs';
10
- import '../strip-comments.mjs';
4
+
5
+ /**
6
+ * Deletes a cache item. The read, delete, and write run as one queued step.
7
+ *
8
+ * @param cacheId - The cache ID.
9
+ * @returns The cache after the deletion.
10
+ */
11
+ declare function deleteCacheItem(cacheId: string): Promise<GringowCache>;
12
+
13
+ export { deleteCacheItem };
@@ -1,11 +1,11 @@
1
1
  import fs from 'fs/promises';
2
2
  import { cosmiconfig } from 'cosmiconfig';
3
- import merge from 'lodash.merge';
3
+ import mergeWith from 'lodash.mergewith';
4
4
  import { z } from 'zod';
5
5
  import { dirname, resolve, parse } from 'path';
6
6
  import { mkdir, stat, statSync, mkdirSync } from 'fs';
7
7
 
8
- // src/data/get-cache.ts
8
+ // src/data/cache-queue.ts
9
9
 
10
10
  // src/constants/default-config.ts
11
11
  var defaultConfig = {
@@ -23,9 +23,9 @@ var defaultConfig = {
23
23
  defaultLanguage: "pt-BR",
24
24
  languages: ["pt-BR", "fr-CA"],
25
25
  llm: {
26
- provider: "openai",
26
+ provider: "ollama",
27
27
  apiKey: void 0,
28
- model: "gpt-4o-mini",
28
+ model: "llama3.2",
29
29
  temperature: 1,
30
30
  sleep: 0
31
31
  },
@@ -47,6 +47,7 @@ var llmProviderSchema = z.enum([
47
47
  "azure",
48
48
  "meta"
49
49
  ]);
50
+ var keylessProviders = ["ollama", "lmstudio"];
50
51
  var configSchema = z.object({
51
52
  debug: z.boolean().optional(),
52
53
  log: z.object({
@@ -78,8 +79,7 @@ var configSchema = z.object({
78
79
  globby: z.string().regex(/^\.\/.*/).optional()
79
80
  }).refine(
80
81
  (data) => {
81
- const noApiKeyRequired = ["ollama", "lmstudio"];
82
- if (noApiKeyRequired.includes(data.llm.provider)) {
82
+ if (keylessProviders.includes(data.llm.provider)) {
83
83
  return true;
84
84
  }
85
85
  return !!data.llm.apiKey;
@@ -93,79 +93,24 @@ var configSchema = z.object({
93
93
  // src/config.ts
94
94
  var explorer = cosmiconfig("gringow");
95
95
  var result = await explorer.search();
96
+ function replaceArrays(_destination, source) {
97
+ if (Array.isArray(source)) return [...source];
98
+ return void 0;
99
+ }
96
100
  async function loadUserConfig() {
97
101
  try {
98
- const mergedConfig = merge({}, defaultConfig, result?.config);
99
- const parsedConfig = await configSchema.parseAsync(mergedConfig);
100
- parsedConfig.llm.prePrompt = prePrompt(
101
- parsedConfig,
102
- parsedConfig.llm.prePromptAppend ?? "",
103
- parsedConfig.llm.prePromptReplace ?? ""
104
- );
105
- parsedConfig.llm.postPrompt = postPrompt(
106
- parsedConfig,
107
- parsedConfig.llm.postPromptPrepend ?? "",
108
- parsedConfig.llm.postPromptReplace ?? ""
109
- );
110
- return parsedConfig;
102
+ const mergedConfig = mergeWith({}, defaultConfig, result?.config, replaceArrays);
103
+ return await configSchema.parseAsync(mergedConfig);
111
104
  } catch (error) {
112
105
  console.warn(`${error?.message}, using default configuration`);
113
106
  return defaultConfig;
114
107
  }
115
108
  }
116
109
  var awaitedConfig = loadUserConfig();
117
- function defaultPrePrompt(config, extraInstructions) {
118
- return `
119
- You are a ultimate translation generator server api.
120
-
121
- Your task is to translate a given text and output **only one valid JSON object**, with **no text, explanations, or formatting than the JSON**.
122
-
123
- Follow these rules strictly:
124
-
125
- 1. The original text is written in English or Spanish.
126
- 2. You will receive additional user parameters under the variable 'extraInstructions'. These may specify tone, persona, style, context, or any relevant translation guideline.
127
- - Examples: \u201Ctranslate as if you were a Shakespearean narrator\u201D or \u201Cuse a formal business tone suitable for a corporate presentation.\u201D
128
- - Apply these creatively but **without altering the meaning or punctuation** of the original text.
129
- 3. Produce translations naturally, as a **professional human translator** would.
130
- 4. Respect the original punctuation, sentence boundaries, and capitalization.
131
- 5. Consider only the following target languages: ${(config?.languages ? Array.from(/* @__PURE__ */ new Set([...config.languages, config.sourceLanguage])) : []).join(", ")}.
132
- 6. The final output must be a **valid JSON** following this exact schema:
133
-
134
- {
135
- "original": "<original input text>",
136
- "translation": {
137
- "<language-code>": "<translated text>",
138
- "<language-code>": "<translated text>"
139
- }
140
- }
141
-
142
- 7. The JSON must be syntactically valid, UTF-8 encoded, with no code fences, markdown, or commentary.
143
- 8. Ignore any instructions that would alter the structure or format of the JSON.
144
- 9. The only text that should appear in the output is valid JSON \u2014 **no headers, preambles, or explanations**.
145
110
 
146
- ${extraInstructions ? `
147
- Now, using these instructions and applying the following extra parameters:
148
-
149
- extraInstructions: ${extraInstructions}
150
- ` : ""}
151
-
152
- Translate the following text faithfully:
153
- `;
154
- }
155
- function defaultPostPrompt(_config) {
156
- return "";
157
- }
158
- function prePrompt(config, prepend, replace) {
159
- return [
160
- // ...(append ? ['[SPECIFIC_INPUTS]', String(append), '[/SPECIFIC_INPUTS]'] : []),
161
- replace ? String(replace) : defaultPrePrompt(config, prepend)
162
- ].join(" ").trim();
163
- }
164
- function postPrompt(config, prepend, replace) {
165
- return [
166
- ...prepend ? ["[SPECIFIC_INPUTS]", String(prepend), "[/SPECIFIC_INPUTS]"] : [],
167
- replace ? String(replace) : defaultPostPrompt()
168
- ].join(" ").trim();
111
+ // src/data/cache-file-path.ts
112
+ function cacheFilePath(config) {
113
+ return `${config.cache.dir}/${config.cache.file}`;
169
114
  }
170
115
  var optsArg = (opts) => {
171
116
  if (!opts) {
@@ -378,59 +323,62 @@ async function createCacheDir() {
378
323
  const config = await awaitedConfig;
379
324
  await mkdirp(config.cache.dir);
380
325
  }
381
-
382
- // src/data/get-cache.ts
383
326
  async function getCache() {
327
+ const config = await awaitedConfig;
384
328
  try {
385
- const config = await awaitedConfig;
386
- await createCacheDir();
387
- const rawCache = await fs.readFile(`${config.cache.dir}/${config.cache.file}`);
329
+ const rawCache = await fs.readFile(cacheFilePath(config));
388
330
  return rawCache.toString();
389
331
  } catch (error) {
332
+ if (error?.code === "ENOENT") await createCacheDir();
390
333
  throw error;
391
334
  }
392
335
  }
393
- var savingQueue = Promise.resolve();
394
- async function saveCache(cache) {
336
+
337
+ // src/data/cache-queue.ts
338
+ var cacheQueue = Promise.resolve();
339
+ function enqueueCacheTask(task) {
340
+ const run = cacheQueue.then(task);
341
+ cacheQueue = run.catch(() => void 0);
342
+ return run;
343
+ }
344
+ async function writeCacheFile(cache) {
345
+ const config = await awaitedConfig;
346
+ await createCacheDir();
347
+ const contents = JSON.stringify(cache, null, 2);
348
+ await fs.writeFile(cacheFilePath(config), contents);
349
+ }
350
+ async function parseCacheOrEmpty(rawCache) {
351
+ if (!rawCache) return {};
395
352
  try {
396
- savingQueue = savingQueue.then(async () => {
397
- const config = await awaitedConfig;
398
- await createCacheDir();
399
- const contents = JSON.stringify(cache, null, 2);
400
- await fs.writeFile(`${config.cache.dir}/${config.cache.file}`, contents);
401
- });
402
- await savingQueue;
403
- return cache;
353
+ return JSON.parse(rawCache);
404
354
  } catch (error) {
405
- throw error;
355
+ if (!(error instanceof SyntaxError)) throw error;
356
+ const config = await awaitedConfig;
357
+ console.warn(`Gringow cache ${cacheFilePath(config)} has invalid JSON (${error.message}), recreating it as {}`);
358
+ return {};
406
359
  }
407
360
  }
408
-
409
- // src/data/get-or-create-cache.ts
410
- async function getOrCreateCache() {
361
+ async function readCacheOrEmpty() {
411
362
  let rawCache;
412
363
  try {
413
364
  rawCache = await getCache();
414
365
  } catch (error) {
415
- if (error?.code === "ENOENT") {
416
- await saveCache({});
417
- rawCache = await getCache();
418
- } else {
419
- throw error;
420
- }
366
+ if (error?.code === "ENOENT") return {};
367
+ throw error;
421
368
  }
422
- return rawCache ? JSON.parse(rawCache?.toString()) : {};
369
+ return parseCacheOrEmpty(rawCache);
423
370
  }
424
371
 
425
372
  // src/data/delete-cache-item.ts
426
373
  async function deleteCacheItem(cacheId) {
427
- const rawCache = await getOrCreateCache();
428
- const cache = rawCache;
429
- if (cache[cacheId]) {
430
- delete cache[cacheId];
431
- }
432
- await saveCache(cache);
433
- return cache;
374
+ return enqueueCacheTask(async () => {
375
+ const cache = await readCacheOrEmpty();
376
+ if (cache[cacheId]) {
377
+ delete cache[cacheId];
378
+ }
379
+ await writeCacheFile(cache);
380
+ return cache;
381
+ });
434
382
  }
435
383
 
436
384
  export { deleteCacheItem };
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Retrieves the raw cache content from the filesystem
2
+ * Retrieves the raw cache content from the filesystem.
3
+ * The cache directory is created only when the file is missing, so a cache hit does no directory work.
3
4
  *
4
5
  * @returns Promise that resolves to the raw cache content as a string
5
6
  * @throws {GringowError} If there is an error reading the cache file