@inferencesh/app 0.1.3 → 0.1.5
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/file.d.ts +45 -3
- package/dist/file.js +199 -32
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/schema.d.ts +33 -0
- package/dist/schema.js +52 -0
- package/package.json +3 -2
package/dist/file.d.ts
CHANGED
|
@@ -22,22 +22,56 @@ export interface FileData {
|
|
|
22
22
|
* A file in the inference.sh ecosystem.
|
|
23
23
|
*
|
|
24
24
|
* Accepts a URL, local path, or options object.
|
|
25
|
-
* URLs are downloaded
|
|
25
|
+
* URLs are downloaded lazily when `getPath()` is called.
|
|
26
26
|
* Local paths are resolved to absolute paths.
|
|
27
27
|
*
|
|
28
|
+
* For API wrapper apps that only need to forward the URL, use `uri` directly
|
|
29
|
+
* without calling `getPath()` to avoid unnecessary downloads.
|
|
30
|
+
*
|
|
28
31
|
* In JSON output, File serializes to `{ path, uri, content_type, size, filename }`
|
|
29
32
|
* — the engine uploads local `path` files to CDN and replaces with `uri`.
|
|
30
33
|
*/
|
|
31
34
|
export declare class File {
|
|
32
35
|
uri?: string;
|
|
33
|
-
path?: string;
|
|
34
36
|
contentType?: string;
|
|
35
37
|
size?: number;
|
|
36
38
|
filename?: string;
|
|
39
|
+
private _path?;
|
|
40
|
+
private _resolved;
|
|
41
|
+
private _downloading?;
|
|
37
42
|
private constructor();
|
|
43
|
+
/**
|
|
44
|
+
* Get the local file path. Downloads the file lazily if needed.
|
|
45
|
+
* For sync access after download, use the `path` getter.
|
|
46
|
+
*/
|
|
47
|
+
getPath(): Promise<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Sync access to path. Returns undefined if not yet downloaded.
|
|
50
|
+
* Use `getPath()` for lazy downloading.
|
|
51
|
+
*/
|
|
52
|
+
get path(): string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Check if the file has been downloaded/resolved.
|
|
55
|
+
*/
|
|
56
|
+
get isResolved(): boolean;
|
|
57
|
+
private _resolve;
|
|
58
|
+
/**
|
|
59
|
+
* Create a lazy File from a URL or path string.
|
|
60
|
+
* Does NOT download immediately — download happens when `getPath()` is called.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```js
|
|
64
|
+
* const file = File.lazy("https://example.com/image.jpg");
|
|
65
|
+
* console.log(file.uri); // Available immediately
|
|
66
|
+
* const path = await file.getPath(); // Downloads here
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
static lazy(input: string): File;
|
|
38
70
|
/**
|
|
39
71
|
* Create a File from a URL, local path, or options object.
|
|
40
|
-
* URLs are downloaded and cached automatically.
|
|
72
|
+
* URLs are downloaded and cached automatically (eager loading).
|
|
73
|
+
*
|
|
74
|
+
* For lazy loading, use `File.lazy()` instead.
|
|
41
75
|
*
|
|
42
76
|
* @example
|
|
43
77
|
* ```js
|
|
@@ -58,8 +92,14 @@ export declare class File {
|
|
|
58
92
|
static fromPath(localPath: string): File;
|
|
59
93
|
/**
|
|
60
94
|
* Check if the file exists on disk.
|
|
95
|
+
* Note: This checks the current state without triggering download.
|
|
96
|
+
* Use `getPath()` first if you need to ensure the file is downloaded.
|
|
61
97
|
*/
|
|
62
98
|
exists(): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Check if we have a local path (without triggering download).
|
|
101
|
+
*/
|
|
102
|
+
isLocal(): boolean;
|
|
63
103
|
/**
|
|
64
104
|
* Re-read metadata (contentType, size, filename) from disk.
|
|
65
105
|
*/
|
|
@@ -67,10 +107,12 @@ export declare class File {
|
|
|
67
107
|
/**
|
|
68
108
|
* Serialize to a plain object for JSON output.
|
|
69
109
|
* The engine reads `path` fields and uploads them to CDN.
|
|
110
|
+
* Note: Uses internal _path to avoid triggering download during serialization.
|
|
70
111
|
*/
|
|
71
112
|
toJSON(): FileData;
|
|
72
113
|
static getCacheDir(): string;
|
|
73
114
|
private _getCachePath;
|
|
115
|
+
private _decodeDataUri;
|
|
74
116
|
private _downloadUrl;
|
|
75
117
|
private _populateMetadata;
|
|
76
118
|
}
|
package/dist/file.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { createWriteStream, existsSync, mkdirSync, statSync, renameSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { createWriteStream, existsSync, mkdirSync, statSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { basename, resolve, join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { get as httpsGet } from "node:https";
|
|
@@ -9,28 +9,116 @@ import { URL } from "node:url";
|
|
|
9
9
|
* A file in the inference.sh ecosystem.
|
|
10
10
|
*
|
|
11
11
|
* Accepts a URL, local path, or options object.
|
|
12
|
-
* URLs are downloaded
|
|
12
|
+
* URLs are downloaded lazily when `getPath()` is called.
|
|
13
13
|
* Local paths are resolved to absolute paths.
|
|
14
14
|
*
|
|
15
|
+
* For API wrapper apps that only need to forward the URL, use `uri` directly
|
|
16
|
+
* without calling `getPath()` to avoid unnecessary downloads.
|
|
17
|
+
*
|
|
15
18
|
* In JSON output, File serializes to `{ path, uri, content_type, size, filename }`
|
|
16
19
|
* — the engine uploads local `path` files to CDN and replaces with `uri`.
|
|
17
20
|
*/
|
|
18
21
|
export class File {
|
|
19
22
|
uri;
|
|
20
|
-
path;
|
|
21
23
|
contentType;
|
|
22
24
|
size;
|
|
23
25
|
filename;
|
|
26
|
+
_path;
|
|
27
|
+
_resolved = false;
|
|
28
|
+
_downloading;
|
|
24
29
|
constructor(options) {
|
|
25
30
|
this.uri = options.uri;
|
|
26
|
-
this.
|
|
31
|
+
this._path = options.path;
|
|
27
32
|
this.contentType = options.contentType;
|
|
28
33
|
this.size = options.size;
|
|
29
34
|
this.filename = options.filename;
|
|
30
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Get the local file path. Downloads the file lazily if needed.
|
|
38
|
+
* For sync access after download, use the `path` getter.
|
|
39
|
+
*/
|
|
40
|
+
async getPath() {
|
|
41
|
+
if (this._resolved && this._path) {
|
|
42
|
+
return this._path;
|
|
43
|
+
}
|
|
44
|
+
// Avoid concurrent downloads
|
|
45
|
+
if (this._downloading) {
|
|
46
|
+
return this._downloading;
|
|
47
|
+
}
|
|
48
|
+
this._downloading = this._resolve();
|
|
49
|
+
try {
|
|
50
|
+
const path = await this._downloading;
|
|
51
|
+
return path;
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
this._downloading = undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Sync access to path. Returns undefined if not yet downloaded.
|
|
59
|
+
* Use `getPath()` for lazy downloading.
|
|
60
|
+
*/
|
|
61
|
+
get path() {
|
|
62
|
+
return this._path;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if the file has been downloaded/resolved.
|
|
66
|
+
*/
|
|
67
|
+
get isResolved() {
|
|
68
|
+
return this._resolved;
|
|
69
|
+
}
|
|
70
|
+
async _resolve() {
|
|
71
|
+
if (this._resolved && this._path) {
|
|
72
|
+
return this._path;
|
|
73
|
+
}
|
|
74
|
+
if (this.uri) {
|
|
75
|
+
if (isDataUri(this.uri)) {
|
|
76
|
+
this._decodeDataUri(this.uri);
|
|
77
|
+
}
|
|
78
|
+
else if (isUrl(this.uri)) {
|
|
79
|
+
await this._downloadUrl(this.uri);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Treat as local path
|
|
83
|
+
this._path = resolve(this.uri);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (this._path) {
|
|
87
|
+
this._path = resolve(this._path);
|
|
88
|
+
this._populateMetadata();
|
|
89
|
+
}
|
|
90
|
+
this._resolved = true;
|
|
91
|
+
if (!this._path) {
|
|
92
|
+
throw new Error("Failed to resolve file path");
|
|
93
|
+
}
|
|
94
|
+
return this._path;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Create a lazy File from a URL or path string.
|
|
98
|
+
* Does NOT download immediately — download happens when `getPath()` is called.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```js
|
|
102
|
+
* const file = File.lazy("https://example.com/image.jpg");
|
|
103
|
+
* console.log(file.uri); // Available immediately
|
|
104
|
+
* const path = await file.getPath(); // Downloads here
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
static lazy(input) {
|
|
108
|
+
const file = new File({ uri: input });
|
|
109
|
+
// If it's a local path (not URL or data URI), resolve immediately
|
|
110
|
+
if (!isUrl(input) && !isDataUri(input)) {
|
|
111
|
+
file._path = resolve(input);
|
|
112
|
+
file._resolved = true;
|
|
113
|
+
file._populateMetadata();
|
|
114
|
+
}
|
|
115
|
+
return file;
|
|
116
|
+
}
|
|
31
117
|
/**
|
|
32
118
|
* Create a File from a URL, local path, or options object.
|
|
33
|
-
* URLs are downloaded and cached automatically.
|
|
119
|
+
* URLs are downloaded and cached automatically (eager loading).
|
|
120
|
+
*
|
|
121
|
+
* For lazy loading, use `File.lazy()` instead.
|
|
34
122
|
*
|
|
35
123
|
* @example
|
|
36
124
|
* ```js
|
|
@@ -48,7 +136,7 @@ export class File {
|
|
|
48
136
|
if (input instanceof File) {
|
|
49
137
|
return new File({
|
|
50
138
|
uri: input.uri,
|
|
51
|
-
path: input.
|
|
139
|
+
path: input._path,
|
|
52
140
|
contentType: input.contentType,
|
|
53
141
|
size: input.size,
|
|
54
142
|
filename: input.filename,
|
|
@@ -72,23 +160,8 @@ export class File {
|
|
|
72
160
|
throw new Error("Either 'uri' or 'path' must be provided");
|
|
73
161
|
}
|
|
74
162
|
const file = new File(options);
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
if (isUrl(file.uri)) {
|
|
78
|
-
await file._downloadUrl(file.uri);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
// Treat as local path
|
|
82
|
-
file.path = resolve(file.uri);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (file.path) {
|
|
86
|
-
file.path = resolve(file.path);
|
|
87
|
-
file._populateMetadata();
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
throw new Error("Either 'uri' or 'path' must be provided and be valid");
|
|
91
|
-
}
|
|
163
|
+
// Eagerly resolve
|
|
164
|
+
await file.getPath();
|
|
92
165
|
return file;
|
|
93
166
|
}
|
|
94
167
|
/**
|
|
@@ -97,14 +170,23 @@ export class File {
|
|
|
97
170
|
static fromPath(localPath) {
|
|
98
171
|
const absPath = resolve(localPath);
|
|
99
172
|
const file = new File({ path: absPath });
|
|
173
|
+
file._resolved = true;
|
|
100
174
|
file._populateMetadata();
|
|
101
175
|
return file;
|
|
102
176
|
}
|
|
103
177
|
/**
|
|
104
178
|
* Check if the file exists on disk.
|
|
179
|
+
* Note: This checks the current state without triggering download.
|
|
180
|
+
* Use `getPath()` first if you need to ensure the file is downloaded.
|
|
105
181
|
*/
|
|
106
182
|
exists() {
|
|
107
|
-
return this.
|
|
183
|
+
return this._path != null && existsSync(this._path);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if we have a local path (without triggering download).
|
|
187
|
+
*/
|
|
188
|
+
isLocal() {
|
|
189
|
+
return this._path != null;
|
|
108
190
|
}
|
|
109
191
|
/**
|
|
110
192
|
* Re-read metadata (contentType, size, filename) from disk.
|
|
@@ -115,13 +197,14 @@ export class File {
|
|
|
115
197
|
/**
|
|
116
198
|
* Serialize to a plain object for JSON output.
|
|
117
199
|
* The engine reads `path` fields and uploads them to CDN.
|
|
200
|
+
* Note: Uses internal _path to avoid triggering download during serialization.
|
|
118
201
|
*/
|
|
119
202
|
toJSON() {
|
|
120
203
|
const result = {};
|
|
121
204
|
if (this.uri != null)
|
|
122
205
|
result.uri = this.uri;
|
|
123
|
-
if (this.
|
|
124
|
-
result.path = this.
|
|
206
|
+
if (this._path != null)
|
|
207
|
+
result.path = this._path;
|
|
125
208
|
if (this.contentType != null)
|
|
126
209
|
result.content_type = this.contentType;
|
|
127
210
|
if (this.size != null)
|
|
@@ -148,18 +231,48 @@ export class File {
|
|
|
148
231
|
mkdirSync(hashDir, { recursive: true });
|
|
149
232
|
return join(hashDir, fname);
|
|
150
233
|
}
|
|
234
|
+
// --- Data URI ---
|
|
235
|
+
_decodeDataUri(uri) {
|
|
236
|
+
const parsed = parseDataUri(uri);
|
|
237
|
+
// Create cache path based on hash
|
|
238
|
+
const hash = createHash("sha256").update(uri).digest("hex").slice(0, 16);
|
|
239
|
+
const cacheDir = join(File.getCacheDir(), "data_uri", hash);
|
|
240
|
+
// Check for existing cached file
|
|
241
|
+
if (existsSync(cacheDir)) {
|
|
242
|
+
const files = require("node:fs").readdirSync(cacheDir);
|
|
243
|
+
if (files.length > 0) {
|
|
244
|
+
this._path = join(cacheDir, files[0]);
|
|
245
|
+
this._populateMetadata();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Set content type from data URI
|
|
250
|
+
if (!this.contentType) {
|
|
251
|
+
this.contentType = parsed.mediaType;
|
|
252
|
+
}
|
|
253
|
+
// Write to cache
|
|
254
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
255
|
+
const ext = getExtensionForMimeType(parsed.mediaType);
|
|
256
|
+
const filename = `file${ext}`;
|
|
257
|
+
const cachePath = join(cacheDir, filename);
|
|
258
|
+
writeFileSync(cachePath, parsed.data);
|
|
259
|
+
this._path = cachePath;
|
|
260
|
+
this._populateMetadata();
|
|
261
|
+
}
|
|
151
262
|
// --- Download ---
|
|
152
263
|
async _downloadUrl(url) {
|
|
153
264
|
const cachePath = this._getCachePath(url);
|
|
154
265
|
if (existsSync(cachePath)) {
|
|
155
|
-
this.
|
|
266
|
+
this._path = cachePath;
|
|
267
|
+
this._populateMetadata();
|
|
156
268
|
return;
|
|
157
269
|
}
|
|
158
270
|
const tmpPath = cachePath + ".tmp";
|
|
159
271
|
try {
|
|
160
272
|
await downloadToFile(url, tmpPath);
|
|
161
273
|
renameSync(tmpPath, cachePath);
|
|
162
|
-
this.
|
|
274
|
+
this._path = cachePath;
|
|
275
|
+
this._populateMetadata();
|
|
163
276
|
}
|
|
164
277
|
catch (err) {
|
|
165
278
|
try {
|
|
@@ -171,19 +284,19 @@ export class File {
|
|
|
171
284
|
}
|
|
172
285
|
// --- Metadata ---
|
|
173
286
|
_populateMetadata() {
|
|
174
|
-
if (!this.
|
|
287
|
+
if (!this._path || !existsSync(this._path))
|
|
175
288
|
return;
|
|
176
289
|
if (!this.contentType) {
|
|
177
|
-
this.contentType = guessContentType(this.
|
|
290
|
+
this.contentType = guessContentType(this._path);
|
|
178
291
|
}
|
|
179
292
|
if (this.size == null) {
|
|
180
293
|
try {
|
|
181
|
-
this.size = statSync(this.
|
|
294
|
+
this.size = statSync(this._path).size;
|
|
182
295
|
}
|
|
183
296
|
catch { /* ignore */ }
|
|
184
297
|
}
|
|
185
298
|
if (!this.filename) {
|
|
186
|
-
this.filename = basename(this.
|
|
299
|
+
this.filename = basename(this._path);
|
|
187
300
|
}
|
|
188
301
|
}
|
|
189
302
|
}
|
|
@@ -191,6 +304,60 @@ export class File {
|
|
|
191
304
|
function isUrl(s) {
|
|
192
305
|
return s.startsWith("http://") || s.startsWith("https://");
|
|
193
306
|
}
|
|
307
|
+
function isDataUri(s) {
|
|
308
|
+
return s.startsWith("data:");
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Parse a data URI and return the media type and decoded data.
|
|
312
|
+
*
|
|
313
|
+
* Supports formats:
|
|
314
|
+
* - data:image/jpeg;base64,/9j/4AAQ...
|
|
315
|
+
* - data:text/plain,Hello%20World
|
|
316
|
+
* - data:;base64,SGVsbG8= (defaults to text/plain)
|
|
317
|
+
*/
|
|
318
|
+
function parseDataUri(uri) {
|
|
319
|
+
const match = uri.match(/^data:([^;,]*)?(?:;(base64))?,(.*)$/s);
|
|
320
|
+
if (!match) {
|
|
321
|
+
throw new Error("Invalid data URI format");
|
|
322
|
+
}
|
|
323
|
+
const mediaType = match[1] || "text/plain";
|
|
324
|
+
const isBase64 = match[2] === "base64";
|
|
325
|
+
let dataStr = match[3];
|
|
326
|
+
if (isBase64) {
|
|
327
|
+
// Handle URL-safe base64 (- and _ instead of + and /)
|
|
328
|
+
dataStr = dataStr.replace(/-/g, "+").replace(/_/g, "/");
|
|
329
|
+
// Add padding if needed
|
|
330
|
+
const padding = 4 - (dataStr.length % 4);
|
|
331
|
+
if (padding !== 4) {
|
|
332
|
+
dataStr += "=".repeat(padding);
|
|
333
|
+
}
|
|
334
|
+
return { mediaType, data: Buffer.from(dataStr, "base64") };
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
// URL-encoded data
|
|
338
|
+
return { mediaType, data: Buffer.from(decodeURIComponent(dataStr), "utf-8") };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const EXTENSION_MAP = {
|
|
342
|
+
"image/jpeg": ".jpg",
|
|
343
|
+
"image/png": ".png",
|
|
344
|
+
"image/gif": ".gif",
|
|
345
|
+
"image/webp": ".webp",
|
|
346
|
+
"image/svg+xml": ".svg",
|
|
347
|
+
"video/mp4": ".mp4",
|
|
348
|
+
"video/webm": ".webm",
|
|
349
|
+
"audio/mpeg": ".mp3",
|
|
350
|
+
"audio/wav": ".wav",
|
|
351
|
+
"audio/ogg": ".ogg",
|
|
352
|
+
"application/pdf": ".pdf",
|
|
353
|
+
"application/json": ".json",
|
|
354
|
+
"text/plain": ".txt",
|
|
355
|
+
"text/html": ".html",
|
|
356
|
+
"text/csv": ".csv",
|
|
357
|
+
};
|
|
358
|
+
function getExtensionForMimeType(mimeType) {
|
|
359
|
+
return EXTENSION_MAP[mimeType] || "";
|
|
360
|
+
}
|
|
194
361
|
function downloadToFile(url, destPath) {
|
|
195
362
|
return new Promise((resolve, reject) => {
|
|
196
363
|
const parsed = new URL(url);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { File } from "./file.js";
|
|
2
2
|
export type { FileOptions, FileData } from "./file.js";
|
|
3
|
+
export { createFileSchema, isFileSchema, FILE_SCHEMA_MARKER } from "./schema.js";
|
|
3
4
|
export { StorageDir, ensureDir } from "./storage.js";
|
|
4
5
|
export type { StorageDirValue } from "./storage.js";
|
|
5
6
|
export { download } from "./download.js";
|
package/dist/index.js
CHANGED
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema utilities for inference.sh apps.
|
|
3
|
+
*
|
|
4
|
+
* Provides a `fileSchema` that:
|
|
5
|
+
* - Accepts URL strings as input
|
|
6
|
+
* - Transforms to lazy File objects (no download until getPath() called)
|
|
7
|
+
* - Generates JSON Schema with format: "file"
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Symbol to mark a schema as a file schema.
|
|
11
|
+
* The kernel's zodToJsonSchema will detect this and output format: "file".
|
|
12
|
+
*/
|
|
13
|
+
export declare const FILE_SCHEMA_MARKER: unique symbol;
|
|
14
|
+
/**
|
|
15
|
+
* Create a file schema using the app's Zod instance.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```js
|
|
19
|
+
* import { z } from "zod";
|
|
20
|
+
* import { createFileSchema } from "@inferencesh/app";
|
|
21
|
+
*
|
|
22
|
+
* const fileSchema = createFileSchema(z);
|
|
23
|
+
*
|
|
24
|
+
* const InputSchema = z.object({
|
|
25
|
+
* image: fileSchema.describe("Input image"),
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function createFileSchema(z: any): any;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a Zod schema is a file schema (created by createFileSchema).
|
|
32
|
+
*/
|
|
33
|
+
export declare function isFileSchema(schema: any): boolean;
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema utilities for inference.sh apps.
|
|
3
|
+
*
|
|
4
|
+
* Provides a `fileSchema` that:
|
|
5
|
+
* - Accepts URL strings as input
|
|
6
|
+
* - Transforms to lazy File objects (no download until getPath() called)
|
|
7
|
+
* - Generates JSON Schema with format: "file"
|
|
8
|
+
*/
|
|
9
|
+
import { File } from "./file.js";
|
|
10
|
+
// We don't import zod directly to avoid version conflicts.
|
|
11
|
+
// Instead, apps provide their own zod and we work with the schema structure.
|
|
12
|
+
/**
|
|
13
|
+
* Symbol to mark a schema as a file schema.
|
|
14
|
+
* The kernel's zodToJsonSchema will detect this and output format: "file".
|
|
15
|
+
*/
|
|
16
|
+
export const FILE_SCHEMA_MARKER = Symbol.for("inferencesh.fileSchema");
|
|
17
|
+
/**
|
|
18
|
+
* Create a file schema using the app's Zod instance.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```js
|
|
22
|
+
* import { z } from "zod";
|
|
23
|
+
* import { createFileSchema } from "@inferencesh/app";
|
|
24
|
+
*
|
|
25
|
+
* const fileSchema = createFileSchema(z);
|
|
26
|
+
*
|
|
27
|
+
* const InputSchema = z.object({
|
|
28
|
+
* image: fileSchema.describe("Input image"),
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function createFileSchema(z) {
|
|
33
|
+
const schema = z.string().transform((uri) => File.lazy(uri));
|
|
34
|
+
// Mark as file schema for JSON schema generation
|
|
35
|
+
schema._def[FILE_SCHEMA_MARKER] = true;
|
|
36
|
+
return schema;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if a Zod schema is a file schema (created by createFileSchema).
|
|
40
|
+
*/
|
|
41
|
+
export function isFileSchema(schema) {
|
|
42
|
+
if (!schema || typeof schema !== "object")
|
|
43
|
+
return false;
|
|
44
|
+
// Check for our marker
|
|
45
|
+
if (schema._def && schema._def[FILE_SCHEMA_MARKER])
|
|
46
|
+
return true;
|
|
47
|
+
// Check inner type for transforms/effects
|
|
48
|
+
if (schema._def?.typeName === "ZodEffects" && schema._def.schema) {
|
|
49
|
+
return isFileSchema(schema._def.schema);
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inferencesh/app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "App framework for building inference.sh apps — File handling, output metadata, storage utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^22.0.0",
|
|
44
44
|
"rimraf": "^6.0.1",
|
|
45
|
-
"typescript": "^5.8.3"
|
|
45
|
+
"typescript": "^5.8.3",
|
|
46
|
+
"zod": "^4.3.6"
|
|
46
47
|
},
|
|
47
48
|
"files": [
|
|
48
49
|
"dist",
|