appflare 0.0.28 → 0.1.1

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 (151) 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 +747 -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 +353 -0
  34. package/cli/templates/handlers/auth.ts +37 -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/generators/registration/modules/cron.ts +30 -0
  46. package/cli/templates/handlers/generators/registration/modules/realtime.ts +631 -0
  47. package/cli/templates/handlers/generators/registration/modules/scheduler.ts +56 -0
  48. package/cli/templates/handlers/generators/registration/modules/storage.ts +194 -0
  49. package/cli/templates/handlers/generators/registration/sections.ts +210 -0
  50. package/cli/templates/handlers/generators/types/context.ts +64 -0
  51. package/cli/templates/handlers/generators/types/core.ts +101 -0
  52. package/cli/templates/handlers/generators/types/operations.ts +135 -0
  53. package/cli/templates/handlers/generators/types/query-definitions.ts +1029 -0
  54. package/cli/templates/handlers/generators/types/query-runtime.ts +417 -0
  55. package/cli/templates/handlers/index.ts +43 -0
  56. package/cli/templates/handlers/operations.ts +116 -0
  57. package/cli/templates/handlers/registration.ts +83 -0
  58. package/cli/templates/handlers/types.ts +15 -0
  59. package/cli/templates/handlers/utils.ts +48 -0
  60. package/cli/types.ts +108 -0
  61. package/cli/utils/handler-discovery.ts +366 -0
  62. package/cli/utils/json-utils.ts +24 -0
  63. package/cli/utils/path-utils.ts +19 -0
  64. package/cli/utils/schema-discovery.ts +390 -0
  65. package/index.ts +27 -4
  66. package/package.json +23 -20
  67. package/react/index.ts +5 -3
  68. package/react/use-infinite-query.ts +190 -0
  69. package/react/use-mutation.ts +54 -0
  70. package/react/use-query.ts +158 -0
  71. package/schema.ts +262 -0
  72. package/tsconfig.json +2 -4
  73. package/cli/README.md +0 -108
  74. package/cli/core/build.ts +0 -187
  75. package/cli/core/config.ts +0 -92
  76. package/cli/core/discover-handlers.ts +0 -143
  77. package/cli/core/handlers.ts +0 -7
  78. package/cli/core/index.ts +0 -205
  79. package/cli/generators/generate-api-client/client.ts +0 -163
  80. package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
  81. package/cli/generators/generate-api-client/index.ts +0 -973
  82. package/cli/generators/generate-api-client/types.ts +0 -164
  83. package/cli/generators/generate-api-client/utils.ts +0 -22
  84. package/cli/generators/generate-api-client.ts +0 -1
  85. package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
  86. package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
  87. package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
  88. package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
  89. package/cli/generators/generate-cloudflare-worker.ts +0 -4
  90. package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
  91. package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
  92. package/cli/generators/generate-cron-handlers/index.ts +0 -61
  93. package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
  94. package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
  95. package/cli/generators/generate-db-handlers/index.ts +0 -33
  96. package/cli/generators/generate-db-handlers/prepare.ts +0 -24
  97. package/cli/generators/generate-db-handlers/templates.ts +0 -189
  98. package/cli/generators/generate-db-handlers.ts +0 -1
  99. package/cli/generators/generate-hono-server/auth.ts +0 -97
  100. package/cli/generators/generate-hono-server/imports.ts +0 -55
  101. package/cli/generators/generate-hono-server/index.ts +0 -52
  102. package/cli/generators/generate-hono-server/routes.ts +0 -115
  103. package/cli/generators/generate-hono-server/template.ts +0 -371
  104. package/cli/generators/generate-hono-server.ts +0 -1
  105. package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
  106. package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
  107. package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
  108. package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
  109. package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
  110. package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
  111. package/cli/generators/generate-scheduler-handlers.ts +0 -1
  112. package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
  113. package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
  114. package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
  115. package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
  116. package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
  117. package/cli/generators/generate-websocket-durable-object.ts +0 -1
  118. package/cli/schema/schema-static-types.ts +0 -702
  119. package/cli/schema/schema.ts +0 -151
  120. package/cli/utils/tsc.ts +0 -54
  121. package/cli/utils/utils.ts +0 -190
  122. package/cli/utils/zod-utils.ts +0 -121
  123. package/lib/README.md +0 -50
  124. package/lib/db.ts +0 -19
  125. package/lib/location.ts +0 -110
  126. package/lib/values.ts +0 -27
  127. package/react/README.md +0 -67
  128. package/react/hooks/useMutation.ts +0 -89
  129. package/react/hooks/usePaginatedQuery.ts +0 -213
  130. package/react/hooks/useQuery.ts +0 -106
  131. package/react/shared/queryShared.ts +0 -174
  132. package/server/README.md +0 -218
  133. package/server/auth.ts +0 -107
  134. package/server/database/builders.ts +0 -83
  135. package/server/database/context.ts +0 -327
  136. package/server/database/populate.ts +0 -234
  137. package/server/database/query-builder.ts +0 -161
  138. package/server/database/query-utils.ts +0 -25
  139. package/server/db.ts +0 -2
  140. package/server/storage/auth.ts +0 -16
  141. package/server/storage/bucket.ts +0 -22
  142. package/server/storage/context.ts +0 -34
  143. package/server/storage/index.ts +0 -38
  144. package/server/storage/operations.ts +0 -149
  145. package/server/storage/route-handler.ts +0 -60
  146. package/server/storage/types.ts +0 -55
  147. package/server/storage/utils.ts +0 -47
  148. package/server/storage.ts +0 -6
  149. package/server/types/schema-refs.ts +0 -66
  150. package/server/types/types.ts +0 -633
  151. 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
- }