1- import crypto from "node:crypto" ;
2- import fs from "node:fs" ;
3- import path from "node:path" ;
4-
5- import lockfile from "proper-lockfile" ;
6-
71import type {
82 MSTeamsConversationStore ,
93 MSTeamsConversationStoreEntry ,
104 StoredConversationReference ,
115} from "./conversation-store.js" ;
126import { resolveMSTeamsStorePath } from "./storage.js" ;
7+ import { readJsonFile , withFileLock , writeJsonFile } from "./store-fs.js" ;
138
149type ConversationStoreData = {
1510 version : 1 ;
@@ -22,83 +17,6 @@ type ConversationStoreData = {
2217const STORE_FILENAME = "msteams-conversations.json" ;
2318const MAX_CONVERSATIONS = 1000 ;
2419const CONVERSATION_TTL_MS = 365 * 24 * 60 * 60 * 1000 ;
25- const STORE_LOCK_OPTIONS = {
26- retries : {
27- retries : 10 ,
28- factor : 2 ,
29- minTimeout : 100 ,
30- maxTimeout : 10_000 ,
31- randomize : true ,
32- } ,
33- stale : 30_000 ,
34- } as const ;
35-
36- function safeParseJson < T > ( raw : string ) : T | null {
37- try {
38- return JSON . parse ( raw ) as T ;
39- } catch {
40- return null ;
41- }
42- }
43-
44- async function readJsonFile < T > (
45- filePath : string ,
46- fallback : T ,
47- ) : Promise < { value : T ; exists : boolean } > {
48- try {
49- const raw = await fs . promises . readFile ( filePath , "utf-8" ) ;
50- const parsed = safeParseJson < T > ( raw ) ;
51- if ( parsed == null ) return { value : fallback , exists : true } ;
52- return { value : parsed , exists : true } ;
53- } catch ( err ) {
54- const code = ( err as { code ?: string } ) . code ;
55- if ( code === "ENOENT" ) return { value : fallback , exists : false } ;
56- return { value : fallback , exists : false } ;
57- }
58- }
59-
60- async function writeJsonFile ( filePath : string , value : unknown ) : Promise < void > {
61- const dir = path . dirname ( filePath ) ;
62- await fs . promises . mkdir ( dir , { recursive : true , mode : 0o700 } ) ;
63- const tmp = path . join (
64- dir ,
65- `${ path . basename ( filePath ) } .${ crypto . randomUUID ( ) } .tmp` ,
66- ) ;
67- await fs . promises . writeFile ( tmp , `${ JSON . stringify ( value , null , 2 ) } \n` , {
68- encoding : "utf-8" ,
69- } ) ;
70- await fs . promises . chmod ( tmp , 0o600 ) ;
71- await fs . promises . rename ( tmp , filePath ) ;
72- }
73-
74- async function ensureJsonFile ( filePath : string , fallback : unknown ) {
75- try {
76- await fs . promises . access ( filePath ) ;
77- } catch {
78- await writeJsonFile ( filePath , fallback ) ;
79- }
80- }
81-
82- async function withFileLock < T > (
83- filePath : string ,
84- fallback : unknown ,
85- fn : ( ) => Promise < T > ,
86- ) : Promise < T > {
87- await ensureJsonFile ( filePath , fallback ) ;
88- let release : ( ( ) => Promise < void > ) | undefined ;
89- try {
90- release = await lockfile . lock ( filePath , STORE_LOCK_OPTIONS ) ;
91- return await fn ( ) ;
92- } finally {
93- if ( release ) {
94- try {
95- await release ( ) ;
96- } catch {
97- // ignore unlock errors
98- }
99- }
100- }
101- }
10220
10321function parseTimestamp ( value : string | undefined ) : number | null {
10422 if ( ! value ) return null ;
0 commit comments