@phreshos/node 0.1.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/LICENSE +19 -0
- package/README.md +39 -0
- package/dist/address.d.ts +2 -0
- package/dist/address.js +10 -0
- package/dist/client-development.d.ts +29 -0
- package/dist/client-development.js +199 -0
- package/dist/events.d.ts +13 -0
- package/dist/events.js +78 -0
- package/dist/gateway.d.ts +49 -0
- package/dist/gateway.js +151 -0
- package/dist/home.d.ts +2 -0
- package/dist/home.js +16 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.js +5 -0
- package/dist/project.d.ts +37 -0
- package/dist/project.js +272 -0
- package/dist/storage.d.ts +3 -0
- package/dist/storage.js +109 -0
- package/dist/system.d.ts +9 -0
- package/dist/system.js +441 -0
- package/dist/transport.d.ts +11 -0
- package/dist/transport.js +113 -0
- package/dist/uploads.d.ts +21 -0
- package/dist/uploads.js +110 -0
- package/package.json +44 -0
package/dist/uploads.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { isUploadFile } from "@phreshos/core";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { createReadStream, createWriteStream, mkdirSync } from "node:fs";
|
|
4
|
+
import { rename, rm } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { Readable } from "node:stream";
|
|
7
|
+
import { pipeline } from "node:stream/promises";
|
|
8
|
+
/** Owner-local implementation of the System's opaque upload collection. */
|
|
9
|
+
export default class Uploads {
|
|
10
|
+
ask;
|
|
11
|
+
accessPromise = null;
|
|
12
|
+
constructor(ask) {
|
|
13
|
+
this.ask = ask;
|
|
14
|
+
}
|
|
15
|
+
async write(value) {
|
|
16
|
+
const access = await this.access();
|
|
17
|
+
const source = content(value);
|
|
18
|
+
const identity = randomUUID();
|
|
19
|
+
const file = `${identity}.${source.extension}`;
|
|
20
|
+
const temporary = join(access.path, `.${identity}.uploading`);
|
|
21
|
+
const destination = join(access.path, file);
|
|
22
|
+
let size = 0;
|
|
23
|
+
mkdirSync(access.path, { recursive: true });
|
|
24
|
+
try {
|
|
25
|
+
await pipeline(Readable.fromWeb(source.stream), async function* (chunks) {
|
|
26
|
+
for await (const chunk of chunks) {
|
|
27
|
+
size += chunk.byteLength;
|
|
28
|
+
if (size > access.limit)
|
|
29
|
+
throw new Error(`The upload exceeds ${access.limit / 1024 / 1024 / 1024} GB`);
|
|
30
|
+
yield chunk;
|
|
31
|
+
}
|
|
32
|
+
}, createWriteStream(temporary, { flags: "wx" }));
|
|
33
|
+
await rename(temporary, destination);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
await rm(temporary, { force: true }).catch(() => undefined);
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
const upload = await this.stat(file);
|
|
40
|
+
if (!upload)
|
|
41
|
+
throw new Error("The completed upload could not be described");
|
|
42
|
+
return upload;
|
|
43
|
+
}
|
|
44
|
+
async stream(file) {
|
|
45
|
+
requireFile(file);
|
|
46
|
+
const access = await this.access();
|
|
47
|
+
return Readable.toWeb(createReadStream(join(access.path, file)));
|
|
48
|
+
}
|
|
49
|
+
async bytes(file) { return new Uint8Array(await new Response(await this.stream(file)).arrayBuffer()); }
|
|
50
|
+
async text(file) { return new Response(await this.stream(file)).text(); }
|
|
51
|
+
async json(file) { return JSON.parse(await this.text(file)); }
|
|
52
|
+
async stat(file) {
|
|
53
|
+
requireFile(file);
|
|
54
|
+
return await this.ask({ capability: "uploads", operation: "stat", file });
|
|
55
|
+
}
|
|
56
|
+
access() {
|
|
57
|
+
if (!this.accessPromise) {
|
|
58
|
+
const resolving = this.ask({ capability: "uploads", operation: "access" }).then(value => {
|
|
59
|
+
const access = value;
|
|
60
|
+
if (!access || typeof access.path !== "string" || typeof access.limit !== "number")
|
|
61
|
+
throw new Error("The System returned invalid upload access");
|
|
62
|
+
return { path: access.path, limit: access.limit };
|
|
63
|
+
});
|
|
64
|
+
const retained = resolving.catch(error => {
|
|
65
|
+
if (this.accessPromise === retained)
|
|
66
|
+
this.accessPromise = null;
|
|
67
|
+
throw error;
|
|
68
|
+
});
|
|
69
|
+
this.accessPromise = retained;
|
|
70
|
+
}
|
|
71
|
+
return this.accessPromise;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function requireFile(file) {
|
|
75
|
+
if (!isUploadFile(file))
|
|
76
|
+
throw new Error("That is not an upload file");
|
|
77
|
+
}
|
|
78
|
+
function content(value) {
|
|
79
|
+
if (typeof File !== "undefined" && value instanceof File) {
|
|
80
|
+
const type = value.type || "application/octet-stream";
|
|
81
|
+
return { stream: value.stream(), extension: extension(value.name, type) };
|
|
82
|
+
}
|
|
83
|
+
if (typeof Blob !== "undefined" && value instanceof Blob)
|
|
84
|
+
return { stream: value.stream(), extension: extension("", value.type) };
|
|
85
|
+
if (value instanceof ReadableStream)
|
|
86
|
+
return { stream: value, extension: "bin" };
|
|
87
|
+
if (value instanceof Uint8Array)
|
|
88
|
+
return { stream: new Blob([bytes(value)]).stream(), extension: "bin" };
|
|
89
|
+
if (value instanceof ArrayBuffer)
|
|
90
|
+
return { stream: new Blob([value]).stream(), extension: "bin" };
|
|
91
|
+
if (typeof value === "string")
|
|
92
|
+
return { stream: new Blob([value]).stream(), extension: "txt" };
|
|
93
|
+
return { stream: new Blob([JSON.stringify(value)]).stream(), extension: "json" };
|
|
94
|
+
}
|
|
95
|
+
function bytes(value) {
|
|
96
|
+
return value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
|
|
97
|
+
}
|
|
98
|
+
function extension(name, type) {
|
|
99
|
+
const named = /\.([a-z0-9]+)$/i.exec(name)?.[1]?.toLowerCase();
|
|
100
|
+
if (named && /^[a-z0-9]+$/.test(named))
|
|
101
|
+
return named;
|
|
102
|
+
return extensions[type] ?? "bin";
|
|
103
|
+
}
|
|
104
|
+
const extensions = {
|
|
105
|
+
"application/json": "json",
|
|
106
|
+
"image/jpeg": "jpg",
|
|
107
|
+
"image/png": "png",
|
|
108
|
+
"image/svg+xml": "svg",
|
|
109
|
+
"text/plain": "txt"
|
|
110
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phreshos/node",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js access to PhreshOS and Program projects.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/main.js",
|
|
7
|
+
"types": "dist/main.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/main.d.ts",
|
|
11
|
+
"default": "./dist/main.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist", "LICENSE", "README.md"],
|
|
15
|
+
"author": "Zohayr SLILEH",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/PhreshOS/node.git"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"provenance": true
|
|
24
|
+
},
|
|
25
|
+
"packageManager": "bun@1.3.14",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
28
|
+
"check": "tsc --noEmit",
|
|
29
|
+
"build": "node --run clean && tsc --noEmit false --outDir dist --rootDir source",
|
|
30
|
+
"test": "node --run build && node --test tests/*.test.mjs",
|
|
31
|
+
"verify": "node --run check && node --run test",
|
|
32
|
+
"prepack": "node --run build"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@phreshos/core": "^0.1.21",
|
|
36
|
+
"adm-zip": "^0.6.0",
|
|
37
|
+
"jiti": "^2.7.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/adm-zip": "^0.5.8",
|
|
41
|
+
"@types/node": "^26.2.0",
|
|
42
|
+
"typescript": "^6.0.3"
|
|
43
|
+
}
|
|
44
|
+
}
|