@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 +5 -5
- package/README.md +12 -2
- package/package.json +5 -3
- package/src/buffer.mjs +11 -1
- package/src/filesystem.mjs +45 -3
- package/src/index.mjs +32 -7
- package/test/test.mjs +18 -0
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
|
-
- [
|
|
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
|
+
"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.
|
|
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
|
|
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
|
-
|
|
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
|
}
|
package/src/filesystem.mjs
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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(
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|