@protobase/server 0.30.0 → 0.30.2

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.
@@ -0,0 +1,37 @@
1
+ import { accepts } from "../files/media-types.js";
2
+ import { findBucket } from "./bucket-sql.js";
3
+ import { bucketNotFound, mimeTypeRefused } from "./storage-error.js";
4
+ import { asApp } from "./storage-db.js";
5
+ import { writeObject } from "./write-object.js";
6
+ import { incomingObject } from "./object-body.js";
7
+ //#region src/storage/upload-object.ts
8
+ /** The bucket an upload goes to, read as the app, as Supabase Storage reads it: its size limit and allowed types. */
9
+ const uploadBucket = async (deps, id) => {
10
+ const bucket = await asApp(deps, (trx) => findBucket(trx, id));
11
+ if (!bucket) throw bucketNotFound();
12
+ return bucket;
13
+ };
14
+ /**
15
+ * Stores the object a request uploads at `target` (see `writeObject`), within the bucket's size limit and the app's,
16
+ * and of a type the bucket allows. Answers what storage-js reads back: the row's id and `<bucket>/<name>`.
17
+ */
18
+ const uploadObject = async (deps, request, target) => {
19
+ const bucket = await uploadBucket(deps, target.bucket);
20
+ const maxBytes = Math.min(bucket.file_size_limit ?? Infinity, deps.maxBytes);
21
+ const incoming = await incomingObject(request, maxBytes);
22
+ const type = incoming.type.split(";", 1)[0].trim().toLowerCase();
23
+ if (bucket.allowed_mime_types?.length && !accepts(bucket.allowed_mime_types, type)) {
24
+ await incoming.body.cancel();
25
+ throw mimeTypeRefused(type);
26
+ }
27
+ const { id } = await writeObject(deps, target, {
28
+ ...incoming,
29
+ store: (key) => deps.store.put(key, incoming.body)
30
+ });
31
+ return {
32
+ Id: id,
33
+ Key: `${target.bucket}/${target.name}`
34
+ };
35
+ };
36
+ //#endregion
37
+ export { uploadBucket, uploadObject };
@@ -0,0 +1,85 @@
1
+ import { accessDenied, alreadyExists } from "./storage-error.js";
2
+ import { objectKey } from "./object-key.js";
3
+ import { findObject, insertObject, updateObject } from "./object-sql.js";
4
+ import { asApp, becomeCaller } from "./storage-db.js";
5
+ //#region src/storage/write-object.ts
6
+ /** The owner Supabase records for a write: the signed-in user, or nobody for the keys alone. */
7
+ const ownerOf = (caller) => caller.role === "authenticated" && typeof caller.claims.sub === "string" ? caller.claims.sub : null;
8
+ const writeRow = async (trx, deps, target, write) => {
9
+ const existing = await findObject(trx, target.bucket, target.name, true);
10
+ if (existing && !target.upsert) throw alreadyExists();
11
+ await becomeCaller(trx, deps, target.caller);
12
+ if (!existing) return {
13
+ id: await insertObject(trx, target.bucket, target.name, write),
14
+ replaced: void 0
15
+ };
16
+ if (!await updateObject(trx, existing.id, write)) throw accessDenied();
17
+ return {
18
+ id: existing.id,
19
+ replaced: existing
20
+ };
21
+ };
22
+ var ProbeDone = class extends Error {};
23
+ /**
24
+ * Whether the caller may write the object, decided by the restored policies on a row written and rolled back, before
25
+ * any byte is stored: a refused upload costs no storage. Throws the refusal.
26
+ */
27
+ const probeWrite = async (deps, target, type) => {
28
+ const metadata = {
29
+ eTag: "",
30
+ size: 0,
31
+ mimetype: type,
32
+ cacheControl: "",
33
+ lastModified: (/* @__PURE__ */ new Date()).toISOString(),
34
+ contentLength: 0,
35
+ httpStatusCode: 200
36
+ };
37
+ await asApp(deps, async (trx) => {
38
+ await writeRow(trx, deps, target, {
39
+ owner: target.owner,
40
+ version: crypto.randomUUID(),
41
+ metadata,
42
+ user_metadata: null
43
+ });
44
+ throw new ProbeDone();
45
+ }).catch((error) => {
46
+ if (!(error instanceof ProbeDone)) throw error;
47
+ });
48
+ };
49
+ /**
50
+ * Writes an object: checks the caller may (`probeWrite`), stores the bytes under a new version, then writes the row
51
+ * as the caller in one transaction, so the policies decide again on the real row. When that fails, the new bytes are
52
+ * deleted; once it commits, the bytes of the object it replaced are.
53
+ */
54
+ const writeObject = async (deps, target, bytes) => {
55
+ await probeWrite(deps, target, bytes.type);
56
+ const version = crypto.randomUUID();
57
+ const key = await objectKey(target.bucket, target.name, version);
58
+ const { size } = await bytes.store(key);
59
+ const metadata = {
60
+ eTag: `"${version}"`,
61
+ size,
62
+ mimetype: bytes.type,
63
+ cacheControl: bytes.cacheControl,
64
+ lastModified: (/* @__PURE__ */ new Date()).toUTCString(),
65
+ contentLength: size,
66
+ httpStatusCode: 200
67
+ };
68
+ const write = {
69
+ owner: target.owner,
70
+ version,
71
+ metadata,
72
+ user_metadata: bytes.userMetadata
73
+ };
74
+ const written = await asApp(deps, (trx) => writeRow(trx, deps, target, write)).catch(async (error) => {
75
+ await deps.store.delete(key);
76
+ throw error;
77
+ });
78
+ if (written.replaced) await deps.store.delete(await objectKey(target.bucket, target.name, written.replaced.version));
79
+ return {
80
+ id: written.id,
81
+ metadata
82
+ };
83
+ };
84
+ //#endregion
85
+ export { ownerOf, probeWrite, writeObject };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.30.0",
3
+ "version": "0.30.2",
4
4
  "description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -32,9 +32,9 @@
32
32
  "dependencies": {
33
33
  "@better-auth/passkey": "^1.7.7",
34
34
  "@better-auth/sso": "^1.7.7",
35
- "@protobase/layout": "0.30.0",
36
- "@protobase/query": "0.30.0",
37
- "@protobase/schema": "0.30.0",
35
+ "@protobase/layout": "0.30.2",
36
+ "@protobase/query": "0.30.2",
37
+ "@protobase/schema": "0.30.2",
38
38
  "@react-email/render": "^2.1.0",
39
39
  "@scalar/hono-api-reference": "^0.12.10",
40
40
  "@sentry/core": "^11.7.0",