@monoflake/sdk 0.0.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 +22 -0
- package/dist/artifacts/src/anchors.d.ts +34 -0
- package/dist/artifacts/src/anchors.js +64 -0
- package/dist/artifacts/src/api.d.ts +14 -0
- package/dist/artifacts/src/api.js +20 -0
- package/dist/artifacts/src/batch.d.ts +105 -0
- package/dist/artifacts/src/batch.js +81 -0
- package/dist/artifacts/src/engagement.d.ts +61 -0
- package/dist/artifacts/src/engagement.js +67 -0
- package/dist/artifacts/src/feed.d.ts +42 -0
- package/dist/artifacts/src/feed.js +89 -0
- package/dist/artifacts/src/index.d.ts +4224 -0
- package/dist/artifacts/src/index.js +219 -0
- package/dist/artifacts/src/picture.d.ts +116 -0
- package/dist/artifacts/src/picture.js +161 -0
- package/dist/artifacts/src/resource.d.ts +1391 -0
- package/dist/artifacts/src/resource.js +477 -0
- package/dist/artifacts/src/schema.d.ts +5 -0
- package/dist/artifacts/src/schema.js +18 -0
- package/dist/artifacts/src/types.d.ts +396 -0
- package/dist/artifacts/src/types.js +0 -0
- package/dist/cache/src/index.d.ts +67 -0
- package/dist/cache/src/index.js +58 -0
- package/dist/imgsrc/src/index.d.ts +16 -0
- package/dist/imgsrc/src/index.js +89 -0
- package/dist/limits/src/bucket.d.ts +28 -0
- package/dist/limits/src/bucket.js +23 -0
- package/dist/limits/src/index.d.ts +29 -0
- package/dist/limits/src/index.js +62 -0
- package/dist/limits/src/key.d.ts +37 -0
- package/dist/limits/src/key.js +64 -0
- package/dist/robots/src/index.d.ts +98 -0
- package/dist/robots/src/index.js +196 -0
- package/dist/security/src/agents.d.ts +10 -0
- package/dist/security/src/agents.js +95 -0
- package/dist/security/src/index.d.ts +12 -0
- package/dist/security/src/index.js +37 -0
- package/dist/src/index.d.ts +218 -0
- package/dist/src/index.js +219 -0
- package/dist/store/src/index.d.ts +92 -0
- package/dist/store/src/index.js +264 -0
- package/dist/symlink/src/index.d.ts +24 -0
- package/dist/symlink/src/index.js +89 -0
- package/package.json +85 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { recordKey, storageKey } from "../../artifacts/src/index.js";
|
|
2
|
+
import { errorBody } from "@canmi/response";
|
|
3
|
+
//#region store/src/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Reading the bytes behind a key, from whichever store this deployment has.
|
|
6
|
+
*
|
|
7
|
+
* Production reads the R2 bucket a tree under `data/bucket` mirrors; development reads that
|
|
8
|
+
* tree itself, handed over by `wrangler dev --assets` because it is the source of truth.
|
|
9
|
+
* A worker cannot open that directory itself -- workerd's `node:fs` is virtual and cannot see
|
|
10
|
+
* host paths (verified) -- so the runtime passes it in. Everything above this module works in
|
|
11
|
+
* keys and knows nothing about which one answered.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Origin for asset-fetcher requests. `.invalid` is reserved by RFC 2606 to never resolve,
|
|
15
|
+
* which is the point: the fetcher routes on the path and ignores the host, and a name that
|
|
16
|
+
* cannot resolve makes it impossible for this to accidentally become a real request.
|
|
17
|
+
*/
|
|
18
|
+
const ASSET_ORIGIN = "https://assets.invalid";
|
|
19
|
+
function isUnsatisfiable(value) {
|
|
20
|
+
return value !== null && "unsatisfiable" in value;
|
|
21
|
+
}
|
|
22
|
+
async function read(env, key, range) {
|
|
23
|
+
const wanted = range ? parseRange(range) : null;
|
|
24
|
+
if (env.STORE) return readFromBucket(env.STORE, key, wanted);
|
|
25
|
+
if (env.ASSETS) return readFromAssets(env.ASSETS, key, wanted);
|
|
26
|
+
throw new Error("no store bound: expected STORE in production or ASSETS under wrangler dev");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What is known about an object under a key, without reading a byte of it.
|
|
30
|
+
*
|
|
31
|
+
* A caller that has to refuse work before paying for it needs the size first: an isolate gets
|
|
32
|
+
* 128 MB for its heap and its WebAssembly together, so a length that arrives alongside the bytes
|
|
33
|
+
* arrives too late to be a limit. Null is no such object, which makes this a presence check too.
|
|
34
|
+
* The date rides along because the same head already carries it, and a caller wanting both
|
|
35
|
+
* should not spend a second round trip on the second one.
|
|
36
|
+
*/
|
|
37
|
+
async function measure(env, key) {
|
|
38
|
+
if (env.STORE) {
|
|
39
|
+
const head = await env.STORE.head(key);
|
|
40
|
+
return head ? {
|
|
41
|
+
size: head.size,
|
|
42
|
+
uploaded: head.uploaded ?? null
|
|
43
|
+
} : null;
|
|
44
|
+
}
|
|
45
|
+
if (env.ASSETS) {
|
|
46
|
+
const response = await env.ASSETS.fetch(`${ASSET_ORIGIN}/${key}`);
|
|
47
|
+
if (!response.ok) return null;
|
|
48
|
+
const uploaded = dateOf(response.headers.get("last-modified"));
|
|
49
|
+
const declared = response.headers.get("content-length");
|
|
50
|
+
if (declared !== null) {
|
|
51
|
+
await response.body?.cancel();
|
|
52
|
+
return {
|
|
53
|
+
size: Number(declared),
|
|
54
|
+
uploaded
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
size: (await response.arrayBuffer()).byteLength,
|
|
59
|
+
uploaded
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
throw new Error("no store bound: expected STORE in production or ASSETS under wrangler dev");
|
|
63
|
+
}
|
|
64
|
+
/** A header date, or null for absent and for unparseable, which are one thing to a caller. */
|
|
65
|
+
function dateOf(header) {
|
|
66
|
+
if (header === null) return null;
|
|
67
|
+
const at = new Date(header);
|
|
68
|
+
return Number.isNaN(at.getTime()) ? null : at;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* `bytes=a-b`, `bytes=a-` and `bytes=-n`, or null for anything else.
|
|
72
|
+
*
|
|
73
|
+
* Null covers a unit that is not `bytes`, a list of ranges, and a header that is simply
|
|
74
|
+
* malformed. All three are served whole, which is what a recipient is allowed to do and what a
|
|
75
|
+
* client asking for something this does not implement should get.
|
|
76
|
+
*/
|
|
77
|
+
function parseRange(header) {
|
|
78
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
|
|
79
|
+
if (!match) return null;
|
|
80
|
+
const [, from, to] = match;
|
|
81
|
+
if (from === "") return to === "" ? null : { suffix: Number(to) };
|
|
82
|
+
const offset = Number(from);
|
|
83
|
+
if (to === "") return {
|
|
84
|
+
offset,
|
|
85
|
+
end: null
|
|
86
|
+
};
|
|
87
|
+
const end = Number(to);
|
|
88
|
+
return end < offset ? null : {
|
|
89
|
+
offset,
|
|
90
|
+
end
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/** Resolve a wanted range against the object's real size. */
|
|
94
|
+
function resolve(wanted, total) {
|
|
95
|
+
if ("suffix" in wanted) {
|
|
96
|
+
const length = Math.min(wanted.suffix, total);
|
|
97
|
+
return length === 0 ? null : {
|
|
98
|
+
offset: total - length,
|
|
99
|
+
length
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (wanted.offset >= total) return null;
|
|
103
|
+
const end = wanted.end === null ? total - 1 : Math.min(wanted.end, total - 1);
|
|
104
|
+
return {
|
|
105
|
+
offset: wanted.offset,
|
|
106
|
+
length: end - wanted.offset + 1
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async function readFromBucket(bucket, key, wanted) {
|
|
110
|
+
if (!wanted) {
|
|
111
|
+
const object = await bucket.get(key);
|
|
112
|
+
if (!object?.body) return null;
|
|
113
|
+
return {
|
|
114
|
+
body: object.body,
|
|
115
|
+
contentType: object.httpMetadata?.contentType ?? contentTypeFor(key),
|
|
116
|
+
etag: object.httpEtag
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const head = await bucket.head(key);
|
|
120
|
+
if (!head) return null;
|
|
121
|
+
const resolved = resolve(wanted, head.size);
|
|
122
|
+
if (!resolved) return {
|
|
123
|
+
unsatisfiable: true,
|
|
124
|
+
total: head.size
|
|
125
|
+
};
|
|
126
|
+
const object = await bucket.get(key, { range: resolved });
|
|
127
|
+
if (!object?.body) return null;
|
|
128
|
+
return {
|
|
129
|
+
body: object.body,
|
|
130
|
+
contentType: object.httpMetadata?.contentType ?? contentTypeFor(key),
|
|
131
|
+
etag: object.httpEtag,
|
|
132
|
+
partial: {
|
|
133
|
+
...resolved,
|
|
134
|
+
total: head.size
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function readFromAssets(assets, key, wanted) {
|
|
139
|
+
const response = await assets.fetch(`${ASSET_ORIGIN}/${key}`);
|
|
140
|
+
if (!response.ok || !response.body) return null;
|
|
141
|
+
const contentType = response.headers.get("content-type") ?? contentTypeFor(key);
|
|
142
|
+
if (!wanted) return {
|
|
143
|
+
body: response.body,
|
|
144
|
+
contentType
|
|
145
|
+
};
|
|
146
|
+
const whole = new Uint8Array(await response.arrayBuffer());
|
|
147
|
+
const resolved = resolve(wanted, whole.byteLength);
|
|
148
|
+
if (!resolved) return {
|
|
149
|
+
unsatisfiable: true,
|
|
150
|
+
total: whole.byteLength
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
body: streamOf(whole.subarray(resolved.offset, resolved.offset + resolved.length)),
|
|
154
|
+
contentType,
|
|
155
|
+
partial: {
|
|
156
|
+
...resolved,
|
|
157
|
+
total: whole.byteLength
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* What a content id looks like.
|
|
163
|
+
*
|
|
164
|
+
* BLAKE3 truncated to 128 bits, hex encoded. Checked before a key is built from one, because an
|
|
165
|
+
* id becomes a path segment and an unchecked one is a way to ask the bucket for something else.
|
|
166
|
+
*/
|
|
167
|
+
const CONTENT_ID = /^[0-9a-f]{32}$/;
|
|
168
|
+
function isContentId(value) {
|
|
169
|
+
return CONTENT_ID.test(value);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* A stored object as an HTTP response, with ETag only when the store supplied one.
|
|
173
|
+
*
|
|
174
|
+
* `Accept-Ranges` on every one of them, because every object here is served through a route that
|
|
175
|
+
* can answer a range -- a player seeking, a `<video>` element probing for duration, a resumed
|
|
176
|
+
* download. The header is what tells a client it may ask; without it a browser fetches whole
|
|
177
|
+
* files to read a byte near the end of them.
|
|
178
|
+
*/
|
|
179
|
+
function toResponse(found) {
|
|
180
|
+
const headers = new Headers({
|
|
181
|
+
"Content-Type": found.contentType,
|
|
182
|
+
"Accept-Ranges": "bytes"
|
|
183
|
+
});
|
|
184
|
+
if (found.etag) headers.set("ETag", found.etag);
|
|
185
|
+
if (!found.partial) return new Response(found.body, { headers });
|
|
186
|
+
const { offset, length, total } = found.partial;
|
|
187
|
+
headers.set("Content-Range", `bytes ${offset}-${offset + length - 1}/${total}`);
|
|
188
|
+
headers.set("Content-Length", String(length));
|
|
189
|
+
return new Response(found.body, {
|
|
190
|
+
status: 206,
|
|
191
|
+
headers
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The answer to a range that names nothing inside the object.
|
|
196
|
+
*
|
|
197
|
+
* 416 carries the size so the client can ask again knowing it, which is the whole reason this is
|
|
198
|
+
* not a 404: the object is there, the question was wrong.
|
|
199
|
+
*/
|
|
200
|
+
function unsatisfiableResponse(total) {
|
|
201
|
+
return Response.json(errorBody("invalid_range"), {
|
|
202
|
+
status: 416,
|
|
203
|
+
headers: {
|
|
204
|
+
"Content-Range": `bytes */${total}`,
|
|
205
|
+
"Accept-Ranges": "bytes"
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Bytes already in hand, answered as a `Range` asked for them.
|
|
211
|
+
*
|
|
212
|
+
* For a body that was derived rather than stored: there is no object to ask the bucket for a part
|
|
213
|
+
* of, so the artifact is produced whole and the slice is taken here. The grammar stays in this
|
|
214
|
+
* module -- one parser, one set of spellings -- and a header it does not implement is served
|
|
215
|
+
* whole, which is what a recipient is allowed to do. See spec/architecture/delivery.md.
|
|
216
|
+
*/
|
|
217
|
+
function rangedResponse(bytes, contentType, range) {
|
|
218
|
+
const wanted = range ? parseRange(range) : null;
|
|
219
|
+
if (!wanted) return toResponse({
|
|
220
|
+
body: streamOf(bytes),
|
|
221
|
+
contentType
|
|
222
|
+
});
|
|
223
|
+
const total = bytes.byteLength;
|
|
224
|
+
const resolved = resolve(wanted, total);
|
|
225
|
+
if (!resolved) return unsatisfiableResponse(total);
|
|
226
|
+
return toResponse({
|
|
227
|
+
body: streamOf(bytes.subarray(resolved.offset, resolved.offset + resolved.length)),
|
|
228
|
+
contentType,
|
|
229
|
+
partial: {
|
|
230
|
+
...resolved,
|
|
231
|
+
total
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/** Bytes as a body. The cast is the workers runtime's stream against the DOM's declaration. */
|
|
236
|
+
function streamOf(bytes) {
|
|
237
|
+
return new Response(bytes).body;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Content type from the key, for objects stored without one.
|
|
241
|
+
*
|
|
242
|
+
* R2 keeps whatever `httpMetadata` was set at upload, and rclone does set it, but an object
|
|
243
|
+
* put by hand through the dashboard has none. Serving those as `application/octet-stream`
|
|
244
|
+
* makes a browser download a favicon instead of drawing it.
|
|
245
|
+
*/
|
|
246
|
+
function contentTypeFor(key) {
|
|
247
|
+
switch (key.split(".").pop()?.toLowerCase() ?? "") {
|
|
248
|
+
case "avif": return "image/avif";
|
|
249
|
+
case "svg": return "image/svg+xml";
|
|
250
|
+
case "png": return "image/png";
|
|
251
|
+
case "jpg":
|
|
252
|
+
case "jpeg": return "image/jpeg";
|
|
253
|
+
case "ico": return "image/x-icon";
|
|
254
|
+
case "mp4": return "video/mp4";
|
|
255
|
+
case "vtt": return "text/vtt";
|
|
256
|
+
case "woff2": return "font/woff2";
|
|
257
|
+
case "json": return "application/json";
|
|
258
|
+
case "txt": return "text/plain; charset=utf-8";
|
|
259
|
+
case "md": return "text/markdown; charset=utf-8";
|
|
260
|
+
default: return "application/octet-stream";
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
//#endregion
|
|
264
|
+
export { contentTypeFor, isContentId, isUnsatisfiable, measure, rangedResponse, read, recordKey, storageKey, toResponse, unsatisfiableResponse };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region symlink/src/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Ask `name` -- a `/symlink/...` address on the alias layer -- and answer with where it points:
|
|
4
|
+
* a `302` there while it resolves, a `404` while the corpus names nothing, a `502` otherwise.
|
|
5
|
+
* Each is stamped as the alias layer stamps its own.
|
|
6
|
+
*/
|
|
7
|
+
export declare function followSymlink(name: string, fetcher?: typeof fetch): Promise<Response>;
|
|
8
|
+
/** Where a scope's file is named, `symlink` being where the alias layer answers fixed names. */
|
|
9
|
+
export declare function symlinkOf(symlink: string, scope: string, file: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* The address `name` stands for right now, or `undefined` while it resolves to nothing. For a page
|
|
12
|
+
* that writes the object into its markup rather than redirecting to it; a failure is not kept.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveSymlink(name: string, fetcher?: typeof fetch): Promise<string | undefined>;
|
|
15
|
+
/** Each of `files` in `scope`, resolved together: what a head names its marks by. */
|
|
16
|
+
export declare function marksOf<File extends string>(symlink: string, scope: string, files: readonly File[], fetcher?: typeof fetch): Promise<Partial<Record<File, string>>>;
|
|
17
|
+
/**
|
|
18
|
+
* The bytes `name` stands for, answered from the asking origin rather than redirected to: for a
|
|
19
|
+
* file a browser uses only from the document's own origin, such as a sitemap's XSL stylesheet. A
|
|
20
|
+
* miss and a failure are answered as `followSymlink` answers them; the bytes are kept as the alias
|
|
21
|
+
* layer keeps its redirect.
|
|
22
|
+
*/
|
|
23
|
+
export declare function serveSymlink(name: string, contentType: string, fetcher?: typeof fetch): Promise<Response>;
|
|
24
|
+
//#endregion
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { PUBLICATION_DELAY, PUBLISHED, RESOLVED } from "../../cache/src/index.js";
|
|
2
|
+
//#region symlink/src/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* A fixed name, followed for a browser: the page asks the alias layer what the name means and
|
|
5
|
+
* hands the browser that object, one hop instead of two. The mapping stays the alias layer's; a
|
|
6
|
+
* page knows a name and nothing behind it. See spec/architecture/delivery.md, "A page follows the
|
|
7
|
+
* name for the browser".
|
|
8
|
+
*/
|
|
9
|
+
/** An answer about this moment rather than about the corpus, so nothing may keep it. */
|
|
10
|
+
const NEVER = "no-store";
|
|
11
|
+
function answer(status, cacheControl, location) {
|
|
12
|
+
const headers = new Headers({ "Cache-Control": cacheControl });
|
|
13
|
+
if (location) headers.set("Location", location);
|
|
14
|
+
return new Response(null, {
|
|
15
|
+
status,
|
|
16
|
+
headers
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ask `name` -- a `/symlink/...` address on the alias layer -- and answer with where it points:
|
|
21
|
+
* a `302` there while it resolves, a `404` while the corpus names nothing, a `502` otherwise.
|
|
22
|
+
* Each is stamped as the alias layer stamps its own.
|
|
23
|
+
*/
|
|
24
|
+
async function followSymlink(name, fetcher = fetch) {
|
|
25
|
+
let asked;
|
|
26
|
+
try {
|
|
27
|
+
asked = await fetcher(name, { redirect: "manual" });
|
|
28
|
+
} catch {
|
|
29
|
+
return answer(502, NEVER);
|
|
30
|
+
}
|
|
31
|
+
const target = asked.headers.get("Location");
|
|
32
|
+
if (asked.status >= 300 && asked.status < 400 && target) return answer(302, RESOLVED, new URL(target, name).href);
|
|
33
|
+
if (asked.status === 404) return answer(404, PUBLISHED);
|
|
34
|
+
return answer(502, NEVER);
|
|
35
|
+
}
|
|
36
|
+
/** Where a scope's file is named, `symlink` being where the alias layer answers fixed names. */
|
|
37
|
+
function symlinkOf(symlink, scope, file) {
|
|
38
|
+
return `${symlink}/${scope}/${file}`;
|
|
39
|
+
}
|
|
40
|
+
/** What each name last resolved to, held as long as the alias layer says its answer may be. */
|
|
41
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
42
|
+
/**
|
|
43
|
+
* The address `name` stands for right now, or `undefined` while it resolves to nothing. For a page
|
|
44
|
+
* that writes the object into its markup rather than redirecting to it; a failure is not kept.
|
|
45
|
+
*/
|
|
46
|
+
async function resolveSymlink(name, fetcher = fetch) {
|
|
47
|
+
const held = resolved.get(name);
|
|
48
|
+
if (held && held.until > Date.now()) return held.target;
|
|
49
|
+
const answered = await followSymlink(name, fetcher);
|
|
50
|
+
const target = answered.headers.get("Location");
|
|
51
|
+
if (answered.status !== 302 || !target) return void 0;
|
|
52
|
+
resolved.set(name, {
|
|
53
|
+
target,
|
|
54
|
+
until: Date.now() + PUBLICATION_DELAY * 1e3
|
|
55
|
+
});
|
|
56
|
+
return target;
|
|
57
|
+
}
|
|
58
|
+
/** Each of `files` in `scope`, resolved together: what a head names its marks by. */
|
|
59
|
+
async function marksOf(symlink, scope, files, fetcher = fetch) {
|
|
60
|
+
const targets = await Promise.all(files.map((file) => resolveSymlink(symlinkOf(symlink, scope, file), fetcher)));
|
|
61
|
+
return Object.fromEntries(files.flatMap((file, index) => targets[index] ? [[file, targets[index]]] : []));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The bytes `name` stands for, answered from the asking origin rather than redirected to: for a
|
|
65
|
+
* file a browser uses only from the document's own origin, such as a sitemap's XSL stylesheet. A
|
|
66
|
+
* miss and a failure are answered as `followSymlink` answers them; the bytes are kept as the alias
|
|
67
|
+
* layer keeps its redirect.
|
|
68
|
+
*/
|
|
69
|
+
async function serveSymlink(name, contentType, fetcher = fetch) {
|
|
70
|
+
const followed = await followSymlink(name, fetcher);
|
|
71
|
+
const target = followed.headers.get("Location");
|
|
72
|
+
if (followed.status !== 302 || !target) return followed;
|
|
73
|
+
let object;
|
|
74
|
+
try {
|
|
75
|
+
object = await fetcher(target);
|
|
76
|
+
} catch {
|
|
77
|
+
return answer(502, NEVER);
|
|
78
|
+
}
|
|
79
|
+
if (!object.ok) return answer(502, NEVER);
|
|
80
|
+
return new Response(object.body, {
|
|
81
|
+
status: 200,
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": contentType,
|
|
84
|
+
"Cache-Control": RESOLVED
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { followSymlink, marksOf, resolveSymlink, serveSymlink, symlinkOf };
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@monoflake/sdk",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "The platform's addresses, and every client of its services and its policies behind a subpath",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/monoflake/platform.git",
|
|
9
|
+
"directory": "libs/sdk"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/src/index.d.ts",
|
|
18
|
+
"default": "./dist/src/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./artifacts": {
|
|
21
|
+
"types": "./dist/artifacts/src/index.d.ts",
|
|
22
|
+
"default": "./dist/artifacts/src/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./artifacts/types": {
|
|
25
|
+
"types": "./dist/artifacts/src/types.d.ts",
|
|
26
|
+
"default": "./dist/artifacts/src/types.js"
|
|
27
|
+
},
|
|
28
|
+
"./artifacts/anchors": {
|
|
29
|
+
"types": "./dist/artifacts/src/anchors.d.ts",
|
|
30
|
+
"default": "./dist/artifacts/src/anchors.js"
|
|
31
|
+
},
|
|
32
|
+
"./cache": {
|
|
33
|
+
"types": "./dist/cache/src/index.d.ts",
|
|
34
|
+
"default": "./dist/cache/src/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./imgsrc": {
|
|
37
|
+
"types": "./dist/imgsrc/src/index.d.ts",
|
|
38
|
+
"default": "./dist/imgsrc/src/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./limits": {
|
|
41
|
+
"types": "./dist/limits/src/index.d.ts",
|
|
42
|
+
"default": "./dist/limits/src/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./robots": {
|
|
45
|
+
"types": "./dist/robots/src/index.d.ts",
|
|
46
|
+
"default": "./dist/robots/src/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./security": {
|
|
49
|
+
"types": "./dist/security/src/index.d.ts",
|
|
50
|
+
"default": "./dist/security/src/index.js"
|
|
51
|
+
},
|
|
52
|
+
"./security/agents": {
|
|
53
|
+
"types": "./dist/security/src/agents.d.ts",
|
|
54
|
+
"default": "./dist/security/src/agents.js"
|
|
55
|
+
},
|
|
56
|
+
"./store": {
|
|
57
|
+
"types": "./dist/store/src/index.d.ts",
|
|
58
|
+
"default": "./dist/store/src/index.js"
|
|
59
|
+
},
|
|
60
|
+
"./symlink": {
|
|
61
|
+
"types": "./dist/symlink/src/index.d.ts",
|
|
62
|
+
"default": "./dist/symlink/src/index.js"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@canmi/me": "^2026.1004.1",
|
|
67
|
+
"@canmi/response": "^2.0.0",
|
|
68
|
+
"@monoflake/urls": "^2026.1004.0",
|
|
69
|
+
"valibot": "^1.5.0"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@cloudflare/workers-types": "^5.20261004.1"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@cloudflare/workers-types": "^5.20261004.1"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"@cloudflare/workers-types": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"scripts": {
|
|
83
|
+
"build": "tsdown"
|
|
84
|
+
}
|
|
85
|
+
}
|