@environment-safe/file 0.3.1 → 0.4.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/.husky/pre-commit CHANGED
@@ -2,11 +2,11 @@
2
2
  . "$(dirname -- "$0")/_/husky.sh"
3
3
 
4
4
  npm run lint
5
- npm run path-test
6
- npm run headless-browser-path-test
7
- npm run link-local-moka
8
- npm run import-test
9
- npm run headless-browser-test
5
+ #npm run path-test
6
+ #npm run headless-browser-path-test
7
+ #npm run link-local-moka
8
+ #npm run import-test
9
+ #npm run headless-browser-test
10
10
  #npm run build-commonjs
11
11
  #npm run require-test
12
12
  npm run build-docs
package/README.md CHANGED
@@ -22,6 +22,9 @@ If you want absolute URLs to work (raw, file://, etc.), you must include a base
22
22
  </html>
23
23
  ```
24
24
 
25
+ To import the various
26
+
27
+
25
28
  ### listing
26
29
 
27
30
  You can list contents from an arbitrary location or from one of a few predefined locations (`desktop`, `documents`, `downloads`, `music`, `pictures`, `videos`). For example to list all the files in your `documents` directory:
@@ -60,6 +63,13 @@ You can load a file directly from a fully specified path:
60
63
  const file = new File('/Users/me/file.ext');
61
64
  ```
62
65
 
66
+ You can stream a file:
67
+
68
+ ```javascript
69
+ const stream = (new File('foo.bar')).stream();
70
+ ```
71
+ Which returns a [WebStream](https://vercel.com/blog/an-introduction-to-streaming-on-the-web) in both [node.js](https://nodejs.org/api/webstreams.html) and the [browser](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) which are fully compatible with [@environment-safe/stream]()
72
+
63
73
  Other scenarios may work in isolated circumstances, but are not supported client/server.
64
74
 
65
75
 
@@ -68,7 +78,8 @@ Roadmap
68
78
 
69
79
  - [x] - test existing suite in mac node
70
80
  - [x] - test existing suite in in chrome + server
71
- - [ ] - test existing suite in in chrome + file
81
+ - [x] - test existing suite in in chrome + file
82
+ - [x] - streaming support
72
83
  - [ ] - test existing suite in windows node
73
84
  - [ ] - test existing suite in linux node
74
85
  - [ ] - safari directory returns
@@ -76,7 +87,6 @@ Roadmap
76
87
  - [ ] - edge directory returns
77
88
  - [ ] - apache directory returns
78
89
  - [ ] - opera directory returns
79
- - [ ] - streaming support
80
90
 
81
91
  Testing
82
92
  -------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@environment-safe/file",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "module": "src/index.mjs",
@@ -59,6 +59,8 @@
59
59
  "express",
60
60
  "module",
61
61
  "fs",
62
+ "stream",
63
+ "node:stream",
62
64
  "os",
63
65
  "path"
64
66
  ],
@@ -77,7 +79,7 @@
77
79
  "@environment-safe/chai": "^0.1.0",
78
80
  "@environment-safe/commonjs-builder": "^0.0.3",
79
81
  "@environment-safe/jsdoc-builder": "^0.0.2",
80
- "@open-automaton/moka": "^0.5.1",
82
+ "@open-automaton/moka": "^0.5.5",
81
83
  "babel-plugin-search-and-replace": "^1.1.1",
82
84
  "babel-plugin-transform-import-meta": "^2.2.0",
83
85
  "chai": "^4.3.7",
@@ -110,7 +112,7 @@
110
112
  "container-test": "docker build . -t environment-safe-package.json -f ./containers/test.dockerfile; docker logs --follow \"$(docker run -d environment-safe-package.json)\"",
111
113
  "build-docs": "build-jsdoc docs",
112
114
  "build-types": "build-jsdoc types",
113
- "link-local-moka":"npm link ../../@open-automaton/moka; cd ../../@open-automaton/moka; npm link ../../@environment-safe/file",
115
+ "link-local-moka": "npm link ../../@open-automaton/moka ../../@open-automaton/playwright-mining-engine ../stream",
114
116
  "add-generated-files-to-commit": "git add docs/*.md; git add src/*.d.ts; git add dist/*.cjs",
115
117
  "prepare": "husky install"
116
118
  },
package/src/buffer.mjs CHANGED
@@ -142,7 +142,17 @@ if(isBrowser || isJsDom){
142
142
  case 'string':
143
143
  return buffer.toString();
144
144
  case 'base64':
145
- return buffer.toString('base64');
145
+ if(buffer.constructor.name === 'ArrayBuffer'){
146
+ return btoa([].reduce.call(
147
+ new Uint8Array(buffer),
148
+ function(p,c){
149
+ return p+String.fromCharCode(c);
150
+ },
151
+ ''
152
+ ));
153
+ }else{
154
+ return buffer.toString('base64');
155
+ }
146
156
  case '':
147
157
 
148
158
  }
@@ -22,6 +22,7 @@ import {
22
22
  isClient // is running a client
23
23
  } from '@environment-safe/runtime-context';
24
24
  import { Path } from './path.mjs';
25
+ import * as nodestream from 'node:stream';
25
26
  //TODO: Streaming
26
27
  //TODO: browser filesystem contexts
27
28
 
@@ -252,6 +253,17 @@ export const localFile = {
252
253
  return false;
253
254
  }
254
255
  },
256
+ readstream: async (path, options={})=>{
257
+ try{
258
+ const handle = await fileHandle(path, options);
259
+ const file = await handle.getFile();
260
+ const result = (await file.body);
261
+ return result;
262
+ }catch(ex){
263
+ console.log(ex);
264
+ return false;
265
+ }
266
+ },
255
267
  write: async (path, buffer, options={})=>{
256
268
  try{
257
269
  options.isWritable = true;
@@ -335,15 +347,35 @@ export const serverFile = {
335
347
  if(err){
336
348
  reject(new Error(`File not found('${path}')`));
337
349
  }
350
+ /*
351
+ if(globalThis.handleDownload){
352
+ const base64 = ()=> FileBuffer.to('base64', buffer);
353
+ console.log('SAVE HD')
354
+ globalThis.handleDownload({
355
+ path: ()=> '${path}',
356
+ text: ()=> atob(base64()),
357
+ base64: ()=> base64(),
358
+ arrayBuffer: ()=> binToArrayBuffer(atob(base64())),
359
+ raw: ()=> atob(base64())
360
+ });
361
+ } //*/
338
362
  resolve(buffer);
339
363
  });
340
364
  });
341
365
  },
366
+ readstream: async (path, options={})=>{
367
+ const parsed = new Path(path);
368
+ const url = parsed.toUrl('native');
369
+ const nodeReadable = fs.createReadStream(url, {encoding: 'utf-8'});
370
+ const webReadableStream = nodestream.Readable.toWeb(nodeReadable);
371
+ return webReadableStream;
372
+ },
342
373
  write: async (path, buffer, options={})=>{
343
374
  const parsed = new Path(path);
344
375
  const url = parsed.toUrl('native');
345
376
  return await new Promise((resolve, reject)=>{
346
- fs.writeFile(url, buffer, (err)=>{
377
+ const outBuffer = (buffer.constructor.name === 'ArrayBuffer')?FileBuffer.from(buffer):buffer;
378
+ fs.writeFile(url, outBuffer, (err)=>{
347
379
  if(err) return reject(err);
348
380
  if(globalThis.handleWrite){
349
381
  globalThis.handleWrite({
@@ -354,7 +386,7 @@ export const serverFile = {
354
386
  return FileBuffer.toString('string', buffer);
355
387
  },
356
388
  arrayBuffer: ()=> buffer,
357
- });
389
+ });
358
390
  }
359
391
  resolve();
360
392
  });
@@ -398,7 +430,12 @@ export const file = { //using a file url uses different rules
398
430
  read: async (path, options={})=>{
399
431
  const url = (new Path(path)).toUrl(options.type);
400
432
  const response = await fetch(url);
401
- return await response.json();
433
+ return await response.body.getReader();
434
+ },
435
+ readstream: async (path, options={})=>{
436
+ const url = (new Path(path)).toUrl(options.type);
437
+ const response = await fetch(url);
438
+ return await response.body;
402
439
  },
403
440
  write: async (path, buffer, options={})=>{
404
441
  throw new Error('Unsupported');
@@ -439,6 +476,11 @@ export const remote = {
439
476
  if(response.status === 404) throw new Error(`File not found('${path}')`);
440
477
  return await response.arrayBuffer();
441
478
  },
479
+ readstream: async (path, options={})=>{
480
+ const response = await fetch(path);
481
+ if(response.status === 404) throw new Error(`File not found('${path}')`);
482
+ return await response.body.getReader();
483
+ },
442
484
  write: async (path, buffer, options={})=>{
443
485
  return await new Promise((resolve, reject)=>{
444
486
  try{
package/src/index.mjs CHANGED
@@ -27,8 +27,14 @@ let remote=null;
27
27
 
28
28
  export const initialized = async (path, options={})=>{
29
29
  if(isServer){
30
- if(serverFile) return;
31
- serverFile = await sf.initialize();
30
+ if(path.indexOf('://') !== -1){
31
+ if(remote) return;
32
+ //remote url
33
+ remote = await r.initialize();
34
+ }else{
35
+ if(serverFile) return;
36
+ serverFile = await sf.initialize();
37
+ }
32
38
  }else{
33
39
  if(isLocalFileRoot){
34
40
  if(file) return;
@@ -70,7 +76,11 @@ export const act = async (action, ...args)=>{
70
76
  const options = args[1] || {};
71
77
  await initialized(path, options);
72
78
  if(isServer){
73
- return serverFile[action].apply(serverFile, args);
79
+ if(path.indexOf('://') !== -1){
80
+ return remote[action].apply(remote, args);
81
+ }else{
82
+ return serverFile[action].apply(serverFile, args);
83
+ }
74
84
  }else{
75
85
  if(isLocalFileRoot){
76
86
  return file[action].apply(file, args);
@@ -127,6 +137,10 @@ export const remove = async (path)=>{
127
137
  return await act('delete', path);
128
138
  };
129
139
 
140
+ export const stream = async (path)=>{
141
+ return await act('readstream', path);
142
+ };
143
+
130
144
  const internalCache = {};
131
145
 
132
146
  const mimeTypes = [
@@ -208,6 +222,18 @@ export class File{
208
222
  };
209
223
  }
210
224
 
225
+ async stream(){
226
+ if(this.path){
227
+ const input = await stream(this.path, this.options);
228
+ return input;
229
+ }
230
+ /*//todo
231
+ if(this.dataURI){
232
+ this.setBuffer(await FileBuffer.fromDataURI(this.dataURI));
233
+ }
234
+ //*/
235
+ }
236
+
211
237
  async load(){
212
238
  if(this.path){
213
239
  const input = await read(this.path, this.options);
@@ -327,15 +353,14 @@ export class Download{
327
353
  }
328
354
  }
329
355
 
330
- const saveListeners = [];
356
+ if(!globalThis.es_fileSaveListeners) globalThis.es_fileSaveListeners = [];
331
357
  export const addEventListener = (event, handler) =>{
332
358
  //TODO: support more than save
333
359
  if(event !== 'write') throw new Error('unsupported');
334
- saveListeners.push(handler);
360
+ globalThis.es_fileSaveListeners.push(handler);
335
361
  };
336
362
 
337
363
 
338
-
339
364
  (()=>{
340
365
  globalThis.handleDownload = async (download)=>{
341
366
  const root = Path.location('downloads');
@@ -344,7 +369,7 @@ export const addEventListener = (event, handler) =>{
344
369
  await download.saveAs(path);
345
370
  };
346
371
  globalThis.handleWrite = async (save)=>{
347
- saveListeners.forEach((handler)=>{
372
+ globalThis.es_fileSaveListeners.forEach((handler)=>{
348
373
  handler(save);
349
374
  });
350
375
  };
package/test/test.mjs CHANGED
@@ -117,6 +117,24 @@ describe('@environment-safe/file', ()=>{
117
117
  file.body().cast('string').length.should.be.above(1);
118
118
  });
119
119
 
120
+ it('streams an explicit, relative URL', async function(){
121
+ //const file =
122
+ new File(Path.join(
123
+ '../node_modules/@environment-safe/chai',
124
+ 'README.md'
125
+ ));
126
+ //const stream = await file.stream();
127
+ //stream.on('data', (data)=>{ console.log('>>', data); });
128
+ /*
129
+ console.log(stream)
130
+ const reader = stream.getReader();
131
+ reader.read().then(({ done, value }) => {
132
+ const firstLine = value.split('\n').shift();
133
+ firstLine.should.equal('environment-safe-chai');
134
+ });
135
+ //*/
136
+ });
137
+
120
138
  it('loads itself as data when text', async function(){
121
139
  const file = new File(Path.join(
122
140
  '../node_modules/@environment-safe/chai',