appflare 0.0.28 → 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/cli/commands/index.ts +140 -0
- package/cli/generate.ts +149 -0
- package/cli/index.ts +56 -447
- package/cli/load-config.ts +182 -0
- package/cli/schema-compiler.ts +657 -0
- package/cli/templates/auth/README.md +156 -0
- package/cli/templates/auth/config.ts +61 -0
- package/cli/templates/auth/route-config.ts +18 -0
- package/cli/templates/auth/route-handler.ts +18 -0
- package/cli/templates/auth/route-request-utils.ts +55 -0
- package/cli/templates/auth/route.ts +14 -0
- package/cli/templates/core/README.md +266 -0
- package/cli/templates/core/app-creation.ts +19 -0
- package/cli/templates/core/client/appflare.ts +37 -0
- package/cli/templates/core/client/index.ts +6 -0
- package/cli/templates/core/client/storage.ts +100 -0
- package/cli/templates/core/client/types.ts +54 -0
- package/cli/templates/core/client-modules/appflare.ts +112 -0
- package/cli/templates/core/client-modules/handlers/index.ts +740 -0
- package/cli/templates/core/client-modules/handlers.ts +1 -0
- package/cli/templates/core/client-modules/index.ts +7 -0
- package/cli/templates/core/client-modules/storage.ts +180 -0
- package/cli/templates/core/client-modules/types.ts +145 -0
- package/cli/templates/core/client.ts +39 -0
- package/cli/templates/core/drizzle.ts +15 -0
- package/cli/templates/core/export.ts +14 -0
- package/cli/templates/core/handlers-route.ts +23 -0
- package/cli/templates/core/handlers.ts +1 -0
- package/cli/templates/core/imports.ts +8 -0
- package/cli/templates/core/server.ts +38 -0
- package/cli/templates/core/types.ts +6 -0
- package/cli/templates/core/wrangler.ts +109 -0
- package/cli/templates/handlers/README.md +265 -0
- package/cli/templates/handlers/auth.ts +36 -0
- package/cli/templates/handlers/execution.ts +39 -0
- package/cli/templates/handlers/generators/context/context-creation.ts +80 -0
- package/cli/templates/handlers/generators/context/error-helpers.ts +11 -0
- package/cli/templates/handlers/generators/context/scheduler.ts +24 -0
- package/cli/templates/handlers/generators/context/storage-api.ts +112 -0
- package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -0
- package/cli/templates/handlers/generators/context/types.ts +18 -0
- package/cli/templates/handlers/generators/context.ts +43 -0
- package/cli/templates/handlers/generators/execution.ts +15 -0
- package/cli/templates/handlers/generators/handlers.ts +13 -0
- package/cli/templates/handlers/index.ts +43 -0
- package/cli/templates/handlers/operations.ts +116 -0
- package/cli/templates/handlers/registration.ts +1114 -0
- package/cli/templates/handlers/types.ts +960 -0
- package/cli/templates/handlers/utils.ts +48 -0
- package/cli/types.ts +108 -0
- package/cli/utils/handler-discovery.ts +366 -0
- package/cli/utils/json-utils.ts +24 -0
- package/cli/utils/path-utils.ts +19 -0
- package/cli/utils/schema-discovery.ts +390 -0
- package/index.ts +27 -4
- package/package.json +23 -20
- package/react/index.ts +5 -3
- package/react/use-infinite-query.ts +190 -0
- package/react/use-mutation.ts +54 -0
- package/react/use-query.ts +158 -0
- package/schema.ts +262 -0
- package/tsconfig.json +2 -4
- package/cli/README.md +0 -108
- package/cli/core/build.ts +0 -187
- package/cli/core/config.ts +0 -92
- package/cli/core/discover-handlers.ts +0 -143
- package/cli/core/handlers.ts +0 -7
- package/cli/core/index.ts +0 -205
- package/cli/generators/generate-api-client/client.ts +0 -163
- package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
- package/cli/generators/generate-api-client/index.ts +0 -973
- package/cli/generators/generate-api-client/types.ts +0 -164
- package/cli/generators/generate-api-client/utils.ts +0 -22
- package/cli/generators/generate-api-client.ts +0 -1
- package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
- package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
- package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
- package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
- package/cli/generators/generate-cloudflare-worker.ts +0 -4
- package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
- package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
- package/cli/generators/generate-cron-handlers/index.ts +0 -61
- package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
- package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
- package/cli/generators/generate-db-handlers/index.ts +0 -33
- package/cli/generators/generate-db-handlers/prepare.ts +0 -24
- package/cli/generators/generate-db-handlers/templates.ts +0 -189
- package/cli/generators/generate-db-handlers.ts +0 -1
- package/cli/generators/generate-hono-server/auth.ts +0 -97
- package/cli/generators/generate-hono-server/imports.ts +0 -55
- package/cli/generators/generate-hono-server/index.ts +0 -52
- package/cli/generators/generate-hono-server/routes.ts +0 -115
- package/cli/generators/generate-hono-server/template.ts +0 -371
- package/cli/generators/generate-hono-server.ts +0 -1
- package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
- package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
- package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
- package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
- package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
- package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
- package/cli/generators/generate-scheduler-handlers.ts +0 -1
- package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
- package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
- package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
- package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
- package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
- package/cli/generators/generate-websocket-durable-object.ts +0 -1
- package/cli/schema/schema-static-types.ts +0 -702
- package/cli/schema/schema.ts +0 -151
- package/cli/utils/tsc.ts +0 -54
- package/cli/utils/utils.ts +0 -190
- package/cli/utils/zod-utils.ts +0 -121
- package/lib/README.md +0 -50
- package/lib/db.ts +0 -19
- package/lib/location.ts +0 -110
- package/lib/values.ts +0 -27
- package/react/README.md +0 -67
- package/react/hooks/useMutation.ts +0 -89
- package/react/hooks/usePaginatedQuery.ts +0 -213
- package/react/hooks/useQuery.ts +0 -106
- package/react/shared/queryShared.ts +0 -174
- package/server/README.md +0 -218
- package/server/auth.ts +0 -107
- package/server/database/builders.ts +0 -83
- package/server/database/context.ts +0 -327
- package/server/database/populate.ts +0 -234
- package/server/database/query-builder.ts +0 -161
- package/server/database/query-utils.ts +0 -25
- package/server/db.ts +0 -2
- package/server/storage/auth.ts +0 -16
- package/server/storage/bucket.ts +0 -22
- package/server/storage/context.ts +0 -34
- package/server/storage/index.ts +0 -38
- package/server/storage/operations.ts +0 -149
- package/server/storage/route-handler.ts +0 -60
- package/server/storage/types.ts +0 -55
- package/server/storage/utils.ts +0 -47
- package/server/storage.ts +0 -6
- package/server/types/schema-refs.ts +0 -66
- package/server/types/types.ts +0 -633
- package/server/utils/id-utils.ts +0 -230
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import type { Collection, Document, Filter, FindOptions, Sort } from "mongodb";
|
|
2
|
-
import { applyPopulate } from "./populate";
|
|
3
|
-
import {
|
|
4
|
-
normalizeIdFilter,
|
|
5
|
-
stringifyIdField,
|
|
6
|
-
toMongoFilter,
|
|
7
|
-
} from "../utils/id-utils";
|
|
8
|
-
import type { MongoDbQuery, SchemaRefMap } from "../types/types";
|
|
9
|
-
import { buildProjection, normalizeSort } from "./query-utils";
|
|
10
|
-
|
|
11
|
-
export function createQueryBuilder<
|
|
12
|
-
TTableNames extends string,
|
|
13
|
-
TTableDocMap extends Record<TTableNames, any>,
|
|
14
|
-
TableName extends TTableNames,
|
|
15
|
-
>(params: {
|
|
16
|
-
table: TableName;
|
|
17
|
-
getCollection: (table: string) => Collection<Document>;
|
|
18
|
-
refs: SchemaRefMap;
|
|
19
|
-
}): MongoDbQuery<TableName, TTableDocMap, TTableDocMap[TableName]> {
|
|
20
|
-
let filter: Filter<Document> | undefined;
|
|
21
|
-
let sort: Sort | undefined;
|
|
22
|
-
let limit: number | undefined;
|
|
23
|
-
let offset: number | undefined;
|
|
24
|
-
let selectedKeys: string[] | undefined;
|
|
25
|
-
let populateKeys: string[] = [];
|
|
26
|
-
let populateAggregates: Record<
|
|
27
|
-
string,
|
|
28
|
-
{
|
|
29
|
-
count?: boolean;
|
|
30
|
-
sum?: string[];
|
|
31
|
-
avg?: string[];
|
|
32
|
-
}
|
|
33
|
-
> = {};
|
|
34
|
-
let populateIncludeDocs: Record<string, boolean> = {};
|
|
35
|
-
|
|
36
|
-
const normalizeAggregate = (
|
|
37
|
-
value: unknown
|
|
38
|
-
): { count?: boolean; sum?: string[]; avg?: string[] } | undefined => {
|
|
39
|
-
if (!value || typeof value !== "object") return undefined;
|
|
40
|
-
const spec = value as Record<string, unknown>;
|
|
41
|
-
const out: { count?: boolean; sum?: string[]; avg?: string[] } = {};
|
|
42
|
-
if (spec.count) out.count = true;
|
|
43
|
-
if (Array.isArray(spec.sum)) out.sum = spec.sum.map((k) => String(k));
|
|
44
|
-
if (Array.isArray(spec.avg)) out.avg = spec.avg.map((k) => String(k));
|
|
45
|
-
return Object.keys(out).length ? out : undefined;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const addPopulateKey = (
|
|
49
|
-
key: string,
|
|
50
|
-
opts?: {
|
|
51
|
-
aggregate?: { count?: boolean; sum?: string[]; avg?: string[] };
|
|
52
|
-
includeDocs?: boolean;
|
|
53
|
-
}
|
|
54
|
-
) => {
|
|
55
|
-
const ks = String(key);
|
|
56
|
-
if (!populateKeys.includes(ks)) populateKeys.push(ks);
|
|
57
|
-
if (opts?.aggregate) populateAggregates[ks] = opts.aggregate;
|
|
58
|
-
if (opts?.includeDocs !== undefined)
|
|
59
|
-
populateIncludeDocs[ks] = opts.includeDocs;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const api: MongoDbQuery<any, any, any> = {
|
|
63
|
-
where(next) {
|
|
64
|
-
const nextFilter = normalizeIdFilter(
|
|
65
|
-
toMongoFilter(next as unknown as Filter<Document>)
|
|
66
|
-
);
|
|
67
|
-
if (!filter) {
|
|
68
|
-
filter = nextFilter;
|
|
69
|
-
} else {
|
|
70
|
-
filter = { $and: [filter as any, nextFilter as any] } as any;
|
|
71
|
-
}
|
|
72
|
-
return api;
|
|
73
|
-
},
|
|
74
|
-
sort(next) {
|
|
75
|
-
sort = normalizeSort(next as any);
|
|
76
|
-
return api;
|
|
77
|
-
},
|
|
78
|
-
limit(n) {
|
|
79
|
-
limit = n;
|
|
80
|
-
return api;
|
|
81
|
-
},
|
|
82
|
-
offset(n) {
|
|
83
|
-
offset = n;
|
|
84
|
-
return api;
|
|
85
|
-
},
|
|
86
|
-
select(...args) {
|
|
87
|
-
const keys = Array.isArray(args[0])
|
|
88
|
-
? (args[0] as any[])
|
|
89
|
-
: (args as any[]);
|
|
90
|
-
selectedKeys = keys.map(String);
|
|
91
|
-
return api;
|
|
92
|
-
},
|
|
93
|
-
populate(arg: any) {
|
|
94
|
-
if (Array.isArray(arg)) {
|
|
95
|
-
for (const k of arg) addPopulateKey(k as any);
|
|
96
|
-
return api;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (arg && typeof arg === "object") {
|
|
100
|
-
for (const [key, value] of Object.entries(
|
|
101
|
-
arg as Record<string, unknown>
|
|
102
|
-
)) {
|
|
103
|
-
if (!value) continue;
|
|
104
|
-
const includeObj =
|
|
105
|
-
typeof value === "object" && !Array.isArray(value)
|
|
106
|
-
? (value as Record<string, unknown>)
|
|
107
|
-
: undefined;
|
|
108
|
-
const aggregate = normalizeAggregate(includeObj?.aggregate);
|
|
109
|
-
const includeDocs = includeObj?.includeDocs;
|
|
110
|
-
addPopulateKey(key, { aggregate, includeDocs });
|
|
111
|
-
}
|
|
112
|
-
return api;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (arg !== undefined) addPopulateKey(arg as any);
|
|
116
|
-
return api;
|
|
117
|
-
},
|
|
118
|
-
async find() {
|
|
119
|
-
const coll = params.getCollection(params.table as string);
|
|
120
|
-
const options: FindOptions = {};
|
|
121
|
-
if (selectedKeys) {
|
|
122
|
-
// Ensure referenced ids are available for populate even when the caller selected a subset.
|
|
123
|
-
const projectionKeys = Array.from(
|
|
124
|
-
new Set([...selectedKeys, ...populateKeys])
|
|
125
|
-
);
|
|
126
|
-
options.projection = buildProjection(projectionKeys);
|
|
127
|
-
}
|
|
128
|
-
const normalizedFilter = normalizeIdFilter(filter);
|
|
129
|
-
let cursor = coll.find(normalizedFilter ?? ({} as any), options);
|
|
130
|
-
if (sort) cursor = cursor.sort(sort);
|
|
131
|
-
if (offset !== undefined) cursor = cursor.skip(offset);
|
|
132
|
-
if (limit !== undefined) cursor = cursor.limit(limit);
|
|
133
|
-
const docs = (await cursor.toArray()) as Array<Record<string, unknown>>;
|
|
134
|
-
docs.forEach(stringifyIdField);
|
|
135
|
-
|
|
136
|
-
if (populateKeys.length > 0) {
|
|
137
|
-
await applyPopulate({
|
|
138
|
-
docs,
|
|
139
|
-
currentTable: params.table as string,
|
|
140
|
-
populateKeys,
|
|
141
|
-
aggregate: populateAggregates,
|
|
142
|
-
includeDocs: populateIncludeDocs,
|
|
143
|
-
selectedKeys,
|
|
144
|
-
refs: params.refs,
|
|
145
|
-
getCollection: params.getCollection,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return docs as any;
|
|
150
|
-
},
|
|
151
|
-
async findOne() {
|
|
152
|
-
if (limit === undefined || limit > 1) {
|
|
153
|
-
limit = 1;
|
|
154
|
-
}
|
|
155
|
-
const result = await api.find();
|
|
156
|
-
return (result[0] ?? null) as any;
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
return api as any;
|
|
161
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { Sort } from "mongodb";
|
|
2
|
-
import type { QuerySort } from "../types/types";
|
|
3
|
-
|
|
4
|
-
export function buildProjection(keys: string[]): Record<string, 0 | 1> {
|
|
5
|
-
const projection: Record<string, 0 | 1> = {};
|
|
6
|
-
for (const k of keys) projection[k] = 1;
|
|
7
|
-
|
|
8
|
-
// Mongo includes _id by default; keep runtime aligned with schema-types `select()`.
|
|
9
|
-
if (!keys.includes("_id")) projection._id = 0;
|
|
10
|
-
if (!keys.includes("_creationTime")) projection._creationTime = 0;
|
|
11
|
-
return projection;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function normalizeSort(sort: QuerySort<string>): Sort {
|
|
15
|
-
if (Array.isArray(sort)) {
|
|
16
|
-
return Object.fromEntries(
|
|
17
|
-
sort.map(([k, dir]) => [k, dir === "desc" ? -1 : 1])
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
const out: Record<string, 1 | -1> = {};
|
|
21
|
-
for (const [k, v] of Object.entries(sort ?? {})) {
|
|
22
|
-
out[k] = v === "desc" ? -1 : 1;
|
|
23
|
-
}
|
|
24
|
-
return out;
|
|
25
|
-
}
|
package/server/db.ts
DELETED
package/server/storage/auth.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
StorageAuthResult,
|
|
3
|
-
StorageBaseContext,
|
|
4
|
-
StorageRule,
|
|
5
|
-
} from "./types";
|
|
6
|
-
|
|
7
|
-
const allowAnonymous = <Principal>(): StorageAuthResult<Principal> => ({
|
|
8
|
-
allow: true,
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
export async function authorizeRequest<Env, Principal>(
|
|
12
|
-
ctx: StorageBaseContext<Env, Principal>,
|
|
13
|
-
rule: StorageRule<Env, Principal>
|
|
14
|
-
): Promise<StorageAuthResult<Principal>> {
|
|
15
|
-
return rule.authorize ? rule.authorize(ctx) : allowAnonymous<Principal>();
|
|
16
|
-
}
|
package/server/storage/bucket.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { Context } from "hono";
|
|
2
|
-
import type { R2Bucket } from "@cloudflare/workers-types";
|
|
3
|
-
import type { StorageManagerOptions, StorageRule } from "./types";
|
|
4
|
-
|
|
5
|
-
export function resolveBucket<Env, Principal>(
|
|
6
|
-
c: Context<Env>,
|
|
7
|
-
rule: StorageRule<Env, Principal>,
|
|
8
|
-
opts: StorageManagerOptions<Env, Principal>
|
|
9
|
-
): Promise<R2Bucket> {
|
|
10
|
-
const resolver = rule.getBucket ?? opts.getBucket;
|
|
11
|
-
if (resolver) return Promise.resolve(resolver(c));
|
|
12
|
-
if (opts.bucketBinding) {
|
|
13
|
-
const bucket = (c.env as any)?.[opts.bucketBinding];
|
|
14
|
-
if (!bucket) {
|
|
15
|
-
throw new Error(
|
|
16
|
-
`Bucket binding ${opts.bucketBinding} was not found on the worker env.`
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
return Promise.resolve(bucket as R2Bucket);
|
|
20
|
-
}
|
|
21
|
-
throw new Error("Storage manager requires getBucket or bucketBinding.");
|
|
22
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
StorageBaseContext,
|
|
3
|
-
StorageKeyContext,
|
|
4
|
-
StorageRule,
|
|
5
|
-
StorageHttpMethod,
|
|
6
|
-
} from "./types";
|
|
7
|
-
import { deriveDefaultKey, normalizeKey } from "./utils";
|
|
8
|
-
import type { Context } from "hono";
|
|
9
|
-
|
|
10
|
-
export function buildBaseContext<Env, Principal>(
|
|
11
|
-
params: Record<string, string>,
|
|
12
|
-
method: StorageHttpMethod,
|
|
13
|
-
basePath: string,
|
|
14
|
-
c: Context<Env>
|
|
15
|
-
): StorageBaseContext<Env, Principal> {
|
|
16
|
-
const wildcard = params["*"] ?? params["0"] ?? ""; // hono uses "0" for splat
|
|
17
|
-
const defaultKey = deriveDefaultKey(c.req.path, basePath);
|
|
18
|
-
return { c, params, wildcard, defaultKey, method };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function buildKeyContext<Env, Principal>(
|
|
22
|
-
ctx: StorageBaseContext<Env, Principal>,
|
|
23
|
-
principal?: Principal
|
|
24
|
-
): StorageKeyContext<Env, Principal> {
|
|
25
|
-
return { ...ctx, principal };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function deriveStorageKey<Env, Principal>(
|
|
29
|
-
rule: StorageRule<Env, Principal>,
|
|
30
|
-
ctx: StorageKeyContext<Env, Principal>
|
|
31
|
-
): string {
|
|
32
|
-
const key = (rule.deriveKey ?? ((keyCtx) => keyCtx.defaultKey))(ctx);
|
|
33
|
-
return normalizeKey(key);
|
|
34
|
-
}
|
package/server/storage/index.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { Hono } from "hono";
|
|
2
|
-
import { createRouteHandler } from "./route-handler";
|
|
3
|
-
import { ensureMethods, joinPaths, normalizeBasePath } from "./utils";
|
|
4
|
-
import type {
|
|
5
|
-
StorageHttpMethod,
|
|
6
|
-
StorageManagerOptions,
|
|
7
|
-
StorageRule,
|
|
8
|
-
} from "./types";
|
|
9
|
-
|
|
10
|
-
export function createR2StorageManager<Env = unknown, Principal = unknown>(
|
|
11
|
-
options: StorageManagerOptions<Env, Principal>
|
|
12
|
-
): Hono<Env> {
|
|
13
|
-
const basePath = normalizeBasePath(options.basePath);
|
|
14
|
-
const app = new Hono<Env>();
|
|
15
|
-
const defaultCache =
|
|
16
|
-
options.defaultCacheControl ?? "private, max-age=0, must-revalidate";
|
|
17
|
-
|
|
18
|
-
for (const rule of options.rules) {
|
|
19
|
-
const routePath = joinPaths(basePath, rule.route ?? "");
|
|
20
|
-
const methods = ensureMethods(rule.methods);
|
|
21
|
-
|
|
22
|
-
for (const method of methods) {
|
|
23
|
-
app.on(
|
|
24
|
-
method,
|
|
25
|
-
routePath,
|
|
26
|
-
createRouteHandler(options, basePath, defaultCache, rule, method)
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return app;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type {
|
|
35
|
-
StorageManagerOptions,
|
|
36
|
-
StorageRule,
|
|
37
|
-
StorageHttpMethod,
|
|
38
|
-
} from "./types";
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
StorageHttpMethod,
|
|
3
|
-
StorageKeyContext,
|
|
4
|
-
StorageRule,
|
|
5
|
-
} from "./types";
|
|
6
|
-
import type {
|
|
7
|
-
R2Bucket,
|
|
8
|
-
R2ObjectBody,
|
|
9
|
-
R2PutOptions,
|
|
10
|
-
} from "@cloudflare/workers-types";
|
|
11
|
-
|
|
12
|
-
async function respondWithObject(
|
|
13
|
-
object: R2ObjectBody,
|
|
14
|
-
cacheControl: string,
|
|
15
|
-
headOnly: boolean
|
|
16
|
-
): Promise<Response> {
|
|
17
|
-
const headers = new Headers();
|
|
18
|
-
if (object.httpEtag) headers.set("etag", object.httpEtag);
|
|
19
|
-
if (object.size !== undefined)
|
|
20
|
-
headers.set("content-length", `${object.size}`);
|
|
21
|
-
if (object.httpMetadata?.contentType)
|
|
22
|
-
headers.set("content-type", object.httpMetadata.contentType);
|
|
23
|
-
headers.set("cache-control", cacheControl);
|
|
24
|
-
return headOnly
|
|
25
|
-
? new Response(null, { status: 200, headers })
|
|
26
|
-
: new Response(object.body as any, { status: 200, headers });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
type ResolvedPayload = {
|
|
30
|
-
body: ArrayBuffer;
|
|
31
|
-
size: number;
|
|
32
|
-
contentType: string;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
async function resolvePayload<Env, Principal>(
|
|
36
|
-
ctx: StorageKeyContext<Env, Principal>,
|
|
37
|
-
rule: StorageRule<Env, Principal>,
|
|
38
|
-
cacheControl: string
|
|
39
|
-
): Promise<ResolvedPayload | Response> {
|
|
40
|
-
const headerContentType = ctx.c.req.header("content-type") ?? "";
|
|
41
|
-
const isMultipart = headerContentType
|
|
42
|
-
.toLowerCase()
|
|
43
|
-
.startsWith("multipart/form-data");
|
|
44
|
-
|
|
45
|
-
if (isMultipart) {
|
|
46
|
-
const form = await ctx.c.req.formData();
|
|
47
|
-
let file: File | null = null;
|
|
48
|
-
for (const value of form.values()) {
|
|
49
|
-
if (value instanceof File) {
|
|
50
|
-
file = value;
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (!file) return ctx.c.json({ error: "No file found in form-data" }, 400);
|
|
55
|
-
const body = await file.arrayBuffer();
|
|
56
|
-
if (rule.maxSizeBytes && body.byteLength > rule.maxSizeBytes) {
|
|
57
|
-
return ctx.c.json({ error: "Payload too large" }, 413);
|
|
58
|
-
}
|
|
59
|
-
const contentType =
|
|
60
|
-
rule.contentType?.(ctx) ||
|
|
61
|
-
file.type ||
|
|
62
|
-
headerContentType ||
|
|
63
|
-
"application/octet-stream";
|
|
64
|
-
return { body, size: body.byteLength, contentType };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const body = await ctx.c.req.arrayBuffer();
|
|
68
|
-
if (rule.maxSizeBytes && body.byteLength > rule.maxSizeBytes) {
|
|
69
|
-
return ctx.c.json({ error: "Payload too large" }, 413);
|
|
70
|
-
}
|
|
71
|
-
const contentType =
|
|
72
|
-
rule.contentType?.(ctx) || headerContentType || "application/octet-stream";
|
|
73
|
-
return { body, size: body.byteLength, contentType };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function handlePut<Env, Principal>(
|
|
77
|
-
bucket: R2Bucket,
|
|
78
|
-
key: string,
|
|
79
|
-
ctx: StorageKeyContext<Env, Principal>,
|
|
80
|
-
rule: StorageRule<Env, Principal>,
|
|
81
|
-
cacheControl: string
|
|
82
|
-
): Promise<Response> {
|
|
83
|
-
const payload = await resolvePayload(ctx, rule, cacheControl);
|
|
84
|
-
if (payload instanceof Response) return payload;
|
|
85
|
-
|
|
86
|
-
const putOptions: R2PutOptions = {
|
|
87
|
-
httpMetadata: { contentType: payload.contentType, cacheControl },
|
|
88
|
-
};
|
|
89
|
-
await bucket.put(key, payload.body, putOptions);
|
|
90
|
-
return ctx.c.json(
|
|
91
|
-
{
|
|
92
|
-
key,
|
|
93
|
-
size: payload.size,
|
|
94
|
-
contentType: payload.contentType,
|
|
95
|
-
cacheControl,
|
|
96
|
-
},
|
|
97
|
-
201
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function handleGet(
|
|
102
|
-
bucket: R2Bucket,
|
|
103
|
-
key: string,
|
|
104
|
-
cacheControl: string,
|
|
105
|
-
headOnly: boolean
|
|
106
|
-
): Promise<Response> {
|
|
107
|
-
const object = await bucket.get(key);
|
|
108
|
-
if (!object) return new Response(null, { status: 404 });
|
|
109
|
-
return respondWithObject(object, cacheControl, headOnly);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function handleDelete<Env, Principal>(
|
|
113
|
-
bucket: R2Bucket,
|
|
114
|
-
key: string,
|
|
115
|
-
ctx: StorageKeyContext<Env, Principal>
|
|
116
|
-
): Promise<Response> {
|
|
117
|
-
await bucket.delete(key);
|
|
118
|
-
return ctx.c.json({ key, deleted: true }, 200);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export type OperationParams<Env, Principal> = {
|
|
122
|
-
method: StorageHttpMethod;
|
|
123
|
-
bucket: R2Bucket;
|
|
124
|
-
key: string;
|
|
125
|
-
cacheControl: string;
|
|
126
|
-
ctx: StorageKeyContext<Env, Principal>;
|
|
127
|
-
rule: StorageRule<Env, Principal>;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
export async function executeOperation<Env, Principal>(
|
|
131
|
-
params: OperationParams<Env, Principal>
|
|
132
|
-
): Promise<Response> {
|
|
133
|
-
const { method, bucket, key, cacheControl, ctx, rule } = params;
|
|
134
|
-
switch (method) {
|
|
135
|
-
case "GET":
|
|
136
|
-
return handleGet(bucket, key, cacheControl, false);
|
|
137
|
-
case "HEAD":
|
|
138
|
-
return handleGet(bucket, key, cacheControl, true);
|
|
139
|
-
case "PUT":
|
|
140
|
-
case "POST":
|
|
141
|
-
return handlePut<Env, Principal>(bucket, key, ctx, rule, cacheControl);
|
|
142
|
-
case "DELETE":
|
|
143
|
-
return handleDelete(bucket, key, ctx);
|
|
144
|
-
case "OPTIONS":
|
|
145
|
-
return new Response(null, { status: 204 });
|
|
146
|
-
default:
|
|
147
|
-
return ctx.c.json({ error: "Method not allowed" }, 405);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { authorizeRequest } from "./auth";
|
|
2
|
-
import { resolveBucket } from "./bucket";
|
|
3
|
-
import { buildBaseContext, buildKeyContext, deriveStorageKey } from "./context";
|
|
4
|
-
import { executeOperation } from "./operations";
|
|
5
|
-
import type {
|
|
6
|
-
RouteHandler,
|
|
7
|
-
StorageHttpMethod,
|
|
8
|
-
StorageManagerOptions,
|
|
9
|
-
StorageRule,
|
|
10
|
-
} from "./types";
|
|
11
|
-
import { resolveCacheControl } from "./utils";
|
|
12
|
-
import type { Context } from "hono";
|
|
13
|
-
|
|
14
|
-
export function createRouteHandler<Env, Principal>(
|
|
15
|
-
options: StorageManagerOptions<Env, Principal>,
|
|
16
|
-
basePath: string,
|
|
17
|
-
defaultCache: string,
|
|
18
|
-
rule: StorageRule<Env, Principal>,
|
|
19
|
-
method: StorageHttpMethod
|
|
20
|
-
): RouteHandler<Env> {
|
|
21
|
-
return async (c: Context<Env>) => {
|
|
22
|
-
const params = c.req.param();
|
|
23
|
-
const baseCtx = buildBaseContext<Env, Principal>(
|
|
24
|
-
params,
|
|
25
|
-
method,
|
|
26
|
-
basePath,
|
|
27
|
-
c
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
if (method === "OPTIONS") {
|
|
32
|
-
return new Response(null, { status: 204 });
|
|
33
|
-
}
|
|
34
|
-
const bucket = await resolveBucket(c, rule, options);
|
|
35
|
-
const auth = await authorizeRequest(baseCtx, rule);
|
|
36
|
-
if (auth.allow === false) {
|
|
37
|
-
return c.json(
|
|
38
|
-
{ error: auth.message ?? "Unauthorized" },
|
|
39
|
-
(auth.status as any) ?? 403
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const keyCtx = buildKeyContext(baseCtx, auth.principal);
|
|
44
|
-
const key = deriveStorageKey(rule, keyCtx);
|
|
45
|
-
const cacheControl = resolveCacheControl(rule, defaultCache);
|
|
46
|
-
|
|
47
|
-
return executeOperation({
|
|
48
|
-
method,
|
|
49
|
-
bucket,
|
|
50
|
-
key,
|
|
51
|
-
cacheControl,
|
|
52
|
-
ctx: keyCtx,
|
|
53
|
-
rule,
|
|
54
|
-
});
|
|
55
|
-
} catch (err) {
|
|
56
|
-
console.error("R2 storage manager error", err);
|
|
57
|
-
return c.json({ error: "Storage operation failed" }, 500);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
package/server/storage/types.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { Context } from "hono";
|
|
2
|
-
import type { R2Bucket } from "@cloudflare/workers-types";
|
|
3
|
-
|
|
4
|
-
export type StorageHttpMethod =
|
|
5
|
-
| "GET"
|
|
6
|
-
| "HEAD"
|
|
7
|
-
| "PUT"
|
|
8
|
-
| "POST"
|
|
9
|
-
| "DELETE"
|
|
10
|
-
| "OPTIONS";
|
|
11
|
-
export type Awaitable<T> = T | Promise<T>;
|
|
12
|
-
|
|
13
|
-
export type StorageAuthResult<Principal = unknown> =
|
|
14
|
-
| { allow: true; principal?: Principal }
|
|
15
|
-
| { allow: false; status?: number; message?: string };
|
|
16
|
-
|
|
17
|
-
export type StorageBaseContext<Env, Principal> = {
|
|
18
|
-
c: Context<Env>;
|
|
19
|
-
params: Record<string, string>;
|
|
20
|
-
wildcard: string;
|
|
21
|
-
defaultKey: string;
|
|
22
|
-
method: StorageHttpMethod;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type StorageKeyContext<Env, Principal> = StorageBaseContext<
|
|
26
|
-
Env,
|
|
27
|
-
Principal
|
|
28
|
-
> & {
|
|
29
|
-
principal?: Principal;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type BucketResolver<Env> = (c: Context<Env>) => Awaitable<R2Bucket>;
|
|
33
|
-
|
|
34
|
-
export type StorageRule<Env = unknown, Principal = unknown> = {
|
|
35
|
-
route?: string;
|
|
36
|
-
methods?: StorageHttpMethod[];
|
|
37
|
-
deriveKey?: (ctx: StorageKeyContext<Env, Principal>) => string;
|
|
38
|
-
authorize?: (
|
|
39
|
-
ctx: StorageBaseContext<Env, Principal>
|
|
40
|
-
) => Awaitable<StorageAuthResult<Principal>>;
|
|
41
|
-
getBucket?: BucketResolver<Env>;
|
|
42
|
-
maxSizeBytes?: number;
|
|
43
|
-
cacheControl?: string;
|
|
44
|
-
contentType?: (ctx: StorageKeyContext<Env, Principal>) => string | undefined;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export type StorageManagerOptions<Env = unknown, Principal = unknown> = {
|
|
48
|
-
basePath?: string;
|
|
49
|
-
rules: StorageRule<Env, Principal>[];
|
|
50
|
-
defaultCacheControl?: string;
|
|
51
|
-
bucketBinding?: string;
|
|
52
|
-
getBucket?: BucketResolver<Env>;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export type RouteHandler<Env> = (c: Context<Env>) => Promise<Response>;
|
package/server/storage/utils.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { StorageHttpMethod, StorageRule } from "./types";
|
|
2
|
-
|
|
3
|
-
export const DEFAULT_METHODS: StorageHttpMethod[] = [
|
|
4
|
-
"GET",
|
|
5
|
-
"HEAD",
|
|
6
|
-
"PUT",
|
|
7
|
-
"POST",
|
|
8
|
-
"DELETE",
|
|
9
|
-
"OPTIONS",
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
export function normalizeBasePath(basePath?: string): string {
|
|
13
|
-
if (!basePath) return "/storage";
|
|
14
|
-
if (!basePath.startsWith("/")) return `/${basePath}`;
|
|
15
|
-
return basePath;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function joinPaths(base: string, route: string): string {
|
|
19
|
-
const trimmedBase = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
20
|
-
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
21
|
-
return `${trimmedBase}${normalizedRoute}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function deriveDefaultKey(path: string, basePath: string): string {
|
|
25
|
-
const normalizedBase = basePath.endsWith("/") ? basePath : `${basePath}/`;
|
|
26
|
-
const withoutBase = path.startsWith(normalizedBase)
|
|
27
|
-
? path.slice(normalizedBase.length)
|
|
28
|
-
: path.replace(/^\/+/, "");
|
|
29
|
-
return normalizeKey(withoutBase);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function normalizeKey(key: string): string {
|
|
33
|
-
return key.replace(/^\/+/, "").replace(/\/+/g, "/");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function ensureMethods(
|
|
37
|
-
methods?: StorageHttpMethod[]
|
|
38
|
-
): StorageHttpMethod[] {
|
|
39
|
-
return methods && methods.length ? methods : DEFAULT_METHODS;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function resolveCacheControl<Env, Principal>(
|
|
43
|
-
rule: StorageRule<Env, Principal>,
|
|
44
|
-
defaultCache: string
|
|
45
|
-
): string {
|
|
46
|
-
return rule.cacheControl ?? defaultCache;
|
|
47
|
-
}
|
package/server/storage.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { AnyZod, SchemaRefMap } from "./types";
|
|
2
|
-
|
|
3
|
-
export function buildSchemaRefMap(
|
|
4
|
-
schema: Record<string, AnyZod>
|
|
5
|
-
): SchemaRefMap {
|
|
6
|
-
const result: SchemaRefMap = new Map();
|
|
7
|
-
for (const [tableName, validator] of Object.entries(schema)) {
|
|
8
|
-
const tableRefs = new Map<string, string>();
|
|
9
|
-
const shape = getZodObjectShape(validator);
|
|
10
|
-
for (const [field, fieldSchema] of Object.entries(shape)) {
|
|
11
|
-
const ref = extractRefTableName(fieldSchema);
|
|
12
|
-
if (ref) tableRefs.set(field, ref);
|
|
13
|
-
}
|
|
14
|
-
result.set(tableName, tableRefs);
|
|
15
|
-
}
|
|
16
|
-
return result;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function extractRefTableName(schema: AnyZod): string | null {
|
|
20
|
-
if (!schema) return null;
|
|
21
|
-
const def = schema?._def;
|
|
22
|
-
const typeName: string | undefined = def?.typeName ?? def?.type;
|
|
23
|
-
|
|
24
|
-
if (typeName === "ZodOptional" || typeName === "optional") {
|
|
25
|
-
return extractRefTableName(
|
|
26
|
-
def?.innerType ?? def?.schema ?? schema?._def?.innerType
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
if (typeName === "ZodNullable" || typeName === "nullable") {
|
|
30
|
-
return extractRefTableName(def?.innerType ?? def?.schema);
|
|
31
|
-
}
|
|
32
|
-
if (typeName === "ZodDefault" || typeName === "default") {
|
|
33
|
-
return extractRefTableName(def?.innerType ?? def?.schema);
|
|
34
|
-
}
|
|
35
|
-
if (typeName === "ZodArray" || typeName === "array") {
|
|
36
|
-
return extractRefTableName(def?.element ?? def?.innerType ?? def?.type);
|
|
37
|
-
}
|
|
38
|
-
if (typeName === "ZodString" || typeName === "string") {
|
|
39
|
-
const description: string | undefined =
|
|
40
|
-
schema?.description ?? def?.description;
|
|
41
|
-
if (typeof description === "string" && description.startsWith("ref:")) {
|
|
42
|
-
return description.slice("ref:".length);
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function getZodObjectShape(schema: AnyZod): Record<string, AnyZod> {
|
|
51
|
-
if (!schema || typeof schema !== "object") {
|
|
52
|
-
throw new Error(`Schema table is not an object`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const def = schema?._def;
|
|
56
|
-
if (def?.typeName === "ZodObject" || def?.type === "object") {
|
|
57
|
-
const shape = def.shape;
|
|
58
|
-
if (typeof shape === "function") return shape();
|
|
59
|
-
if (shape && typeof shape === "object") return shape;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (typeof schema.shape === "function") return schema.shape();
|
|
63
|
-
if (schema.shape && typeof schema.shape === "object") return schema.shape;
|
|
64
|
-
|
|
65
|
-
throw new Error(`Table schema is not a Zod object`);
|
|
66
|
-
}
|