@stacksjs/storage 0.75.160 → 0.75.162

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/dist/zip.d.ts CHANGED
@@ -41,6 +41,40 @@ export declare function gzipSync(data: Uint8Array, options?: ZlibCompressionOpti
41
41
  export declare function gunzipSync(data: Uint8Array): Uint8Array;
42
42
  export declare function deflateSync(data: Uint8Array, options?: ZlibCompressionOptions): Uint8Array;
43
43
  export declare function inflateSync(data: Uint8Array): Uint8Array;
44
+ /**
45
+ * Build a ZIP archive in memory and return its bytes - for a download made of
46
+ * several files, answered straight from a request:
47
+ *
48
+ * ```ts
49
+ * const bytes = zipEntries([
50
+ * { name: 'members.csv', data: membersCsv },
51
+ * { name: 'README.txt', data: 'What each file holds.' },
52
+ * ])
53
+ * return new Response(bytes, { headers: { 'Content-Type': 'application/zip' } })
54
+ * ```
55
+ *
56
+ * Unlike {@link zip}, nothing touches the disk and no `zip` binary is needed.
57
+ * The format follows PKWARE's APPNOTE 6.3 (section 4): CRC-32 per entry,
58
+ * UTF-8 names (general purpose bit 11), DOS timestamps, and each entry
59
+ * deflated (method 8, Bun's raw DEFLATE) when that is smaller, else stored
60
+ * (method 0). Every unzip tool reads both.
61
+ *
62
+ * Entry order is kept. A repeated name, or a name that is absolute or holds a
63
+ * `..` part, is refused. There is no ZIP64, so an archive past 65,535 entries
64
+ * or 4 GB (any entry, offset or the whole archive) throws rather than writing
65
+ * a file readers would get wrong.
66
+ */
67
+ export declare function zipEntries(entries: ZipEntry[], options?: ZipEntriesOptions): Uint8Array;
44
68
  declare interface ZipOptions {
45
69
  cwd?: string
46
70
  }
71
+ /** One file for {@link zipEntries}. */
72
+ export declare interface ZipEntry {
73
+ name: string
74
+ data: string | Uint8Array
75
+ date?: Date
76
+ }
77
+ export declare interface ZipEntriesOptions {
78
+ level?: number
79
+ date?: Date
80
+ }
package/dist/zip.js CHANGED
@@ -1 +1 @@
1
- import{runCommand}from"@stacksjs/cli";export async function zip(from,to,options){const toPath=to||"archive.zip",sources=Array.isArray(from)?from:[from];return runCommand(["zip","-r",toPath,...sources],options)}export async function unzip(paths,options){const[first,...rest]=Array.isArray(paths)?paths:[paths];if(first===void 0)throw Error("unzip() needs at least one archive");let result=await runCommand(["unzip","-o",first],options);for(const source of rest){if(result.isErr)break;result=await runCommand(["unzip","-o",source],options)}return result}export function archive(paths,to,options){return zip(paths,to,options)}export function unarchive(paths,options){return unzip(paths,options)}export function compress(paths,to,options){return zip(paths,to,options)}export function decompress(paths,options){return unzip(paths,options)}export function gzipSync(data,options){return Bun.gzipSync(data,options)}export function gunzipSync(data){return Bun.gunzipSync(data)}export function deflateSync(data,options){return Bun.deflateSync(data,options)}export function inflateSync(data){return Bun.inflateSync(data)}
1
+ import{runCommand}from"@stacksjs/cli";export async function zip(from,to,options){const toPath=to||"archive.zip",sources=Array.isArray(from)?from:[from];return runCommand(["zip","-r",toPath,...sources],options)}export async function unzip(paths,options){const[first,...rest]=Array.isArray(paths)?paths:[paths];if(first===void 0)throw Error("unzip() needs at least one archive");let result=await runCommand(["unzip","-o",first],options);for(const source of rest){if(result.isErr)break;result=await runCommand(["unzip","-o",source],options)}return result}export function archive(paths,to,options){return zip(paths,to,options)}export function unarchive(paths,options){return unzip(paths,options)}export function compress(paths,to,options){return zip(paths,to,options)}export function decompress(paths,options){return unzip(paths,options)}export function gzipSync(data,options){return Bun.gzipSync(data,options)}export function gunzipSync(data){return Bun.gunzipSync(data)}export function deflateSync(data,options){return Bun.deflateSync(data,options)}export function inflateSync(data){return Bun.inflateSync(data)}const ZIP32_LIMIT=4294967295,zipText=new TextEncoder;function dosDateTime(at){if(Number.isNaN(at.getTime()))throw Error("zipEntries(): an entry has an invalid date");if(at.getFullYear()<1980)return{date:33,time:0};if(at.getFullYear()>2107)return{date:65439,time:49021};return{date:at.getFullYear()-1980<<9|at.getMonth()+1<<5|at.getDate(),time:at.getHours()<<11|at.getMinutes()<<5|at.getSeconds()>>1}}function zipEntryName(name){const path=String(name??"").replace(/\\/g,"/");if(!path||path.startsWith("/")||/^[a-z]:/i.test(path)||path.includes("\x00")||path.split("/").some((part)=>part===""||part==="."||part===".."))throw Error(`zipEntries(): "${name}" is not a safe file name inside a ZIP (relative, forward slashes, no empty, "." or ".." parts)`);return path}export function zipEntries(entries,options={}){if(entries.length>65535)throw Error(`zipEntries(): ${entries.length} entries is more than the 65,535 a ZIP without ZIP64 holds`);const level=options.level??6;if(!Number.isInteger(level)||level<0||level>9)throw Error(`zipEntries(): level must be an integer from 0 to 9, got ${level}`);const tooBig=(what)=>Error(`zipEntries(): ${what} is past the 4 GB a ZIP without ZIP64 holds`),fallbackDate=options.date??new Date,seen=new Set,parts=[],centrals=[];let offset=0;for(const entry of entries){const path=zipEntryName(entry.name);if(seen.has(path))throw Error(`zipEntries(): two entries are named "${path}"`);seen.add(path);const name=zipText.encode(path);if(name.length>65535)throw Error(`zipEntries(): the name "${path.slice(0,40)}..." is longer than 65,535 bytes`);const raw=typeof entry.data==="string"?zipText.encode(entry.data):entry.data;if(raw.length>=ZIP32_LIMIT)throw tooBig(`"${path}" (${raw.length} bytes)`);if(offset>=ZIP32_LIMIT)throw tooBig(`the archive before "${path}"`);const crc=Bun.hash.crc32(raw)>>>0,deflated=level>0&&raw.length>0?deflateSync(raw,{level}):void 0,method=deflated&&deflated.length<raw.length?8:0,data=method===8?deflated:raw,{date,time}=dosDateTime(entry.date??fallbackDate),local=new Uint8Array(30+name.length),head=new DataView(local.buffer);head.setUint32(0,67324752,!0);head.setUint16(4,20,!0);head.setUint16(6,2048,!0);head.setUint16(8,method,!0);head.setUint16(10,time,!0);head.setUint16(12,date,!0);head.setUint32(14,crc,!0);head.setUint32(18,data.length,!0);head.setUint32(22,raw.length,!0);head.setUint16(26,name.length,!0);head.setUint16(28,0,!0);local.set(name,30);const central=new Uint8Array(46+name.length),dir=new DataView(central.buffer);dir.setUint32(0,33639248,!0);dir.setUint16(4,788,!0);dir.setUint16(6,20,!0);dir.setUint16(8,2048,!0);dir.setUint16(10,method,!0);dir.setUint16(12,time,!0);dir.setUint16(14,date,!0);dir.setUint32(16,crc,!0);dir.setUint32(20,data.length,!0);dir.setUint32(24,raw.length,!0);dir.setUint16(28,name.length,!0);dir.setUint32(38,2175008768,!0);dir.setUint32(42,offset,!0);central.set(name,46);parts.push(local,data);centrals.push(central);offset+=local.length+data.length}const directorySize=centrals.reduce((total,part)=>total+part.length,0);if(offset>=ZIP32_LIMIT||offset+directorySize+22>ZIP32_LIMIT)throw tooBig("the archive");const end=new Uint8Array(22),tail=new DataView(end.buffer);tail.setUint32(0,101010256,!0);tail.setUint16(8,entries.length,!0);tail.setUint16(10,entries.length,!0);tail.setUint32(12,directorySize,!0);tail.setUint32(16,offset,!0);const out=new Uint8Array(offset+directorySize+end.length);let at=0;for(const part of[...parts,...centrals,end]){out.set(part,at);at+=part.length}return out}
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.160",
5
+ "version": "0.75.162",
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.160",
65
- "@stacksjs/cli": "0.75.160",
66
- "@stacksjs/config": "0.75.160",
67
- "@stacksjs/env": "0.75.160",
68
- "@stacksjs/error-handling": "0.75.160",
69
- "@stacksjs/logging": "0.75.160",
70
- "@stacksjs/path": "0.75.160",
71
- "@stacksjs/strings": "0.75.160",
64
+ "@stacksjs/arrays": "0.75.162",
65
+ "@stacksjs/cli": "0.75.162",
66
+ "@stacksjs/config": "0.75.162",
67
+ "@stacksjs/env": "0.75.162",
68
+ "@stacksjs/error-handling": "0.75.162",
69
+ "@stacksjs/logging": "0.75.162",
70
+ "@stacksjs/path": "0.75.162",
71
+ "@stacksjs/strings": "0.75.162",
72
72
  "@stacksjs/ts-cloud": "^0.16.52",
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.160",
77
+ "@stacksjs/types": "0.75.162",
78
78
  "better-dx": "^0.2.24"
79
79
  }
80
80
  }