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.
Files changed (141) hide show
  1. package/cli/commands/index.ts +140 -0
  2. package/cli/generate.ts +149 -0
  3. package/cli/index.ts +56 -447
  4. package/cli/load-config.ts +182 -0
  5. package/cli/schema-compiler.ts +657 -0
  6. package/cli/templates/auth/README.md +156 -0
  7. package/cli/templates/auth/config.ts +61 -0
  8. package/cli/templates/auth/route-config.ts +18 -0
  9. package/cli/templates/auth/route-handler.ts +18 -0
  10. package/cli/templates/auth/route-request-utils.ts +55 -0
  11. package/cli/templates/auth/route.ts +14 -0
  12. package/cli/templates/core/README.md +266 -0
  13. package/cli/templates/core/app-creation.ts +19 -0
  14. package/cli/templates/core/client/appflare.ts +37 -0
  15. package/cli/templates/core/client/index.ts +6 -0
  16. package/cli/templates/core/client/storage.ts +100 -0
  17. package/cli/templates/core/client/types.ts +54 -0
  18. package/cli/templates/core/client-modules/appflare.ts +112 -0
  19. package/cli/templates/core/client-modules/handlers/index.ts +740 -0
  20. package/cli/templates/core/client-modules/handlers.ts +1 -0
  21. package/cli/templates/core/client-modules/index.ts +7 -0
  22. package/cli/templates/core/client-modules/storage.ts +180 -0
  23. package/cli/templates/core/client-modules/types.ts +145 -0
  24. package/cli/templates/core/client.ts +39 -0
  25. package/cli/templates/core/drizzle.ts +15 -0
  26. package/cli/templates/core/export.ts +14 -0
  27. package/cli/templates/core/handlers-route.ts +23 -0
  28. package/cli/templates/core/handlers.ts +1 -0
  29. package/cli/templates/core/imports.ts +8 -0
  30. package/cli/templates/core/server.ts +38 -0
  31. package/cli/templates/core/types.ts +6 -0
  32. package/cli/templates/core/wrangler.ts +109 -0
  33. package/cli/templates/handlers/README.md +265 -0
  34. package/cli/templates/handlers/auth.ts +36 -0
  35. package/cli/templates/handlers/execution.ts +39 -0
  36. package/cli/templates/handlers/generators/context/context-creation.ts +80 -0
  37. package/cli/templates/handlers/generators/context/error-helpers.ts +11 -0
  38. package/cli/templates/handlers/generators/context/scheduler.ts +24 -0
  39. package/cli/templates/handlers/generators/context/storage-api.ts +112 -0
  40. package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -0
  41. package/cli/templates/handlers/generators/context/types.ts +18 -0
  42. package/cli/templates/handlers/generators/context.ts +43 -0
  43. package/cli/templates/handlers/generators/execution.ts +15 -0
  44. package/cli/templates/handlers/generators/handlers.ts +13 -0
  45. package/cli/templates/handlers/index.ts +43 -0
  46. package/cli/templates/handlers/operations.ts +116 -0
  47. package/cli/templates/handlers/registration.ts +1114 -0
  48. package/cli/templates/handlers/types.ts +960 -0
  49. package/cli/templates/handlers/utils.ts +48 -0
  50. package/cli/types.ts +108 -0
  51. package/cli/utils/handler-discovery.ts +366 -0
  52. package/cli/utils/json-utils.ts +24 -0
  53. package/cli/utils/path-utils.ts +19 -0
  54. package/cli/utils/schema-discovery.ts +390 -0
  55. package/index.ts +27 -4
  56. package/package.json +23 -20
  57. package/react/index.ts +5 -3
  58. package/react/use-infinite-query.ts +190 -0
  59. package/react/use-mutation.ts +54 -0
  60. package/react/use-query.ts +158 -0
  61. package/schema.ts +262 -0
  62. package/tsconfig.json +2 -4
  63. package/cli/README.md +0 -108
  64. package/cli/core/build.ts +0 -187
  65. package/cli/core/config.ts +0 -92
  66. package/cli/core/discover-handlers.ts +0 -143
  67. package/cli/core/handlers.ts +0 -7
  68. package/cli/core/index.ts +0 -205
  69. package/cli/generators/generate-api-client/client.ts +0 -163
  70. package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
  71. package/cli/generators/generate-api-client/index.ts +0 -973
  72. package/cli/generators/generate-api-client/types.ts +0 -164
  73. package/cli/generators/generate-api-client/utils.ts +0 -22
  74. package/cli/generators/generate-api-client.ts +0 -1
  75. package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
  76. package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
  77. package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
  78. package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
  79. package/cli/generators/generate-cloudflare-worker.ts +0 -4
  80. package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
  81. package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
  82. package/cli/generators/generate-cron-handlers/index.ts +0 -61
  83. package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
  84. package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
  85. package/cli/generators/generate-db-handlers/index.ts +0 -33
  86. package/cli/generators/generate-db-handlers/prepare.ts +0 -24
  87. package/cli/generators/generate-db-handlers/templates.ts +0 -189
  88. package/cli/generators/generate-db-handlers.ts +0 -1
  89. package/cli/generators/generate-hono-server/auth.ts +0 -97
  90. package/cli/generators/generate-hono-server/imports.ts +0 -55
  91. package/cli/generators/generate-hono-server/index.ts +0 -52
  92. package/cli/generators/generate-hono-server/routes.ts +0 -115
  93. package/cli/generators/generate-hono-server/template.ts +0 -371
  94. package/cli/generators/generate-hono-server.ts +0 -1
  95. package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
  96. package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
  97. package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
  98. package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
  99. package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
  100. package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
  101. package/cli/generators/generate-scheduler-handlers.ts +0 -1
  102. package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
  103. package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
  104. package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
  105. package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
  106. package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
  107. package/cli/generators/generate-websocket-durable-object.ts +0 -1
  108. package/cli/schema/schema-static-types.ts +0 -702
  109. package/cli/schema/schema.ts +0 -151
  110. package/cli/utils/tsc.ts +0 -54
  111. package/cli/utils/utils.ts +0 -190
  112. package/cli/utils/zod-utils.ts +0 -121
  113. package/lib/README.md +0 -50
  114. package/lib/db.ts +0 -19
  115. package/lib/location.ts +0 -110
  116. package/lib/values.ts +0 -27
  117. package/react/README.md +0 -67
  118. package/react/hooks/useMutation.ts +0 -89
  119. package/react/hooks/usePaginatedQuery.ts +0 -213
  120. package/react/hooks/useQuery.ts +0 -106
  121. package/react/shared/queryShared.ts +0 -174
  122. package/server/README.md +0 -218
  123. package/server/auth.ts +0 -107
  124. package/server/database/builders.ts +0 -83
  125. package/server/database/context.ts +0 -327
  126. package/server/database/populate.ts +0 -234
  127. package/server/database/query-builder.ts +0 -161
  128. package/server/database/query-utils.ts +0 -25
  129. package/server/db.ts +0 -2
  130. package/server/storage/auth.ts +0 -16
  131. package/server/storage/bucket.ts +0 -22
  132. package/server/storage/context.ts +0 -34
  133. package/server/storage/index.ts +0 -38
  134. package/server/storage/operations.ts +0 -149
  135. package/server/storage/route-handler.ts +0 -60
  136. package/server/storage/types.ts +0 -55
  137. package/server/storage/utils.ts +0 -47
  138. package/server/storage.ts +0 -6
  139. package/server/types/schema-refs.ts +0 -66
  140. package/server/types/types.ts +0 -633
  141. 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
@@ -1,2 +0,0 @@
1
- export * from "./database/context";
2
- export * from "./types/types";
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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>;
@@ -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,6 +0,0 @@
1
- export { createR2StorageManager } from "./storage/index";
2
- export type {
3
- StorageManagerOptions,
4
- StorageRule,
5
- StorageHttpMethod,
6
- } from "./storage/types";
@@ -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
- }