@voyantjs/hono 0.83.0 → 0.84.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.
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export type { ExpandedHonoBundles, ExpandedHonoPlugins, HonoBundle, HonoPlugin,
10
10
  export { defineHonoBundle, defineHonoPlugin, expandHonoBundles, expandHonoPlugins, } from "./plugin.js";
11
11
  export type { CreatePublicCapabilityOptions, PublicCapabilityCookieOptions, PublicCapabilityPayload, VerifyPublicCapabilityOptions, } from "./public-capability.js";
12
12
  export { createPublicCapabilityToken, extractPublicCapabilityToken, serializePublicCapabilityCookie, verifyPublicCapabilityToken, } from "./public-capability.js";
13
+ export { type CreatePublicDocumentDeliveryInput, createDrizzlePublicDocumentDeliveryGrantStore, createPublicDocumentDeliveryGrant, createPublicDocumentDeliveryHonoModule, createPublicDocumentDeliveryRoutes, type PublicDocumentDeliveryAccessContext, type PublicDocumentDeliveryEnvelope, type PublicDocumentDeliveryGrant, type PublicDocumentDeliveryGrantStore, type PublicDocumentDeliveryResolution, type PublicDocumentDeliveryRouteOptions, type PublicDocumentDeliverySource, type RevokePublicDocumentDeliveryGrantInput, resolvePublicDocumentDeliveryGrant, revokePublicDocumentDeliveryGrant, } from "./public-document-delivery.js";
13
14
  export type { DbFactory, LogEntry, LoggerProvider, VoyantAppConfig, VoyantAuthIntegration, VoyantAuthPermissionArgs, VoyantAuthResolveArgs, VoyantBindings, VoyantDb, VoyantExecutionContext, VoyantQueryRuntime, VoyantRequestAuthContext, VoyantVariables, } from "./types.js";
14
15
  export { ApiHttpError, ForbiddenApiError, normalizeValidationError, parseJsonBody, parseOptionalJsonBody, parseQuery, RequestValidationError, UnauthorizedApiError, } from "./validation.js";
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,eAAe,EACf,SAAS,EACT,YAAY,EACZ,aAAa,GACd,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,wBAAwB,EACxB,0BAA0B,EAC1B,wBAAwB,EACxB,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAA;AACtE,OAAO,EACL,qBAAqB,EACrB,IAAI,EACJ,0BAA0B,EAC1B,EAAE,EACF,aAAa,EACb,cAAc,EACd,KAAK,qBAAqB,EAC1B,cAAc,EACd,WAAW,EACX,MAAM,EACN,2BAA2B,EAC3B,SAAS,EACT,SAAS,EACT,YAAY,EACZ,WAAW,EACX,iBAAiB,GAClB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC5D,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,EACV,UAAU,GACX,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,6BAA6B,EAC7B,6BAA6B,EAC7B,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,+BAA+B,EAC/B,2BAA2B,GAC5B,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EACV,SAAS,EACT,QAAQ,EACR,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,EACd,QAAQ,EACR,sBAAsB,EACtB,kBAAkB,EAClB,wBAAwB,EACxB,eAAe,GAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,wBAAwB,EACxB,aAAa,EACb,qBAAqB,EACrB,UAAU,EACV,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,eAAe,EACf,SAAS,EACT,YAAY,EACZ,aAAa,GACd,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,wBAAwB,EACxB,0BAA0B,EAC1B,wBAAwB,EACxB,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,6BAA6B,EAAE,MAAM,wBAAwB,CAAA;AACtE,OAAO,EACL,qBAAqB,EACrB,IAAI,EACJ,0BAA0B,EAC1B,EAAE,EACF,aAAa,EACb,cAAc,EACd,KAAK,qBAAqB,EAC1B,cAAc,EACd,WAAW,EACX,MAAM,EACN,2BAA2B,EAC3B,SAAS,EACT,SAAS,EACT,YAAY,EACZ,WAAW,EACX,iBAAiB,GAClB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC5D,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,EACV,UAAU,GACX,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,6BAA6B,EAC7B,6BAA6B,EAC7B,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,+BAA+B,EAC/B,2BAA2B,GAC5B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACL,KAAK,iCAAiC,EACtC,6CAA6C,EAC7C,iCAAiC,EACjC,sCAAsC,EACtC,kCAAkC,EAClC,KAAK,mCAAmC,EACxC,KAAK,8BAA8B,EACnC,KAAK,2BAA2B,EAChC,KAAK,gCAAgC,EACrC,KAAK,gCAAgC,EACrC,KAAK,kCAAkC,EACvC,KAAK,4BAA4B,EACjC,KAAK,sCAAsC,EAC3C,kCAAkC,EAClC,iCAAiC,GAClC,MAAM,+BAA+B,CAAA;AACtC,YAAY,EACV,SAAS,EACT,QAAQ,EACR,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,EACd,QAAQ,EACR,sBAAsB,EACtB,kBAAkB,EAClB,wBAAwB,EACxB,eAAe,GAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,wBAAwB,EACxB,aAAa,EACb,qBAAqB,EACrB,UAAU,EACV,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,iBAAiB,CAAA"}
package/dist/index.js CHANGED
@@ -4,4 +4,5 @@ export { resolveStoredDocumentDownload } from "./document-download.js";
4
4
  export { consoleLoggerProvider, cors, DEFAULT_IDEMPOTENCY_TTL_MS, db, errorBoundary, handleApiError, idempotencyKey, LIVE_LIMITS, logger, purgeExpiredIdempotencyKeys, rateLimit, requestId, requireActor, requireAuth, requirePermission, } from "./middleware/index.js";
5
5
  export { defineHonoBundle, defineHonoPlugin, expandHonoBundles, expandHonoPlugins, } from "./plugin.js";
6
6
  export { createPublicCapabilityToken, extractPublicCapabilityToken, serializePublicCapabilityCookie, verifyPublicCapabilityToken, } from "./public-capability.js";
7
+ export { createDrizzlePublicDocumentDeliveryGrantStore, createPublicDocumentDeliveryGrant, createPublicDocumentDeliveryHonoModule, createPublicDocumentDeliveryRoutes, resolvePublicDocumentDeliveryGrant, revokePublicDocumentDeliveryGrant, } from "./public-document-delivery.js";
7
8
  export { ApiHttpError, ForbiddenApiError, normalizeValidationError, parseJsonBody, parseOptionalJsonBody, parseQuery, RequestValidationError, UnauthorizedApiError, } from "./validation.js";
@@ -0,0 +1,111 @@
1
+ import type { InsertInfraPublicDocumentDeliveryGrant, SelectInfraPublicDocumentDeliveryGrant } from "@voyantjs/db/schema/infra";
2
+ import type { StorageProvider } from "@voyantjs/storage";
3
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
4
+ import type { DocumentDownloadEnvelope } from "./document-download.js";
5
+ import type { HonoModule } from "./module.js";
6
+ export interface PublicDocumentDeliveryEnvelope extends DocumentDownloadEnvelope {
7
+ grantId: string;
8
+ }
9
+ export interface PublicDocumentDeliverySource {
10
+ module?: string | null;
11
+ entity?: string | null;
12
+ id?: string | null;
13
+ }
14
+ export interface CreatePublicDocumentDeliveryInput {
15
+ storageKey: string;
16
+ publicBaseUrl: string;
17
+ publicPath?: string;
18
+ ttlSeconds?: number;
19
+ expiresAt?: Date;
20
+ filename?: string | null;
21
+ contentType?: string | null;
22
+ storageProvider?: string | null;
23
+ source?: PublicDocumentDeliverySource | null;
24
+ createdBy?: string | null;
25
+ createdByType?: string | null;
26
+ metadata?: unknown;
27
+ now?: Date;
28
+ }
29
+ export interface PublicDocumentDeliveryAccessContext {
30
+ accessedAt?: Date;
31
+ ip?: string | null;
32
+ userAgent?: string | null;
33
+ }
34
+ export interface RevokePublicDocumentDeliveryGrantInput {
35
+ id: string;
36
+ revokedAt?: Date;
37
+ revokedBy?: string | null;
38
+ }
39
+ export type PublicDocumentDeliveryGrant = SelectInfraPublicDocumentDeliveryGrant;
40
+ export type PublicDocumentDeliveryResolution = {
41
+ status: "ready";
42
+ grant: PublicDocumentDeliveryGrant;
43
+ } | {
44
+ status: "not_found";
45
+ } | {
46
+ status: "expired";
47
+ grant: PublicDocumentDeliveryGrant;
48
+ } | {
49
+ status: "revoked";
50
+ grant: PublicDocumentDeliveryGrant;
51
+ };
52
+ export interface PublicDocumentDeliveryGrantStore {
53
+ create(input: InsertInfraPublicDocumentDeliveryGrant): Promise<PublicDocumentDeliveryGrant>;
54
+ findByTokenHash(tokenHash: string): Promise<PublicDocumentDeliveryGrant | null>;
55
+ recordAccess(id: string, context: Required<PublicDocumentDeliveryAccessContext>): Promise<void>;
56
+ revoke(input: RevokePublicDocumentDeliveryGrantInput): Promise<PublicDocumentDeliveryGrant | null>;
57
+ }
58
+ type Env<TBindings extends object = Record<string, unknown>> = {
59
+ Bindings: TBindings;
60
+ Variables: {
61
+ db: PostgresJsDatabase;
62
+ };
63
+ };
64
+ export interface PublicDocumentDeliveryRouteOptions<TBindings extends object = Record<string, unknown>> {
65
+ storage?: StorageProvider | null;
66
+ resolveStorage?: (bindings: TBindings) => StorageProvider | null | undefined;
67
+ store?: PublicDocumentDeliveryGrantStore;
68
+ resolveStore?: (bindings: TBindings, db: PostgresJsDatabase) => PublicDocumentDeliveryGrantStore | undefined;
69
+ }
70
+ export declare function createDrizzlePublicDocumentDeliveryGrantStore(db: PostgresJsDatabase): PublicDocumentDeliveryGrantStore;
71
+ export declare function createPublicDocumentDeliveryGrant(store: PublicDocumentDeliveryGrantStore, input: CreatePublicDocumentDeliveryInput): Promise<PublicDocumentDeliveryEnvelope>;
72
+ export declare function resolvePublicDocumentDeliveryGrant(store: PublicDocumentDeliveryGrantStore, token: string, now?: Date): Promise<PublicDocumentDeliveryResolution>;
73
+ export declare function revokePublicDocumentDeliveryGrant(store: PublicDocumentDeliveryGrantStore, input: RevokePublicDocumentDeliveryGrantInput): Promise<{
74
+ metadata: unknown;
75
+ id: string;
76
+ createdAt: Date;
77
+ expiresAt: Date;
78
+ revokedAt: Date | null;
79
+ tokenHash: string;
80
+ createdBy: string | null;
81
+ filename: string | null;
82
+ storageKey: string;
83
+ storageProvider: string | null;
84
+ contentType: string;
85
+ sourceModule: string | null;
86
+ sourceEntity: string | null;
87
+ sourceId: string | null;
88
+ createdByType: string | null;
89
+ accessCount: number;
90
+ lastAccessedAt: Date | null;
91
+ lastAccessedIp: string | null;
92
+ lastAccessedUserAgent: string | null;
93
+ revokedBy: string | null;
94
+ } | null>;
95
+ export declare function createPublicDocumentDeliveryRoutes<TBindings extends object = Record<string, unknown>>(options?: PublicDocumentDeliveryRouteOptions<TBindings>): import("hono/hono-base").HonoBase<Env<TBindings>, {
96
+ "/:token": {
97
+ $get: {
98
+ input: {
99
+ param: {
100
+ token: string;
101
+ };
102
+ };
103
+ output: {};
104
+ outputFormat: string;
105
+ status: import("hono/utils/http-status").StatusCode;
106
+ };
107
+ };
108
+ }, "/", "/:token">;
109
+ export declare function createPublicDocumentDeliveryHonoModule<TBindings extends object = Record<string, unknown>>(options?: PublicDocumentDeliveryRouteOptions<TBindings>): HonoModule;
110
+ export {};
111
+ //# sourceMappingURL=public-document-delivery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-document-delivery.d.ts","sourceRoot":"","sources":["../src/public-document-delivery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,233 @@
1
+ import { infraPublicDocumentDeliveryGrantsTable } from "@voyantjs/db/schema/infra";
2
+ import { and, eq, isNull, sql } from "drizzle-orm";
3
+ import { Hono } from "hono";
4
+ import { sha256Base64Url } from "./auth/crypto.js";
5
+ const DEFAULT_PUBLIC_DOCUMENT_TTL_SECONDS = 24 * 60 * 60;
6
+ const MAX_PUBLIC_DOCUMENT_TTL_SECONDS = 30 * 24 * 60 * 60;
7
+ const PUBLIC_DOCUMENT_TOKEN_BYTES = 32;
8
+ const DEFAULT_CONTENT_TYPE = "application/octet-stream";
9
+ const DEFAULT_PUBLIC_DOCUMENT_PATH = "/v1/public/documents";
10
+ function base64UrlEncode(bytes) {
11
+ let binary = "";
12
+ for (const byte of bytes) {
13
+ binary += String.fromCharCode(byte);
14
+ }
15
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
16
+ }
17
+ function createOpaqueToken() {
18
+ const bytes = new Uint8Array(PUBLIC_DOCUMENT_TOKEN_BYTES);
19
+ crypto.getRandomValues(bytes);
20
+ return base64UrlEncode(bytes);
21
+ }
22
+ function normalizePublicBaseUrl(publicBaseUrl) {
23
+ const trimmed = publicBaseUrl.trim().replace(/\/+$/, "");
24
+ if (!/^https?:\/\//i.test(trimmed)) {
25
+ throw new Error("publicBaseUrl must be an absolute HTTP(S) URL");
26
+ }
27
+ return trimmed;
28
+ }
29
+ function normalizePublicPath(path) {
30
+ const value = path?.trim() || DEFAULT_PUBLIC_DOCUMENT_PATH;
31
+ return `/${value.replace(/^\/+|\/+$/g, "")}`;
32
+ }
33
+ function resolveExpiresAt(input) {
34
+ const now = input.now ?? new Date();
35
+ if (input.expiresAt) {
36
+ if (input.expiresAt <= now) {
37
+ throw new Error("expiresAt must be in the future");
38
+ }
39
+ const maxExpiresAt = new Date(now.getTime() + MAX_PUBLIC_DOCUMENT_TTL_SECONDS * 1000);
40
+ if (input.expiresAt > maxExpiresAt) {
41
+ throw new Error("expiresAt exceeds the public document delivery maximum TTL");
42
+ }
43
+ return input.expiresAt;
44
+ }
45
+ const ttlSeconds = input.ttlSeconds ?? DEFAULT_PUBLIC_DOCUMENT_TTL_SECONDS;
46
+ if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {
47
+ throw new Error("ttlSeconds must be greater than zero");
48
+ }
49
+ return new Date(now.getTime() + Math.min(ttlSeconds, MAX_PUBLIC_DOCUMENT_TTL_SECONDS) * 1000);
50
+ }
51
+ function maybeString(value) {
52
+ const trimmed = value?.trim();
53
+ return trimmed && trimmed.length > 0 ? trimmed : null;
54
+ }
55
+ function safeAsciiFilename(filename) {
56
+ const normalized = maybeString(filename);
57
+ if (!normalized)
58
+ return "document";
59
+ const safe = normalized
60
+ .normalize("NFKD")
61
+ .replace(/[^\w .-]+/g, "-")
62
+ .replace(/[\r\n"\\]+/g, "-")
63
+ .replace(/\s+/g, " ")
64
+ .trim()
65
+ .slice(0, 160);
66
+ return safe || "document";
67
+ }
68
+ function filenameFromStorageKey(storageKey) {
69
+ return storageKey.split("/").filter(Boolean).at(-1) ?? "document";
70
+ }
71
+ function contentDisposition(filename) {
72
+ return `attachment; filename="${safeAsciiFilename(filename)}"`;
73
+ }
74
+ function safeContentType(contentType) {
75
+ const normalized = maybeString(contentType);
76
+ if (!normalized)
77
+ return DEFAULT_CONTENT_TYPE;
78
+ return /^[A-Za-z0-9!#$&^_.+-]+\/[A-Za-z0-9!#$&^_.+-]+(?:;\s*[A-Za-z0-9!#$&^_.+-]+=[A-Za-z0-9!#$&^_.+-]+)*$/.test(normalized)
79
+ ? normalized
80
+ : DEFAULT_CONTENT_TYPE;
81
+ }
82
+ function isPlausiblePublicDocumentToken(token) {
83
+ return /^[A-Za-z0-9_-]{32,256}$/.test(token);
84
+ }
85
+ function getClientIp(headers) {
86
+ return (headers.get("cf-connecting-ip") ??
87
+ headers.get("x-forwarded-for")?.split(",")[0]?.trim() ??
88
+ headers.get("x-real-ip") ??
89
+ null);
90
+ }
91
+ function getStore(options, bindings, db) {
92
+ return (options.resolveStore?.(bindings, db) ??
93
+ options.store ??
94
+ createDrizzlePublicDocumentDeliveryGrantStore(db));
95
+ }
96
+ function getStorage(options, bindings) {
97
+ return options.resolveStorage?.(bindings) ?? options.storage ?? null;
98
+ }
99
+ export function createDrizzlePublicDocumentDeliveryGrantStore(db) {
100
+ return {
101
+ async create(input) {
102
+ const [row] = await db
103
+ .insert(infraPublicDocumentDeliveryGrantsTable)
104
+ .values(input)
105
+ .returning();
106
+ if (!row)
107
+ throw new Error("Failed to create public document delivery grant");
108
+ return row;
109
+ },
110
+ async findByTokenHash(tokenHash) {
111
+ const [row] = await db
112
+ .select()
113
+ .from(infraPublicDocumentDeliveryGrantsTable)
114
+ .where(eq(infraPublicDocumentDeliveryGrantsTable.tokenHash, tokenHash))
115
+ .limit(1);
116
+ return row ?? null;
117
+ },
118
+ async recordAccess(id, context) {
119
+ await db
120
+ .update(infraPublicDocumentDeliveryGrantsTable)
121
+ .set({
122
+ accessCount: sql `${infraPublicDocumentDeliveryGrantsTable.accessCount} + 1`,
123
+ lastAccessedAt: context.accessedAt,
124
+ lastAccessedIp: context.ip,
125
+ lastAccessedUserAgent: context.userAgent,
126
+ })
127
+ .where(eq(infraPublicDocumentDeliveryGrantsTable.id, id));
128
+ },
129
+ async revoke(input) {
130
+ const [row] = await db
131
+ .update(infraPublicDocumentDeliveryGrantsTable)
132
+ .set({
133
+ revokedAt: input.revokedAt ?? new Date(),
134
+ revokedBy: input.revokedBy ?? null,
135
+ })
136
+ .where(and(eq(infraPublicDocumentDeliveryGrantsTable.id, input.id), isNull(infraPublicDocumentDeliveryGrantsTable.revokedAt)))
137
+ .returning();
138
+ return row ?? null;
139
+ },
140
+ };
141
+ }
142
+ export async function createPublicDocumentDeliveryGrant(store, input) {
143
+ const storageKey = maybeString(input.storageKey);
144
+ if (!storageKey) {
145
+ throw new Error("storageKey is required");
146
+ }
147
+ const token = createOpaqueToken();
148
+ const tokenHash = await sha256Base64Url(token);
149
+ const expiresAt = resolveExpiresAt(input);
150
+ const filename = maybeString(input.filename) ?? filenameFromStorageKey(storageKey);
151
+ const contentType = maybeString(input.contentType) ?? DEFAULT_CONTENT_TYPE;
152
+ const grant = await store.create({
153
+ tokenHash,
154
+ storageKey,
155
+ storageProvider: maybeString(input.storageProvider),
156
+ filename,
157
+ contentType,
158
+ sourceModule: maybeString(input.source?.module),
159
+ sourceEntity: maybeString(input.source?.entity),
160
+ sourceId: maybeString(input.source?.id),
161
+ createdBy: maybeString(input.createdBy),
162
+ createdByType: maybeString(input.createdByType),
163
+ metadata: input.metadata ?? null,
164
+ expiresAt,
165
+ });
166
+ return {
167
+ grantId: grant.id,
168
+ url: `${normalizePublicBaseUrl(input.publicBaseUrl)}${normalizePublicPath(input.publicPath)}/${token}`,
169
+ expiresAt: expiresAt.toISOString(),
170
+ filename,
171
+ };
172
+ }
173
+ export async function resolvePublicDocumentDeliveryGrant(store, token, now = new Date()) {
174
+ if (!isPlausiblePublicDocumentToken(token)) {
175
+ return { status: "not_found" };
176
+ }
177
+ const grant = await store.findByTokenHash(await sha256Base64Url(token));
178
+ if (!grant) {
179
+ return { status: "not_found" };
180
+ }
181
+ if (grant.revokedAt) {
182
+ return { status: "revoked", grant };
183
+ }
184
+ if (grant.expiresAt <= now) {
185
+ return { status: "expired", grant };
186
+ }
187
+ return { status: "ready", grant };
188
+ }
189
+ export async function revokePublicDocumentDeliveryGrant(store, input) {
190
+ return store.revoke(input);
191
+ }
192
+ export function createPublicDocumentDeliveryRoutes(options = {}) {
193
+ return new Hono().get("/:token", async (c) => {
194
+ const token = c.req.param("token");
195
+ const store = getStore(options, c.env, c.get("db"));
196
+ const resolution = await resolvePublicDocumentDeliveryGrant(store, token);
197
+ if (resolution.status === "not_found") {
198
+ return c.body(null, 404);
199
+ }
200
+ if (resolution.status === "expired" || resolution.status === "revoked") {
201
+ return c.body(null, 410);
202
+ }
203
+ const storage = getStorage(options, c.env);
204
+ if (!storage) {
205
+ return c.json({ error: "Document storage is not configured" }, 503);
206
+ }
207
+ const body = await storage.get(resolution.grant.storageKey);
208
+ if (!body) {
209
+ return c.body(null, 404);
210
+ }
211
+ await store.recordAccess(resolution.grant.id, {
212
+ accessedAt: new Date(),
213
+ ip: getClientIp(c.req.raw.headers),
214
+ userAgent: c.req.header("user-agent") ?? null,
215
+ });
216
+ return new Response(body, {
217
+ headers: {
218
+ "Cache-Control": "private, max-age=0, must-revalidate",
219
+ "Content-Type": safeContentType(resolution.grant.contentType),
220
+ "Content-Length": String(body.byteLength),
221
+ "Content-Disposition": contentDisposition(resolution.grant.filename),
222
+ },
223
+ });
224
+ });
225
+ }
226
+ export function createPublicDocumentDeliveryHonoModule(options = {}) {
227
+ return {
228
+ module: {
229
+ name: "documents",
230
+ },
231
+ publicRoutes: createPublicDocumentDeliveryRoutes(options),
232
+ };
233
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/hono",
3
- "version": "0.83.0",
3
+ "version": "0.84.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -29,6 +29,11 @@
29
29
  "import": "./dist/document-download.js",
30
30
  "default": "./dist/document-download.js"
31
31
  },
32
+ "./public-document-delivery": {
33
+ "types": "./dist/public-document-delivery.d.ts",
34
+ "import": "./dist/public-document-delivery.js",
35
+ "default": "./dist/public-document-delivery.js"
36
+ },
32
37
  "./types": {
33
38
  "types": "./dist/types.d.ts",
34
39
  "import": "./dist/types.js",
@@ -99,18 +104,19 @@
99
104
  "drizzle-orm": "^0.45.2",
100
105
  "hono": "^4.12.10",
101
106
  "zod": "^4.3.6",
102
- "@voyantjs/core": "0.83.0",
103
- "@voyantjs/db": "0.83.0",
104
- "@voyantjs/types": "0.83.0",
105
- "@voyantjs/utils": "0.83.0",
106
- "@voyantjs/workflows": "0.83.0"
107
+ "@voyantjs/core": "0.84.1",
108
+ "@voyantjs/db": "0.84.1",
109
+ "@voyantjs/storage": "0.84.1",
110
+ "@voyantjs/types": "0.84.1",
111
+ "@voyantjs/utils": "0.84.1",
112
+ "@voyantjs/workflows": "0.84.1"
107
113
  },
108
114
  "devDependencies": {
109
115
  "@cloudflare/workers-types": "^4.20260426.1",
110
116
  "typescript": "^6.0.2",
111
117
  "vitest": "^4.1.2",
112
118
  "@voyantjs/voyant-typescript-config": "0.1.0",
113
- "@voyantjs/workflows-orchestrator": "0.83.0"
119
+ "@voyantjs/workflows-orchestrator": "0.84.1"
114
120
  },
115
121
  "files": [
116
122
  "dist"