@sudobility/shapeshyft_service 1.0.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 (127) hide show
  1. package/CLAUDE.md +35 -0
  2. package/README.md +17 -0
  3. package/dist/context.d.ts +62 -0
  4. package/dist/context.d.ts.map +1 -0
  5. package/dist/context.js +55 -0
  6. package/dist/context.js.map +1 -0
  7. package/dist/contracts.d.ts +110 -0
  8. package/dist/contracts.d.ts.map +1 -0
  9. package/dist/contracts.js +7 -0
  10. package/dist/contracts.js.map +1 -0
  11. package/dist/index.d.ts +17 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +16 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/lib/api-key.d.ts +22 -0
  16. package/dist/lib/api-key.d.ts.map +1 -0
  17. package/dist/lib/api-key.js +87 -0
  18. package/dist/lib/api-key.js.map +1 -0
  19. package/dist/lib/encryption.d.ts +22 -0
  20. package/dist/lib/encryption.d.ts.map +1 -0
  21. package/dist/lib/encryption.js +41 -0
  22. package/dist/lib/encryption.js.map +1 -0
  23. package/dist/lib/entity-api-key.d.ts +21 -0
  24. package/dist/lib/entity-api-key.d.ts.map +1 -0
  25. package/dist/lib/entity-api-key.js +54 -0
  26. package/dist/lib/entity-api-key.js.map +1 -0
  27. package/dist/lib/entity-helpers.d.ts +90 -0
  28. package/dist/lib/entity-helpers.d.ts.map +1 -0
  29. package/dist/lib/entity-helpers.js +136 -0
  30. package/dist/lib/entity-helpers.js.map +1 -0
  31. package/dist/lib/money.d.ts +7 -0
  32. package/dist/lib/money.d.ts.map +1 -0
  33. package/dist/lib/money.js +9 -0
  34. package/dist/lib/money.js.map +1 -0
  35. package/dist/lib/public-project.d.ts +14 -0
  36. package/dist/lib/public-project.d.ts.map +1 -0
  37. package/dist/lib/public-project.js +17 -0
  38. package/dist/lib/public-project.js.map +1 -0
  39. package/dist/lib/user-api-key-cache.d.ts +35 -0
  40. package/dist/lib/user-api-key-cache.d.ts.map +1 -0
  41. package/dist/lib/user-api-key-cache.js +99 -0
  42. package/dist/lib/user-api-key-cache.js.map +1 -0
  43. package/dist/lib/user-api-key.d.ts +46 -0
  44. package/dist/lib/user-api-key.d.ts.map +1 -0
  45. package/dist/lib/user-api-key.js +102 -0
  46. package/dist/lib/user-api-key.js.map +1 -0
  47. package/dist/middleware/firebaseAuth.d.ts +34 -0
  48. package/dist/middleware/firebaseAuth.d.ts.map +1 -0
  49. package/dist/middleware/firebaseAuth.js +133 -0
  50. package/dist/middleware/firebaseAuth.js.map +1 -0
  51. package/dist/middleware/rateLimit.d.ts +29 -0
  52. package/dist/middleware/rateLimit.d.ts.map +1 -0
  53. package/dist/middleware/rateLimit.js +85 -0
  54. package/dist/middleware/rateLimit.js.map +1 -0
  55. package/dist/middleware/subscription.d.ts +8 -0
  56. package/dist/middleware/subscription.d.ts.map +1 -0
  57. package/dist/middleware/subscription.js +19 -0
  58. package/dist/middleware/subscription.js.map +1 -0
  59. package/dist/routes/ai.d.ts +10 -0
  60. package/dist/routes/ai.d.ts.map +1 -0
  61. package/dist/routes/ai.js +698 -0
  62. package/dist/routes/ai.js.map +1 -0
  63. package/dist/routes/analytics.d.ts +9 -0
  64. package/dist/routes/analytics.d.ts.map +1 -0
  65. package/dist/routes/analytics.js +192 -0
  66. package/dist/routes/analytics.js.map +1 -0
  67. package/dist/routes/endpoints.d.ts +10 -0
  68. package/dist/routes/endpoints.d.ts.map +1 -0
  69. package/dist/routes/endpoints.js +255 -0
  70. package/dist/routes/endpoints.js.map +1 -0
  71. package/dist/routes/entities.d.ts +15 -0
  72. package/dist/routes/entities.d.ts.map +1 -0
  73. package/dist/routes/entities.js +332 -0
  74. package/dist/routes/entities.js.map +1 -0
  75. package/dist/routes/entity-api-keys.d.ts +17 -0
  76. package/dist/routes/entity-api-keys.d.ts.map +1 -0
  77. package/dist/routes/entity-api-keys.js +142 -0
  78. package/dist/routes/entity-api-keys.js.map +1 -0
  79. package/dist/routes/invitations.d.ts +15 -0
  80. package/dist/routes/invitations.d.ts.map +1 -0
  81. package/dist/routes/invitations.js +58 -0
  82. package/dist/routes/invitations.js.map +1 -0
  83. package/dist/routes/projects.d.ts +9 -0
  84. package/dist/routes/projects.d.ts.map +1 -0
  85. package/dist/routes/projects.js +241 -0
  86. package/dist/routes/projects.js.map +1 -0
  87. package/dist/routes/providers.d.ts +9 -0
  88. package/dist/routes/providers.d.ts.map +1 -0
  89. package/dist/routes/providers.js +89 -0
  90. package/dist/routes/providers.js.map +1 -0
  91. package/dist/routes/ratelimits.d.ts +9 -0
  92. package/dist/routes/ratelimits.d.ts.map +1 -0
  93. package/dist/routes/ratelimits.js +171 -0
  94. package/dist/routes/ratelimits.js.map +1 -0
  95. package/dist/routes/settings.d.ts +9 -0
  96. package/dist/routes/settings.d.ts.map +1 -0
  97. package/dist/routes/settings.js +138 -0
  98. package/dist/routes/settings.js.map +1 -0
  99. package/dist/routes/storage.d.ts +9 -0
  100. package/dist/routes/storage.d.ts.map +1 -0
  101. package/dist/routes/storage.js +180 -0
  102. package/dist/routes/storage.js.map +1 -0
  103. package/dist/routes/user-api-keys.d.ts +14 -0
  104. package/dist/routes/user-api-keys.d.ts.map +1 -0
  105. package/dist/routes/user-api-keys.js +237 -0
  106. package/dist/routes/user-api-keys.js.map +1 -0
  107. package/dist/routes/users.d.ts +7 -0
  108. package/dist/routes/users.d.ts.map +1 -0
  109. package/dist/routes/users.js +102 -0
  110. package/dist/routes/users.js.map +1 -0
  111. package/dist/schema/init.d.ts +12 -0
  112. package/dist/schema/init.d.ts.map +1 -0
  113. package/dist/schema/init.js +404 -0
  114. package/dist/schema/init.js.map +1 -0
  115. package/dist/schema/tables.d.ts +2110 -0
  116. package/dist/schema/tables.d.ts.map +1 -0
  117. package/dist/schema/tables.js +251 -0
  118. package/dist/schema/tables.js.map +1 -0
  119. package/dist/schemas/index.d.ts +671 -0
  120. package/dist/schemas/index.d.ts.map +1 -0
  121. package/dist/schemas/index.js +299 -0
  122. package/dist/schemas/index.js.map +1 -0
  123. package/dist/service.d.ts +41 -0
  124. package/dist/service.d.ts.map +1 -0
  125. package/dist/service.js +70 -0
  126. package/dist/service.js.map +1 -0
  127. package/package.json +85 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @fileoverview Firebase authentication middleware
3
+ *
4
+ * Accepts a personal API key, an entity API key, or a Firebase ID token. Key
5
+ * prefixes, auth functions, and storage all come from the service context.
6
+ */
7
+ import { errorResponse } from "@sudobility/shapeshyft_engine/types";
8
+ import { eq } from "drizzle-orm";
9
+ /**
10
+ * Path prefix an entity API key may reach.
11
+ *
12
+ * Entity keys are scoped to entity-owned resources: projects, endpoints,
13
+ * provider keys, storage, analytics. They must not reach `/users/...`, which
14
+ * would let a key read or mint credentials belonging to the person who created
15
+ * it -- an escalation from "acts as the entity" to "acts as its author".
16
+ */
17
+ const ENTITY_API_KEY_PATH_PREFIX = "/entities/";
18
+ export function createFirebaseAuthMiddleware(ctx) {
19
+ const { db, logger } = ctx;
20
+ const { users } = ctx.tables;
21
+ const { extractUserApiKeyFromHeaders } = ctx.userApiKeys;
22
+ const { resolveUserApiKey } = ctx.userApiKeyCache;
23
+ const { extractEntityApiKeyFromHeaders } = ctx.entityApiKeyFormat;
24
+ const { entityHelpers } = ctx.entityAccess;
25
+ const { verifyIdToken, isSiteAdmin, isAnonymousUser } = ctx.auth;
26
+ const userPrefix = ctx.userApiKeys.prefix;
27
+ const entityPrefix = ctx.entityApiKeyFormat.prefixWithSeparator;
28
+ /**
29
+ * Ensure user record exists in database.
30
+ * Uses firebase_uid as the primary key.
31
+ */
32
+ async function ensureUserExists(firebaseUid, email) {
33
+ const existing = await db
34
+ .select()
35
+ .from(users)
36
+ .where(eq(users.firebase_uid, firebaseUid));
37
+ if (existing.length === 0) {
38
+ await db.insert(users).values({
39
+ firebase_uid: firebaseUid,
40
+ email: email ?? null,
41
+ });
42
+ }
43
+ }
44
+ /**
45
+ * Firebase authentication middleware.
46
+ *
47
+ * Verifies Firebase token and sets context variables:
48
+ * - firebaseUser: The decoded Firebase token
49
+ * - userId: The Firebase UID
50
+ * - userEmail: The user's email (or null)
51
+ * - siteAdmin: Whether the user is a site admin
52
+ *
53
+ * Also ensures user record exists in database (fire-and-forget).
54
+ */
55
+ async function firebaseAuthMiddleware(c, next) {
56
+ // 1. Personal API key ("<user prefix>..."), from X-API-Key or Authorization: Bearer.
57
+ // A resolved key sets exactly the same context variables a token would, minus
58
+ // firebaseUser, so every downstream handler behaves identically.
59
+ const userApiKey = extractUserApiKeyFromHeaders(name => c.req.header(name));
60
+ if (userApiKey) {
61
+ const resolved = await resolveUserApiKey(userApiKey);
62
+ if (!resolved) {
63
+ return c.json(errorResponse("Invalid or inactive API key"), 401);
64
+ }
65
+ c.set("userId", resolved.userId);
66
+ c.set("userEmail", resolved.userEmail);
67
+ c.set("siteAdmin", isSiteAdmin(resolved.userEmail));
68
+ c.set("authMethod", "api_key");
69
+ c.set("apiKeyId", resolved.keyId);
70
+ await next();
71
+ return;
72
+ }
73
+ // 2. Entity API key ("<entity prefix>_..."). Authenticates as the entity itself: no
74
+ // user identity, so downstream permission checks match on the entity instead
75
+ // of a membership role. `userId` is still populated with the key's author so
76
+ // audit columns keep a value.
77
+ const entityApiKey = extractEntityApiKeyFromHeaders(name => c.req.header(name));
78
+ if (entityApiKey) {
79
+ if (!entityHelpers.apiKeys) {
80
+ return c.json(errorResponse("Entity API keys are not enabled"), 501);
81
+ }
82
+ const identity = await entityHelpers.apiKeys.verifyKey(entityApiKey);
83
+ if (!identity) {
84
+ return c.json(errorResponse("Invalid or inactive API key"), 401);
85
+ }
86
+ if (!c.req.path.includes(ENTITY_API_KEY_PATH_PREFIX)) {
87
+ return c.json(errorResponse("Entity API keys may only access entity-scoped routes. Use a Firebase token or personal API key for this request."), 403);
88
+ }
89
+ c.set("userId", identity.createdByUserId);
90
+ c.set("userEmail", null);
91
+ c.set("siteAdmin", false);
92
+ c.set("authMethod", "entity_api_key");
93
+ c.set("entityApiKeyId", identity.keyId);
94
+ c.set("entityApiKeyEntityId", identity.entityId);
95
+ // Best-effort usage bookkeeping; never blocks the request
96
+ entityHelpers.apiKeys
97
+ .touchLastUsed(identity.keyId)
98
+ .catch(err => logger.error("Failed to stamp API key usage:", err));
99
+ await next();
100
+ return;
101
+ }
102
+ // 3. Firebase ID token
103
+ const authHeader = c.req.header("Authorization");
104
+ if (!authHeader) {
105
+ return c.json(errorResponse(`Authorization required. Provide a Firebase ID token as 'Authorization: Bearer <token>', a personal API key as 'X-API-Key: ${userPrefix}...', or an entity API key as 'X-API-Key: ${entityPrefix}...'`), 401);
106
+ }
107
+ const [type, token] = authHeader.split(" ");
108
+ if (type !== "Bearer" || !token) {
109
+ return c.json(errorResponse("Invalid authorization format. Use: Bearer <token>"), 401);
110
+ }
111
+ try {
112
+ const decodedToken = await verifyIdToken(token);
113
+ if (isAnonymousUser(decodedToken)) {
114
+ return c.json(errorResponse("Anonymous users cannot access this resource"), 403);
115
+ }
116
+ const userId = decodedToken.uid;
117
+ const userEmail = decodedToken.email ?? null;
118
+ c.set("firebaseUser", decodedToken);
119
+ c.set("userId", userId);
120
+ c.set("userEmail", userEmail);
121
+ c.set("siteAdmin", isSiteAdmin(userEmail));
122
+ c.set("authMethod", "firebase");
123
+ // Ensure user exists in database (fire-and-forget)
124
+ ensureUserExists(userId, userEmail).catch(err => logger.error("Failed to ensure user exists:", err));
125
+ await next();
126
+ }
127
+ catch {
128
+ return c.json(errorResponse("Invalid or expired Firebase token"), 401);
129
+ }
130
+ }
131
+ return firebaseAuthMiddleware;
132
+ }
133
+ //# sourceMappingURL=firebaseAuth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firebaseAuth.js","sourceRoot":"","sources":["../../src/middleware/firebaseAuth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { Context, Next } from "hono";
2
+ import { RateLimitRouteHandler, type RateLimitsConfig } from "@sudobility/ratelimit_service";
3
+ import type { ServiceDb } from "../contracts.js";
4
+ import type { ServiceTables } from "../schema/tables.js";
5
+ /**
6
+ * Default rate limit tiers
7
+ *
8
+ * - none: Free tier users (no subscription)
9
+ * - bandwidth_dev: Users with dev bandwidth entitlement
10
+ * - bandwidth_pro: Pro users with higher limits
11
+ * - bandwidth_ultra: Enterprise users with unlimited access
12
+ */
13
+ export declare const DEFAULT_RATE_LIMITS_CONFIG: RateLimitsConfig;
14
+ export declare const DEFAULT_ENTITLEMENT_DISPLAY_NAMES: Record<string, string>;
15
+ export declare function createRateLimiting(opts: {
16
+ db: ServiceDb;
17
+ rateLimitCounters: ServiceTables["rateLimitCounters"];
18
+ revenueCatApiKey: string | undefined;
19
+ rateLimitsConfig: RateLimitsConfig;
20
+ entitlementDisplayNames: Record<string, string>;
21
+ }): {
22
+ rateLimitsConfig: RateLimitsConfig;
23
+ entitlementDisplayNames: Record<string, string>;
24
+ getRateLimitRouteHandler: () => RateLimitRouteHandler;
25
+ rateLimitHandler: (c: Context, next: Next) => Promise<void>;
26
+ getTestMode: (c: Context) => boolean;
27
+ };
28
+ export type RateLimiting = ReturnType<typeof createRateLimiting>;
29
+ //# sourceMappingURL=rateLimit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAEL,qBAAqB,EACrB,KAAK,gBAAgB,EACtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,EAAE,gBAKxC,CAAC;AAEF,eAAO,MAAM,iCAAiC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKpE,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,EAAE,EAAE,SAAS,CAAC;IACd,iBAAiB,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;IACtD,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjD;;;oCAuBsC,qBAAqB;0BAqCvB,OAAO,QAAQ,IAAI;qBA3C9B,OAAO,KAAG,OAAO;EAsD1C;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
@@ -0,0 +1,85 @@
1
+ import { createRateLimitMiddleware, RateLimitRouteHandler, } from "@sudobility/ratelimit_service";
2
+ /**
3
+ * Default rate limit tiers
4
+ *
5
+ * - none: Free tier users (no subscription)
6
+ * - bandwidth_dev: Users with dev bandwidth entitlement
7
+ * - bandwidth_pro: Pro users with higher limits
8
+ * - bandwidth_ultra: Enterprise users with unlimited access
9
+ */
10
+ export const DEFAULT_RATE_LIMITS_CONFIG = {
11
+ none: { hourly: 10, daily: 120, monthly: 1800 },
12
+ bandwidth_dev: { hourly: 100, daily: 1200, monthly: 18000 },
13
+ bandwidth_pro: { hourly: 800, daily: 10000, monthly: 150000 },
14
+ bandwidth_ultra: { hourly: undefined, daily: undefined, monthly: undefined },
15
+ };
16
+ export const DEFAULT_ENTITLEMENT_DISPLAY_NAMES = {
17
+ none: "Free",
18
+ bandwidth_dev: "Developer",
19
+ bandwidth_pro: "Pro",
20
+ bandwidth_ultra: "Ultra",
21
+ };
22
+ export function createRateLimiting(opts) {
23
+ const { rateLimitsConfig, entitlementDisplayNames } = opts;
24
+ // Lazily initialized so a missing RevenueCat key fails on first use, not at boot
25
+ let routeHandler = null;
26
+ let middleware = null;
27
+ function requireRevenueCatKey() {
28
+ if (!opts.revenueCatApiKey) {
29
+ throw new Error("Required environment variable REVENUECAT_API_KEY is not set");
30
+ }
31
+ return opts.revenueCatApiKey;
32
+ }
33
+ /** Extract testMode from URL query parameter to filter sandbox purchases. */
34
+ function getTestMode(c) {
35
+ const url = new URL(c.req.url);
36
+ return url.searchParams.get("testMode") === "true";
37
+ }
38
+ /** The route handler for rate limit endpoints. */
39
+ function getRateLimitRouteHandler() {
40
+ if (!routeHandler) {
41
+ routeHandler = new RateLimitRouteHandler({
42
+ revenueCatApiKey: requireRevenueCatKey(),
43
+ rateLimitsConfig,
44
+ db: opts.db,
45
+ rateLimitsTable: opts.rateLimitCounters,
46
+ entitlementDisplayNames,
47
+ });
48
+ }
49
+ return routeHandler;
50
+ }
51
+ function getMiddleware() {
52
+ if (!middleware) {
53
+ middleware = createRateLimitMiddleware({
54
+ revenueCatApiKey: requireRevenueCatKey(),
55
+ rateLimitsConfig,
56
+ // Cast to any to avoid type conflicts between different drizzle-orm/hono
57
+ // instances when using bun link for local development
58
+ db: opts.db,
59
+ rateLimitsTable: opts.rateLimitCounters,
60
+ getUserId: (c) => {
61
+ // Set by the auth middleware for every auth method
62
+ const userId = c.get("userId");
63
+ if (!userId) {
64
+ throw new Error("Authenticated user not found in context");
65
+ }
66
+ return userId;
67
+ },
68
+ getTestMode: (c) => getTestMode(c),
69
+ });
70
+ }
71
+ return middleware;
72
+ }
73
+ /** Rate limit middleware for endpoints that need it. */
74
+ async function rateLimitHandler(c, next) {
75
+ await getMiddleware()(c, next);
76
+ }
77
+ return {
78
+ rateLimitsConfig,
79
+ entitlementDisplayNames,
80
+ getRateLimitRouteHandler,
81
+ rateLimitHandler,
82
+ getTestMode,
83
+ };
84
+ }
85
+ //# sourceMappingURL=rateLimit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Context } from "hono";
2
+ import { SubscriptionHelper } from "@sudobility/subscription_service";
3
+ export declare function createSubscriptionAccess(revenueCatApiKey: string | undefined): {
4
+ getSubscriptionHelper: () => SubscriptionHelper | null;
5
+ getTestMode: (c: Context) => boolean;
6
+ };
7
+ export type SubscriptionAccess = ReturnType<typeof createSubscriptionAccess>;
8
+ //# sourceMappingURL=subscription.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../src/middleware/subscription.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAEtE,wBAAgB,wBAAwB,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS;iCAIzC,kBAAkB,GAAG,IAAI;qBAQnC,OAAO,KAAG,OAAO;EAM1C;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { SubscriptionHelper } from "@sudobility/subscription_service";
2
+ export function createSubscriptionAccess(revenueCatApiKey) {
3
+ let subscriptionHelper = null;
4
+ /** Null when RevenueCat is not configured. */
5
+ function getSubscriptionHelper() {
6
+ if (!revenueCatApiKey)
7
+ return null;
8
+ if (!subscriptionHelper) {
9
+ subscriptionHelper = new SubscriptionHelper({ revenueCatApiKey });
10
+ }
11
+ return subscriptionHelper;
12
+ }
13
+ function getTestMode(c) {
14
+ const url = new URL(c.req.url);
15
+ return url.searchParams.get("testMode") === "true";
16
+ }
17
+ return { getSubscriptionHelper, getTestMode };
18
+ }
19
+ //# sourceMappingURL=subscription.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../src/middleware/subscription.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAEtE,MAAM,UAAU,wBAAwB,CAAC,gBAAoC;IAC3E,IAAI,kBAAkB,GAA8B,IAAI,CAAC;IAEzD,8CAA8C;IAC9C,SAAS,qBAAqB;QAC5B,IAAI,CAAC,gBAAgB;YAAE,OAAO,IAAI,CAAC;QACnC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxB,kBAAkB,GAAG,IAAI,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED,SAAS,WAAW,CAAC,CAAU;QAC7B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,MAAM,CAAC;IACrD,CAAC;IAED,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @fileoverview Public AI inference routes
3
+ * @description Handles AI endpoint invocation and prompt generation.
4
+ * These routes use project API key authentication (not Firebase),
5
+ * with optional IP allowlisting and rate limiting per entity.
6
+ */
7
+ import { Hono } from "hono";
8
+ import type { ServiceContext } from "../context.js";
9
+ export declare function createAiRouter(ctx: ServiceContext): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
10
+ //# sourceMappingURL=ai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/routes/ai.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AA6B5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AASpD,wBAAgB,cAAc,CAAC,GAAG,EAAE,cAAc,8EAm5BjD"}