create-monolayer-app 0.2.2 → 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/package.json +2 -2
- package/template/_gitignore +9 -4
- package/template/app/routes/files.ts +63 -0
- package/template/app/routes.ts +5 -2
- package/template/oxfmt.config.ts +6 -1
- package/template/oxlint.config.ts +3 -8
- package/template/package.json +2 -1
- package/template/vendor/file-bucket/client.ts +196 -0
- package/template/vendor/file-bucket/errors.ts +26 -0
- package/template/vendor/file-bucket/index.ts +7 -0
- package/template/vendor/file-bucket/multipart.ts +304 -0
- package/template/vendor/file-bucket/protocol.ts +222 -0
- package/template/vendor/file-bucket/react-router.ts +222 -0
- package/template/vendor/file-bucket/server.ts +544 -0
- package/template/vendor/file-bucket/transfers.ts +23 -0
- package/template/vendor/file-bucket/transport.ts +37 -0
- package/template/vendor/file-bucket/types.ts +153 -0
- package/template/vendor/file-bucket/vite.d.ts +7 -0
- package/template/vendor/file-bucket/vite.js +2482 -0
- package/template/vendor/file-bucket/xml.ts +325 -0
- package/template/vendor/package-app/package-app.mjs +116 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-monolayer-app",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI to scaffold a new
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "CLI to scaffold a new monolayer application",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-monolayer-app": "./bin/index.js"
|
package/template/_gitignore
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
.DS_Store
|
|
2
2
|
|
|
3
3
|
.env
|
|
4
|
-
/node_modules/
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
node_modules/
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
/
|
|
7
|
+
# React Router
|
|
8
|
+
.react-router/
|
|
9
|
+
build/
|
|
10
10
|
|
|
11
11
|
.pglite_development/
|
|
12
12
|
.pglite_test/
|
|
13
|
+
|
|
14
|
+
.config/kysely.config.js
|
|
15
|
+
.config/kysely.config.js.map
|
|
16
|
+
|
|
17
|
+
.emulator/
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { FileBucket } from "../../vendor/file-bucket";
|
|
2
|
+
import { parseFileBucketRequest } from "../../vendor/file-bucket/protocol";
|
|
3
|
+
import {
|
|
4
|
+
dispatch,
|
|
5
|
+
MAX_BATCH_KEYS,
|
|
6
|
+
MAX_REQUEST_BYTES,
|
|
7
|
+
readLimitedJson,
|
|
8
|
+
type FileBucketRequest,
|
|
9
|
+
} from "../../vendor/file-bucket/react-router";
|
|
10
|
+
|
|
11
|
+
export const action = async ({
|
|
12
|
+
request,
|
|
13
|
+
}: {
|
|
14
|
+
request: Request;
|
|
15
|
+
}): Promise<Response> => {
|
|
16
|
+
if (request.method !== "POST") {
|
|
17
|
+
return new Response("Method not allowed", {
|
|
18
|
+
status: 405,
|
|
19
|
+
headers: { Allow: "POST" },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const contentTypeHeader = request.headers.get("content-type");
|
|
24
|
+
const mediaType = contentTypeHeader?.toLowerCase().split(";")[0]?.trim();
|
|
25
|
+
if (mediaType !== "application/json") {
|
|
26
|
+
return new Response("Unsupported Media Type", { status: 415 });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const contentLengthHeader = request.headers.get("content-length");
|
|
30
|
+
if (contentLengthHeader !== null) {
|
|
31
|
+
const declaredLength = Number(contentLengthHeader);
|
|
32
|
+
if (!Number.isNaN(declaredLength) && declaredLength > MAX_REQUEST_BYTES) {
|
|
33
|
+
return new Response("Request too large", { status: 413 });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let value: unknown;
|
|
38
|
+
try {
|
|
39
|
+
value = await readLimitedJson(request, MAX_REQUEST_BYTES);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error instanceof Response) return error;
|
|
42
|
+
return new Response("Invalid JSON", { status: 400 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let input: Readonly<FileBucketRequest>;
|
|
46
|
+
try {
|
|
47
|
+
input = parseFileBucketRequest(value, { maxBatchKeys: MAX_BATCH_KEYS });
|
|
48
|
+
} catch {
|
|
49
|
+
return new Response("Invalid file request", { status: 400 });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const bucket = new FileBucket(input.bucketId);
|
|
53
|
+
const result = await dispatch(bucket, input);
|
|
54
|
+
if (result === undefined) {
|
|
55
|
+
return new Response(null, {
|
|
56
|
+
status: 204,
|
|
57
|
+
headers: { "Cache-Control": "no-store" },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return Response.json(result, {
|
|
61
|
+
headers: { "Cache-Control": "private, no-store" },
|
|
62
|
+
});
|
|
63
|
+
};
|
package/template/app/routes.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
import { type RouteConfig, index } from "@react-router/dev/routes";
|
|
1
|
+
import { type RouteConfig, index, route } from "@react-router/dev/routes";
|
|
2
2
|
|
|
3
|
-
export default [
|
|
3
|
+
export default [
|
|
4
|
+
index("routes/home.tsx"),
|
|
5
|
+
route("resources/files", "routes/files.ts"),
|
|
6
|
+
] satisfies RouteConfig;
|
package/template/oxfmt.config.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { defineConfig } from "oxlint";
|
|
2
2
|
import antiSlop from "ultracite/oxlint/anti-slop";
|
|
3
3
|
import core from "ultracite/oxlint/core";
|
|
4
|
-
import { jsPluginSettings, selectJsPlugins } from "ultracite/oxlint/js-plugins";
|
|
5
4
|
import react from "ultracite/oxlint/react";
|
|
6
5
|
import remix from "ultracite/oxlint/remix";
|
|
7
6
|
|
|
8
|
-
const jsPlugins = selectJsPlugins(["react-doctor"]);
|
|
9
|
-
|
|
10
7
|
export default defineConfig({
|
|
11
|
-
extends: [core, react, remix, antiSlop
|
|
12
|
-
ignorePatterns: core.ignorePatterns,
|
|
13
|
-
jsPlugins: jsPlugins.jsPlugins,
|
|
8
|
+
extends: [core, react, remix, antiSlop],
|
|
9
|
+
ignorePatterns: [...(core.ignorePatterns ?? []), "vendor/file-bucket/**"],
|
|
14
10
|
rules: {
|
|
15
11
|
"react/function-component-definition": [
|
|
16
12
|
"error",
|
|
@@ -28,8 +24,8 @@ export default defineConfig({
|
|
|
28
24
|
],
|
|
29
25
|
"no-use-before-define": "off",
|
|
30
26
|
"no-empty-pattern": "off",
|
|
31
|
-
"react-doctor/rendering-svg-precision": "off",
|
|
32
27
|
"arrow-body-style": "off",
|
|
28
|
+
curly: "off",
|
|
33
29
|
},
|
|
34
30
|
overrides: [
|
|
35
31
|
{
|
|
@@ -41,5 +37,4 @@ export default defineConfig({
|
|
|
41
37
|
},
|
|
42
38
|
},
|
|
43
39
|
],
|
|
44
|
-
settings: jsPluginSettings,
|
|
45
40
|
});
|
package/template/package.json
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"postmigrate:down": "npm run db:create-types",
|
|
17
17
|
"migrate:list": "kysely migrate:list",
|
|
18
18
|
"migrate:make": "tsx vendor/kysely-dsql/generator/create-migration.ts",
|
|
19
|
-
"db:create-types": "tsx vendor/kysely-dsql/generator/generate-types-from-pglite.ts"
|
|
19
|
+
"db:create-types": "tsx vendor/kysely-dsql/generator/generate-types-from-pglite.ts",
|
|
20
|
+
"package-app": "node vendor/package-app/package-app.mjs"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"@aws-sdk/dsql-signer": "^3.1126.0",
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { FileBucket } from "./index.js";
|
|
2
|
+
import { createMultipartUploadClient } from "./multipart.js";
|
|
3
|
+
import { uploadSignedUrl } from "./transfers.js";
|
|
4
|
+
import { request } from "./transport.js";
|
|
5
|
+
import type { GetUrlOptions, PutOptions, PutUrlOptions } from "./types.js";
|
|
6
|
+
|
|
7
|
+
export { createMultipartUploadClient } from "./multipart.js";
|
|
8
|
+
export type { UploadGrant } from "./protocol.js";
|
|
9
|
+
export type {
|
|
10
|
+
FileMultipartUploadDescriptor,
|
|
11
|
+
FileMultipartUploadClient,
|
|
12
|
+
UploadOptions,
|
|
13
|
+
UploadProgress,
|
|
14
|
+
GetUrlOptions,
|
|
15
|
+
PutOptions,
|
|
16
|
+
PutUrlOptions,
|
|
17
|
+
} from "./types.js";
|
|
18
|
+
export type BrowserUploadBody =
|
|
19
|
+
| Blob
|
|
20
|
+
| string
|
|
21
|
+
| Uint8Array<ArrayBuffer>
|
|
22
|
+
| ArrayBuffer;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Delete a single object from storage via the signing resource endpoint.
|
|
26
|
+
*
|
|
27
|
+
* Deleting a missing key succeeds. Cancellation stops waiting but cannot undo
|
|
28
|
+
* server-side deletion. Endpoint failures reject the promise.
|
|
29
|
+
*
|
|
30
|
+
* @param bucket - The `FileBucket` target definition.
|
|
31
|
+
* @param path - Key of the object to delete.
|
|
32
|
+
* @param options - Optional settings including an `AbortSignal`.
|
|
33
|
+
* @returns A promise that resolves when the deletion is completed by the server.
|
|
34
|
+
*/
|
|
35
|
+
function deleteFile(
|
|
36
|
+
bucket: FileBucket,
|
|
37
|
+
path: string,
|
|
38
|
+
options: { signal?: AbortSignal } = {}
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
return request(
|
|
41
|
+
{ operation: "delete", bucketId: bucket.name, path },
|
|
42
|
+
options.signal
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Delete multiple objects from storage in batch via the signing resource endpoint.
|
|
48
|
+
*
|
|
49
|
+
* An empty list sends no request unless cancellation has already been requested,
|
|
50
|
+
* in which case the promise rejects. Batches are not atomic: some keys may be
|
|
51
|
+
* deleted before an error. Cancellation cannot undo completed deletions.
|
|
52
|
+
*
|
|
53
|
+
* @param bucket - The `FileBucket` target definition.
|
|
54
|
+
* @param paths - Array of object keys to delete.
|
|
55
|
+
* @param options - Optional settings including an `AbortSignal`.
|
|
56
|
+
* @returns A promise that resolves when batch deletion is completed by the server.
|
|
57
|
+
*/
|
|
58
|
+
export async function deleteBatch(
|
|
59
|
+
bucket: FileBucket,
|
|
60
|
+
paths: string[],
|
|
61
|
+
options: { signal?: AbortSignal } = {}
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
options.signal?.throwIfAborted();
|
|
64
|
+
if (!paths.length) return;
|
|
65
|
+
await request(
|
|
66
|
+
{ operation: "deleteBatch", bucketId: bucket.name, paths },
|
|
67
|
+
options.signal
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Request a presigned GET URL for an object from the server without initiating a download.
|
|
73
|
+
*
|
|
74
|
+
* Signing does not check object existence. Content disposition is unchanged by
|
|
75
|
+
* default; URL lifetime defaults to 3600 seconds. Cancellation applies to signing.
|
|
76
|
+
*
|
|
77
|
+
* @param bucket - The `FileBucket` target definition.
|
|
78
|
+
* @param path - Key of the object to access.
|
|
79
|
+
* @param options - Optional URL configuration such as `expiresIn`, `contentDisposition`, and `signal`.
|
|
80
|
+
* @returns A promise resolving to the presigned GET URL string.
|
|
81
|
+
*/
|
|
82
|
+
export async function getUrl(
|
|
83
|
+
bucket: FileBucket,
|
|
84
|
+
path: string,
|
|
85
|
+
options: GetUrlOptions & { signal?: AbortSignal } = {}
|
|
86
|
+
): Promise<string> {
|
|
87
|
+
const { signal, ...signingOptions } = options;
|
|
88
|
+
const result = await request(
|
|
89
|
+
{ ...signingOptions, operation: "getUrl", bucketId: bucket.name, path },
|
|
90
|
+
signal
|
|
91
|
+
);
|
|
92
|
+
return result.url;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Request a presigned single-PUT upload URL from the server without sending file bytes.
|
|
97
|
+
*
|
|
98
|
+
* Send the raw body with the same content-type and cache-control headers used
|
|
99
|
+
* when signing. Metadata is carried in the signed query. No multipart session
|
|
100
|
+
* is created. URL lifetime defaults to 3600 seconds; cancellation applies to signing.
|
|
101
|
+
*
|
|
102
|
+
* @param bucket - The `FileBucket` target definition.
|
|
103
|
+
* @param path - Target key for the uploaded object.
|
|
104
|
+
* @param options - Optional URL configuration such as `expiresIn`, `contentType`, `metadata`, `cacheControl`, and `signal`.
|
|
105
|
+
* @returns A promise resolving to the presigned PUT URL string.
|
|
106
|
+
*/
|
|
107
|
+
export async function putUrl(
|
|
108
|
+
bucket: FileBucket,
|
|
109
|
+
path: string,
|
|
110
|
+
options: PutUrlOptions & { signal?: AbortSignal } = {}
|
|
111
|
+
): Promise<string> {
|
|
112
|
+
const { signal, ...signingOptions } = options;
|
|
113
|
+
const result = await request(
|
|
114
|
+
{ ...signingOptions, operation: "putUrl", bucketId: bucket.name, path },
|
|
115
|
+
signal
|
|
116
|
+
);
|
|
117
|
+
return result.url;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Request an attachment download URL from the server and trigger native browser download navigation.
|
|
122
|
+
*
|
|
123
|
+
* Resolves as soon as the browser initiates the download link; does not buffer file bytes in memory.
|
|
124
|
+
* It cannot confirm saving or report subsequent storage errors. Cancellation
|
|
125
|
+
* applies before handoff and cannot stop the native download afterward.
|
|
126
|
+
*
|
|
127
|
+
* @param bucket - The `FileBucket` target definition.
|
|
128
|
+
* @param path - Key of the object to download.
|
|
129
|
+
* @param options - Optional settings including an `AbortSignal`.
|
|
130
|
+
* @returns A promise that resolves once the download has been triggered in the browser.
|
|
131
|
+
*/
|
|
132
|
+
export async function get(
|
|
133
|
+
bucket: FileBucket,
|
|
134
|
+
path: string,
|
|
135
|
+
options: { signal?: AbortSignal } = {}
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
const grant = await request(
|
|
138
|
+
{ operation: "get", bucketId: bucket.name, path },
|
|
139
|
+
options.signal
|
|
140
|
+
);
|
|
141
|
+
const link = document.createElement("a");
|
|
142
|
+
link.href = grant.url;
|
|
143
|
+
link.download = "";
|
|
144
|
+
link.hidden = true;
|
|
145
|
+
document.body.append(link);
|
|
146
|
+
try {
|
|
147
|
+
link.click();
|
|
148
|
+
} finally {
|
|
149
|
+
link.remove();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Upload a file or data directly to S3 from the browser using a server-issued grant.
|
|
155
|
+
*
|
|
156
|
+
* Uses `/resources/files` for signing, then sends bytes directly to storage.
|
|
157
|
+
* The standard resource action selects a single PUT below 8 MiB and multipart
|
|
158
|
+
* at or above 8 MiB. Reusing a key overwrites its object.
|
|
159
|
+
*
|
|
160
|
+
* Single PUT reports progress after completion; multipart reports completed part
|
|
161
|
+
* bytes and attempts cleanup on cancellation or failure. A Blob's content type
|
|
162
|
+
* is used unless overridden. Endpoint or storage failures reject the promise.
|
|
163
|
+
*
|
|
164
|
+
* @param bucket - The `FileBucket` target definition.
|
|
165
|
+
* @param path - Key of the destination object.
|
|
166
|
+
* @param body - The payload to upload (`Blob`, `File`, `string`, `Uint8Array`, or `ArrayBuffer`).
|
|
167
|
+
* @param options - Optional upload settings including `contentType`, `metadata`, `cacheControl`, `onProgress`, and `signal`.
|
|
168
|
+
* @returns A promise that resolves when the upload is confirmed complete.
|
|
169
|
+
*/
|
|
170
|
+
export async function put(
|
|
171
|
+
bucket: FileBucket,
|
|
172
|
+
path: string,
|
|
173
|
+
body: BrowserUploadBody,
|
|
174
|
+
options: PutOptions = {}
|
|
175
|
+
): Promise<void> {
|
|
176
|
+
options.signal?.throwIfAborted();
|
|
177
|
+
const file = body instanceof Blob ? body : new Blob([body]);
|
|
178
|
+
const grant = await request(
|
|
179
|
+
{
|
|
180
|
+
operation: "put",
|
|
181
|
+
bucketId: bucket.name,
|
|
182
|
+
path,
|
|
183
|
+
size: file.size,
|
|
184
|
+
contentType: options.contentType ?? (file.type || undefined),
|
|
185
|
+
metadata: options.metadata,
|
|
186
|
+
cacheControl: options.cacheControl,
|
|
187
|
+
},
|
|
188
|
+
options.signal
|
|
189
|
+
);
|
|
190
|
+
if (grant.type === "multipart") {
|
|
191
|
+
await createMultipartUploadClient(grant.descriptor).upload(file, options);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
await uploadSignedUrl(grant.url, file, grant.headers, options);
|
|
195
|
+
}
|
|
196
|
+
export { deleteFile as delete };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Modified
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Error names returned by FileBucket actions.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* try {
|
|
11
|
+
* await put(bucket, 'report.txt', body);
|
|
12
|
+
* } catch (e: unknown) {
|
|
13
|
+
* if (e instanceof Error && e.name === FileBucketErrors.FileTooLarge) {
|
|
14
|
+
* // handle an oversized file
|
|
15
|
+
* }
|
|
16
|
+
* throw e;
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export const FileBucketErrors = {
|
|
21
|
+
FileNotFound: "NoSuchKey",
|
|
22
|
+
FileTooLarge: "EntityTooLarge",
|
|
23
|
+
UploadExpired: "UploadExpired",
|
|
24
|
+
UploadCompletionUnknown: "UploadCompletionUnknown",
|
|
25
|
+
InvalidUploadState: "InvalidUploadState",
|
|
26
|
+
} as const;
|