@monochromatic-dev/module-logger 0.1.0 → 0.3.0
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.
- package/CHANGELOG.md +27 -0
- package/README.md +58 -12
- package/dist/final/neutral/browser.d.mts +60 -0
- package/dist/final/neutral/browser.mjs +1 -0
- package/dist/final/neutral/index.d.mts +366 -572
- package/dist/final/neutral/index.mjs +2 -3
- package/dist/final/neutral/indexed-db-hsIfv7Cv.mjs +2 -0
- package/dist/final/neutral/types-BkkBXgY3.d.mts +76 -0
- package/dist/final/node/file-CRGb1hDK.mjs +1 -0
- package/dist/final/node/index.d.mts +366 -572
- package/dist/final/node/index.mjs +3 -3
- package/dist/final/node/node.d.mts +103 -0
- package/dist/final/node/node.mjs +1 -0
- package/dist/final/node/types-BkkBXgY3.d.mts +76 -0
- package/package.json +19 -5
- package/src/artifact-platform-split.unit.test.ts +140 -0
- package/src/browser.ts +14 -0
- package/src/create-logger.ts +249 -151
- package/src/create-logger.unit.test.ts +527 -75
- package/src/default-sinks.neutral.ts +34 -0
- package/src/default-sinks.node.ts +32 -0
- package/src/error-format.ts +23 -23
- package/src/index.ts +2 -0
- package/src/logger.ts +23 -50
- package/src/node.ts +23 -0
- package/src/sink/console-control-chars.ts +64 -64
- package/src/sink/console-control-chars.unit.test.ts +14 -14
- package/src/sink/console.ts +194 -194
- package/src/sink/console.unit.test.ts +18 -18
- package/src/sink/file.ts +136 -140
- package/src/sink/file.unit.test.ts +19 -26
- package/src/sink/index.ts +4 -7
- package/src/sink/indexed-db-util.ts +42 -42
- package/src/sink/indexed-db.browser.test.ts +7 -7
- package/src/sink/indexed-db.ts +109 -109
- package/src/sink/indexed-db.unit.test.ts +5 -13
- package/src/sink/local-storage-key.ts +73 -73
- package/src/sink/local-storage-key.unit.test.ts +8 -8
- package/src/sink/local-storage-quota.ts +37 -37
- package/src/sink/local-storage-quota.unit.test.ts +8 -8
- package/src/sink/local-storage-store.ts +113 -113
- package/src/sink/local-storage-store.unit.test.ts +35 -35
- package/src/sink/local-storage.ts +72 -72
- package/src/sink/local-storage.unit.test.ts +27 -27
- package/src/sink/noop.ts +20 -20
- package/src/sink/noop.unit.test.ts +1 -1
- package/src/sink/opfs.browser.test.ts +7 -7
- package/src/sink/opfs.ts +62 -62
- package/src/sink/opfs.unit.test.ts +5 -13
- package/src/sink/record-buffer.ts +84 -84
- package/src/sink/record-buffer.unit.test.ts +20 -20
- package/src/sink/session-storage-quota.ts +34 -34
- package/src/sink/session-storage-quota.unit.test.ts +8 -8
- package/src/sink/session-storage-store.ts +72 -72
- package/src/sink/session-storage.ts +48 -48
- package/src/sink/session-storage.unit.test.ts +39 -39
- package/src/sink/web-storage-quota-error.ts +22 -22
- package/src/sink/web-storage-quota-error.unit.test.ts +2 -2
- package/src/sink/web-storage-runtime.ts +24 -24
- package/src/startup.unit.test.ts +18 -18
- package/src/tagged.ts +35 -35
- package/src/tagged.unit.test.ts +8 -8
- package/src/types.ts +39 -39
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import{t as __exportAll}from"./rolldown-runtime-5duEfhBv.mjs";function _usingCtx(){var r=typeof SuppressedError==`function`?SuppressedError:function(r,e){var n=Error();return n.name=`SuppressedError`,n.error=r,n.suppressed=e,n},e={},n=[];function using(r,e){if(e!=null){if(Object(e)!==e)throw TypeError(`using declarations can only be used with objects, functions, null, or undefined.`);if(r)var o=e[Symbol.asyncDispose||Symbol.for(`Symbol.asyncDispose`)];if(o===void 0&&(o=e[Symbol.dispose||Symbol.for(`Symbol.dispose`)],r))var t=o;if(typeof o!=`function`)throw TypeError(`Object is not disposable.`);t&&(o=function(){try{t.call(e)}catch(r){return Promise.reject(r)}}),n.push({v:e,d:o,a:r})}else r&&n.push({d:e,a:r});return e}return{e,u:using.bind(null,!1),a:using.bind(null,!0),d:function(){var o,t=this.e,s=0;function next(){for(;o=n.pop();)try{if(!o.a&&s===1)return s=0,n.push(o),Promise.resolve().then(next);if(o.d){var r=o.d.call(o.v);if(o.a)return s|=2,Promise.resolve(r).then(next,err)}else s|=1}catch(r){return err(r)}if(s===1)return t===e?Promise.resolve():Promise.reject(t);if(t!==e)throw t}function err(n){return t=t===e?n:new r(n,t),next()}return next()}}}async function withTimeout({promise,ms,label}){try{var _usingCtx$1=_usingCtx();let{promise:timeoutPromise,reject}=Promise.withResolvers(),timer=setTimeout(function(){reject(Error(`Timed out after ${String(ms)}ms: ${label}`))},ms);return _usingCtx$1.u({[Symbol.dispose](){clearTimeout(timer)}}),await Promise.race([promise,timeoutPromise])}catch(_){_usingCtx$1.e=_}finally{_usingCtx$1.d()}}
|
|
2
|
-
`);if(!(level===`debug`&&writeDebugRunToProcessStderr(text)))try{let method=LEVEL_TO_CONSOLE_METHOD[level],consoleFn=console[method];typeof consoleFn==`function`&&consoleFn.call(console,text)}catch(error){reportLoggerInternalError({context:`console method call failed while emitting log run`,error})}}function groupRuns(records){return records.reduce(function(runs,record){let tail=runs.at(-1);return tail!==void 0&&tail.level===record.level?(tail.records.push(record),runs):(runs.push({level:record.level,records:[record]}),runs)},[])}function verifyConsole(){try{return typeof console>`u`||typeof(hasProcessStderr()?console.info:console.debug)!=`function`||typeof queueMicrotask!=`function`?Promise.resolve(!1):Promise.resolve(!0)}catch(error){return reportLoggerInternalError({context:`console sink verification failed`,error}),Promise.resolve(!1)}}function createConsoleSink(){let state={buffer:[],scheduled:!1,verboseCache:VERBOSE_UNCOMPUTED};function getVerbose(){let cached=state.verboseCache;if(cached!==VERBOSE_UNCOMPUTED)return cached;let computed=detectVerbose();return state.verboseCache=computed,computed}function flushBuffer(){if(state.scheduled=!1,state.buffer.length===0)return;let records=state.buffer.splice(0);for(let run of groupRuns(records))emitRun({level:run.level,records:run.records})}function write(record){return!getVerbose()&&SILENT_LEVELS.has(record.level)||record.level===`warn`&&isWarnSuppressed()?Promise.resolve():(state.buffer.push(record),state.scheduled||(state.scheduled=!0,queueMicrotask(flushBuffer)),Promise.resolve())}function flush(){return flushBuffer(),Promise.resolve()}return{flush,verify:verifyConsole,write}}const NO_NODE_MODULES_FOUND=Symbol(`logger:no-node-modules-found`);async function findNodeModulesUp({cwd,stat,dirname,join,reportError=reportLoggerInternalError}){let candidate=join(cwd,`node_modules`);try{if((await stat(candidate)).isDirectory())return candidate}catch(error){Error.isError(error)&&`code`in error&&error.code===`ENOENT`||reportError({context:`node_modules candidate ${candidate} unavailable during file sink search`,error})}let parent=dirname(cwd);return parent===cwd?NO_NODE_MODULES_FOUND:await findNodeModulesUp({cwd:parent,stat,dirname,join,reportError})}function createFileSink(){let state={};async function runVerify(){if(globalThis.process===void 0||globalThis.process.versions?.node===void 0)return!1;try{let fs=await import(`node:fs/promises`),{dirname,join}=await import(`node:path`);state.appendFile=fs.appendFile;let nodeModulesDir=await findNodeModulesUp({cwd:process.cwd(),stat:fs.stat,dirname,join});if(nodeModulesDir===NO_NODE_MODULES_FOUND)return!1;let LOG_DIR=join(nodeModulesDir,`.monochromatic`);await fs.mkdir(LOG_DIR,{recursive:!0});let timestamp=new Date().toISOString().replaceAll(`:`,`-`);state.filePath=join(LOG_DIR,`${timestamp}.log.jsonl`);let testData=`{"test":true,"timestamp":${Date.now()}}\n`;return await state.appendFile(state.filePath,testData),(await fs.readFile(state.filePath,`utf8`)).includes(`"test":true`)}catch(error){return reportLoggerInternalError({context:`file sink verification failed`,error}),!1}}function verify(){return state.verifyPromise===void 0&&(state.verifyPromise=runVerify()),state.verifyPromise}async function write(record){if(!(!state.filePath||!state.appendFile))try{await state.appendFile(state.filePath,`${JSON.stringify(record)}\n`)}catch(error){reportLoggerInternalError({context:`file sink record append failed`,error})}}return{verify,write}}function awaitRequest(request){return new Promise(function(resolve,reject){request.addEventListener(`success`,function(){resolve(request.result)}),request.addEventListener(`error`,function(){reject(request.error??Error(`IndexedDB request failed without an error object`))})})}function awaitTransaction(transaction){return new Promise(function(resolve,reject){transaction.addEventListener(`complete`,function(){resolve()}),transaction.addEventListener(`error`,function(){reject(transaction.error??Error(`IndexedDB transaction failed without an error object`))}),transaction.addEventListener(`abort`,function(){reject(transaction.error??Error(`IndexedDB transaction aborted without an error object`))})})}const FLUSH_BUFFER_CAP_CHARS=32768,FLUSH_IMMEDIATELY_BY_LEVEL={debug:!1,error:!0,fatal:!0,info:!1,trace:!1,warn:!0};function isUnrefableTimer(timer){return typeof timer!=`object`||!timer||!(`unref`in timer)?!1:typeof timer.unref==`function`}function createRecordBuffer({onFlush}){let entries=[],bufferState={chars:0};function charsWith(serialized){let separatorChars=+(entries.length>0);return bufferState.chars+separatorChars+serialized.length}function drain(){if(bufferState.timer!==void 0&&(globalThis.clearTimeout(bufferState.timer),delete bufferState.timer),entries.length===0)return;let batch=entries.join(`
|
|
3
|
-
`);entries.length=0,bufferState.chars=0,onFlush(batch)}function scheduleDeadlineFlush(){if(bufferState.timer!==void 0)return;let timer=globalThis.setTimeout(drain,250);isUnrefableTimer(timer)&&timer.unref(),bufferState.timer=timer}function add(entry){entries.length>0&&charsWith(entry.serialized)>FLUSH_BUFFER_CAP_CHARS&&drain(),bufferState.chars=charsWith(entry.serialized),entries.push(entry.serialized),FLUSH_IMMEDIATELY_BY_LEVEL[entry.level]||bufferState.chars>=FLUSH_BUFFER_CAP_CHARS?drain():scheduleDeadlineFlush()}return globalThis.addEventListener?.(`pagehide`,drain),globalThis.document?.addEventListener(`visibilitychange`,function(){globalThis.document?.visibilityState===`hidden`&&drain()}),{add,drain}}const BATCH_STORE=`batch`,MAX_STORED_BATCHES=2048;async function openLogDatabase(){let request=globalThis.indexedDB.open(`monochromatic.log`,1);return request.onupgradeneeded=function(){request.result.createObjectStore(BATCH_STORE,{autoIncrement:!0})},await awaitRequest(request)}async function persistBatch({database,batch}){let transaction=database.transaction(BATCH_STORE,`readwrite`),store=transaction.objectStore(BATCH_STORE);store.add(batch);let count=await awaitRequest(store.count());if(count>MAX_STORED_BATCHES){let newestStale=(await awaitRequest(store.getAllKeys(null,count-MAX_STORED_BATCHES))).at(-1);newestStale!==void 0&&store.delete(IDBKeyRange.upperBound(newestStale))}await awaitTransaction(transaction)}function createIndexedDbSink(){let state={},pendingBatchWrites=new Set;async function verify(){try{if(globalThis.indexedDB===void 0)return!1;let database=await openLogDatabase(),transaction=database.transaction(BATCH_STORE,`readwrite`),store=transaction.objectStore(BATCH_STORE),probeValue=`probe-${Date.now()}`,probeKey=await awaitRequest(store.add(probeValue)),readBack=await awaitRequest(store.get(probeKey));return store.delete(probeKey),await awaitTransaction(transaction),readBack===probeValue&&(state.database=database,!0)}catch(error){return`indexedDB`in globalThis&&reportLoggerInternalError({context:`IndexedDB sink verification failed`,error}),!1}}async function writeBatch(batch){if(state.database)try{await persistBatch({database:state.database,batch})}catch(error){reportLoggerInternalError({context:`IndexedDB sink record write failed`,error})}}async function removePendingWhenSettled(pending){await pending,pendingBatchWrites.delete(pending)}function handOffBatch(batch){let pending=writeBatch(batch);pendingBatchWrites.add(pending),removePendingWhenSettled(pending)}let buffer=createRecordBuffer({onFlush:handOffBatch});function write(record){return buffer.add({level:record.level,serialized:JSON.stringify(record)}),Promise.resolve()}async function flush(){buffer.drain();let writes=[...pendingBatchWrites];await Promise.all(writes)}return{flush,verify,write}}function isDigits(text){if(text.length===0)return!1;for(let character of text)if(character<`0`||character>`9`)return!1;return!0}function buildLogKey({stamp,nonce,index}){return`monochromatic.log.${stamp}.${nonce}.${index}`}function parseLogKey(key){if(!key.startsWith(`monochromatic.log.`))return{};let segments=key.slice(18).split(`.`);if(segments.length!==3)return{};let[stampText,nonce,indexText]=segments;return stampText===void 0||nonce===void 0||indexText===void 0||!isDigits(stampText)||nonce.length===0||!isDigits(indexText)?{}:{parsed:{key,stamp:Math.trunc(Number(stampText)),nonce,index:Math.trunc(Number(indexText))}}}function compareLogKeys({first,second}){return first.stamp===second.stamp?first.nonce===second.nonce?first.index-second.index:first.nonce<second.nonce?-1:1:first.stamp-second.stamp}function detectWebStorageRuntime(){return`Deno`in globalThis?`deno`:`Bun`in globalThis?`bun`:typeof process<`u`&&typeof process.versions.node==`string`?`node`:`document`in globalThis?`browser`:`unknown`}const RUNTIME_QUOTA_CHARS$1={browser:5242880,bun:1/0,deno:10477569,node:5242880,unknown:1/0};function detectLocalStorageQuotaChars(){return RUNTIME_QUOTA_CHARS$1[detectWebStorageRuntime()]}const QUOTA_EXCEEDED_NAMES=new Set([`QuotaExceededError`,`NS_ERROR_DOM_QUOTA_REACHED`]);function isQuotaExceededError(error){return typeof error==`object`&&!!error&&`name`in error&&typeof error.name==`string`&"A_EXCEEDED_NAMES.has(error.name)}function createLocalStorageStore(){let runIdentity={stamp:Date.now(),nonce:Math.random().toString(36).slice(2,6).padEnd(4,`0`)},state={lineCounter:0,oldestIndex:0,usedChars:0,reportedFailure:!1,adoptedPrior:!1},prior={entries:[],cursor:0},capChars=detectLocalStorageQuotaChars()/2;function ownKey(index){return buildLogKey({stamp:runIdentity.stamp,nonce:runIdentity.nonce,index})}function adoptPriorEntries(){let total=globalThis.localStorage.length,found=[];for(let slot=0;slot<total;slot++){let key=globalThis.localStorage.key(slot);if(key===null)continue;let{parsed}=parseLogKey(key);if(parsed===void 0||parsed.stamp===runIdentity.stamp&&parsed.nonce===runIdentity.nonce)continue;let value=globalThis.localStorage.getItem(key);value!==null&&found.push({...parsed,chars:value.length})}prior.entries=found.toSorted(function(first,second){return compareLogKeys({first,second})}),state.usedChars+=found.reduce(function(sum,entry){return sum+entry.chars},0)}function hasEvictable(){let priorCount=prior.entries.length;return prior.cursor<priorCount||state.oldestIndex<state.lineCounter}function evictOldestPrior(){let entry=prior.entries[prior.cursor];return entry!==void 0&&(prior.cursor++,globalThis.localStorage.removeItem(entry.key),state.usedChars=Math.max(0,state.usedChars-entry.chars),!0)}function evictOldestOwn(){let key=ownKey(state.oldestIndex),evicted=globalThis.localStorage.getItem(key);globalThis.localStorage.removeItem(key),state.oldestIndex++,evicted!==null&&(state.usedChars=Math.max(0,state.usedChars-evicted.length))}function evictOldest(){evictOldestPrior()||state.oldestIndex<state.lineCounter&&evictOldestOwn()}function persist(batch){state.adoptedPrior||(state.adoptedPrior=!0,adoptPriorEntries());let batchChars=batch.length;for(;hasEvictable()&&state.usedChars+batchChars>capChars;)evictOldest();let maxWriteAttempts=prior.entries.length-prior.cursor+(state.lineCounter-state.oldestIndex)+1;for(let writeAttempt=0;writeAttempt<maxWriteAttempts;writeAttempt++)try{globalThis.localStorage.setItem(ownKey(state.lineCounter),batch),state.lineCounter++,state.usedChars+=batchChars,state.reportedFailure=!1;return}catch(error){if(isQuotaExceededError(error)&&hasEvictable()){evictOldest();continue}state.reportedFailure||=(reportLoggerInternalError({context:`localStorage sink record write failed (repeats suppressed until a write next succeeds)`,error}),!0);return}}return{persist}}const NODE_LOCALSTORAGE_FLAG=`--localstorage-file`;function nodeWithoutLocalStorageFile(){if(detectWebStorageRuntime()!==`node`||`document`in globalThis)return!1;let flaggedInExecArgv=process.execArgv.some(function(argument){return argument.startsWith(NODE_LOCALSTORAGE_FLAG)}),flaggedInNodeOptions=(process.env.NODE_OPTIONS??``).includes(NODE_LOCALSTORAGE_FLAG);return!(flaggedInExecArgv||flaggedInNodeOptions)}function verifyLocalStorage(){if(nodeWithoutLocalStorageFile())return Promise.resolve(!1);try{let testKey=`__monochromatic_verify__`,testValue=`test-${Date.now()}`;globalThis.localStorage.setItem(testKey,testValue);let readBack=globalThis.localStorage.getItem(testKey);return globalThis.localStorage.removeItem(testKey),Promise.resolve(readBack===testValue)}catch(error){return`localStorage`in globalThis&&reportLoggerInternalError({context:`localStorage sink verification failed`,error}),Promise.resolve(!1)}}function createLocalStorageSink(){let buffer=createRecordBuffer({onFlush:createLocalStorageStore().persist});function write(record){return buffer.add({level:record.level,serialized:JSON.stringify(record)}),Promise.resolve()}function flush(){return buffer.drain(),Promise.resolve()}return{flush,verify:verifyLocalStorage,write}}const RUNTIME_QUOTA_CHARS={browser:5242880,bun:1/0,deno:10485760,node:5242880,unknown:1/0};function detectSessionStorageQuotaChars(){return RUNTIME_QUOTA_CHARS[detectWebStorageRuntime()]}function storageKey(index){return`monochromatic.log.${index}`}function createSessionStorageStore(){let state={lineCounter:0,oldestIndex:0,usedChars:0,reportedFailure:!1},capChars=detectSessionStorageQuotaChars()/2;function evictOldest(){let key=storageKey(state.oldestIndex),evicted=globalThis.sessionStorage.getItem(key);globalThis.sessionStorage.removeItem(key),state.oldestIndex++,evicted!==null&&(state.usedChars=Math.max(0,state.usedChars-evicted.length))}function persist(batch){let batchChars=batch.length;for(;state.oldestIndex<state.lineCounter&&state.usedChars+batchChars>capChars;)evictOldest();let maxWriteAttempts=state.lineCounter-state.oldestIndex+1;for(let writeAttempt=0;writeAttempt<maxWriteAttempts;writeAttempt++)try{globalThis.sessionStorage.setItem(storageKey(state.lineCounter),batch),state.lineCounter++,state.usedChars+=batchChars,state.reportedFailure=!1;return}catch(error){if(isQuotaExceededError(error)&&state.oldestIndex<state.lineCounter){evictOldest();continue}state.reportedFailure||=(reportLoggerInternalError({context:`sessionStorage sink record write failed (repeats suppressed until a write next succeeds)`,error}),!0);return}}return{persist}}function verifySessionStorage(){try{let testKey=`__monochromatic_verify__`,testValue=`test-${Date.now()}`;globalThis.sessionStorage.setItem(testKey,testValue);let readBack=globalThis.sessionStorage.getItem(testKey);return globalThis.sessionStorage.removeItem(testKey),Promise.resolve(readBack===testValue)}catch(error){return`sessionStorage`in globalThis&&reportLoggerInternalError({context:`sessionStorage sink verification failed`,error}),Promise.resolve(!1)}}function createSessionStorageSink(){let buffer=createRecordBuffer({onFlush:createSessionStorageStore().persist});function write(record){return buffer.add({level:record.level,serialized:JSON.stringify(record)}),Promise.resolve()}function flush(){return buffer.drain(),Promise.resolve()}return{flush,verify:verifySessionStorage,write}}const{initPromise:defaultInitPromise,logger:defaultLogger}=createLogger({sinks:[createConsoleSink(),createIndexedDbSink(),createSessionStorageSink(),createLocalStorageSink(),createFileSink()]}),initPromise=defaultInitPromise,logger=defaultLogger;function verify(){return Promise.resolve(!0)}function write(_record){return Promise.resolve()}function createNoopSink(){return{verify,write}}function createOpfsSink(){let state={},pendingBatchWrites=new Set;async function verify(){try{let opfsRoot=await navigator.storage.getDirectory(),timestamp=new Date().toISOString().replaceAll(`:`,`-`),fileHandle=await opfsRoot.getFileHandle(`monochromatic-${timestamp}.log.jsonl`,{create:!0}),probeWritable=await fileHandle.createWritable({keepExistingData:!0}),testData=`{"test":true,"timestamp":${Date.now()}}\n`;await probeWritable.write(testData),await probeWritable.close();let available=(await(await fileHandle.getFile()).text()).includes(`"test":true`);return available&&(state.writable=await fileHandle.createWritable({keepExistingData:!0})),available}catch(error){let opfsStorage=globalThis.navigator?.storage;return opfsStorage!==void 0&&`getDirectory`in opfsStorage&&reportLoggerInternalError({context:`OPFS sink verification failed`,error}),!1}}async function writeBatch(batch){if(state.writable)try{await state.writable.write(`${batch}\n`)}catch(error){reportLoggerInternalError({context:`OPFS sink record write failed`,error})}}async function removePendingWhenSettled(pending){await pending,pendingBatchWrites.delete(pending)}function handOffBatch(batch){let pending=writeBatch(batch);pendingBatchWrites.add(pending),removePendingWhenSettled(pending)}let buffer=createRecordBuffer({onFlush:handOffBatch});function write(record){return buffer.add({level:record.level,serialized:JSON.stringify(record)}),Promise.resolve()}async function flush(){buffer.drain();let writes=[...pendingBatchWrites];await Promise.all(writes)}return{flush,verify,write}}var sink_exports=__exportAll({NO_NODE_MODULES_FOUND:()=>NO_NODE_MODULES_FOUND,createConsoleSink:()=>createConsoleSink,createFileSink:()=>createFileSink,createIndexedDbSink:()=>createIndexedDbSink,createLocalStorageSink:()=>createLocalStorageSink,createNoopSink:()=>createNoopSink,createOpfsSink:()=>createOpfsSink,createSessionStorageSink:()=>createSessionStorageSink,findNodeModulesUp:()=>findNodeModulesUp});function tagged({tag,l=logger}){let prefix=`[${tag}] `;return{debug:function(message){l.debug(`${prefix}${message}`)},error:function(message){l.error(`${prefix}${message}`)},fatal:function(message){l.fatal(`${prefix}${message}`)},flush:function(){return l.flush()},info:function(message){l.info(`${prefix}${message}`)},trace:function(message){l.trace(`${prefix}${message}`)},warn:function(message){l.warn(`${prefix}${message}`)}}}export{DEFAULT_FLUSH_DEADLINE_MS,buildLogKey as _buildLogKey,compareLogKeys as _compareLogKeys,createLocalStorageStore as _createLocalStorageStore,createRecordBuffer as _createRecordBuffer,detectLocalStorageQuotaChars as _detectLocalStorageQuotaChars,detectSessionStorageQuotaChars as _detectSessionStorageQuotaChars,isQuotaExceededError as _isQuotaExceededError,neutralizeControlCharacters as _neutralizeControlCharacters,parseLogKey as _parseLogKey,createLogger,initPromise,logger,sink_exports as sinks,tagged};
|
|
1
|
+
import{t as __exportAll}from"./rolldown-runtime-5duEfhBv.mjs";import{n as createRecordBuffer,r as reportLoggerInternalError,t as createIndexedDbSink}from"./indexed-db-hsIfv7Cv.mjs";function _usingCtx(){var r=typeof SuppressedError==`function`?SuppressedError:function(r,e){var n=Error();return n.name=`SuppressedError`,n.error=r,n.suppressed=e,n},e={},n=[];function using(r,e){if(e!=null){if(Object(e)!==e)throw TypeError(`using declarations can only be used with objects, functions, null, or undefined.`);if(r)var o=e[Symbol.asyncDispose||Symbol.for(`Symbol.asyncDispose`)];if(o===void 0&&(o=e[Symbol.dispose||Symbol.for(`Symbol.dispose`)],r))var t=o;if(typeof o!=`function`)throw TypeError(`Object is not disposable.`);t&&(o=function(){try{t.call(e)}catch(r){return Promise.reject(r)}}),n.push({v:e,d:o,a:r})}else r&&n.push({d:e,a:r});return e}return{e,u:using.bind(null,!1),a:using.bind(null,!0),d:function(){var o,t=this.e,s=0;function next(){for(;o=n.pop();)try{if(!o.a&&s===1)return s=0,n.push(o),Promise.resolve().then(next);if(o.d){var r=o.d.call(o.v);if(o.a)return s|=2,Promise.resolve(r).then(next,err)}else s|=1}catch(r){return err(r)}if(s===1)return t===e?Promise.resolve():Promise.reject(t);if(t!==e)throw t}function err(n){return t=t===e?n:new r(n,t),next()}return next()}}}async function withTimeout({promise,ms,label}){try{var _usingCtx$1=_usingCtx();let{promise:timeoutPromise,reject}=Promise.withResolvers(),timer=setTimeout(function(){reject(Error(`Timed out after ${String(ms)}ms: ${label}`))},ms);return _usingCtx$1.u({[Symbol.dispose](){clearTimeout(timer)}}),await Promise.race([promise,timeoutPromise])}catch(_){_usingCtx$1.e=_}finally{_usingCtx$1.d()}}const DEFAULT_FLUSH_DEADLINE_MS=5e3,DEFAULT_VERIFY_TIMEOUT_MS=5e3,STARTUP_BUFFER_CAP=1e4;async function trackWrite({writePromise}){try{await writePromise}catch(error){reportLoggerInternalError({context:`sink write promise rejected while being tracked`,error})}}function createLogger({sinks,flushDeadlineMs=DEFAULT_FLUSH_DEADLINE_MS,verifyTimeoutMs=DEFAULT_VERIFY_TIMEOUT_MS}){let entries=sinks.map(function(sink){return{available:!1,sink}}),startupRecords=[],pendingWrites=new Set,state={droppedStartupRecords:0,hasAvailableSink:!1,initialized:!1};function bufferStartupRecord({record}){startupRecords.length>=1e4&&(startupRecords.shift(),state.droppedStartupRecords+=1),startupRecords.push(record)}function recomputeAvailability(){state.hasAvailableSink=entries.some(function(entry){return entry.available})}function getSinkEntry({entryIndex}){let entry=entries[entryIndex];if(entry===void 0)throw Error(`Missing logger sink entry at index ${entryIndex}.`);return entry}function markEntryUnavailable({entryIndex}){let entry=getSinkEntry({entryIndex});entry.available=!1,recomputeAvailability()}async function removePendingWriteWhenSettled({trackedWrite}){await trackedWrite,pendingWrites.delete(trackedWrite)}function writeRecordToEntry({entryIndex,record}){try{let trackedWrite=trackWrite({writePromise:getSinkEntry({entryIndex}).sink.write(record)});pendingWrites.add(trackedWrite),removePendingWriteWhenSettled({trackedWrite})}catch(error){reportLoggerInternalError({context:`sink write threw synchronously while dispatching record`,error})}}function replayStartupRecordsToEntry({entryIndex}){startupRecords.forEach(function(record){writeRecordToEntry({entryIndex,record})})}function setEntryAvailability({entryIndex,available}){let entry=getSinkEntry({entryIndex});entry.available=available,recomputeAvailability(),available&&replayStartupRecordsToEntry({entryIndex})}async function verifyAndApply({entryIndex}){try{let entry=getSinkEntry({entryIndex});setEntryAvailability({available:await withTimeout({label:`sink ${entryIndex} verify`,ms:verifyTimeoutMs,promise:entry.sink.verify()}),entryIndex})}catch(error){reportLoggerInternalError({context:`sink verification failed for entry ${entryIndex}`,error}),markEntryUnavailable({entryIndex})}}function emitDroppedStartupMarker(){let dropped=state.droppedStartupRecords;if(dropped===0)return;let marker={level:`warn`,message:`${dropped} startup record${dropped===1?``:`s`} dropped before a backend verified (buffer cap ${STARTUP_BUFFER_CAP})`,timestamp:Date.now()};entries.forEach(function(entry,entryIndex){entry.available&&writeRecordToEntry({entryIndex,record:marker})})}async function initialize(){state.initialized||(await Promise.all(entries.map(function(_entry,entryIndex){return verifyAndApply({entryIndex})})),state.initialized=!0,startupRecords.length=0,emitDroppedStartupMarker())}let initPromise=initialize();async function drainPendingWrites(){let writes=[...pendingWrites];await Promise.all(writes)}function createMethod(level){return function(message){if(!state.hasAvailableSink&&state.initialized)throw Error(`No logging backends available`);let record={level,message,timestamp:Date.now()};state.initialized||bufferStartupRecord({record}),entries.map(function(_entry,entryIndex){return entryIndex}).filter(function(entryIndex){return getSinkEntry({entryIndex}).available}).forEach(function(entryIndex){writeRecordToEntry({entryIndex,record})})}}async function runSinkFlushHooks(){await Promise.all(entries.map(async function(entry,entryIndex){let sinkFlush=entry.sink.flush;if(entry.available&&typeof sinkFlush==`function`)try{await sinkFlush()}catch(error){reportLoggerInternalError({context:`sink flush failed for entry ${entryIndex}`,error}),markEntryUnavailable({entryIndex})}}))}async function drainEverything(){await initPromise,await drainPendingWrites(),await runSinkFlushHooks()}function abandonPendingWrites(){pendingWrites.clear()}async function flushAll(){try{await withTimeout({label:`logger flush`,ms:flushDeadlineMs,promise:drainEverything()})}catch(error){reportLoggerInternalError({context:`flush deadline of ${flushDeadlineMs}ms elapsed; abandoning in-flight sink work`,error}),abandonPendingWrites()}}return{initPromise,logger:{debug:createMethod(`debug`),error:createMethod(`error`),fatal:createMethod(`fatal`),flush:flushAll,info:createMethod(`info`),trace:createMethod(`trace`),warn:createMethod(`warn`)}}}function isNeutralizedControl(codeUnit){return codeUnit<32?codeUnit!==10&&codeUnit!==9:codeUnit===127||codeUnit>=128&&codeUnit<=159}function escapeCodeUnit(codeUnit){return`\\u${codeUnit.toString(16).toUpperCase().padStart(4,`0`)}`}function neutralizeControlCharacters(text){let pieces=[];for(let character of text){let codeUnit=character.charCodeAt(0);pieces.push(isNeutralizedControl(codeUnit)?escapeCodeUnit(codeUnit):character)}return pieces.join(``)}const VERBOSE_UNCOMPUTED=Symbol(`logger:verbose-detection-uncomputed`),SILENT_LEVELS=new Set([`debug`,`trace`]);function detectVerbose(){try{if(typeof process<`u`&&process.env.MONOCHROMATIC_VERBOSE===`true`)return!0}catch(error){reportLoggerInternalError({context:`MONOCHROMATIC_VERBOSE environment probe failed during verbose detection`,error})}try{if(typeof process<`u`&&Array.isArray(process.argv)&&process.argv.includes(`--verbose`))return!0}catch(error){reportLoggerInternalError({context:`process argv probe failed during verbose detection`,error})}try{if(`window`in globalThis)return!0}catch(error){reportLoggerInternalError({context:`window probe failed during verbose detection`,error})}return!1}function isWarnSuppressed(){try{return typeof process<`u`&&process.env.MONOCHROMATIC_WARN===`false`}catch(error){return reportLoggerInternalError({context:`MONOCHROMATIC_WARN environment probe failed during warn suppression detection`,error}),!1}}const LEVEL_TO_CONSOLE_METHOD={debug:`debug`,error:`error`,fatal:`error`,info:`info`,trace:`trace`,warn:`warn`};function formatRecord(record){return`[${record.level}] [${new Date(record.timestamp).toISOString()}] ${neutralizeControlCharacters(record.message)}`}function hasProcessStderr(){try{return typeof process>`u`?!1:typeof process.stderr.write==`function`}catch(error){return reportLoggerInternalError({context:`process stderr availability probe failed`,error}),!1}}function writeDebugRunToProcessStderr(text){try{return hasProcessStderr()?(process.stderr.write(`${text}\n`),!0):!1}catch(error){return reportLoggerInternalError({context:`debug run process stderr write failed`,error}),!1}}function emitRun({records,level}){let text=records.map(function(r){return formatRecord(r)}).join(`
|
|
2
|
+
`);if(!(level===`debug`&&writeDebugRunToProcessStderr(text)))try{let method=LEVEL_TO_CONSOLE_METHOD[level],consoleFn=console[method];typeof consoleFn==`function`&&consoleFn.call(console,text)}catch(error){reportLoggerInternalError({context:`console method call failed while emitting log run`,error})}}function groupRuns(records){return records.reduce(function(runs,record){let tail=runs.at(-1);return tail!==void 0&&tail.level===record.level?(tail.records.push(record),runs):(runs.push({level:record.level,records:[record]}),runs)},[])}function verifyConsole(){try{return typeof console>`u`||typeof(hasProcessStderr()?console.info:console.debug)!=`function`||typeof queueMicrotask!=`function`?Promise.resolve(!1):Promise.resolve(!0)}catch(error){return reportLoggerInternalError({context:`console sink verification failed`,error}),Promise.resolve(!1)}}function createConsoleSink(){let state={buffer:[],scheduled:!1,verboseCache:VERBOSE_UNCOMPUTED};function getVerbose(){let cached=state.verboseCache;if(cached!==VERBOSE_UNCOMPUTED)return cached;let computed=detectVerbose();return state.verboseCache=computed,computed}function flushBuffer(){if(state.scheduled=!1,state.buffer.length===0)return;let records=state.buffer.splice(0);for(let run of groupRuns(records))emitRun({level:run.level,records:run.records})}function write(record){return!getVerbose()&&SILENT_LEVELS.has(record.level)||record.level===`warn`&&isWarnSuppressed()?Promise.resolve():(state.buffer.push(record),state.scheduled||(state.scheduled=!0,queueMicrotask(flushBuffer)),Promise.resolve())}function flush(){return flushBuffer(),Promise.resolve()}return{flush,verify:verifyConsole,write}}function isDigits(text){if(text.length===0)return!1;for(let character of text)if(character<`0`||character>`9`)return!1;return!0}function buildLogKey({stamp,nonce,index}){return`monochromatic.log.${stamp}.${nonce}.${index}`}function parseLogKey(key){if(!key.startsWith(`monochromatic.log.`))return{};let segments=key.slice(18).split(`.`);if(segments.length!==3)return{};let[stampText,nonce,indexText]=segments;return stampText===void 0||nonce===void 0||indexText===void 0||!isDigits(stampText)||nonce.length===0||!isDigits(indexText)?{}:{parsed:{key,stamp:Math.trunc(Number(stampText)),nonce,index:Math.trunc(Number(indexText))}}}function compareLogKeys({first,second}){return first.stamp===second.stamp?first.nonce===second.nonce?first.index-second.index:first.nonce<second.nonce?-1:1:first.stamp-second.stamp}function detectWebStorageRuntime(){return`Deno`in globalThis?`deno`:`Bun`in globalThis?`bun`:typeof process<`u`&&typeof process.versions.node==`string`?`node`:`document`in globalThis?`browser`:`unknown`}const RUNTIME_QUOTA_CHARS$1={browser:5242880,bun:1/0,deno:10477569,node:5242880,unknown:1/0};function detectLocalStorageQuotaChars(){return RUNTIME_QUOTA_CHARS$1[detectWebStorageRuntime()]}const QUOTA_EXCEEDED_NAMES=new Set([`QuotaExceededError`,`NS_ERROR_DOM_QUOTA_REACHED`]);function isQuotaExceededError(error){return typeof error==`object`&&!!error&&`name`in error&&typeof error.name==`string`&"A_EXCEEDED_NAMES.has(error.name)}function createLocalStorageStore(){let runIdentity={stamp:Date.now(),nonce:Math.random().toString(36).slice(2,6).padEnd(4,`0`)},state={lineCounter:0,oldestIndex:0,usedChars:0,reportedFailure:!1,adoptedPrior:!1},prior={entries:[],cursor:0},capChars=detectLocalStorageQuotaChars()/2;function ownKey(index){return buildLogKey({stamp:runIdentity.stamp,nonce:runIdentity.nonce,index})}function adoptPriorEntries(){let total=globalThis.localStorage.length,found=[];for(let slot=0;slot<total;slot++){let key=globalThis.localStorage.key(slot);if(key===null)continue;let{parsed}=parseLogKey(key);if(parsed===void 0||parsed.stamp===runIdentity.stamp&&parsed.nonce===runIdentity.nonce)continue;let value=globalThis.localStorage.getItem(key);value!==null&&found.push({...parsed,chars:value.length})}prior.entries=found.toSorted(function(first,second){return compareLogKeys({first,second})}),state.usedChars+=found.reduce(function(sum,entry){return sum+entry.chars},0)}function hasEvictable(){let priorCount=prior.entries.length;return prior.cursor<priorCount||state.oldestIndex<state.lineCounter}function evictOldestPrior(){let entry=prior.entries[prior.cursor];return entry!==void 0&&(prior.cursor++,globalThis.localStorage.removeItem(entry.key),state.usedChars=Math.max(0,state.usedChars-entry.chars),!0)}function evictOldestOwn(){let key=ownKey(state.oldestIndex),evicted=globalThis.localStorage.getItem(key);globalThis.localStorage.removeItem(key),state.oldestIndex++,evicted!==null&&(state.usedChars=Math.max(0,state.usedChars-evicted.length))}function evictOldest(){evictOldestPrior()||state.oldestIndex<state.lineCounter&&evictOldestOwn()}function persist(batch){state.adoptedPrior||(state.adoptedPrior=!0,adoptPriorEntries());let batchChars=batch.length;for(;hasEvictable()&&state.usedChars+batchChars>capChars;)evictOldest();let maxWriteAttempts=prior.entries.length-prior.cursor+(state.lineCounter-state.oldestIndex)+1;for(let writeAttempt=0;writeAttempt<maxWriteAttempts;writeAttempt++)try{globalThis.localStorage.setItem(ownKey(state.lineCounter),batch),state.lineCounter++,state.usedChars+=batchChars,state.reportedFailure=!1;return}catch(error){if(isQuotaExceededError(error)&&hasEvictable()){evictOldest();continue}state.reportedFailure||=(reportLoggerInternalError({context:`localStorage sink record write failed (repeats suppressed until a write next succeeds)`,error}),!0);return}}return{persist}}const NODE_LOCALSTORAGE_FLAG=`--localstorage-file`;function nodeWithoutLocalStorageFile(){if(detectWebStorageRuntime()!==`node`||`document`in globalThis)return!1;let flaggedInExecArgv=process.execArgv.some(function(argument){return argument.startsWith(NODE_LOCALSTORAGE_FLAG)}),flaggedInNodeOptions=(process.env.NODE_OPTIONS??``).includes(NODE_LOCALSTORAGE_FLAG);return!(flaggedInExecArgv||flaggedInNodeOptions)}function verifyLocalStorage(){if(nodeWithoutLocalStorageFile())return Promise.resolve(!1);try{let testKey=`__monochromatic_verify__`,testValue=`test-${Date.now()}`;globalThis.localStorage.setItem(testKey,testValue);let readBack=globalThis.localStorage.getItem(testKey);return globalThis.localStorage.removeItem(testKey),Promise.resolve(readBack===testValue)}catch(error){return`localStorage`in globalThis&&reportLoggerInternalError({context:`localStorage sink verification failed`,error}),Promise.resolve(!1)}}function createLocalStorageSink(){let store=createLocalStorageStore(),buffer=createRecordBuffer({onFlush:store.persist});function write(record){return buffer.add({level:record.level,serialized:JSON.stringify(record)}),Promise.resolve()}function flush(){return buffer.drain(),Promise.resolve()}return{flush,verify:verifyLocalStorage,write}}const RUNTIME_QUOTA_CHARS={browser:5242880,bun:1/0,deno:10485760,node:5242880,unknown:1/0};function detectSessionStorageQuotaChars(){return RUNTIME_QUOTA_CHARS[detectWebStorageRuntime()]}function storageKey(index){return`monochromatic.log.${index}`}function createSessionStorageStore(){let state={lineCounter:0,oldestIndex:0,usedChars:0,reportedFailure:!1},capChars=detectSessionStorageQuotaChars()/2;function evictOldest(){let key=storageKey(state.oldestIndex),evicted=globalThis.sessionStorage.getItem(key);globalThis.sessionStorage.removeItem(key),state.oldestIndex++,evicted!==null&&(state.usedChars=Math.max(0,state.usedChars-evicted.length))}function persist(batch){let batchChars=batch.length;for(;state.oldestIndex<state.lineCounter&&state.usedChars+batchChars>capChars;)evictOldest();let maxWriteAttempts=state.lineCounter-state.oldestIndex+1;for(let writeAttempt=0;writeAttempt<maxWriteAttempts;writeAttempt++)try{globalThis.sessionStorage.setItem(storageKey(state.lineCounter),batch),state.lineCounter++,state.usedChars+=batchChars,state.reportedFailure=!1;return}catch(error){if(isQuotaExceededError(error)&&state.oldestIndex<state.lineCounter){evictOldest();continue}state.reportedFailure||=(reportLoggerInternalError({context:`sessionStorage sink record write failed (repeats suppressed until a write next succeeds)`,error}),!0);return}}return{persist}}function verifySessionStorage(){try{let testKey=`__monochromatic_verify__`,testValue=`test-${Date.now()}`;globalThis.sessionStorage.setItem(testKey,testValue);let readBack=globalThis.sessionStorage.getItem(testKey);return globalThis.sessionStorage.removeItem(testKey),Promise.resolve(readBack===testValue)}catch(error){return`sessionStorage`in globalThis&&reportLoggerInternalError({context:`sessionStorage sink verification failed`,error}),Promise.resolve(!1)}}function createSessionStorageSink(){let store=createSessionStorageStore(),buffer=createRecordBuffer({onFlush:store.persist});function write(record){return buffer.add({level:record.level,serialized:JSON.stringify(record)}),Promise.resolve()}function flush(){return buffer.drain(),Promise.resolve()}return{flush,verify:verifySessionStorage,write}}const{initPromise:defaultInitPromise,logger:defaultLogger}=createLogger({sinks:[createConsoleSink(),createIndexedDbSink(),createSessionStorageSink(),createLocalStorageSink()]}),initPromise=defaultInitPromise,logger=defaultLogger;function verify(){return Promise.resolve(!0)}function write(_record){return Promise.resolve()}function createNoopSink(){return{verify,write}}var sink_exports=__exportAll({createConsoleSink:()=>createConsoleSink,createLocalStorageSink:()=>createLocalStorageSink,createNoopSink:()=>createNoopSink,createSessionStorageSink:()=>createSessionStorageSink});function tagged({tag,l=logger}){let prefix=`[${tag}] `;return{debug:function(message){l.debug(`${prefix}${message}`)},error:function(message){l.error(`${prefix}${message}`)},fatal:function(message){l.fatal(`${prefix}${message}`)},flush:function(){return l.flush()},info:function(message){l.info(`${prefix}${message}`)},trace:function(message){l.trace(`${prefix}${message}`)},warn:function(message){l.warn(`${prefix}${message}`)}}}export{DEFAULT_FLUSH_DEADLINE_MS,DEFAULT_VERIFY_TIMEOUT_MS,STARTUP_BUFFER_CAP,buildLogKey as _buildLogKey,compareLogKeys as _compareLogKeys,createLocalStorageStore as _createLocalStorageStore,createRecordBuffer as _createRecordBuffer,detectLocalStorageQuotaChars as _detectLocalStorageQuotaChars,detectSessionStorageQuotaChars as _detectSessionStorageQuotaChars,isQuotaExceededError as _isQuotaExceededError,neutralizeControlCharacters as _neutralizeControlCharacters,parseLogKey as _parseLogKey,createLogger,initPromise,logger,sink_exports as sinks,tagged};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function caughtValueText(value){return Error.isError(value)?value.message:String(value)}function reportLoggerInternalError({context,error}){console.warn(`logger internal error: ${context}: ${caughtValueText(error)}`)}function awaitRequest(request){return new Promise(function(resolve,reject){request.addEventListener(`success`,function(){resolve(request.result)}),request.addEventListener(`error`,function(){reject(request.error??Error(`IndexedDB request failed without an error object`))})})}function awaitTransaction(transaction){return new Promise(function(resolve,reject){transaction.addEventListener(`complete`,function(){resolve()}),transaction.addEventListener(`error`,function(){reject(transaction.error??Error(`IndexedDB transaction failed without an error object`))}),transaction.addEventListener(`abort`,function(){reject(transaction.error??Error(`IndexedDB transaction aborted without an error object`))})})}const FLUSH_BUFFER_CAP_CHARS=32768,FLUSH_IMMEDIATELY_BY_LEVEL={debug:!1,error:!0,fatal:!0,info:!1,trace:!1,warn:!0};function isUnrefableTimer(timer){return typeof timer!=`object`||!timer||!(`unref`in timer)?!1:typeof timer.unref==`function`}function createRecordBuffer({onFlush}){let entries=[],bufferState={chars:0};function charsWith(serialized){let separatorChars=+(entries.length>0);return bufferState.chars+separatorChars+serialized.length}function drain(){if(bufferState.timer!==void 0&&(globalThis.clearTimeout(bufferState.timer),delete bufferState.timer),entries.length===0)return;let batch=entries.join(`
|
|
2
|
+
`);entries.length=0,bufferState.chars=0,onFlush(batch)}function scheduleDeadlineFlush(){if(bufferState.timer!==void 0)return;let timer=globalThis.setTimeout(drain,250);isUnrefableTimer(timer)&&timer.unref(),bufferState.timer=timer}function add(entry){entries.length>0&&charsWith(entry.serialized)>FLUSH_BUFFER_CAP_CHARS&&drain(),bufferState.chars=charsWith(entry.serialized),entries.push(entry.serialized),FLUSH_IMMEDIATELY_BY_LEVEL[entry.level]||bufferState.chars>=FLUSH_BUFFER_CAP_CHARS?drain():scheduleDeadlineFlush()}return globalThis.addEventListener?.(`pagehide`,drain),globalThis.document?.addEventListener(`visibilitychange`,function(){globalThis.document?.visibilityState===`hidden`&&drain()}),{add,drain}}const BATCH_STORE=`batch`,MAX_STORED_BATCHES=2048;async function openLogDatabase(){let request=globalThis.indexedDB.open(`monochromatic.log`,1);return request.onupgradeneeded=function(){request.result.createObjectStore(BATCH_STORE,{autoIncrement:!0})},await awaitRequest(request)}async function persistBatch({database,batch}){let transaction=database.transaction(BATCH_STORE,`readwrite`),store=transaction.objectStore(BATCH_STORE);store.add(batch);let count=await awaitRequest(store.count());if(count>MAX_STORED_BATCHES){let newestStale=(await awaitRequest(store.getAllKeys(null,count-MAX_STORED_BATCHES))).at(-1);newestStale!==void 0&&store.delete(IDBKeyRange.upperBound(newestStale))}await awaitTransaction(transaction)}function createIndexedDbSink(){let state={},pendingBatchWrites=new Set;async function verify(){try{if(globalThis.indexedDB===void 0)return!1;let database=await openLogDatabase(),transaction=database.transaction(BATCH_STORE,`readwrite`),store=transaction.objectStore(BATCH_STORE),probeValue=`probe-${Date.now()}`,probeKey=await awaitRequest(store.add(probeValue)),readBack=await awaitRequest(store.get(probeKey));return store.delete(probeKey),await awaitTransaction(transaction),readBack===probeValue&&(state.database=database,!0)}catch(error){return`indexedDB`in globalThis&&reportLoggerInternalError({context:`IndexedDB sink verification failed`,error}),!1}}async function writeBatch(batch){if(state.database)try{await persistBatch({database:state.database,batch})}catch(error){reportLoggerInternalError({context:`IndexedDB sink record write failed`,error})}}async function removePendingWhenSettled(pending){await pending,pendingBatchWrites.delete(pending)}function handOffBatch(batch){let pending=writeBatch(batch);pendingBatchWrites.add(pending),removePendingWhenSettled(pending)}let buffer=createRecordBuffer({onFlush:handOffBatch});function write(record){return buffer.add({level:record.level,serialized:JSON.stringify(record)}),Promise.resolve()}async function flush(){buffer.drain();let writes=[...pendingBatchWrites];await Promise.all(writes)}return{flush,verify,write}}export{createRecordBuffer as n,reportLoggerInternalError as r,createIndexedDbSink as t};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
Log severity levels ordered from least to most severe.
|
|
4
|
+
*/
|
|
5
|
+
type Level = "debug" | "error" | "fatal" | "info" | "trace" | "warn";
|
|
6
|
+
/**
|
|
7
|
+
Structured log record written to sinks.
|
|
8
|
+
*/
|
|
9
|
+
type LogRecord = {
|
|
10
|
+
readonly level: Level;
|
|
11
|
+
readonly message: string;
|
|
12
|
+
readonly timestamp: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
Optional drain hook for sinks that buffer records internally.
|
|
16
|
+
Called via logger-level {@link Logger.flush} to force buffered work
|
|
17
|
+
through before a process exit, critical error boundary, or assertion.
|
|
18
|
+
|
|
19
|
+
Always async: sinks whose drain is synchronous return an
|
|
20
|
+
already-resolved promise so callers `await` uniformly. A `void` arm is
|
|
21
|
+
not used; under the `no-optional-escape` rule `T | void` is a banned
|
|
22
|
+
fake-optional encoding, and there is no real synchronous value to carry.
|
|
23
|
+
*/
|
|
24
|
+
type SinkFlush = () => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
Verification function that checks if a sink backend is available.
|
|
27
|
+
May run setup side effects (resolving a log path, opening a writable
|
|
28
|
+
stream) and reports whether the backend is usable. A sink whose
|
|
29
|
+
verification resolves `false` (or rejects) is dropped by the logger and
|
|
30
|
+
receives no further records.
|
|
31
|
+
|
|
32
|
+
Always async, matching `write` and `flush`: a synchronous check returns an
|
|
33
|
+
already-resolved promise (`Promise.resolve(check)`) so the logger awaits
|
|
34
|
+
verification uniformly with no sync/async branch.
|
|
35
|
+
*/
|
|
36
|
+
type Verify = () => Promise<boolean>;
|
|
37
|
+
/**
|
|
38
|
+
Sink that receives log records. A sink is a self-describing adapter: it
|
|
39
|
+
carries everything the logger must know to use it, namely how to
|
|
40
|
+
`verify` its backend is available, how to `write` a record, and
|
|
41
|
+
optionally how to `flush` buffered work. Holding `verify` on the sink
|
|
42
|
+
(rather than as a sibling export the logger pairs by hand) lets the
|
|
43
|
+
logger treat a registry as a plain `Sink[]` and lets a test supply one
|
|
44
|
+
self-contained fake.
|
|
45
|
+
|
|
46
|
+
Sinks that buffer records (e.g. microtask-batched console) may
|
|
47
|
+
expose a `flush` hook so callers can force emission on demand.
|
|
48
|
+
|
|
49
|
+
`write` is always async: a synchronous sink does its work eagerly and
|
|
50
|
+
returns an already-resolved promise, so the logger observes a uniform
|
|
51
|
+
`Promise<void>`. A rejected write is handled per sink and does not
|
|
52
|
+
disable the backend; only a failed `verify` drops a sink. A `void` arm
|
|
53
|
+
is not used, for the reason stated on {@link SinkFlush}.
|
|
54
|
+
*/
|
|
55
|
+
type Sink = {
|
|
56
|
+
readonly flush?: SinkFlush;
|
|
57
|
+
readonly verify: Verify;
|
|
58
|
+
readonly write: (record: LogRecord) => Promise<void>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
Logger interface with 6 log levels plus `flush` for startup and sink drains.
|
|
62
|
+
`flush()` resolves once startup verification has completed, tracked sink
|
|
63
|
+
writes have settled, and every available sink's own {@link SinkFlush} hook
|
|
64
|
+
has settled. Safe to call even when no sink buffers.
|
|
65
|
+
*/
|
|
66
|
+
type Logger = {
|
|
67
|
+
readonly debug: (message: string) => void;
|
|
68
|
+
readonly error: (message: string) => void;
|
|
69
|
+
readonly fatal: (message: string) => void;
|
|
70
|
+
readonly flush: () => Promise<void>;
|
|
71
|
+
readonly info: (message: string) => void;
|
|
72
|
+
readonly trace: (message: string) => void;
|
|
73
|
+
readonly warn: (message: string) => void;
|
|
74
|
+
};
|
|
75
|
+
//#endregion
|
|
76
|
+
export { SinkFlush as a, Sink as i, LogRecord as n, Verify as o, Logger as r, Level as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{appendFile,mkdir,readFile,stat}from"node:fs/promises";import{dirname,join}from"node:path";function caughtValueText(value){return Error.isError(value)?value.message:String(value)}function reportLoggerInternalError({context,error}){console.warn(`logger internal error: ${context}: ${caughtValueText(error)}`)}const NO_NODE_MODULES_FOUND=Symbol(`logger:no-node-modules-found`);async function findNodeModulesUp({cwd,stat:statPath,dirname:parentOf,join:joinPath,reportError=reportLoggerInternalError}){let candidate=joinPath(cwd,`node_modules`);try{if((await statPath(candidate)).isDirectory())return candidate}catch(error){Error.isError(error)&&`code`in error&&error.code===`ENOENT`||reportError({context:`node_modules candidate ${candidate} unavailable during file sink search`,error})}let parent=parentOf(cwd);return parent===cwd?NO_NODE_MODULES_FOUND:await findNodeModulesUp({cwd:parent,stat:statPath,dirname:parentOf,join:joinPath,reportError})}function createFileSink(){let state={};async function runVerify(){try{let nodeModulesDir=await findNodeModulesUp({cwd:process.cwd(),stat,dirname,join});if(nodeModulesDir===NO_NODE_MODULES_FOUND)return!1;let LOG_DIR=join(nodeModulesDir,`.monochromatic`);await mkdir(LOG_DIR,{recursive:!0});let timestamp=new Date().toISOString().replaceAll(`:`,`-`),filePath=join(LOG_DIR,`${timestamp}.log.jsonl`);state.filePath=filePath;let testData=`{"test":true,"timestamp":${Date.now()}}\n`;return await appendFile(filePath,testData),(await readFile(filePath,`utf8`)).includes(`"test":true`)}catch(error){return reportLoggerInternalError({context:`file sink verification failed`,error}),!1}}function verify(){return state.verifyPromise===void 0&&(state.verifyPromise=runVerify()),state.verifyPromise}async function write(record){let{filePath}=state;if(filePath!==void 0)try{await appendFile(filePath,`${JSON.stringify(record)}\n`)}catch(error){reportLoggerInternalError({context:`file sink record append failed`,error})}}return{verify,write}}export{reportLoggerInternalError as i,createFileSink as n,findNodeModulesUp as r,NO_NODE_MODULES_FOUND as t};
|