@stacksjs/storage 0.75.210 → 0.75.212

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,4 +1,4 @@
1
1
  import{existsSync,lstatSync,mkdirSync,mkdtempSync,readFileSync,readdirSync,readlinkSync,realpathSync,renameSync,rmSync,writeFileSync}from"node:fs";import{basename,dirname,join,resolve}from"node:path";import process from"node:process";function vanished(error){return error instanceof Error&&"code"in error&&(error.code==="ENOENT"||error.code==="ESRCH")}function beneath(path,root){const clean=path.replace(/ \(deleted\)$/,"");return clean===root||clean.startsWith(`${root}/`)}function mountedWorkspace(table,path){return table.split(`
2
- `).some((line)=>{const fields=line.split(" ");return[fields[3],fields[4]].some((field)=>field&&beneath(field.replace(/\\([0-7]{3})/g,(_match,octal)=>String.fromCharCode(Number.parseInt(octal,8))),path))})}export function inspectTemporaryWorkspace(path){let operation="mounts";try{if(process.platform==="linux"){if(mountedWorkspace(readFileSync("/proc/self/mountinfo","utf8"),path))return{unused:!1,reason:"mounted"};const uid=process.getuid?.();for(const pid of readdirSync("/proc").filter((name)=>/^\d+$/.test(name))){const directory=`/proc/${pid}`;try{operation="process-owner";const identities=/^Uid:\s+(.+)$/m.exec(readFileSync(`${directory}/status`,"utf8"))?.[1]?.trim().split(/\s+/).map(Number);if(!identities?.length)return{unused:!1,reason:"inspection-unavailable",pid:Number(pid),operation};if(uid!==0&&!identities.includes(Number(uid)))continue;operation="process-mounts";if(mountedWorkspace(readFileSync(`${directory}/mountinfo`,"utf8"),path))return{unused:!1,reason:"mounted",pid:Number(pid)};operation="process-paths";const references=[readlinkSync(`${directory}/cwd`),readlinkSync(`${directory}/exe`)];operation="process-files";for(const fd of readdirSync(`${directory}/fd`))try{references.push(readlinkSync(`${directory}/fd/${fd}`))}catch(error){if(!vanished(error))throw error}operation="process-maps";for(const line of readFileSync(`${directory}/maps`,"utf8").split(`
3
- `)){const mapped=/^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(.+)$/.exec(line)?.[1];if(mapped)references.push(mapped)}if(references.some((reference)=>beneath(reference,path)))return{unused:!1,reason:"process-reference",pid:Number(pid)}}catch(error){if(!vanished(error))return{unused:!1,reason:"inspection-unavailable",pid:Number(pid),operation,code:error instanceof Error&&"code"in error?String(error.code):void 0}}}return{unused:!0,reason:"unused"}}if(process.platform==="darwin"){const mounts=Bun.spawnSync(["mount"],{stdout:"pipe",stderr:"pipe"});if(mounts.exitCode!==0)return{unused:!1,reason:"inspection-unavailable",operation};for(const line of mounts.stdout.toString().split(`
4
- `)){const mount=/ on (.+) \(/.exec(line)?.[1];if(mount&&beneath(mount,path))return{unused:!1,reason:"mounted"}}const uid=process.getuid?.();if(uid===void 0)return{unused:!1,reason:"inspection-unavailable",operation:"process-owner"};operation="process-files";const files=Bun.spawnSync(["lsof","-nP","-F0n",...uid===0?[]:["-u",String(uid)]],{stdout:"pipe",stderr:"pipe"});if(files.exitCode!==0||files.stderr.length>0)return{unused:!1,reason:"inspection-unavailable",operation};const used=files.stdout.toString().split("\x00").some((field)=>field.startsWith("n")&&beneath(field.slice(1),path));return{unused:!used,reason:used?"process-reference":"unused"}}}catch(error){return{unused:!1,reason:"inspection-unavailable",operation,code:error instanceof Error&&"code"in error?String(error.code):void 0}}return{unused:!1,reason:"unsupported-platform"}}export function temporaryWorkspaceIsUnused(path){return inspectTemporaryWorkspace(path).unused}export class TemporaryWorkspaceJournal{directory;temporaryRoot;prefix;ownerUid;active=new Map;rootInode;rootDevice;journalInode;journalDevice;constructor(options){if(typeof options.prefix!=="string"||!/^[a-z0-9_-]+$/i.test(options.prefix))throw TypeError("Invalid temporary workspace prefix");const currentUid=process.getuid?.(),ownerUid=options.ownerUid??currentUid;if(currentUid===void 0||ownerUid===void 0||!Number.isSafeInteger(ownerUid)||ownerUid<0)throw Error("Workspace ownership requires a POSIX user identity");if(ownerUid!==currentUid&&currentUid!==0)throw Error("Only root may maintain another user's workspace journal");if(ownerUid!==currentUid&&!existsSync(options.directory))throw Error("Another user's journal must already exist");mkdirSync(options.directory,{recursive:!0,mode:448});const journal=lstatSync(options.directory);if(!journal.isDirectory()||journal.isSymbolicLink()||journal.uid!==ownerUid||(journal.mode&63)!==0)throw Error("A workspace journal must be a private directory owned by its declared user");this.ownerUid=ownerUid;this.directory=realpathSync(options.directory);this.temporaryRoot=realpathSync(options.temporaryRoot);if(this.directory===this.temporaryRoot)throw Error("The journal cannot be the temporary root");this.prefix=options.prefix;const root=lstatSync(this.temporaryRoot,{bigint:!0}),journalIdentity=lstatSync(this.directory,{bigint:!0});this.rootInode=String(root.ino);this.rootDevice=String(root.dev);this.journalInode=String(journalIdentity.ino);this.journalDevice=String(journalIdentity.dev)}create(key){if(typeof key!=="string"||!/^[a-z0-9_-]{1,80}$/i.test(key))throw TypeError("Invalid workspace key");if(this.ownerUid!==process.getuid?.())throw Error("Maintenance cannot create workspaces for another user");if(!this.rootsUnchanged())throw Error("The workspace or journal root changed");const path=mkdtempSync(join(this.temporaryRoot,`${this.prefix}${key}-`)),identity=lstatSync(path,{bigint:!0}),record=Object.freeze({id:crypto.randomUUID(),key,path,inode:String(identity.ino),device:String(identity.dev),uid:String(identity.uid)}),file=join(this.directory,`${record.id}.json`);try{writeFileSync(`${file}.tmp`,JSON.stringify(record),{flag:"wx",mode:384});renameSync(`${file}.tmp`,file);this.active.set(record.id,record);return record}catch(error){rmSync(path,{recursive:!0,force:!0});rmSync(`${file}.tmp`,{force:!0});throw error}}list(){if(!this.rootsUnchanged())return[];const records=[];for(const entry of readdirSync(this.directory).slice(0,1000)){if(!entry.endsWith(".json"))continue;const record=this.read(entry.slice(0,-5));if(record)records.push(record)}return records}complete(record){if(!record||this.active.get(record.id)!==record)return!1;if(!this.remove(record))return!1;this.active.delete(record.id);return!0}async retire(id,mayRetire){const verdict=await this.inspectRetirement(id,mayRetire);if(!verdict.ready||!verdict.record||!this.remove(verdict.record))return!1;this.active.delete(verdict.record.id);return!0}async inspectRetirement(id,mayRetire){const record=this.read(id);if(!record)return{ready:!1,reason:"record-invalid"};if(await mayRetire(record)!==!0)return{ready:!1,reason:"not-authorized",record};if(!this.rootsUnchanged())return{ready:!1,reason:"identity-changed",record};const identity=this.identity(record);if(identity==="changed"||identity==="unavailable")return{ready:!1,reason:"identity-changed",record};if(identity==="missing")return{ready:!0,reason:"ready",record};const usage=inspectTemporaryWorkspace(record.path);return{ready:usage.unused,reason:usage.unused?"ready":usage.reason,record,usage}}identity(record){try{const identity=lstatSync(record.path,{bigint:!0});return identity.isDirectory()&&!identity.isSymbolicLink()&&String(identity.uid)===record.uid&&String(identity.uid)===String(this.ownerUid)&&String(identity.ino)===record.inode&&String(identity.dev)===record.device?"same":"changed"}catch(error){return vanished(error)?"missing":"unavailable"}}remove(record){try{if(!this.rootsUnchanged())return!1;const identity=this.identity(record);if(identity==="changed"||identity==="unavailable")return!1;if(identity==="same")rmSync(record.path,{recursive:!0})}catch(error){if(!vanished(error)||this.identity(record)!=="missing")return!1}rmSync(join(this.directory,`${record.id}.json`),{force:!0});return!0}read(id){if(typeof id!=="string"||!/^[0-9a-f-]{36}$/.test(id))return null;if(!this.rootsUnchanged())return null;try{const file=join(this.directory,`${id}.json`),identity=lstatSync(file);if(!identity.isFile()||identity.isSymbolicLink()||identity.uid!==this.ownerUid||(identity.mode&63)!==0||identity.size>4096)return null;const value=JSON.parse(readFileSync(file,"utf8"));if(!value||value.id!==id||typeof value.key!=="string"||!/^[a-z0-9_-]{1,80}$/i.test(value.key)||typeof value.path!=="string"||resolve(value.path)!==value.path||dirname(value.path)!==this.temporaryRoot||!basename(value.path).startsWith(`${this.prefix}${value.key}-`)||!["inode","device","uid"].every((key)=>typeof value[key]==="string"&&/^\d+$/.test(value[key])))return null;return Object.freeze(value)}catch{return null}}rootsUnchanged(){try{const root=lstatSync(this.temporaryRoot,{bigint:!0}),journal=lstatSync(this.directory,{bigint:!0});return root.isDirectory()&&!root.isSymbolicLink()&&String(root.ino)===this.rootInode&&String(root.dev)===this.rootDevice&&journal.isDirectory()&&!journal.isSymbolicLink()&&String(journal.ino)===this.journalInode&&String(journal.dev)===this.journalDevice&&String(journal.uid)===String(this.ownerUid)&&(journal.mode&0o077n)===0n}catch{return!1}}}
2
+ `).some((line)=>{const fields=line.split(" ");return[fields[3],fields[4]].some((field)=>field&&beneath(field.replace(/\\([0-7]{3})/g,(_match,octal)=>String.fromCharCode(Number.parseInt(octal,8))),path))})}function inspectWorkspaceMounts(path){try{if(process.platform==="linux"){const table=readFileSync("/proc/self/mountinfo","utf8");if(!table.trim())return{unused:!1,reason:"inspection-unavailable",operation:"mounts"};const mounted=mountedWorkspace(table,path);return{unused:!mounted,reason:mounted?"mounted":"unused"}}if(process.platform==="darwin"){const mounts=Bun.spawnSync(["mount"],{stdout:"pipe",stderr:"pipe"});if(mounts.exitCode!==0||mounts.stderr.length>0||mounts.stdout.length===0)return{unused:!1,reason:"inspection-unavailable",operation:"mounts"};const mounted=mounts.stdout.toString().split(`
3
+ `).some((line)=>{const target=/ on (.+) \(/.exec(line)?.[1];return target!==void 0&&beneath(target,path)});return{unused:!mounted,reason:mounted?"mounted":"unused"}}}catch(error){return{unused:!1,reason:"inspection-unavailable",operation:"mounts",code:error instanceof Error&&"code"in error?String(error.code):void 0}}return{unused:!1,reason:"unsupported-platform"}}export function inspectTemporaryWorkspace(path){let operation="mounts";try{const mounts=inspectWorkspaceMounts(path);if(!mounts.unused)return mounts;if(process.platform==="linux"){const uid=process.getuid?.();for(const pid of readdirSync("/proc").filter((name)=>/^\d+$/.test(name))){const directory=`/proc/${pid}`;try{operation="process-owner";const identities=/^Uid:\s+(.+)$/m.exec(readFileSync(`${directory}/status`,"utf8"))?.[1]?.trim().split(/\s+/).map(Number);if(!identities?.length)return{unused:!1,reason:"inspection-unavailable",pid:Number(pid),operation};if(uid!==0&&!identities.includes(Number(uid)))continue;operation="process-mounts";if(mountedWorkspace(readFileSync(`${directory}/mountinfo`,"utf8"),path))return{unused:!1,reason:"mounted",pid:Number(pid)};operation="process-paths";const references=[readlinkSync(`${directory}/cwd`),readlinkSync(`${directory}/exe`)];operation="process-files";for(const fd of readdirSync(`${directory}/fd`))try{references.push(readlinkSync(`${directory}/fd/${fd}`))}catch(error){if(!vanished(error))throw error}operation="process-maps";for(const line of readFileSync(`${directory}/maps`,"utf8").split(`
4
+ `)){const mapped=/^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(.+)$/.exec(line)?.[1];if(mapped)references.push(mapped)}if(references.some((reference)=>beneath(reference,path)))return{unused:!1,reason:"process-reference",pid:Number(pid)}}catch(error){if(!vanished(error))return{unused:!1,reason:"inspection-unavailable",pid:Number(pid),operation,code:error instanceof Error&&"code"in error?String(error.code):void 0}}}return{unused:!0,reason:"unused"}}if(process.platform==="darwin"){const uid=process.getuid?.();if(uid===void 0)return{unused:!1,reason:"inspection-unavailable",operation:"process-owner"};operation="process-files";const files=Bun.spawnSync(["lsof","-nP","-F0n",...uid===0?[]:["-u",String(uid)]],{stdout:"pipe",stderr:"pipe"});if(files.exitCode!==0||files.stderr.length>0)return{unused:!1,reason:"inspection-unavailable",operation};const used=files.stdout.toString().split("\x00").some((field)=>field.startsWith("n")&&beneath(field.slice(1),path));return{unused:!used,reason:used?"process-reference":"unused"}}}catch(error){return{unused:!1,reason:"inspection-unavailable",operation,code:error instanceof Error&&"code"in error?String(error.code):void 0}}return{unused:!1,reason:"unsupported-platform"}}export function temporaryWorkspaceIsUnused(path){return inspectTemporaryWorkspace(path).unused}export class TemporaryWorkspaceJournal{directory;temporaryRoot;prefix;ownerUid;active=new Map;rootInode;rootDevice;journalInode;journalDevice;constructor(options){if(typeof options.prefix!=="string"||!/^[a-z0-9_-]+$/i.test(options.prefix))throw TypeError("Invalid temporary workspace prefix");const currentUid=process.getuid?.(),ownerUid=options.ownerUid??currentUid;if(currentUid===void 0||ownerUid===void 0||!Number.isSafeInteger(ownerUid)||ownerUid<0)throw Error("Workspace ownership requires a POSIX user identity");if(ownerUid!==currentUid&&currentUid!==0)throw Error("Only root may maintain another user's workspace journal");if(ownerUid!==currentUid&&!existsSync(options.directory))throw Error("Another user's journal must already exist");mkdirSync(options.directory,{recursive:!0,mode:448});const journal=lstatSync(options.directory);if(!journal.isDirectory()||journal.isSymbolicLink()||journal.uid!==ownerUid||(journal.mode&63)!==0)throw Error("A workspace journal must be a private directory owned by its declared user");this.ownerUid=ownerUid;this.directory=realpathSync(options.directory);this.temporaryRoot=realpathSync(options.temporaryRoot);if(this.directory===this.temporaryRoot)throw Error("The journal cannot be the temporary root");this.prefix=options.prefix;const root=lstatSync(this.temporaryRoot,{bigint:!0}),journalIdentity=lstatSync(this.directory,{bigint:!0});this.rootInode=String(root.ino);this.rootDevice=String(root.dev);this.journalInode=String(journalIdentity.ino);this.journalDevice=String(journalIdentity.dev)}create(key){if(typeof key!=="string"||!/^[a-z0-9_-]{1,80}$/i.test(key))throw TypeError("Invalid workspace key");if(this.ownerUid!==process.getuid?.())throw Error("Maintenance cannot create workspaces for another user");if(!this.rootsUnchanged())throw Error("The workspace or journal root changed");const path=mkdtempSync(join(this.temporaryRoot,`${this.prefix}${key}-`)),identity=lstatSync(path,{bigint:!0}),record=Object.freeze({id:crypto.randomUUID(),key,path,inode:String(identity.ino),device:String(identity.dev),uid:String(identity.uid)}),file=join(this.directory,`${record.id}.json`);try{writeFileSync(`${file}.tmp`,JSON.stringify(record),{flag:"wx",mode:384});renameSync(`${file}.tmp`,file);this.active.set(record.id,record);return record}catch(error){rmSync(path,{recursive:!0,force:!0});rmSync(`${file}.tmp`,{force:!0});throw error}}list(){if(!this.rootsUnchanged())return[];const records=[];for(const entry of readdirSync(this.directory).slice(0,1000)){if(!entry.endsWith(".json"))continue;const record=this.read(entry.slice(0,-5));if(record)records.push(record)}return records}complete(record){if(!record||this.active.get(record.id)!==record)return!1;if(!this.remove(record))return!1;this.active.delete(record.id);return!0}async retire(id,mayRetire){const verdict=await this.inspectRetirement(id,mayRetire);if(!verdict.ready||!verdict.record||!this.remove(verdict.record))return!1;this.active.delete(verdict.record.id);return!0}async inspectRetirement(id,mayRetire){const record=this.read(id);if(!record)return{ready:!1,reason:"record-invalid"};if(await mayRetire(record)!==!0)return{ready:!1,reason:"not-authorized",record};if(!this.rootsUnchanged())return{ready:!1,reason:"identity-changed",record};const identity=this.identity(record);if(identity==="changed"||identity==="unavailable")return{ready:!1,reason:"identity-changed",record};if(identity==="missing")return{ready:!0,reason:"ready",record};const usage=inspectTemporaryWorkspace(record.path);return{ready:usage.unused,reason:usage.unused?"ready":usage.reason,record,usage}}identity(record){try{const identity=lstatSync(record.path,{bigint:!0});return identity.isDirectory()&&!identity.isSymbolicLink()&&String(identity.uid)===record.uid&&String(identity.uid)===String(this.ownerUid)&&String(identity.ino)===record.inode&&String(identity.dev)===record.device?"same":"changed"}catch(error){return vanished(error)?"missing":"unavailable"}}remove(record){try{if(!this.rootsUnchanged())return!1;const identity=this.identity(record);if(identity==="changed"||identity==="unavailable")return!1;if(identity==="same"){if(!inspectWorkspaceMounts(record.path).unused)return!1;rmSync(record.path,{recursive:!0})}}catch(error){if(!vanished(error)||this.identity(record)!=="missing")return!1}rmSync(join(this.directory,`${record.id}.json`),{force:!0});return!0}read(id){if(typeof id!=="string"||!/^[0-9a-f-]{36}$/.test(id))return null;if(!this.rootsUnchanged())return null;try{const file=join(this.directory,`${id}.json`),identity=lstatSync(file);if(!identity.isFile()||identity.isSymbolicLink()||identity.uid!==this.ownerUid||(identity.mode&63)!==0||identity.size>4096)return null;const value=JSON.parse(readFileSync(file,"utf8"));if(!value||value.id!==id||typeof value.key!=="string"||!/^[a-z0-9_-]{1,80}$/i.test(value.key)||typeof value.path!=="string"||resolve(value.path)!==value.path||dirname(value.path)!==this.temporaryRoot||!basename(value.path).startsWith(`${this.prefix}${value.key}-`)||!["inode","device","uid"].every((key)=>typeof value[key]==="string"&&/^\d+$/.test(value[key])))return null;return Object.freeze(value)}catch{return null}}rootsUnchanged(){try{const root=lstatSync(this.temporaryRoot,{bigint:!0}),journal=lstatSync(this.directory,{bigint:!0});return root.isDirectory()&&!root.isSymbolicLink()&&String(root.ino)===this.rootInode&&String(root.dev)===this.rootDevice&&journal.isDirectory()&&!journal.isSymbolicLink()&&String(journal.ino)===this.journalInode&&String(journal.dev)===this.journalDevice&&String(journal.uid)===String(this.ownerUid)&&(journal.mode&0o077n)===0n}catch{return!1}}}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/storage",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.210",
5
+ "version": "0.75.212",
6
6
  "description": "The Stacks file system.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -61,20 +61,20 @@
61
61
  "prepublishOnly": "bun run build"
62
62
  },
63
63
  "dependencies": {
64
- "@stacksjs/arrays": "0.75.210",
65
- "@stacksjs/cli": "0.75.210",
66
- "@stacksjs/config": "0.75.210",
67
- "@stacksjs/env": "0.75.210",
68
- "@stacksjs/error-handling": "0.75.210",
69
- "@stacksjs/logging": "0.75.210",
70
- "@stacksjs/path": "0.75.210",
71
- "@stacksjs/strings": "0.75.210",
64
+ "@stacksjs/arrays": "0.75.212",
65
+ "@stacksjs/cli": "0.75.212",
66
+ "@stacksjs/config": "0.75.212",
67
+ "@stacksjs/env": "0.75.212",
68
+ "@stacksjs/error-handling": "0.75.212",
69
+ "@stacksjs/logging": "0.75.212",
70
+ "@stacksjs/path": "0.75.212",
71
+ "@stacksjs/strings": "0.75.212",
72
72
  "@stacksjs/ts-cloud": "^0.16.58",
73
73
  "@stacksjs/ts-xml": "^0.1.0",
74
74
  "ts-images": "^0.2.23"
75
75
  },
76
76
  "devDependencies": {
77
- "@stacksjs/types": "0.75.210",
77
+ "@stacksjs/types": "0.75.212",
78
78
  "better-dx": "^0.2.24"
79
79
  }
80
80
  }