@brokr/sdk 1.0.0 → 2.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.
- package/dist/account.js +34 -0
- package/dist/account.mjs +7 -0
- package/dist/auth.js +628 -114
- package/dist/auth.mjs +611 -111
- package/dist/chat.js +34 -0
- package/dist/chat.mjs +7 -0
- package/dist/events.js +64 -0
- package/dist/events.mjs +37 -0
- package/dist/feature.js +6304 -0
- package/dist/feature.mjs +6278 -0
- package/dist/files.js +428 -0
- package/dist/files.mjs +408 -0
- package/dist/index.d.ts +18 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4069 -454
- package/dist/index.mjs +4040 -448
- package/dist/logs.js +148 -0
- package/dist/logs.mjs +124 -0
- package/dist/management.js +14 -13
- package/dist/management.mjs +14 -13
- package/dist/next.js +2725 -0
- package/dist/next.mjs +2710 -0
- package/dist/notifications.js +140 -0
- package/dist/notifications.mjs +110 -0
- package/dist/payments.js +32 -0
- package/dist/payments.mjs +7 -0
- package/dist/react-notifications.js +286 -0
- package/dist/react-notifications.mjs +254 -0
- package/dist/react-styles.js +2718 -0
- package/dist/react-styles.mjs +2682 -0
- package/dist/react-theme.js +4194 -0
- package/dist/react-theme.mjs +4170 -0
- package/dist/react.js +8512 -209
- package/dist/react.mjs +8488 -179
- package/dist/runtime.js +2113 -385
- package/dist/runtime.mjs +2085 -397
- package/dist/src/account/config.d.ts +42 -0
- package/dist/src/account/config.d.ts.map +1 -0
- package/dist/src/account/index.d.ts +3 -0
- package/dist/src/account/index.d.ts.map +1 -0
- package/dist/src/ai/client.d.ts +58 -0
- package/dist/src/ai/client.d.ts.map +1 -0
- package/dist/src/ai/conversation-title.d.ts +13 -0
- package/dist/src/ai/conversation-title.d.ts.map +1 -0
- package/dist/src/ai/types.d.ts +81 -0
- package/dist/src/ai/types.d.ts.map +1 -0
- package/dist/src/auth.d.ts +133 -20
- package/dist/src/auth.d.ts.map +1 -1
- package/dist/src/chat/config.d.ts +61 -0
- package/dist/src/chat/config.d.ts.map +1 -0
- package/dist/src/chat/index.d.ts +3 -0
- package/dist/src/chat/index.d.ts.map +1 -0
- package/dist/src/chat/sse-parser.d.ts +44 -0
- package/dist/src/chat/sse-parser.d.ts.map +1 -0
- package/dist/src/dev-console.d.ts +18 -0
- package/dist/src/dev-console.d.ts.map +1 -0
- package/dist/src/email/client.d.ts +33 -0
- package/dist/src/email/client.d.ts.map +1 -0
- package/dist/src/email/templates.d.ts +15 -0
- package/dist/src/email/templates.d.ts.map +1 -0
- package/dist/src/email/types.d.ts +35 -0
- package/dist/src/email/types.d.ts.map +1 -0
- package/dist/src/env-detect.d.ts +25 -0
- package/dist/src/env-detect.d.ts.map +1 -0
- package/dist/src/errors.d.ts +53 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/events/client.d.ts +25 -0
- package/dist/src/events/client.d.ts.map +1 -0
- package/dist/src/events/index.d.ts +9 -0
- package/dist/src/events/index.d.ts.map +1 -0
- package/dist/src/events/types.d.ts +10 -0
- package/dist/src/events/types.d.ts.map +1 -0
- package/dist/src/feature/canonical.d.ts +33 -0
- package/dist/src/feature/canonical.d.ts.map +1 -0
- package/dist/src/feature/create-feature.d.ts +33 -0
- package/dist/src/feature/create-feature.d.ts.map +1 -0
- package/dist/src/feature/db.d.ts +16 -0
- package/dist/src/feature/db.d.ts.map +1 -0
- package/dist/src/feature/handlers.d.ts +28 -0
- package/dist/src/feature/handlers.d.ts.map +1 -0
- package/dist/src/feature/index.d.ts +21 -0
- package/dist/src/feature/index.d.ts.map +1 -0
- package/dist/src/feature/manifest.d.ts +173 -0
- package/dist/src/feature/manifest.d.ts.map +1 -0
- package/dist/src/feature/mapping.d.ts +18 -0
- package/dist/src/feature/mapping.d.ts.map +1 -0
- package/dist/src/feature/runtime.d.ts +45 -0
- package/dist/src/feature/runtime.d.ts.map +1 -0
- package/dist/src/feature/types.d.ts +65 -0
- package/dist/src/feature/types.d.ts.map +1 -0
- package/dist/src/files/client.d.ts +28 -0
- package/dist/src/files/client.d.ts.map +1 -0
- package/dist/src/files/types.d.ts +28 -0
- package/dist/src/files/types.d.ts.map +1 -0
- package/dist/src/fix-registry.d.ts +8 -0
- package/dist/src/fix-registry.d.ts.map +1 -0
- package/dist/src/gateway.d.ts +32 -0
- package/dist/src/gateway.d.ts.map +1 -0
- package/dist/src/logs/capture.d.ts +56 -0
- package/dist/src/logs/capture.d.ts.map +1 -0
- package/dist/src/logs/index.d.ts +2 -0
- package/dist/src/logs/index.d.ts.map +1 -0
- package/dist/src/management.d.ts +1 -1
- package/dist/src/management.d.ts.map +1 -1
- package/dist/src/models.d.ts +32 -0
- package/dist/src/models.d.ts.map +1 -0
- package/dist/src/next/auth.d.ts +54 -0
- package/dist/src/next/auth.d.ts.map +1 -0
- package/dist/src/next/chat.d.ts +31 -0
- package/dist/src/next/chat.d.ts.map +1 -0
- package/dist/src/next/index.d.ts +14 -0
- package/dist/src/next/index.d.ts.map +1 -0
- package/dist/src/next/notifications.d.ts +67 -0
- package/dist/src/next/notifications.d.ts.map +1 -0
- package/dist/src/notifications/built-ins.d.ts +9 -0
- package/dist/src/notifications/built-ins.d.ts.map +1 -0
- package/dist/src/notifications/client.d.ts +38 -0
- package/dist/src/notifications/client.d.ts.map +1 -0
- package/dist/src/notifications/config.d.ts +71 -0
- package/dist/src/notifications/config.d.ts.map +1 -0
- package/dist/src/notifications/index.d.ts +6 -0
- package/dist/src/notifications/index.d.ts.map +1 -0
- package/dist/src/notifications/registry.d.ts +67 -0
- package/dist/src/notifications/registry.d.ts.map +1 -0
- package/dist/src/notifications/types.d.ts +48 -0
- package/dist/src/notifications/types.d.ts.map +1 -0
- package/dist/src/payments/client.d.ts +64 -0
- package/dist/src/payments/client.d.ts.map +1 -0
- package/dist/src/payments/config.d.ts +46 -0
- package/dist/src/payments/config.d.ts.map +1 -0
- package/dist/src/payments/entitlements.d.ts +48 -0
- package/dist/src/payments/entitlements.d.ts.map +1 -0
- package/dist/src/payments/types.d.ts +135 -0
- package/dist/src/payments/types.d.ts.map +1 -0
- package/dist/src/react/BrokrErrorBoundary.d.ts +23 -0
- package/dist/src/react/BrokrErrorBoundary.d.ts.map +1 -0
- package/dist/src/react/account/AccountPanel.d.ts +12 -0
- package/dist/src/react/account/AccountPanel.d.ts.map +1 -0
- package/dist/src/react/account/Avatar.d.ts +11 -0
- package/dist/src/react/account/Avatar.d.ts.map +1 -0
- package/dist/src/react/account/ProfilePhotoButton.d.ts +7 -0
- package/dist/src/react/account/ProfilePhotoButton.d.ts.map +1 -0
- package/dist/src/react/account/UserButton.d.ts +7 -0
- package/dist/src/react/account/UserButton.d.ts.map +1 -0
- package/dist/src/react/auth-pages/AuthPageShell.d.ts +9 -0
- package/dist/src/react/auth-pages/AuthPageShell.d.ts.map +1 -0
- package/dist/src/react/auth-pages/SignInPage.d.ts +9 -0
- package/dist/src/react/auth-pages/SignInPage.d.ts.map +1 -0
- package/dist/src/react/auth-pages/SignUpPage.d.ts +8 -0
- package/dist/src/react/auth-pages/SignUpPage.d.ts.map +1 -0
- package/dist/src/react/auth.d.ts +1 -49
- package/dist/src/react/auth.d.ts.map +1 -1
- package/dist/src/react/chat/AIChat.d.ts +4 -0
- package/dist/src/react/chat/AIChat.d.ts.map +1 -0
- package/dist/src/react/chat/ChatContext.d.ts +76 -0
- package/dist/src/react/chat/ChatContext.d.ts.map +1 -0
- package/dist/src/react/chat/ChatInput.d.ts +3 -0
- package/dist/src/react/chat/ChatInput.d.ts.map +1 -0
- package/dist/src/react/chat/MarkdownRenderer.d.ts +5 -0
- package/dist/src/react/chat/MarkdownRenderer.d.ts.map +1 -0
- package/dist/src/react/chat/MessageBubble.d.ts +14 -0
- package/dist/src/react/chat/MessageBubble.d.ts.map +1 -0
- package/dist/src/react/chat/MessagePane.d.ts +10 -0
- package/dist/src/react/chat/MessagePane.d.ts.map +1 -0
- package/dist/src/react/chat/ModelSelector.d.ts +13 -0
- package/dist/src/react/chat/ModelSelector.d.ts.map +1 -0
- package/dist/src/react/chat/ThreadSidebar.d.ts +3 -0
- package/dist/src/react/chat/ThreadSidebar.d.ts.map +1 -0
- package/dist/src/react/chat/index.d.ts +5 -0
- package/dist/src/react/chat/index.d.ts.map +1 -0
- package/dist/src/react/chat/token-limit.d.ts +14 -0
- package/dist/src/react/chat/token-limit.d.ts.map +1 -0
- package/dist/src/react/chat/types.d.ts +65 -0
- package/dist/src/react/chat/types.d.ts.map +1 -0
- package/dist/src/react/chat/useChat.d.ts +57 -0
- package/dist/src/react/chat/useChat.d.ts.map +1 -0
- package/dist/src/react/composites/FabAI.d.ts +15 -0
- package/dist/src/react/composites/FabAI.d.ts.map +1 -0
- package/dist/src/react/composites/FeedbackWidget.d.ts +10 -0
- package/dist/src/react/composites/FeedbackWidget.d.ts.map +1 -0
- package/dist/src/react/composites/SmartUpload.d.ts +12 -0
- package/dist/src/react/composites/SmartUpload.d.ts.map +1 -0
- package/dist/src/react/config.d.ts +23 -0
- package/dist/src/react/config.d.ts.map +1 -0
- package/dist/src/react/context.d.ts +4 -0
- package/dist/src/react/context.d.ts.map +1 -0
- package/dist/src/react/css/account.d.ts +2 -0
- package/dist/src/react/css/account.d.ts.map +1 -0
- package/dist/src/react/css/animations.d.ts +2 -0
- package/dist/src/react/css/animations.d.ts.map +1 -0
- package/dist/src/react/css/auth.d.ts +2 -0
- package/dist/src/react/css/auth.d.ts.map +1 -0
- package/dist/src/react/css/chat-extras.d.ts +2 -0
- package/dist/src/react/css/chat-extras.d.ts.map +1 -0
- package/dist/src/react/css/chat.d.ts +2 -0
- package/dist/src/react/css/chat.d.ts.map +1 -0
- package/dist/src/react/css/composites.d.ts +2 -0
- package/dist/src/react/css/composites.d.ts.map +1 -0
- package/dist/src/react/css/gates.d.ts +2 -0
- package/dist/src/react/css/gates.d.ts.map +1 -0
- package/dist/src/react/css/index.d.ts +3 -0
- package/dist/src/react/css/index.d.ts.map +1 -0
- package/dist/src/react/css/markdown.d.ts +2 -0
- package/dist/src/react/css/markdown.d.ts.map +1 -0
- package/dist/src/react/css/notifications.d.ts +2 -0
- package/dist/src/react/css/notifications.d.ts.map +1 -0
- package/dist/src/react/css/primitives.d.ts +2 -0
- package/dist/src/react/css/primitives.d.ts.map +1 -0
- package/dist/src/react/css/reset.d.ts +2 -0
- package/dist/src/react/css/reset.d.ts.map +1 -0
- package/dist/src/react/css/responsive.d.ts +2 -0
- package/dist/src/react/css/responsive.d.ts.map +1 -0
- package/dist/src/react/css/skeleton.d.ts +2 -0
- package/dist/src/react/css/skeleton.d.ts.map +1 -0
- package/dist/src/react/css/tokens.d.ts +2 -0
- package/dist/src/react/css/tokens.d.ts.map +1 -0
- package/dist/src/react/gates/AuthWall.d.ts +7 -0
- package/dist/src/react/gates/AuthWall.d.ts.map +1 -0
- package/dist/src/react/gates/BillingBoundary.d.ts +4 -0
- package/dist/src/react/gates/BillingBoundary.d.ts.map +1 -0
- package/dist/src/react/gates/Gate.d.ts +9 -0
- package/dist/src/react/gates/Gate.d.ts.map +1 -0
- package/dist/src/react/gates/RequirePlan.d.ts +4 -0
- package/dist/src/react/gates/RequirePlan.d.ts.map +1 -0
- package/dist/src/react/gates/RequireUser.d.ts +4 -0
- package/dist/src/react/gates/RequireUser.d.ts.map +1 -0
- package/dist/src/react/gates/UsageGate.d.ts +4 -0
- package/dist/src/react/gates/UsageGate.d.ts.map +1 -0
- package/dist/src/react/helpers.d.ts +7 -0
- package/dist/src/react/helpers.d.ts.map +1 -0
- package/dist/src/react/hooks/use-theme.d.ts +15 -0
- package/dist/src/react/hooks/use-theme.d.ts.map +1 -0
- package/dist/src/react/hooks/use-user.d.ts +12 -0
- package/dist/src/react/hooks/use-user.d.ts.map +1 -0
- package/dist/src/react/icons.d.ts +26 -0
- package/dist/src/react/icons.d.ts.map +1 -0
- package/dist/src/react/index.d.ts +48 -0
- package/dist/src/react/index.d.ts.map +1 -0
- package/dist/src/react/notifications/NotificationBell.d.ts +7 -0
- package/dist/src/react/notifications/NotificationBell.d.ts.map +1 -0
- package/dist/src/react/notifications/NotificationList.d.ts +7 -0
- package/dist/src/react/notifications/NotificationList.d.ts.map +1 -0
- package/dist/src/react/notifications/Toast.d.ts +13 -0
- package/dist/src/react/notifications/Toast.d.ts.map +1 -0
- package/dist/src/react/notifications/index.d.ts +8 -0
- package/dist/src/react/notifications/index.d.ts.map +1 -0
- package/dist/src/react/notifications/provider.d.ts +14 -0
- package/dist/src/react/notifications/provider.d.ts.map +1 -0
- package/dist/src/react/notifications/use-notifications.d.ts +24 -0
- package/dist/src/react/notifications/use-notifications.d.ts.map +1 -0
- package/dist/src/react/payments/AutoReloadToggle.d.ts +6 -0
- package/dist/src/react/payments/AutoReloadToggle.d.ts.map +1 -0
- package/dist/src/react/payments/Balance.d.ts +8 -0
- package/dist/src/react/payments/Balance.d.ts.map +1 -0
- package/dist/src/react/payments/CancelSubscription.d.ts +6 -0
- package/dist/src/react/payments/CancelSubscription.d.ts.map +1 -0
- package/dist/src/react/payments/CheckoutButton.d.ts +7 -0
- package/dist/src/react/payments/CheckoutButton.d.ts.map +1 -0
- package/dist/src/react/payments/CustomerPortalButton.d.ts +6 -0
- package/dist/src/react/payments/CustomerPortalButton.d.ts.map +1 -0
- package/dist/src/react/payments/FeatureMeter.d.ts +6 -0
- package/dist/src/react/payments/FeatureMeter.d.ts.map +1 -0
- package/dist/src/react/payments/Plans.d.ts +8 -0
- package/dist/src/react/payments/Plans.d.ts.map +1 -0
- package/dist/src/react/payments/TopUpButton.d.ts +8 -0
- package/dist/src/react/payments/TopUpButton.d.ts.map +1 -0
- package/dist/src/react/payments/UpdateBilling.d.ts +3 -0
- package/dist/src/react/payments/UpdateBilling.d.ts.map +1 -0
- package/dist/src/react/payments/UpgradePrompt.d.ts +8 -0
- package/dist/src/react/payments/UpgradePrompt.d.ts.map +1 -0
- package/dist/src/react/primitives/Skeleton.d.ts +15 -0
- package/dist/src/react/primitives/Skeleton.d.ts.map +1 -0
- package/dist/src/react/provider.d.ts +21 -0
- package/dist/src/react/provider.d.ts.map +1 -0
- package/dist/src/react/request.d.ts +2 -0
- package/dist/src/react/request.d.ts.map +1 -0
- package/dist/src/react/styles-entry.d.ts +4 -0
- package/dist/src/react/styles-entry.d.ts.map +1 -0
- package/dist/src/react/styles.d.ts +2 -0
- package/dist/src/react/styles.d.ts.map +1 -0
- package/dist/src/react/theme-entry.d.ts +3 -0
- package/dist/src/react/theme-entry.d.ts.map +1 -0
- package/dist/src/react/theme.d.ts +6 -0
- package/dist/src/react/theme.d.ts.map +1 -0
- package/dist/src/react/types.d.ts +191 -0
- package/dist/src/react/types.d.ts.map +1 -0
- package/dist/src/react/use-brokr-theme.d.ts +6 -0
- package/dist/src/react/use-brokr-theme.d.ts.map +1 -0
- package/dist/src/runtime.d.ts +69 -180
- package/dist/src/runtime.d.ts.map +1 -1
- package/dist/src/storage/client.d.ts +113 -0
- package/dist/src/storage/client.d.ts.map +1 -0
- package/dist/src/storage/types.d.ts +60 -0
- package/dist/src/storage/types.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +70 -9
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var
|
|
7
|
-
return
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
8
10
|
};
|
|
9
11
|
var __export = (target, all) => {
|
|
10
12
|
for (var name in all)
|
|
@@ -18,14 +20,3186 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
20
|
}
|
|
19
21
|
return to;
|
|
20
22
|
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
21
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
32
|
|
|
33
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/headers.js
|
|
34
|
+
var require_headers = __commonJS({
|
|
35
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/headers.js"(exports2, module2) {
|
|
36
|
+
"use strict";
|
|
37
|
+
var __defProp2 = Object.defineProperty;
|
|
38
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
39
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
40
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
41
|
+
var __export2 = (target, all) => {
|
|
42
|
+
for (var name in all)
|
|
43
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
44
|
+
};
|
|
45
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
46
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
47
|
+
for (let key of __getOwnPropNames2(from))
|
|
48
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
49
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
50
|
+
}
|
|
51
|
+
return to;
|
|
52
|
+
};
|
|
53
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
54
|
+
var headers_exports = {};
|
|
55
|
+
__export2(headers_exports, {
|
|
56
|
+
CITY_HEADER_NAME: () => CITY_HEADER_NAME,
|
|
57
|
+
COUNTRY_HEADER_NAME: () => COUNTRY_HEADER_NAME,
|
|
58
|
+
EMOJI_FLAG_UNICODE_STARTING_POSITION: () => EMOJI_FLAG_UNICODE_STARTING_POSITION,
|
|
59
|
+
IP_HEADER_NAME: () => IP_HEADER_NAME,
|
|
60
|
+
LATITUDE_HEADER_NAME: () => LATITUDE_HEADER_NAME,
|
|
61
|
+
LONGITUDE_HEADER_NAME: () => LONGITUDE_HEADER_NAME,
|
|
62
|
+
POSTAL_CODE_HEADER_NAME: () => POSTAL_CODE_HEADER_NAME,
|
|
63
|
+
REGION_HEADER_NAME: () => REGION_HEADER_NAME,
|
|
64
|
+
REQUEST_ID_HEADER_NAME: () => REQUEST_ID_HEADER_NAME,
|
|
65
|
+
geolocation: () => geolocation2,
|
|
66
|
+
ipAddress: () => ipAddress2
|
|
67
|
+
});
|
|
68
|
+
module2.exports = __toCommonJS2(headers_exports);
|
|
69
|
+
var CITY_HEADER_NAME = "x-vercel-ip-city";
|
|
70
|
+
var COUNTRY_HEADER_NAME = "x-vercel-ip-country";
|
|
71
|
+
var IP_HEADER_NAME = "x-real-ip";
|
|
72
|
+
var LATITUDE_HEADER_NAME = "x-vercel-ip-latitude";
|
|
73
|
+
var LONGITUDE_HEADER_NAME = "x-vercel-ip-longitude";
|
|
74
|
+
var REGION_HEADER_NAME = "x-vercel-ip-country-region";
|
|
75
|
+
var POSTAL_CODE_HEADER_NAME = "x-vercel-ip-postal-code";
|
|
76
|
+
var REQUEST_ID_HEADER_NAME = "x-vercel-id";
|
|
77
|
+
var EMOJI_FLAG_UNICODE_STARTING_POSITION = 127397;
|
|
78
|
+
function getHeader(headers, key) {
|
|
79
|
+
return headers.get(key) ?? void 0;
|
|
80
|
+
}
|
|
81
|
+
function getHeaderWithDecode(request, key) {
|
|
82
|
+
const header = getHeader(request.headers, key);
|
|
83
|
+
return header ? decodeURIComponent(header) : void 0;
|
|
84
|
+
}
|
|
85
|
+
function getFlag(countryCode) {
|
|
86
|
+
const regex = new RegExp("^[A-Z]{2}$").test(countryCode);
|
|
87
|
+
if (!countryCode || !regex)
|
|
88
|
+
return void 0;
|
|
89
|
+
return String.fromCodePoint(
|
|
90
|
+
...countryCode.split("").map((char) => EMOJI_FLAG_UNICODE_STARTING_POSITION + char.charCodeAt(0))
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
function ipAddress2(input) {
|
|
94
|
+
const headers = "headers" in input ? input.headers : input;
|
|
95
|
+
return getHeader(headers, IP_HEADER_NAME);
|
|
96
|
+
}
|
|
97
|
+
function getRegionFromRequestId(requestId) {
|
|
98
|
+
if (!requestId) {
|
|
99
|
+
return "dev1";
|
|
100
|
+
}
|
|
101
|
+
return requestId.split(":")[0];
|
|
102
|
+
}
|
|
103
|
+
function geolocation2(request) {
|
|
104
|
+
return {
|
|
105
|
+
// city name may be encoded to support multi-byte characters
|
|
106
|
+
city: getHeaderWithDecode(request, CITY_HEADER_NAME),
|
|
107
|
+
country: getHeader(request.headers, COUNTRY_HEADER_NAME),
|
|
108
|
+
flag: getFlag(getHeader(request.headers, COUNTRY_HEADER_NAME)),
|
|
109
|
+
countryRegion: getHeader(request.headers, REGION_HEADER_NAME),
|
|
110
|
+
region: getRegionFromRequestId(
|
|
111
|
+
getHeader(request.headers, REQUEST_ID_HEADER_NAME)
|
|
112
|
+
),
|
|
113
|
+
latitude: getHeader(request.headers, LATITUDE_HEADER_NAME),
|
|
114
|
+
longitude: getHeader(request.headers, LONGITUDE_HEADER_NAME),
|
|
115
|
+
postalCode: getHeader(request.headers, POSTAL_CODE_HEADER_NAME)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/get-env.js
|
|
122
|
+
var require_get_env = __commonJS({
|
|
123
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/get-env.js"(exports2, module2) {
|
|
124
|
+
"use strict";
|
|
125
|
+
var __defProp2 = Object.defineProperty;
|
|
126
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
127
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
128
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
129
|
+
var __export2 = (target, all) => {
|
|
130
|
+
for (var name in all)
|
|
131
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
132
|
+
};
|
|
133
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
134
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
135
|
+
for (let key of __getOwnPropNames2(from))
|
|
136
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
137
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
138
|
+
}
|
|
139
|
+
return to;
|
|
140
|
+
};
|
|
141
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
142
|
+
var get_env_exports = {};
|
|
143
|
+
__export2(get_env_exports, {
|
|
144
|
+
getEnv: () => getEnv2
|
|
145
|
+
});
|
|
146
|
+
module2.exports = __toCommonJS2(get_env_exports);
|
|
147
|
+
var getEnv2 = (env = process.env) => ({
|
|
148
|
+
/**
|
|
149
|
+
* An indicator to show that System Environment Variables have been exposed to your project's Deployments.
|
|
150
|
+
* @example "1"
|
|
151
|
+
*/
|
|
152
|
+
VERCEL: get(env, "VERCEL"),
|
|
153
|
+
/**
|
|
154
|
+
* An indicator that the code is running in a Continuous Integration environment.
|
|
155
|
+
* @example "1"
|
|
156
|
+
*/
|
|
157
|
+
CI: get(env, "CI"),
|
|
158
|
+
/**
|
|
159
|
+
* The Environment that the app is deployed and running on.
|
|
160
|
+
* @example "production"
|
|
161
|
+
*/
|
|
162
|
+
VERCEL_ENV: get(env, "VERCEL_ENV"),
|
|
163
|
+
/**
|
|
164
|
+
* The domain name of the generated deployment URL. The value does not include the protocol scheme https://.
|
|
165
|
+
* NOTE: This Variable cannot be used in conjunction with Standard Deployment Protection.
|
|
166
|
+
* @example "*.vercel.app"
|
|
167
|
+
*/
|
|
168
|
+
VERCEL_URL: get(env, "VERCEL_URL"),
|
|
169
|
+
/**
|
|
170
|
+
* The domain name of the generated Git branch URL. The value does not include the protocol scheme https://.
|
|
171
|
+
* @example "*-git-*.vercel.app"
|
|
172
|
+
*/
|
|
173
|
+
VERCEL_BRANCH_URL: get(env, "VERCEL_BRANCH_URL"),
|
|
174
|
+
/**
|
|
175
|
+
* A production domain name of the project. This is useful to reliably generate links that point to production such as OG-image URLs.
|
|
176
|
+
* The value does not include the protocol scheme https://.
|
|
177
|
+
* @example "myproject.vercel.app"
|
|
178
|
+
*/
|
|
179
|
+
VERCEL_PROJECT_PRODUCTION_URL: get(env, "VERCEL_PROJECT_PRODUCTION_URL"),
|
|
180
|
+
/**
|
|
181
|
+
* The ID of the Region where the app is running.
|
|
182
|
+
*
|
|
183
|
+
* Possible values:
|
|
184
|
+
* - arn1 (Stockholm, Sweden)
|
|
185
|
+
* - bom1 (Mumbai, India)
|
|
186
|
+
* - cdg1 (Paris, France)
|
|
187
|
+
* - cle1 (Cleveland, USA)
|
|
188
|
+
* - cpt1 (Cape Town, South Africa)
|
|
189
|
+
* - dub1 (Dublin, Ireland)
|
|
190
|
+
* - fra1 (Frankfurt, Germany)
|
|
191
|
+
* - gru1 (São Paulo, Brazil)
|
|
192
|
+
* - hkg1 (Hong Kong)
|
|
193
|
+
* - hnd1 (Tokyo, Japan)
|
|
194
|
+
* - iad1 (Washington, D.C., USA)
|
|
195
|
+
* - icn1 (Seoul, South Korea)
|
|
196
|
+
* - kix1 (Osaka, Japan)
|
|
197
|
+
* - lhr1 (London, United Kingdom)
|
|
198
|
+
* - pdx1 (Portland, USA)
|
|
199
|
+
* - sfo1 (San Francisco, USA)
|
|
200
|
+
* - sin1 (Singapore)
|
|
201
|
+
* - syd1 (Sydney, Australia)
|
|
202
|
+
* - dev1 (Development Region)
|
|
203
|
+
*
|
|
204
|
+
* @example "iad1"
|
|
205
|
+
*/
|
|
206
|
+
VERCEL_REGION: get(env, "VERCEL_REGION"),
|
|
207
|
+
/**
|
|
208
|
+
* The unique identifier for the deployment, which can be used to implement Skew Protection.
|
|
209
|
+
* @example "dpl_7Gw5ZMBpQA8h9GF832KGp7nwbuh3"
|
|
210
|
+
*/
|
|
211
|
+
VERCEL_DEPLOYMENT_ID: get(env, "VERCEL_DEPLOYMENT_ID"),
|
|
212
|
+
/**
|
|
213
|
+
* When Skew Protection is enabled in Project Settings, this value is set to 1.
|
|
214
|
+
* @example "1"
|
|
215
|
+
*/
|
|
216
|
+
VERCEL_SKEW_PROTECTION_ENABLED: get(env, "VERCEL_SKEW_PROTECTION_ENABLED"),
|
|
217
|
+
/**
|
|
218
|
+
* The Protection Bypass for Automation value, if the secret has been generated in the project's Deployment Protection settings.
|
|
219
|
+
*/
|
|
220
|
+
VERCEL_AUTOMATION_BYPASS_SECRET: get(env, "VERCEL_AUTOMATION_BYPASS_SECRET"),
|
|
221
|
+
/**
|
|
222
|
+
* The Git Provider the deployment is triggered from.
|
|
223
|
+
* @example "github"
|
|
224
|
+
*/
|
|
225
|
+
VERCEL_GIT_PROVIDER: get(env, "VERCEL_GIT_PROVIDER"),
|
|
226
|
+
/**
|
|
227
|
+
* The origin repository the deployment is triggered from.
|
|
228
|
+
* @example "my-site"
|
|
229
|
+
*/
|
|
230
|
+
VERCEL_GIT_REPO_SLUG: get(env, "VERCEL_GIT_REPO_SLUG"),
|
|
231
|
+
/**
|
|
232
|
+
* The account that owns the repository the deployment is triggered from.
|
|
233
|
+
* @example "acme"
|
|
234
|
+
*/
|
|
235
|
+
VERCEL_GIT_REPO_OWNER: get(env, "VERCEL_GIT_REPO_OWNER"),
|
|
236
|
+
/**
|
|
237
|
+
* The ID of the repository the deployment is triggered from.
|
|
238
|
+
* @example "117716146"
|
|
239
|
+
*/
|
|
240
|
+
VERCEL_GIT_REPO_ID: get(env, "VERCEL_GIT_REPO_ID"),
|
|
241
|
+
/**
|
|
242
|
+
* The git branch of the commit the deployment was triggered by.
|
|
243
|
+
* @example "improve-about-page"
|
|
244
|
+
*/
|
|
245
|
+
VERCEL_GIT_COMMIT_REF: get(env, "VERCEL_GIT_COMMIT_REF"),
|
|
246
|
+
/**
|
|
247
|
+
* The git SHA of the commit the deployment was triggered by.
|
|
248
|
+
* @example "fa1eade47b73733d6312d5abfad33ce9e4068081"
|
|
249
|
+
*/
|
|
250
|
+
VERCEL_GIT_COMMIT_SHA: get(env, "VERCEL_GIT_COMMIT_SHA"),
|
|
251
|
+
/**
|
|
252
|
+
* The message attached to the commit the deployment was triggered by.
|
|
253
|
+
* @example "Update about page"
|
|
254
|
+
*/
|
|
255
|
+
VERCEL_GIT_COMMIT_MESSAGE: get(env, "VERCEL_GIT_COMMIT_MESSAGE"),
|
|
256
|
+
/**
|
|
257
|
+
* The username attached to the author of the commit that the project was deployed by.
|
|
258
|
+
* @example "johndoe"
|
|
259
|
+
*/
|
|
260
|
+
VERCEL_GIT_COMMIT_AUTHOR_LOGIN: get(env, "VERCEL_GIT_COMMIT_AUTHOR_LOGIN"),
|
|
261
|
+
/**
|
|
262
|
+
* The name attached to the author of the commit that the project was deployed by.
|
|
263
|
+
* @example "John Doe"
|
|
264
|
+
*/
|
|
265
|
+
VERCEL_GIT_COMMIT_AUTHOR_NAME: get(env, "VERCEL_GIT_COMMIT_AUTHOR_NAME"),
|
|
266
|
+
/**
|
|
267
|
+
* The git SHA of the last successful deployment for the project and branch.
|
|
268
|
+
* NOTE: This Variable is only exposed when an Ignored Build Step is provided.
|
|
269
|
+
* @example "fa1eade47b73733d6312d5abfad33ce9e4068080"
|
|
270
|
+
*/
|
|
271
|
+
VERCEL_GIT_PREVIOUS_SHA: get(env, "VERCEL_GIT_PREVIOUS_SHA"),
|
|
272
|
+
/**
|
|
273
|
+
* The pull request id the deployment was triggered by. If a deployment is created on a branch before a pull request is made, this value will be an empty string.
|
|
274
|
+
* @example "23"
|
|
275
|
+
*/
|
|
276
|
+
VERCEL_GIT_PULL_REQUEST_ID: get(env, "VERCEL_GIT_PULL_REQUEST_ID")
|
|
277
|
+
});
|
|
278
|
+
var get = (env, key) => {
|
|
279
|
+
const value = env[key];
|
|
280
|
+
return value === "" ? void 0 : value;
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/get-context.js
|
|
286
|
+
var require_get_context = __commonJS({
|
|
287
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/get-context.js"(exports2, module2) {
|
|
288
|
+
"use strict";
|
|
289
|
+
var __defProp2 = Object.defineProperty;
|
|
290
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
291
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
292
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
293
|
+
var __export2 = (target, all) => {
|
|
294
|
+
for (var name in all)
|
|
295
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
296
|
+
};
|
|
297
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
298
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
299
|
+
for (let key of __getOwnPropNames2(from))
|
|
300
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
301
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
302
|
+
}
|
|
303
|
+
return to;
|
|
304
|
+
};
|
|
305
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
306
|
+
var get_context_exports = {};
|
|
307
|
+
__export2(get_context_exports, {
|
|
308
|
+
SYMBOL_FOR_REQ_CONTEXT: () => SYMBOL_FOR_REQ_CONTEXT,
|
|
309
|
+
getContext: () => getContext
|
|
310
|
+
});
|
|
311
|
+
module2.exports = __toCommonJS2(get_context_exports);
|
|
312
|
+
var SYMBOL_FOR_REQ_CONTEXT = Symbol.for("@vercel/request-context");
|
|
313
|
+
function getContext() {
|
|
314
|
+
const fromSymbol = globalThis;
|
|
315
|
+
return fromSymbol[SYMBOL_FOR_REQ_CONTEXT]?.get?.() ?? {};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/wait-until.js
|
|
321
|
+
var require_wait_until = __commonJS({
|
|
322
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/wait-until.js"(exports2, module2) {
|
|
323
|
+
"use strict";
|
|
324
|
+
var __defProp2 = Object.defineProperty;
|
|
325
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
326
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
327
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
328
|
+
var __export2 = (target, all) => {
|
|
329
|
+
for (var name in all)
|
|
330
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
331
|
+
};
|
|
332
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
333
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
334
|
+
for (let key of __getOwnPropNames2(from))
|
|
335
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
336
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
337
|
+
}
|
|
338
|
+
return to;
|
|
339
|
+
};
|
|
340
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
341
|
+
var wait_until_exports = {};
|
|
342
|
+
__export2(wait_until_exports, {
|
|
343
|
+
waitUntil: () => waitUntil2
|
|
344
|
+
});
|
|
345
|
+
module2.exports = __toCommonJS2(wait_until_exports);
|
|
346
|
+
var import_get_context = require_get_context();
|
|
347
|
+
var waitUntil2 = (promise) => {
|
|
348
|
+
if (promise === null || typeof promise !== "object" || typeof promise.then !== "function") {
|
|
349
|
+
throw new TypeError(
|
|
350
|
+
`waitUntil can only be called with a Promise, got ${typeof promise}`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
return (0, import_get_context.getContext)().waitUntil?.(promise);
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/middleware.js
|
|
359
|
+
var require_middleware = __commonJS({
|
|
360
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/middleware.js"(exports2, module2) {
|
|
361
|
+
"use strict";
|
|
362
|
+
var __defProp2 = Object.defineProperty;
|
|
363
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
364
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
365
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
366
|
+
var __export2 = (target, all) => {
|
|
367
|
+
for (var name in all)
|
|
368
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
369
|
+
};
|
|
370
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
371
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
372
|
+
for (let key of __getOwnPropNames2(from))
|
|
373
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
374
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
375
|
+
}
|
|
376
|
+
return to;
|
|
377
|
+
};
|
|
378
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
379
|
+
var middleware_exports = {};
|
|
380
|
+
__export2(middleware_exports, {
|
|
381
|
+
next: () => next2,
|
|
382
|
+
rewrite: () => rewrite2
|
|
383
|
+
});
|
|
384
|
+
module2.exports = __toCommonJS2(middleware_exports);
|
|
385
|
+
function handleMiddlewareField(init, headers) {
|
|
386
|
+
if (init?.request?.headers) {
|
|
387
|
+
if (!(init.request.headers instanceof Headers)) {
|
|
388
|
+
throw new Error("request.headers must be an instance of Headers");
|
|
389
|
+
}
|
|
390
|
+
const keys = [];
|
|
391
|
+
for (const [key, value] of init.request.headers) {
|
|
392
|
+
headers.set("x-middleware-request-" + key, value);
|
|
393
|
+
keys.push(key);
|
|
394
|
+
}
|
|
395
|
+
headers.set("x-middleware-override-headers", keys.join(","));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function rewrite2(destination, init) {
|
|
399
|
+
const headers = new Headers(init?.headers ?? {});
|
|
400
|
+
headers.set("x-middleware-rewrite", String(destination));
|
|
401
|
+
handleMiddlewareField(init, headers);
|
|
402
|
+
return new Response(null, {
|
|
403
|
+
...init,
|
|
404
|
+
headers
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
function next2(init) {
|
|
408
|
+
const headers = new Headers(init?.headers ?? {});
|
|
409
|
+
headers.set("x-middleware-next", "1");
|
|
410
|
+
handleMiddlewareField(init, headers);
|
|
411
|
+
return new Response(null, {
|
|
412
|
+
...init,
|
|
413
|
+
headers
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/cache/in-memory-cache.js
|
|
420
|
+
var require_in_memory_cache = __commonJS({
|
|
421
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/cache/in-memory-cache.js"(exports2, module2) {
|
|
422
|
+
"use strict";
|
|
423
|
+
var __defProp2 = Object.defineProperty;
|
|
424
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
425
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
426
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
427
|
+
var __export2 = (target, all) => {
|
|
428
|
+
for (var name in all)
|
|
429
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
430
|
+
};
|
|
431
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
432
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
433
|
+
for (let key of __getOwnPropNames2(from))
|
|
434
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
435
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
436
|
+
}
|
|
437
|
+
return to;
|
|
438
|
+
};
|
|
439
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
440
|
+
var in_memory_cache_exports = {};
|
|
441
|
+
__export2(in_memory_cache_exports, {
|
|
442
|
+
InMemoryCache: () => InMemoryCache
|
|
443
|
+
});
|
|
444
|
+
module2.exports = __toCommonJS2(in_memory_cache_exports);
|
|
445
|
+
var InMemoryCache = class {
|
|
446
|
+
constructor() {
|
|
447
|
+
this.cache = {};
|
|
448
|
+
}
|
|
449
|
+
async get(key) {
|
|
450
|
+
const entry = this.cache[key];
|
|
451
|
+
if (entry) {
|
|
452
|
+
if (entry.ttl && entry.lastModified + entry.ttl * 1e3 < Date.now()) {
|
|
453
|
+
await this.delete(key);
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
return JSON.parse(entry.value);
|
|
457
|
+
}
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
async set(key, value, options) {
|
|
461
|
+
const serialized = JSON.stringify(value ?? null);
|
|
462
|
+
this.cache[key] = {
|
|
463
|
+
value: serialized,
|
|
464
|
+
lastModified: Date.now(),
|
|
465
|
+
ttl: options?.ttl,
|
|
466
|
+
tags: new Set(options?.tags || [])
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
async delete(key) {
|
|
470
|
+
delete this.cache[key];
|
|
471
|
+
}
|
|
472
|
+
async expireTag(tag) {
|
|
473
|
+
const tags = Array.isArray(tag) ? tag : [tag];
|
|
474
|
+
for (const key in this.cache) {
|
|
475
|
+
if (Object.prototype.hasOwnProperty.call(this.cache, key)) {
|
|
476
|
+
const entry = this.cache[key];
|
|
477
|
+
if (tags.some((t) => entry.tags.has(t))) {
|
|
478
|
+
delete this.cache[key];
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/cache/build-client.js
|
|
488
|
+
var require_build_client = __commonJS({
|
|
489
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/cache/build-client.js"(exports2, module2) {
|
|
490
|
+
"use strict";
|
|
491
|
+
var __defProp2 = Object.defineProperty;
|
|
492
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
493
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
494
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
495
|
+
var __export2 = (target, all) => {
|
|
496
|
+
for (var name in all)
|
|
497
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
498
|
+
};
|
|
499
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
500
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
501
|
+
for (let key of __getOwnPropNames2(from))
|
|
502
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
503
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
504
|
+
}
|
|
505
|
+
return to;
|
|
506
|
+
};
|
|
507
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
508
|
+
var build_client_exports = {};
|
|
509
|
+
__export2(build_client_exports, {
|
|
510
|
+
BuildCache: () => BuildCache
|
|
511
|
+
});
|
|
512
|
+
module2.exports = __toCommonJS2(build_client_exports);
|
|
513
|
+
var import_index = require_cache();
|
|
514
|
+
var BuildCache = class {
|
|
515
|
+
constructor({
|
|
516
|
+
endpoint,
|
|
517
|
+
headers,
|
|
518
|
+
onError,
|
|
519
|
+
timeout = 500
|
|
520
|
+
}) {
|
|
521
|
+
this.get = async (key) => {
|
|
522
|
+
const controller = new AbortController();
|
|
523
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
524
|
+
try {
|
|
525
|
+
const res = await fetch(`${this.endpoint}${key}`, {
|
|
526
|
+
headers: this.headers,
|
|
527
|
+
method: "GET",
|
|
528
|
+
signal: controller.signal
|
|
529
|
+
});
|
|
530
|
+
if (res.status === 404) {
|
|
531
|
+
clearTimeout(timeoutId);
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
if (res.status === 200) {
|
|
535
|
+
const cacheState = res.headers.get(
|
|
536
|
+
import_index.HEADERS_VERCEL_CACHE_STATE
|
|
537
|
+
);
|
|
538
|
+
if (cacheState !== import_index.PkgCacheState.Fresh) {
|
|
539
|
+
res.body?.cancel?.();
|
|
540
|
+
clearTimeout(timeoutId);
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
const result = await res.json();
|
|
544
|
+
clearTimeout(timeoutId);
|
|
545
|
+
return result;
|
|
546
|
+
} else {
|
|
547
|
+
clearTimeout(timeoutId);
|
|
548
|
+
throw new Error(`Failed to get cache: ${res.statusText}`);
|
|
549
|
+
}
|
|
550
|
+
} catch (error) {
|
|
551
|
+
clearTimeout(timeoutId);
|
|
552
|
+
if (error.name === "AbortError") {
|
|
553
|
+
const timeoutError = new Error(
|
|
554
|
+
`Cache request timed out after ${this.timeout}ms`
|
|
555
|
+
);
|
|
556
|
+
timeoutError.stack = error.stack;
|
|
557
|
+
this.onError?.(timeoutError);
|
|
558
|
+
} else {
|
|
559
|
+
this.onError?.(error);
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
this.set = async (key, value, options) => {
|
|
565
|
+
const controller = new AbortController();
|
|
566
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
567
|
+
try {
|
|
568
|
+
const optionalHeaders = {};
|
|
569
|
+
if (options?.ttl) {
|
|
570
|
+
optionalHeaders[import_index.HEADERS_VERCEL_REVALIDATE] = options.ttl.toString();
|
|
571
|
+
}
|
|
572
|
+
if (options?.tags && options.tags.length > 0) {
|
|
573
|
+
optionalHeaders[import_index.HEADERS_VERCEL_CACHE_TAGS] = options.tags.join(",");
|
|
574
|
+
}
|
|
575
|
+
if (options?.name) {
|
|
576
|
+
optionalHeaders[import_index.HEADERS_VERCEL_CACHE_ITEM_NAME] = options.name;
|
|
577
|
+
}
|
|
578
|
+
const res = await fetch(`${this.endpoint}${key}`, {
|
|
579
|
+
method: "POST",
|
|
580
|
+
headers: {
|
|
581
|
+
...this.headers,
|
|
582
|
+
...optionalHeaders
|
|
583
|
+
},
|
|
584
|
+
body: JSON.stringify(value),
|
|
585
|
+
signal: controller.signal
|
|
586
|
+
});
|
|
587
|
+
clearTimeout(timeoutId);
|
|
588
|
+
if (res.status !== 200) {
|
|
589
|
+
throw new Error(`Failed to set cache: ${res.status} ${res.statusText}`);
|
|
590
|
+
}
|
|
591
|
+
} catch (error) {
|
|
592
|
+
clearTimeout(timeoutId);
|
|
593
|
+
if (error.name === "AbortError") {
|
|
594
|
+
const timeoutError = new Error(
|
|
595
|
+
`Cache request timed out after ${this.timeout}ms`
|
|
596
|
+
);
|
|
597
|
+
timeoutError.stack = error.stack;
|
|
598
|
+
this.onError?.(timeoutError);
|
|
599
|
+
} else {
|
|
600
|
+
this.onError?.(error);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
this.delete = async (key) => {
|
|
605
|
+
const controller = new AbortController();
|
|
606
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
607
|
+
try {
|
|
608
|
+
const res = await fetch(`${this.endpoint}${key}`, {
|
|
609
|
+
method: "DELETE",
|
|
610
|
+
headers: this.headers,
|
|
611
|
+
signal: controller.signal
|
|
612
|
+
});
|
|
613
|
+
clearTimeout(timeoutId);
|
|
614
|
+
if (res.status !== 200) {
|
|
615
|
+
throw new Error(`Failed to delete cache: ${res.statusText}`);
|
|
616
|
+
}
|
|
617
|
+
} catch (error) {
|
|
618
|
+
clearTimeout(timeoutId);
|
|
619
|
+
if (error.name === "AbortError") {
|
|
620
|
+
const timeoutError = new Error(
|
|
621
|
+
`Cache request timed out after ${this.timeout}ms`
|
|
622
|
+
);
|
|
623
|
+
timeoutError.stack = error.stack;
|
|
624
|
+
this.onError?.(timeoutError);
|
|
625
|
+
} else {
|
|
626
|
+
this.onError?.(error);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
this.expireTag = async (tag) => {
|
|
631
|
+
const controller = new AbortController();
|
|
632
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
633
|
+
try {
|
|
634
|
+
if (Array.isArray(tag)) {
|
|
635
|
+
tag = tag.join(",");
|
|
636
|
+
}
|
|
637
|
+
const res = await fetch(`${this.endpoint}revalidate?tags=${tag}`, {
|
|
638
|
+
method: "POST",
|
|
639
|
+
headers: this.headers,
|
|
640
|
+
signal: controller.signal
|
|
641
|
+
});
|
|
642
|
+
clearTimeout(timeoutId);
|
|
643
|
+
if (res.status !== 200) {
|
|
644
|
+
throw new Error(`Failed to revalidate tag: ${res.statusText}`);
|
|
645
|
+
}
|
|
646
|
+
} catch (error) {
|
|
647
|
+
clearTimeout(timeoutId);
|
|
648
|
+
if (error.name === "AbortError") {
|
|
649
|
+
const timeoutError = new Error(
|
|
650
|
+
`Cache request timed out after ${this.timeout}ms`
|
|
651
|
+
);
|
|
652
|
+
timeoutError.stack = error.stack;
|
|
653
|
+
this.onError?.(timeoutError);
|
|
654
|
+
} else {
|
|
655
|
+
this.onError?.(error);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
this.endpoint = endpoint;
|
|
660
|
+
this.headers = headers;
|
|
661
|
+
this.onError = onError;
|
|
662
|
+
this.timeout = timeout;
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/cache/index.js
|
|
669
|
+
var require_cache = __commonJS({
|
|
670
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/cache/index.js"(exports2, module2) {
|
|
671
|
+
"use strict";
|
|
672
|
+
var __defProp2 = Object.defineProperty;
|
|
673
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
674
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
675
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
676
|
+
var __export2 = (target, all) => {
|
|
677
|
+
for (var name in all)
|
|
678
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
679
|
+
};
|
|
680
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
681
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
682
|
+
for (let key of __getOwnPropNames2(from))
|
|
683
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
684
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
685
|
+
}
|
|
686
|
+
return to;
|
|
687
|
+
};
|
|
688
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
689
|
+
var cache_exports = {};
|
|
690
|
+
__export2(cache_exports, {
|
|
691
|
+
HEADERS_VERCEL_CACHE_ITEM_NAME: () => HEADERS_VERCEL_CACHE_ITEM_NAME,
|
|
692
|
+
HEADERS_VERCEL_CACHE_STATE: () => HEADERS_VERCEL_CACHE_STATE,
|
|
693
|
+
HEADERS_VERCEL_CACHE_TAGS: () => HEADERS_VERCEL_CACHE_TAGS,
|
|
694
|
+
HEADERS_VERCEL_REVALIDATE: () => HEADERS_VERCEL_REVALIDATE,
|
|
695
|
+
PkgCacheState: () => PkgCacheState,
|
|
696
|
+
getCache: () => getCache2
|
|
697
|
+
});
|
|
698
|
+
module2.exports = __toCommonJS2(cache_exports);
|
|
699
|
+
var import_get_context = require_get_context();
|
|
700
|
+
var import_in_memory_cache = require_in_memory_cache();
|
|
701
|
+
var import_build_client = require_build_client();
|
|
702
|
+
var defaultKeyHashFunction = (key) => {
|
|
703
|
+
let hash = 5381;
|
|
704
|
+
for (let i = 0; i < key.length; i++) {
|
|
705
|
+
hash = hash * 33 ^ key.charCodeAt(i);
|
|
706
|
+
}
|
|
707
|
+
return (hash >>> 0).toString(16);
|
|
708
|
+
};
|
|
709
|
+
var defaultNamespaceSeparator = "$";
|
|
710
|
+
var inMemoryCacheInstance = null;
|
|
711
|
+
var buildCacheInstance = null;
|
|
712
|
+
var getCache2 = (cacheOptions) => {
|
|
713
|
+
const resolveCache = () => {
|
|
714
|
+
let cache;
|
|
715
|
+
if ((0, import_get_context.getContext)().cache) {
|
|
716
|
+
cache = (0, import_get_context.getContext)().cache;
|
|
717
|
+
} else {
|
|
718
|
+
cache = getCacheImplementation(
|
|
719
|
+
process.env.SUSPENSE_CACHE_DEBUG === "true"
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
return cache;
|
|
723
|
+
};
|
|
724
|
+
return wrapWithKeyTransformation(
|
|
725
|
+
resolveCache,
|
|
726
|
+
createKeyTransformer(cacheOptions)
|
|
727
|
+
);
|
|
728
|
+
};
|
|
729
|
+
function createKeyTransformer(cacheOptions) {
|
|
730
|
+
const hashFunction = cacheOptions?.keyHashFunction || defaultKeyHashFunction;
|
|
731
|
+
return (key) => {
|
|
732
|
+
if (!cacheOptions?.namespace)
|
|
733
|
+
return hashFunction(key);
|
|
734
|
+
const separator = cacheOptions.namespaceSeparator || defaultNamespaceSeparator;
|
|
735
|
+
return `${cacheOptions.namespace}${separator}${hashFunction(key)}`;
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function wrapWithKeyTransformation(resolveCache, makeKey) {
|
|
739
|
+
return {
|
|
740
|
+
get: (key) => {
|
|
741
|
+
return resolveCache().get(makeKey(key));
|
|
742
|
+
},
|
|
743
|
+
set: (key, value, options) => {
|
|
744
|
+
return resolveCache().set(makeKey(key), value, options);
|
|
745
|
+
},
|
|
746
|
+
delete: (key) => {
|
|
747
|
+
return resolveCache().delete(makeKey(key));
|
|
748
|
+
},
|
|
749
|
+
expireTag: (tag) => {
|
|
750
|
+
return resolveCache().expireTag(tag);
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
var warnedCacheUnavailable = false;
|
|
755
|
+
function getCacheImplementation(debug) {
|
|
756
|
+
if (!inMemoryCacheInstance) {
|
|
757
|
+
inMemoryCacheInstance = new import_in_memory_cache.InMemoryCache();
|
|
758
|
+
}
|
|
759
|
+
if (process.env.RUNTIME_CACHE_DISABLE_BUILD_CACHE === "true") {
|
|
760
|
+
debug && console.log("Using InMemoryCache as build cache is disabled");
|
|
761
|
+
return inMemoryCacheInstance;
|
|
762
|
+
}
|
|
763
|
+
const { RUNTIME_CACHE_ENDPOINT, RUNTIME_CACHE_HEADERS } = process.env;
|
|
764
|
+
if (debug) {
|
|
765
|
+
console.log("Runtime cache environment variables:", {
|
|
766
|
+
RUNTIME_CACHE_ENDPOINT,
|
|
767
|
+
RUNTIME_CACHE_HEADERS
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
if (!RUNTIME_CACHE_ENDPOINT || !RUNTIME_CACHE_HEADERS) {
|
|
771
|
+
if (!warnedCacheUnavailable) {
|
|
772
|
+
console.warn(
|
|
773
|
+
"Runtime Cache unavailable in this environment. Falling back to in-memory cache."
|
|
774
|
+
);
|
|
775
|
+
warnedCacheUnavailable = true;
|
|
776
|
+
}
|
|
777
|
+
return inMemoryCacheInstance;
|
|
778
|
+
}
|
|
779
|
+
if (!buildCacheInstance) {
|
|
780
|
+
let parsedHeaders = {};
|
|
781
|
+
try {
|
|
782
|
+
parsedHeaders = JSON.parse(RUNTIME_CACHE_HEADERS);
|
|
783
|
+
} catch (e) {
|
|
784
|
+
console.error("Failed to parse RUNTIME_CACHE_HEADERS:", e);
|
|
785
|
+
return inMemoryCacheInstance;
|
|
786
|
+
}
|
|
787
|
+
let timeout = 500;
|
|
788
|
+
if (process.env.RUNTIME_CACHE_TIMEOUT) {
|
|
789
|
+
const parsed = parseInt(process.env.RUNTIME_CACHE_TIMEOUT, 10);
|
|
790
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
791
|
+
timeout = parsed;
|
|
792
|
+
} else {
|
|
793
|
+
console.warn(
|
|
794
|
+
`Invalid RUNTIME_CACHE_TIMEOUT value: "${process.env.RUNTIME_CACHE_TIMEOUT}". Using default: ${timeout}ms`
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
buildCacheInstance = new import_build_client.BuildCache({
|
|
799
|
+
endpoint: RUNTIME_CACHE_ENDPOINT,
|
|
800
|
+
headers: parsedHeaders,
|
|
801
|
+
onError: (error) => console.error(error),
|
|
802
|
+
timeout
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
return buildCacheInstance;
|
|
806
|
+
}
|
|
807
|
+
var PkgCacheState = /* @__PURE__ */ ((PkgCacheState2) => {
|
|
808
|
+
PkgCacheState2["Fresh"] = "fresh";
|
|
809
|
+
PkgCacheState2["Stale"] = "stale";
|
|
810
|
+
PkgCacheState2["Expired"] = "expired";
|
|
811
|
+
PkgCacheState2["NotFound"] = "notFound";
|
|
812
|
+
PkgCacheState2["Error"] = "error";
|
|
813
|
+
return PkgCacheState2;
|
|
814
|
+
})(PkgCacheState || {});
|
|
815
|
+
var HEADERS_VERCEL_CACHE_STATE = "x-vercel-cache-state";
|
|
816
|
+
var HEADERS_VERCEL_REVALIDATE = "x-vercel-revalidate";
|
|
817
|
+
var HEADERS_VERCEL_CACHE_TAGS = "x-vercel-cache-tags";
|
|
818
|
+
var HEADERS_VERCEL_CACHE_ITEM_NAME = "x-vercel-cache-item-name";
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/db-connections/index.js
|
|
823
|
+
var require_db_connections = __commonJS({
|
|
824
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/db-connections/index.js"(exports2, module2) {
|
|
825
|
+
"use strict";
|
|
826
|
+
var __defProp2 = Object.defineProperty;
|
|
827
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
828
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
829
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
830
|
+
var __export2 = (target, all) => {
|
|
831
|
+
for (var name in all)
|
|
832
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
833
|
+
};
|
|
834
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
835
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
836
|
+
for (let key of __getOwnPropNames2(from))
|
|
837
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
838
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
839
|
+
}
|
|
840
|
+
return to;
|
|
841
|
+
};
|
|
842
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
843
|
+
var db_connections_exports = {};
|
|
844
|
+
__export2(db_connections_exports, {
|
|
845
|
+
attachDatabasePool: () => attachDatabasePool2,
|
|
846
|
+
experimental_attachDatabasePool: () => experimental_attachDatabasePool2
|
|
847
|
+
});
|
|
848
|
+
module2.exports = __toCommonJS2(db_connections_exports);
|
|
849
|
+
var import_get_context = require_get_context();
|
|
850
|
+
var DEBUG = !!process.env.DEBUG;
|
|
851
|
+
function getIdleTimeout(dbPool) {
|
|
852
|
+
if ("options" in dbPool && dbPool.options) {
|
|
853
|
+
if ("idleTimeoutMillis" in dbPool.options) {
|
|
854
|
+
return typeof dbPool.options.idleTimeoutMillis === "number" ? dbPool.options.idleTimeoutMillis : 1e4;
|
|
855
|
+
}
|
|
856
|
+
if ("maxIdleTimeMS" in dbPool.options) {
|
|
857
|
+
return typeof dbPool.options.maxIdleTimeMS === "number" ? dbPool.options.maxIdleTimeMS : 0;
|
|
858
|
+
}
|
|
859
|
+
if ("status" in dbPool) {
|
|
860
|
+
return 5e3;
|
|
861
|
+
}
|
|
862
|
+
if ("connect" in dbPool && "execute" in dbPool) {
|
|
863
|
+
return 3e4;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if ("config" in dbPool && dbPool.config) {
|
|
867
|
+
if ("connectionConfig" in dbPool.config && dbPool.config.connectionConfig) {
|
|
868
|
+
return dbPool.config.connectionConfig.idleTimeout || 6e4;
|
|
869
|
+
}
|
|
870
|
+
if ("idleTimeout" in dbPool.config) {
|
|
871
|
+
return typeof dbPool.config.idleTimeout === "number" ? dbPool.config.idleTimeout : 6e4;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if ("poolTimeout" in dbPool) {
|
|
875
|
+
return typeof dbPool.poolTimeout === "number" ? dbPool.poolTimeout : 6e4;
|
|
876
|
+
}
|
|
877
|
+
if ("idleTimeout" in dbPool) {
|
|
878
|
+
return typeof dbPool.idleTimeout === "number" ? dbPool.idleTimeout : 0;
|
|
879
|
+
}
|
|
880
|
+
return 1e4;
|
|
881
|
+
}
|
|
882
|
+
var idleTimeout = null;
|
|
883
|
+
var idleTimeoutResolve = () => {
|
|
884
|
+
};
|
|
885
|
+
var bootTime = Date.now();
|
|
886
|
+
var maximumDuration = 15 * 60 * 1e3 - 1e3;
|
|
887
|
+
function waitUntilIdleTimeout(dbPool) {
|
|
888
|
+
if (!process.env.VERCEL_URL || // This is not set during builds where we don't need to wait for idle connections using the mechanism
|
|
889
|
+
!process.env.VERCEL_REGION) {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (idleTimeout) {
|
|
893
|
+
clearTimeout(idleTimeout);
|
|
894
|
+
idleTimeoutResolve();
|
|
895
|
+
}
|
|
896
|
+
const promise = new Promise((resolve) => {
|
|
897
|
+
idleTimeoutResolve = resolve;
|
|
898
|
+
});
|
|
899
|
+
const waitTime = Math.min(
|
|
900
|
+
getIdleTimeout(dbPool) + 100,
|
|
901
|
+
Math.max(100, maximumDuration - (Date.now() - bootTime))
|
|
902
|
+
);
|
|
903
|
+
idleTimeout = setTimeout(() => {
|
|
904
|
+
idleTimeoutResolve?.();
|
|
905
|
+
if (DEBUG) {
|
|
906
|
+
console.log("Database pool idle timeout reached. Releasing connections.");
|
|
907
|
+
}
|
|
908
|
+
}, waitTime);
|
|
909
|
+
const requestContext = (0, import_get_context.getContext)();
|
|
910
|
+
if (requestContext?.waitUntil) {
|
|
911
|
+
requestContext.waitUntil(promise);
|
|
912
|
+
} else {
|
|
913
|
+
console.warn("Pool release event triggered outside of request scope.");
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
function attachDatabasePool2(dbPool) {
|
|
917
|
+
if (idleTimeout) {
|
|
918
|
+
idleTimeoutResolve?.();
|
|
919
|
+
clearTimeout(idleTimeout);
|
|
920
|
+
}
|
|
921
|
+
if ("on" in dbPool && dbPool.on && "options" in dbPool && "idleTimeoutMillis" in dbPool.options) {
|
|
922
|
+
const pgPool = dbPool;
|
|
923
|
+
pgPool.on("release", () => {
|
|
924
|
+
if (DEBUG) {
|
|
925
|
+
console.log("Client released from pool");
|
|
926
|
+
}
|
|
927
|
+
waitUntilIdleTimeout(dbPool);
|
|
928
|
+
});
|
|
929
|
+
return;
|
|
930
|
+
} else if ("on" in dbPool && dbPool.on && "config" in dbPool && dbPool.config && "connectionConfig" in dbPool.config) {
|
|
931
|
+
const mysqlPool = dbPool;
|
|
932
|
+
mysqlPool.on("release", () => {
|
|
933
|
+
if (DEBUG) {
|
|
934
|
+
console.log("MySQL client released from pool");
|
|
935
|
+
}
|
|
936
|
+
waitUntilIdleTimeout(dbPool);
|
|
937
|
+
});
|
|
938
|
+
return;
|
|
939
|
+
} else if ("on" in dbPool && dbPool.on && "config" in dbPool && dbPool.config && "idleTimeout" in dbPool.config) {
|
|
940
|
+
const mysql2Pool = dbPool;
|
|
941
|
+
mysql2Pool.on("release", () => {
|
|
942
|
+
if (DEBUG) {
|
|
943
|
+
console.log("MySQL2/MariaDB client released from pool");
|
|
944
|
+
}
|
|
945
|
+
waitUntilIdleTimeout(dbPool);
|
|
946
|
+
});
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if ("on" in dbPool && dbPool.on && "options" in dbPool && dbPool.options && "maxIdleTimeMS" in dbPool.options) {
|
|
950
|
+
const mongoPool = dbPool;
|
|
951
|
+
mongoPool.on("connectionCheckedOut", () => {
|
|
952
|
+
if (DEBUG) {
|
|
953
|
+
console.log("MongoDB connection checked out");
|
|
954
|
+
}
|
|
955
|
+
waitUntilIdleTimeout(dbPool);
|
|
956
|
+
});
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
if ("on" in dbPool && dbPool.on && "options" in dbPool && dbPool.options && "socket" in dbPool.options) {
|
|
960
|
+
const redisPool = dbPool;
|
|
961
|
+
redisPool.on("end", () => {
|
|
962
|
+
if (DEBUG) {
|
|
963
|
+
console.log("Redis connection ended");
|
|
964
|
+
}
|
|
965
|
+
waitUntilIdleTimeout(dbPool);
|
|
966
|
+
});
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
throw new Error("Unsupported database pool type");
|
|
970
|
+
}
|
|
971
|
+
var experimental_attachDatabasePool2 = attachDatabasePool2;
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/purge/index.js
|
|
976
|
+
var require_purge = __commonJS({
|
|
977
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/purge/index.js"(exports2, module2) {
|
|
978
|
+
"use strict";
|
|
979
|
+
var __defProp2 = Object.defineProperty;
|
|
980
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
981
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
982
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
983
|
+
var __export2 = (target, all) => {
|
|
984
|
+
for (var name in all)
|
|
985
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
986
|
+
};
|
|
987
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
988
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
989
|
+
for (let key of __getOwnPropNames2(from))
|
|
990
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
991
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
992
|
+
}
|
|
993
|
+
return to;
|
|
994
|
+
};
|
|
995
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
996
|
+
var purge_exports = {};
|
|
997
|
+
__export2(purge_exports, {
|
|
998
|
+
dangerouslyDeleteBySrcImage: () => dangerouslyDeleteBySrcImage2,
|
|
999
|
+
dangerouslyDeleteByTag: () => dangerouslyDeleteByTag2,
|
|
1000
|
+
invalidateBySrcImage: () => invalidateBySrcImage2,
|
|
1001
|
+
invalidateByTag: () => invalidateByTag2
|
|
1002
|
+
});
|
|
1003
|
+
module2.exports = __toCommonJS2(purge_exports);
|
|
1004
|
+
var import_get_context = require_get_context();
|
|
1005
|
+
var invalidateByTag2 = (tag) => {
|
|
1006
|
+
const api = (0, import_get_context.getContext)().purge;
|
|
1007
|
+
if (api) {
|
|
1008
|
+
return api.invalidateByTag(tag);
|
|
1009
|
+
}
|
|
1010
|
+
return Promise.resolve();
|
|
1011
|
+
};
|
|
1012
|
+
var dangerouslyDeleteByTag2 = (tag, options) => {
|
|
1013
|
+
const api = (0, import_get_context.getContext)().purge;
|
|
1014
|
+
if (api) {
|
|
1015
|
+
return api.dangerouslyDeleteByTag(tag, options);
|
|
1016
|
+
}
|
|
1017
|
+
return Promise.resolve();
|
|
1018
|
+
};
|
|
1019
|
+
var invalidateBySrcImage2 = (src) => {
|
|
1020
|
+
const api = (0, import_get_context.getContext)().purge;
|
|
1021
|
+
return api ? api.invalidateBySrcImage(src) : Promise.resolve();
|
|
1022
|
+
};
|
|
1023
|
+
var dangerouslyDeleteBySrcImage2 = (src, options) => {
|
|
1024
|
+
const api = (0, import_get_context.getContext)().purge;
|
|
1025
|
+
return api ? api.dangerouslyDeleteBySrcImage(src, options) : Promise.resolve();
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/addcachetag/index.js
|
|
1031
|
+
var require_addcachetag = __commonJS({
|
|
1032
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/addcachetag/index.js"(exports2, module2) {
|
|
1033
|
+
"use strict";
|
|
1034
|
+
var __defProp2 = Object.defineProperty;
|
|
1035
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
1036
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
1037
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
1038
|
+
var __export2 = (target, all) => {
|
|
1039
|
+
for (var name in all)
|
|
1040
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
1041
|
+
};
|
|
1042
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
1043
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
1044
|
+
for (let key of __getOwnPropNames2(from))
|
|
1045
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
1046
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
1047
|
+
}
|
|
1048
|
+
return to;
|
|
1049
|
+
};
|
|
1050
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
1051
|
+
var addcachetag_exports = {};
|
|
1052
|
+
__export2(addcachetag_exports, {
|
|
1053
|
+
addCacheTag: () => addCacheTag2
|
|
1054
|
+
});
|
|
1055
|
+
module2.exports = __toCommonJS2(addcachetag_exports);
|
|
1056
|
+
var import_get_context = require_get_context();
|
|
1057
|
+
var addCacheTag2 = (tag) => {
|
|
1058
|
+
const addCacheTag22 = (0, import_get_context.getContext)().addCacheTag;
|
|
1059
|
+
if (addCacheTag22) {
|
|
1060
|
+
return addCacheTag22(tag);
|
|
1061
|
+
}
|
|
1062
|
+
return Promise.resolve();
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
// ../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/index.js
|
|
1068
|
+
var require_functions = __commonJS({
|
|
1069
|
+
"../../node_modules/.pnpm/@vercel+functions@3.4.3_@aws-sdk+credential-provider-web-identity@3.972.20/node_modules/@vercel/functions/index.js"(exports2, module2) {
|
|
1070
|
+
"use strict";
|
|
1071
|
+
var __defProp2 = Object.defineProperty;
|
|
1072
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
1073
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
1074
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
1075
|
+
var __export2 = (target, all) => {
|
|
1076
|
+
for (var name in all)
|
|
1077
|
+
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
1078
|
+
};
|
|
1079
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
1080
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
1081
|
+
for (let key of __getOwnPropNames2(from))
|
|
1082
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
1083
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
1084
|
+
}
|
|
1085
|
+
return to;
|
|
1086
|
+
};
|
|
1087
|
+
var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
|
|
1088
|
+
var src_exports = {};
|
|
1089
|
+
__export2(src_exports, {
|
|
1090
|
+
addCacheTag: () => import_addcachetag.addCacheTag,
|
|
1091
|
+
attachDatabasePool: () => import_db_connections.attachDatabasePool,
|
|
1092
|
+
dangerouslyDeleteBySrcImage: () => import_purge.dangerouslyDeleteBySrcImage,
|
|
1093
|
+
dangerouslyDeleteByTag: () => import_purge.dangerouslyDeleteByTag,
|
|
1094
|
+
experimental_attachDatabasePool: () => import_db_connections.experimental_attachDatabasePool,
|
|
1095
|
+
geolocation: () => import_headers.geolocation,
|
|
1096
|
+
getCache: () => import_cache.getCache,
|
|
1097
|
+
getEnv: () => import_get_env.getEnv,
|
|
1098
|
+
invalidateBySrcImage: () => import_purge.invalidateBySrcImage,
|
|
1099
|
+
invalidateByTag: () => import_purge.invalidateByTag,
|
|
1100
|
+
ipAddress: () => import_headers.ipAddress,
|
|
1101
|
+
next: () => import_middleware.next,
|
|
1102
|
+
rewrite: () => import_middleware.rewrite,
|
|
1103
|
+
waitUntil: () => import_wait_until.waitUntil
|
|
1104
|
+
});
|
|
1105
|
+
module2.exports = __toCommonJS2(src_exports);
|
|
1106
|
+
var import_headers = require_headers();
|
|
1107
|
+
var import_get_env = require_get_env();
|
|
1108
|
+
var import_wait_until = require_wait_until();
|
|
1109
|
+
var import_middleware = require_middleware();
|
|
1110
|
+
var import_cache = require_cache();
|
|
1111
|
+
var import_db_connections = require_db_connections();
|
|
1112
|
+
var import_purge = require_purge();
|
|
1113
|
+
var import_addcachetag = require_addcachetag();
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
// index.ts
|
|
1118
|
+
var index_exports = {};
|
|
1119
|
+
__export(index_exports, {
|
|
1120
|
+
BrokrAIClient: () => BrokrAIClient,
|
|
1121
|
+
BrokrAuthError: () => BrokrAuthError,
|
|
1122
|
+
BrokrClient: () => BrokrClient,
|
|
1123
|
+
BrokrEmailClient: () => BrokrEmailClient,
|
|
1124
|
+
BrokrEntitlementsClient: () => BrokrEntitlementsClient,
|
|
1125
|
+
BrokrError: () => BrokrError,
|
|
1126
|
+
BrokrFilesClient: () => BrokrFilesClient,
|
|
1127
|
+
BrokrNetworkError: () => BrokrNetworkError,
|
|
1128
|
+
BrokrNotFoundError: () => BrokrNotFoundError,
|
|
1129
|
+
BrokrPaymentsClient: () => BrokrPaymentsClient,
|
|
1130
|
+
BrokrRateLimitError: () => BrokrRateLimitError,
|
|
1131
|
+
BrokrRuntime: () => BrokrRuntime,
|
|
1132
|
+
BrokrStorageClient: () => BrokrStorageClient,
|
|
1133
|
+
BrokrTimeoutError: () => BrokrTimeoutError,
|
|
1134
|
+
BrokrValidationError: () => BrokrValidationError,
|
|
1135
|
+
authMiddleware: () => authMiddleware,
|
|
1136
|
+
buildFallbackConversationTitle: () => buildFallbackConversationTitle,
|
|
1137
|
+
create: () => create,
|
|
1138
|
+
createBrokr: () => createBrokr,
|
|
1139
|
+
createBrokrClient: () => createBrokrClient,
|
|
1140
|
+
createBrokrHandlers: () => createBrokrHandlers,
|
|
1141
|
+
generateConversationTitle: () => generateConversationTitle,
|
|
1142
|
+
models: () => models,
|
|
1143
|
+
providers: () => providers,
|
|
1144
|
+
resolveProviderByModel: () => resolveProviderByModel,
|
|
1145
|
+
sanitizeConversationTitle: () => sanitizeConversationTitle
|
|
1146
|
+
});
|
|
1147
|
+
module.exports = __toCommonJS(index_exports);
|
|
1148
|
+
|
|
1149
|
+
// src/fix-registry.ts
|
|
1150
|
+
var FIX_REGISTRY = {
|
|
1151
|
+
AUTH_TOKEN_INVALID: [
|
|
1152
|
+
"\u2192 Run `brokr env pull --stack <name>` to refresh your token",
|
|
1153
|
+
"\u2192 Or run `brokr link account` to re-authenticate"
|
|
1154
|
+
].join("\n"),
|
|
1155
|
+
AUTH_SESSION_EXPIRED: [
|
|
1156
|
+
"\u2192 Run `brokr link account` to re-authenticate"
|
|
1157
|
+
].join("\n"),
|
|
1158
|
+
BROKR_TOKEN_MISSING: [
|
|
1159
|
+
"\u2192 Run `brokr env pull --stack <name>` to sync your environment",
|
|
1160
|
+
"\u2192 Or add BROKR_TOKEN to your .env.local manually"
|
|
1161
|
+
].join("\n"),
|
|
1162
|
+
DATABASE_PROVISION_FAILED: [
|
|
1163
|
+
"\u2192 Run `brokr status <stack>` to check current state",
|
|
1164
|
+
"\u2192 Run `brokr retry <stack>` to re-trigger provisioning"
|
|
1165
|
+
].join("\n"),
|
|
1166
|
+
DATABASE_QUOTA_REACHED: [
|
|
1167
|
+
"\u2192 Upgrade your plan or delete unused databases",
|
|
1168
|
+
"\u2192 Check usage at brokr.sh/billing"
|
|
1169
|
+
].join("\n"),
|
|
1170
|
+
DATABASE_HEALTH_TIMEOUT: [
|
|
1171
|
+
"\u2192 Run `brokr status <stack>` to check database state",
|
|
1172
|
+
"\u2192 If persistent, check provider status page"
|
|
1173
|
+
].join("\n"),
|
|
1174
|
+
DEPLOYMENT_FAILED: [
|
|
1175
|
+
"\u2192 Run `brokr logs --stack <name>` to see build logs",
|
|
1176
|
+
"\u2192 Fix the build error and run `brokr deploy`"
|
|
1177
|
+
].join("\n"),
|
|
1178
|
+
DEPLOYMENT_RATE_LIMITED: [
|
|
1179
|
+
"\u2192 Wait a minute and retry",
|
|
1180
|
+
"\u2192 Vercel rate limits apply per project"
|
|
1181
|
+
].join("\n"),
|
|
1182
|
+
REPO_ALREADY_EXISTS: [
|
|
1183
|
+
"\u2192 Use a different stack name",
|
|
1184
|
+
"\u2192 Or run `brokr status <stack>` to check the existing stack"
|
|
1185
|
+
].join("\n"),
|
|
1186
|
+
REPO_CREATE_FAILED: [
|
|
1187
|
+
"\u2192 Check your GitHub connection: `brokr link account`",
|
|
1188
|
+
"\u2192 Ensure the Brokr GitHub App is installed"
|
|
1189
|
+
].join("\n"),
|
|
1190
|
+
EMAIL_DOMAIN_FAILED: [
|
|
1191
|
+
"\u2192 Check DNS records are propagated",
|
|
1192
|
+
"\u2192 Run `brokr status <stack>` for details"
|
|
1193
|
+
].join("\n"),
|
|
1194
|
+
DNS_RECORD_FAILED: [
|
|
1195
|
+
"\u2192 Check Cloudflare dashboard for zone status",
|
|
1196
|
+
"\u2192 Retry with `brokr retry <stack>`"
|
|
1197
|
+
].join("\n"),
|
|
1198
|
+
INSUFFICIENT_CREDITS: [
|
|
1199
|
+
"\u2192 Top up credits at brokr.sh/billing",
|
|
1200
|
+
"\u2192 Or run `brokr billing topup`"
|
|
1201
|
+
].join("\n"),
|
|
1202
|
+
AI_BUDGET_EXCEEDED: [
|
|
1203
|
+
"\u2192 Top up credits at brokr.sh/billing",
|
|
1204
|
+
"\u2192 Or run `brokr billing topup`"
|
|
1205
|
+
].join("\n"),
|
|
1206
|
+
AI_PROVIDER_UNAVAILABLE: [
|
|
1207
|
+
"\u2192 The AI provider is temporarily down",
|
|
1208
|
+
"\u2192 Retry in a few seconds \u2014 this is usually transient"
|
|
1209
|
+
].join("\n"),
|
|
1210
|
+
AI_MODEL_NOT_FOUND: [
|
|
1211
|
+
"\u2192 Check the model name in your configuration",
|
|
1212
|
+
"\u2192 See available models at brokr.sh/docs/ai-models"
|
|
1213
|
+
].join("\n"),
|
|
1214
|
+
ENV_VAR_MISSING: [
|
|
1215
|
+
"\u2192 Run `brokr env pull --stack <name>` to sync environment",
|
|
1216
|
+
"\u2192 Check required vars in your template README"
|
|
1217
|
+
].join("\n"),
|
|
1218
|
+
STACK_LIMIT_REACHED: [
|
|
1219
|
+
"\u2192 Delete unused stacks with `brokr delete <stack>`",
|
|
1220
|
+
"\u2192 Or upgrade your plan at brokr.sh/billing"
|
|
1221
|
+
].join("\n"),
|
|
1222
|
+
STACK_NOT_FOUND: [
|
|
1223
|
+
"\u2192 Check the stack name: `brokr list`",
|
|
1224
|
+
"\u2192 Create a new stack: `brokr create --name <name>`"
|
|
1225
|
+
].join("\n"),
|
|
1226
|
+
RATE_LIMITED: [
|
|
1227
|
+
"\u2192 Wait a moment and retry",
|
|
1228
|
+
"\u2192 If persistent, check brokr.sh/status"
|
|
1229
|
+
].join("\n"),
|
|
1230
|
+
GATEWAY_AUTH_FAILED: [
|
|
1231
|
+
"\u2192 Your BROKR_TOKEN may be expired",
|
|
1232
|
+
"\u2192 Run `brokr env pull --stack <name>` to refresh"
|
|
1233
|
+
].join("\n"),
|
|
1234
|
+
BROKR_TOKEN_INVALID: [
|
|
1235
|
+
"\u2192 Your BROKR_TOKEN has expired or been revoked",
|
|
1236
|
+
"\u2192 Run `brokr env pull --stack <name>` to get a fresh token"
|
|
1237
|
+
].join("\n"),
|
|
1238
|
+
AI_STREAM_ERROR: [
|
|
1239
|
+
"\u2192 The AI stream was interrupted",
|
|
1240
|
+
"\u2192 Retry the request \u2014 this is usually transient"
|
|
1241
|
+
].join("\n"),
|
|
1242
|
+
EMAIL_SEND_FAILED: [
|
|
1243
|
+
"\u2192 Email delivery failed",
|
|
1244
|
+
"\u2192 Check that your domain DNS is verified: `brokr status <stack>`"
|
|
1245
|
+
].join("\n"),
|
|
1246
|
+
EMAIL_NOT_CONFIGURED: [
|
|
1247
|
+
"\u2192 Email is not set up for this stack",
|
|
1248
|
+
"\u2192 Add email capability: `brokr add email --stack <name>`"
|
|
1249
|
+
].join("\n"),
|
|
1250
|
+
EMAIL_INVALID_FROM: [
|
|
1251
|
+
"\u2192 The from address must match your verified domain",
|
|
1252
|
+
"\u2192 Check your domain: `brokr status <stack>`"
|
|
1253
|
+
].join("\n"),
|
|
1254
|
+
STORAGE_UPLOAD_FAILED: [
|
|
1255
|
+
"\u2192 File upload failed",
|
|
1256
|
+
"\u2192 Check file size limits and retry"
|
|
1257
|
+
].join("\n"),
|
|
1258
|
+
STORAGE_NOT_FOUND: [
|
|
1259
|
+
"\u2192 The requested file does not exist",
|
|
1260
|
+
"\u2192 Check the key and try again"
|
|
1261
|
+
].join("\n"),
|
|
1262
|
+
PAYMENTS_NOT_CONFIGURED: [
|
|
1263
|
+
"\u2192 Payments are not set up for this stack",
|
|
1264
|
+
"\u2192 Run `brokr payments sync` to configure Stripe"
|
|
1265
|
+
].join("\n"),
|
|
1266
|
+
PAYMENTS_FAILED: [
|
|
1267
|
+
"\u2192 Payment processing failed",
|
|
1268
|
+
"\u2192 Check your Stripe dashboard for details"
|
|
1269
|
+
].join("\n"),
|
|
1270
|
+
GATEWAY_INTERNAL: [
|
|
1271
|
+
"\u2192 The Brokr gateway encountered an internal error",
|
|
1272
|
+
"\u2192 Check brokr.sh/status for service health"
|
|
1273
|
+
].join("\n"),
|
|
1274
|
+
UPSTREAM_ERROR: [
|
|
1275
|
+
"\u2192 An upstream service is temporarily unavailable",
|
|
1276
|
+
"\u2192 Retry in a few seconds \u2014 this is usually transient"
|
|
1277
|
+
].join("\n"),
|
|
1278
|
+
NETWORK_ERROR: [
|
|
1279
|
+
"\u2192 Could not reach the Brokr gateway",
|
|
1280
|
+
"\u2192 Check your internet connection",
|
|
1281
|
+
"\u2192 Check brokr.sh/status for service health"
|
|
1282
|
+
].join("\n"),
|
|
1283
|
+
TIMEOUT: [
|
|
1284
|
+
"\u2192 Request timed out",
|
|
1285
|
+
"\u2192 Retry \u2014 if persistent, check brokr.sh/status"
|
|
1286
|
+
].join("\n")
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
// src/errors.ts
|
|
1290
|
+
var BrokrError = class extends Error {
|
|
1291
|
+
constructor(message, code, capability, retryable = false, errorCode, requestId, hint) {
|
|
1292
|
+
super(message);
|
|
1293
|
+
this.code = code;
|
|
1294
|
+
this.capability = capability;
|
|
1295
|
+
this.retryable = retryable;
|
|
1296
|
+
this.errorCode = errorCode;
|
|
1297
|
+
this.requestId = requestId;
|
|
1298
|
+
this.hint = hint;
|
|
1299
|
+
this.name = "BrokrError";
|
|
1300
|
+
}
|
|
1301
|
+
/** Multi-line terminal fix block — looked up from error code. */
|
|
1302
|
+
get fix() {
|
|
1303
|
+
if (!this.errorCode) return void 0;
|
|
1304
|
+
return FIX_REGISTRY[this.errorCode];
|
|
1305
|
+
}
|
|
1306
|
+
/** Documentation URL for this error code. */
|
|
1307
|
+
get docsUrl() {
|
|
1308
|
+
if (!this.errorCode) return void 0;
|
|
1309
|
+
return `https://brokr.sh/errors/${this.errorCode.toLowerCase().replace(/_/g, "-")}`;
|
|
1310
|
+
}
|
|
1311
|
+
/** Safe message for end users — zero technical language. */
|
|
1312
|
+
toUserMessage() {
|
|
1313
|
+
switch (this.code) {
|
|
1314
|
+
case "RATE_LIMITED":
|
|
1315
|
+
return "Please wait a moment and try again.";
|
|
1316
|
+
case "TIMEOUT":
|
|
1317
|
+
return "The request took too long. Please try again.";
|
|
1318
|
+
case "NETWORK_ERROR":
|
|
1319
|
+
return "Connection issue. Check your internet and try again.";
|
|
1320
|
+
case "BROKR_TOKEN_MISSING":
|
|
1321
|
+
return "App is not connected to Brokr. Contact the developer.";
|
|
1322
|
+
case "BROKR_TOKEN_INVALID":
|
|
1323
|
+
return "Session expired. Please refresh.";
|
|
1324
|
+
case "AI_BUDGET_EXCEEDED":
|
|
1325
|
+
return "AI usage limit reached. The developer needs to add credits.";
|
|
1326
|
+
case "AI_PROVIDER_UNAVAILABLE":
|
|
1327
|
+
return "AI service is temporarily down. Please retry shortly.";
|
|
1328
|
+
case "AI_PROVIDER_RATE_LIMITED":
|
|
1329
|
+
return "Too many AI requests. Please wait a moment.";
|
|
1330
|
+
case "INSUFFICIENT_CREDITS":
|
|
1331
|
+
return "Not enough credits. Please top up your balance.";
|
|
1332
|
+
case "STORAGE_UPLOAD_FAILED":
|
|
1333
|
+
return "File upload failed. Please try again.";
|
|
1334
|
+
case "STORAGE_NOT_FOUND":
|
|
1335
|
+
return "File not found.";
|
|
1336
|
+
case "EMAIL_SEND_FAILED":
|
|
1337
|
+
return "Email could not be sent. Please try again.";
|
|
1338
|
+
case "EMAIL_NOT_CONFIGURED":
|
|
1339
|
+
return "Email is not set up for this app.";
|
|
1340
|
+
case "PAYMENTS_NOT_CONFIGURED":
|
|
1341
|
+
return "Payments are not configured. Contact the developer.";
|
|
1342
|
+
case "PAYMENTS_FAILED":
|
|
1343
|
+
return "Payment processing failed. Please try again.";
|
|
1344
|
+
case "NOT_FOUND":
|
|
1345
|
+
return "The requested resource was not found.";
|
|
1346
|
+
case "VALIDATION_ERROR":
|
|
1347
|
+
return "Invalid input. Please check your data and try again.";
|
|
1348
|
+
default:
|
|
1349
|
+
return "Something went wrong. We're looking into it.";
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
toString() {
|
|
1353
|
+
return `${this.name} [${this.errorCode ?? this.code}]: ${this.message}`;
|
|
1354
|
+
}
|
|
1355
|
+
toJSON() {
|
|
1356
|
+
return {
|
|
1357
|
+
name: this.name,
|
|
1358
|
+
code: this.code,
|
|
1359
|
+
errorCode: this.errorCode,
|
|
1360
|
+
message: this.message,
|
|
1361
|
+
capability: this.capability,
|
|
1362
|
+
retryable: this.retryable,
|
|
1363
|
+
requestId: this.requestId,
|
|
1364
|
+
hint: this.hint,
|
|
1365
|
+
component: this.component
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
var BrokrAuthError = class extends BrokrError {
|
|
1370
|
+
constructor(message, code) {
|
|
1371
|
+
super(message, code, "auth", false);
|
|
1372
|
+
this.name = "BrokrAuthError";
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
var BrokrRateLimitError = class extends BrokrError {
|
|
1376
|
+
constructor(message, retryAfter, capability) {
|
|
1377
|
+
super(message, "RATE_LIMITED", capability, true);
|
|
1378
|
+
this.retryAfter = retryAfter;
|
|
1379
|
+
this.name = "BrokrRateLimitError";
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
var BrokrNetworkError = class extends BrokrError {
|
|
1383
|
+
constructor(message, capability) {
|
|
1384
|
+
super(message, "NETWORK_ERROR", capability, true);
|
|
1385
|
+
this.name = "BrokrNetworkError";
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
var BrokrTimeoutError = class extends BrokrError {
|
|
1389
|
+
constructor(message, capability) {
|
|
1390
|
+
super(message, "TIMEOUT", capability, true);
|
|
1391
|
+
this.name = "BrokrTimeoutError";
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
var BrokrNotFoundError = class extends BrokrError {
|
|
1395
|
+
constructor(message, capability) {
|
|
1396
|
+
super(message, "NOT_FOUND", capability, false);
|
|
1397
|
+
this.name = "BrokrNotFoundError";
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
var BrokrValidationError = class extends BrokrError {
|
|
1401
|
+
constructor(message, capability) {
|
|
1402
|
+
super(message, "VALIDATION_ERROR", capability, false);
|
|
1403
|
+
this.name = "BrokrValidationError";
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
// src/gateway.ts
|
|
1408
|
+
var GATEWAY_URL = "https://api.brokr.sh";
|
|
1409
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
1410
|
+
function resolveToken() {
|
|
1411
|
+
return typeof process !== "undefined" ? process.env.BROKR_TOKEN : void 0;
|
|
1412
|
+
}
|
|
1413
|
+
function requireToken(token, capability) {
|
|
1414
|
+
if (!token) {
|
|
1415
|
+
let hint = "brokr env pull --stack <name>";
|
|
1416
|
+
try {
|
|
1417
|
+
if (typeof process !== "undefined") {
|
|
1418
|
+
const fs = require("fs");
|
|
1419
|
+
const path = require("path");
|
|
1420
|
+
const brokrFile = path.join(process.cwd(), ".brokr");
|
|
1421
|
+
if (fs.existsSync(brokrFile)) {
|
|
1422
|
+
const data = JSON.parse(fs.readFileSync(brokrFile, "utf8"));
|
|
1423
|
+
if (data?.stackName) hint = `brokr env pull --stack ${data.stackName}`;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
} catch {
|
|
1427
|
+
}
|
|
1428
|
+
throw new BrokrAuthError(
|
|
1429
|
+
`BROKR_TOKEN is not set. Run: ${hint}`,
|
|
1430
|
+
"BROKR_TOKEN_MISSING"
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
async function gatewayFetch(gatewayUrl, token, path, body, capability) {
|
|
1435
|
+
const controller = new AbortController();
|
|
1436
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
1437
|
+
let res;
|
|
1438
|
+
try {
|
|
1439
|
+
res = await fetch(`${gatewayUrl}${path}`, {
|
|
1440
|
+
method: "POST",
|
|
1441
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
1442
|
+
body: JSON.stringify(body),
|
|
1443
|
+
signal: controller.signal
|
|
1444
|
+
});
|
|
1445
|
+
} catch (err) {
|
|
1446
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
1447
|
+
throw new BrokrTimeoutError(
|
|
1448
|
+
`Request timed out after ${FETCH_TIMEOUT_MS / 1e3}s`,
|
|
1449
|
+
capability
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
throw new BrokrNetworkError(
|
|
1453
|
+
"Could not reach Brokr gateway. Check your network.",
|
|
1454
|
+
capability
|
|
1455
|
+
);
|
|
1456
|
+
} finally {
|
|
1457
|
+
clearTimeout(timeout);
|
|
1458
|
+
}
|
|
1459
|
+
if (res.status === 429) {
|
|
1460
|
+
const rawRetryAfter = parseInt(res.headers.get("Retry-After") ?? "60", 10);
|
|
1461
|
+
const retryAfter = Number.isFinite(rawRetryAfter) ? rawRetryAfter : 60;
|
|
1462
|
+
const data = await res.json().catch(() => ({}));
|
|
1463
|
+
const errorMsg = typeof data.error === "string" ? data.error : `Rate limited (retry after ${retryAfter}s)`;
|
|
1464
|
+
throw new BrokrRateLimitError(
|
|
1465
|
+
errorMsg,
|
|
1466
|
+
retryAfter,
|
|
1467
|
+
capability
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
if (res.status === 401) {
|
|
1471
|
+
throw new BrokrAuthError("Invalid or expired BROKR_TOKEN.", "BROKR_TOKEN_INVALID");
|
|
1472
|
+
}
|
|
1473
|
+
if (!res.ok) {
|
|
1474
|
+
const body2 = await res.json().catch(() => ({}));
|
|
1475
|
+
const errObj = typeof body2.error === "object" && body2.error !== null ? body2.error : void 0;
|
|
1476
|
+
const errorData = body2.data ?? errObj?.data ?? body2;
|
|
1477
|
+
const errorCode = errorData.errorCode ?? body2.code;
|
|
1478
|
+
const hint = errorData.hint;
|
|
1479
|
+
const requestId = errorData.requestId ?? res.headers.get("x-request-id");
|
|
1480
|
+
const retryable = errorData.retryable;
|
|
1481
|
+
const errorStr = typeof body2.error === "string" ? body2.error : void 0;
|
|
1482
|
+
const message = body2.message ?? errObj?.message ?? errorStr ?? `${capability} request failed (HTTP ${res.status})`;
|
|
1483
|
+
throw new BrokrError(
|
|
1484
|
+
message,
|
|
1485
|
+
errorCode ?? `${capability.toUpperCase()}_FAILED`,
|
|
1486
|
+
capability,
|
|
1487
|
+
retryable ?? false,
|
|
1488
|
+
errorCode ?? void 0,
|
|
1489
|
+
requestId ?? void 0,
|
|
1490
|
+
hint ?? void 0
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
try {
|
|
1494
|
+
return await res.json();
|
|
1495
|
+
} catch {
|
|
1496
|
+
throw new BrokrError(
|
|
1497
|
+
`${capability} returned invalid response (expected JSON).`,
|
|
1498
|
+
`${capability.toUpperCase()}_INVALID_RESPONSE`,
|
|
1499
|
+
capability,
|
|
1500
|
+
true
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
async function gatewayStream(gatewayUrl, token, path, body, capability) {
|
|
1505
|
+
const controller = new AbortController();
|
|
1506
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
1507
|
+
let res;
|
|
1508
|
+
try {
|
|
1509
|
+
res = await fetch(`${gatewayUrl}${path}`, {
|
|
1510
|
+
method: "POST",
|
|
1511
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
1512
|
+
body: JSON.stringify(body),
|
|
1513
|
+
signal: controller.signal
|
|
1514
|
+
});
|
|
1515
|
+
} catch (err) {
|
|
1516
|
+
clearTimeout(timeout);
|
|
1517
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
1518
|
+
throw new BrokrTimeoutError(
|
|
1519
|
+
`Stream request timed out after ${FETCH_TIMEOUT_MS / 1e3}s`,
|
|
1520
|
+
capability
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
throw new BrokrNetworkError(
|
|
1524
|
+
"Could not reach Brokr gateway. Check your network.",
|
|
1525
|
+
capability
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
clearTimeout(timeout);
|
|
1529
|
+
if (res.status === 429) {
|
|
1530
|
+
const retryAfter = parseInt(res.headers.get("Retry-After") ?? "60", 10);
|
|
1531
|
+
throw new BrokrRateLimitError("Rate limited.", retryAfter, capability);
|
|
1532
|
+
}
|
|
1533
|
+
if (res.status === 401) {
|
|
1534
|
+
throw new BrokrAuthError("Invalid or expired BROKR_TOKEN.", "BROKR_TOKEN_INVALID");
|
|
1535
|
+
}
|
|
1536
|
+
if (!res.ok || !res.body) {
|
|
1537
|
+
throw new BrokrError(
|
|
1538
|
+
`${capability} stream failed (HTTP ${res.status})`,
|
|
1539
|
+
`${capability.toUpperCase()}_STREAM_FAILED`,
|
|
1540
|
+
capability
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
return res;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// src/env-detect.ts
|
|
1547
|
+
var cached = null;
|
|
1548
|
+
function detectEnv() {
|
|
1549
|
+
if (cached) return cached;
|
|
1550
|
+
cached = _detect();
|
|
1551
|
+
return cached;
|
|
1552
|
+
}
|
|
1553
|
+
function _detect() {
|
|
1554
|
+
if (typeof process !== "undefined" && process.env) {
|
|
1555
|
+
const vercel = process.env.VERCEL;
|
|
1556
|
+
const vercelEnv = process.env.VERCEL_ENV;
|
|
1557
|
+
if (vercel === "1" || vercel === "true") {
|
|
1558
|
+
if (vercelEnv === "preview") return "staging";
|
|
1559
|
+
if (vercelEnv === "production") return "production";
|
|
1560
|
+
return "production";
|
|
1561
|
+
}
|
|
1562
|
+
return "development";
|
|
1563
|
+
}
|
|
1564
|
+
if (typeof window !== "undefined" && window.location) {
|
|
1565
|
+
const host = window.location.hostname;
|
|
1566
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "::1") {
|
|
1567
|
+
return "development";
|
|
1568
|
+
}
|
|
1569
|
+
if (host.includes("-staging.brokr.sh") || host.includes(".preview.brokr.sh")) {
|
|
1570
|
+
return "staging";
|
|
1571
|
+
}
|
|
1572
|
+
if (host.endsWith(".brokr.sh") || host === "brokr.sh") {
|
|
1573
|
+
return "production";
|
|
1574
|
+
}
|
|
1575
|
+
return "production";
|
|
1576
|
+
}
|
|
1577
|
+
return "production";
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// src/dev-console.ts
|
|
1581
|
+
var BrokrDevConsole = class _BrokrDevConsole {
|
|
1582
|
+
static {
|
|
1583
|
+
this.installed = false;
|
|
1584
|
+
}
|
|
1585
|
+
static {
|
|
1586
|
+
this.env = "production";
|
|
1587
|
+
}
|
|
1588
|
+
static install() {
|
|
1589
|
+
if (_BrokrDevConsole.installed) return;
|
|
1590
|
+
const env = detectEnv();
|
|
1591
|
+
if (env === "production") return;
|
|
1592
|
+
_BrokrDevConsole.installed = true;
|
|
1593
|
+
_BrokrDevConsole.env = env;
|
|
1594
|
+
if (typeof process !== "undefined" && process.on) {
|
|
1595
|
+
process.on("unhandledRejection", (err) => {
|
|
1596
|
+
if (err instanceof BrokrError) {
|
|
1597
|
+
try {
|
|
1598
|
+
_BrokrDevConsole.print(err);
|
|
1599
|
+
} catch {
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
static print(err) {
|
|
1606
|
+
if (_BrokrDevConsole.env === "staging") {
|
|
1607
|
+
const hint = err.hint ?? err.message;
|
|
1608
|
+
console.error(`[brokr] ${err.errorCode ?? err.code}: ${hint}`);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
const lines = [
|
|
1612
|
+
"",
|
|
1613
|
+
"\u2501".repeat(41),
|
|
1614
|
+
"",
|
|
1615
|
+
` Brokr${err.component ? ` \u2014 ${err.component}` : ""}`,
|
|
1616
|
+
"",
|
|
1617
|
+
` ${err.message}`
|
|
1618
|
+
];
|
|
1619
|
+
if (err.fix) {
|
|
1620
|
+
lines.push("", " Fix it:");
|
|
1621
|
+
for (const line of err.fix.split("\n")) lines.push(` ${line}`);
|
|
1622
|
+
}
|
|
1623
|
+
if (err.docsUrl) {
|
|
1624
|
+
lines.push("", ` Still broken? \u2192 ${err.docsUrl}`);
|
|
1625
|
+
}
|
|
1626
|
+
lines.push("", "\u2501".repeat(41), "");
|
|
1627
|
+
console.error(lines.join("\n"));
|
|
1628
|
+
}
|
|
1629
|
+
};
|
|
1630
|
+
|
|
1631
|
+
// src/logs/capture.ts
|
|
1632
|
+
var DEFAULT_BATCH_SIZE = 20;
|
|
1633
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 5e3;
|
|
1634
|
+
var buffer = [];
|
|
1635
|
+
var flushTimer = null;
|
|
1636
|
+
var config = null;
|
|
1637
|
+
var intercepted = false;
|
|
1638
|
+
function initCapture(opts = {}) {
|
|
1639
|
+
if (config) return;
|
|
1640
|
+
const token = opts.token ?? (typeof process !== "undefined" ? process.env.BROKR_TOKEN : void 0);
|
|
1641
|
+
const stackId = opts.stackId ?? resolveStackId();
|
|
1642
|
+
if (!token || !stackId) {
|
|
1643
|
+
if (typeof process !== "undefined") {
|
|
1644
|
+
console.log(`[brokr] Log capture skipped: token=${token ? "present" : "MISSING"} stackId=${stackId ?? "MISSING"}`);
|
|
1645
|
+
}
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
const apiUrl = opts.apiUrl ?? opts.gatewayUrl ?? (typeof process !== "undefined" ? process.env.BROKR_GATEWAY_URL : void 0) ?? "https://api.brokr.sh";
|
|
1649
|
+
config = {
|
|
1650
|
+
token,
|
|
1651
|
+
stackId,
|
|
1652
|
+
apiUrl: apiUrl.replace(/\/+$/, ""),
|
|
1653
|
+
batchSize: opts.batchSize ?? DEFAULT_BATCH_SIZE,
|
|
1654
|
+
flushIntervalMs: opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS
|
|
1655
|
+
};
|
|
1656
|
+
if (typeof process !== "undefined") {
|
|
1657
|
+
console.log(`[brokr] Log capture initialized \u2192 ${apiUrl}/v1/logs/ingest (stack: ${stackId.slice(0, 8)}...)`);
|
|
1658
|
+
}
|
|
1659
|
+
if (flushTimer) clearInterval(flushTimer);
|
|
1660
|
+
flushTimer = setInterval(() => flush(), config.flushIntervalMs);
|
|
1661
|
+
if (typeof process !== "undefined" && process.on) {
|
|
1662
|
+
process.on("beforeExit", () => flush());
|
|
1663
|
+
}
|
|
1664
|
+
if (typeof process !== "undefined" && !intercepted) {
|
|
1665
|
+
intercepted = true;
|
|
1666
|
+
const origError = console.error;
|
|
1667
|
+
const origWarn = console.warn;
|
|
1668
|
+
console.error = (...args) => {
|
|
1669
|
+
origError.apply(console, args);
|
|
1670
|
+
try {
|
|
1671
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
1672
|
+
if (!msg.startsWith("[brokr]")) capture("error", msg, "console.error");
|
|
1673
|
+
} catch {
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
console.warn = (...args) => {
|
|
1677
|
+
origWarn.apply(console, args);
|
|
1678
|
+
try {
|
|
1679
|
+
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
1680
|
+
if (!msg.startsWith("[brokr]")) capture("warn", msg, "console.warn");
|
|
1681
|
+
} catch {
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
function capture(level, message, source, stackTrace) {
|
|
1687
|
+
if (!config) return;
|
|
1688
|
+
buffer.push({
|
|
1689
|
+
level,
|
|
1690
|
+
message: message.slice(0, 1e4),
|
|
1691
|
+
source: source ?? "app",
|
|
1692
|
+
stackTrace: stackTrace?.slice(0, 5e4),
|
|
1693
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1694
|
+
});
|
|
1695
|
+
if (buffer.length >= (config.batchSize ?? DEFAULT_BATCH_SIZE)) {
|
|
1696
|
+
flush();
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
function captureRequest(method, path, statusCode, durationMs) {
|
|
1700
|
+
const level = statusCode >= 500 ? "error" : statusCode >= 400 ? "warn" : "info";
|
|
1701
|
+
const dur = durationMs != null ? ` ${durationMs}ms` : "";
|
|
1702
|
+
capture(level, `${method} ${path} ${statusCode}${dur}`, path);
|
|
1703
|
+
}
|
|
1704
|
+
function flush() {
|
|
1705
|
+
if (!config || buffer.length === 0) return;
|
|
1706
|
+
const entries = buffer.splice(0);
|
|
1707
|
+
const { token, stackId, apiUrl } = config;
|
|
1708
|
+
fetch(`${apiUrl}/v1/logs/ingest`, {
|
|
1709
|
+
method: "POST",
|
|
1710
|
+
headers: {
|
|
1711
|
+
"Content-Type": "application/json",
|
|
1712
|
+
Authorization: `Bearer ${token}`
|
|
1713
|
+
},
|
|
1714
|
+
body: JSON.stringify({ stackId, entries })
|
|
1715
|
+
}).catch((err) => {
|
|
1716
|
+
if (typeof console !== "undefined") {
|
|
1717
|
+
console.warn("[brokr] Log flush failed:", err instanceof Error ? err.message : err);
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
function resolveStackId() {
|
|
1722
|
+
if (typeof process === "undefined") return void 0;
|
|
1723
|
+
if (process.env.BROKR_STACK_ID) return process.env.BROKR_STACK_ID;
|
|
1724
|
+
try {
|
|
1725
|
+
const fs = require("fs");
|
|
1726
|
+
const path = require("path");
|
|
1727
|
+
const brokrFile = path.join(process.cwd(), ".brokr");
|
|
1728
|
+
if (fs.existsSync(brokrFile)) {
|
|
1729
|
+
const content = fs.readFileSync(brokrFile, "utf8");
|
|
1730
|
+
const match = content.match(/BROKR_STACK_ID=(.+)/);
|
|
1731
|
+
if (match) return match[1].trim();
|
|
1732
|
+
}
|
|
1733
|
+
} catch {
|
|
1734
|
+
}
|
|
1735
|
+
return void 0;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// src/ai/client.ts
|
|
1739
|
+
function normalizeInput(input) {
|
|
1740
|
+
if (typeof input === "string") {
|
|
1741
|
+
return [{ role: "user", content: input }];
|
|
1742
|
+
}
|
|
1743
|
+
return input;
|
|
1744
|
+
}
|
|
1745
|
+
var BrokrAIClient = class {
|
|
1746
|
+
constructor(_token, _gatewayUrl) {
|
|
1747
|
+
this._token = _token;
|
|
1748
|
+
this._gatewayUrl = _gatewayUrl;
|
|
1749
|
+
}
|
|
1750
|
+
// ---------------------------------------------------------------------------
|
|
1751
|
+
// Core chat
|
|
1752
|
+
// ---------------------------------------------------------------------------
|
|
1753
|
+
/**
|
|
1754
|
+
* Send a chat completion request.
|
|
1755
|
+
* Accepts a string (auto-wrapped as user message) or a message array.
|
|
1756
|
+
*/
|
|
1757
|
+
async chat(input, options) {
|
|
1758
|
+
requireToken(this._token, "ai");
|
|
1759
|
+
const messages = normalizeInput(input);
|
|
1760
|
+
try {
|
|
1761
|
+
const data = await gatewayFetch(this._gatewayUrl, this._token, "/v1/chat/completions", {
|
|
1762
|
+
messages,
|
|
1763
|
+
model: options?.model,
|
|
1764
|
+
max_tokens: options?.maxTokens,
|
|
1765
|
+
temperature: options?.temperature
|
|
1766
|
+
}, "ai");
|
|
1767
|
+
return {
|
|
1768
|
+
content: data.choices?.[0]?.message?.content ?? "",
|
|
1769
|
+
model: data.model ?? "",
|
|
1770
|
+
usage: {
|
|
1771
|
+
promptTokens: data.usage?.prompt_tokens ?? 0,
|
|
1772
|
+
completionTokens: data.usage?.completion_tokens ?? 0
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
} catch (err) {
|
|
1776
|
+
if (err instanceof BrokrError) err.component = "AIChat";
|
|
1777
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1778
|
+
capture("error", `AI chat failed: ${msg}`, "brokr.ai.chat", err instanceof Error ? err.stack : void 0);
|
|
1779
|
+
throw err;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Stream a chat completion. Yields text strings directly.
|
|
1784
|
+
* Accepts a string (auto-wrapped as user message) or a message array.
|
|
1785
|
+
*/
|
|
1786
|
+
/**
|
|
1787
|
+
* Stream a chat completion. Yields text strings directly.
|
|
1788
|
+
* Tries streaming first — if the gateway or provider doesn't support it,
|
|
1789
|
+
* falls back to a non-streaming call and yields the full response at once.
|
|
1790
|
+
*/
|
|
1791
|
+
async *stream(input, options) {
|
|
1792
|
+
requireToken(this._token, "ai");
|
|
1793
|
+
const messages = normalizeInput(input);
|
|
1794
|
+
let res;
|
|
1795
|
+
try {
|
|
1796
|
+
res = await gatewayStream(
|
|
1797
|
+
this._gatewayUrl,
|
|
1798
|
+
this._token,
|
|
1799
|
+
"/v1/chat/completions",
|
|
1800
|
+
{
|
|
1801
|
+
messages,
|
|
1802
|
+
stream: true,
|
|
1803
|
+
model: options?.model,
|
|
1804
|
+
max_tokens: options?.maxTokens,
|
|
1805
|
+
temperature: options?.temperature
|
|
1806
|
+
},
|
|
1807
|
+
"ai"
|
|
1808
|
+
);
|
|
1809
|
+
} catch (err) {
|
|
1810
|
+
if (err instanceof BrokrError) err.component = "AIStream";
|
|
1811
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1812
|
+
capture("error", `AI stream failed: ${msg}`, "brokr.ai.stream", err instanceof Error ? err.stack : void 0);
|
|
1813
|
+
try {
|
|
1814
|
+
const fallback = await this.chat(input, options);
|
|
1815
|
+
yield fallback.content;
|
|
1816
|
+
} catch (fallbackErr) {
|
|
1817
|
+
throw err;
|
|
1818
|
+
}
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
1822
|
+
if (!ct.includes("text/event-stream")) {
|
|
1823
|
+
try {
|
|
1824
|
+
const data = await res.json();
|
|
1825
|
+
const content = data.choices?.[0]?.message?.content ?? "";
|
|
1826
|
+
if (content) yield content;
|
|
1827
|
+
} catch {
|
|
1828
|
+
const fallback = await this.chat(input, options);
|
|
1829
|
+
yield fallback.content;
|
|
1830
|
+
}
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
const reader = res.body.getReader();
|
|
1834
|
+
const decoder = new TextDecoder();
|
|
1835
|
+
let buffer2 = "";
|
|
1836
|
+
while (true) {
|
|
1837
|
+
const { done, value } = await reader.read();
|
|
1838
|
+
if (done) break;
|
|
1839
|
+
buffer2 += decoder.decode(value, { stream: true });
|
|
1840
|
+
const lines = buffer2.split("\n");
|
|
1841
|
+
buffer2 = lines.pop() ?? "";
|
|
1842
|
+
for (const line of lines) {
|
|
1843
|
+
if (!line.startsWith("data: ")) continue;
|
|
1844
|
+
const payload = line.slice(6).trim();
|
|
1845
|
+
if (payload === "[DONE]") return;
|
|
1846
|
+
try {
|
|
1847
|
+
const parsed = JSON.parse(payload);
|
|
1848
|
+
const delta = parsed.choices?.[0]?.delta?.content ?? "";
|
|
1849
|
+
if (delta) yield delta;
|
|
1850
|
+
} catch {
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
// ---------------------------------------------------------------------------
|
|
1856
|
+
// Higher-level primitives
|
|
1857
|
+
// ---------------------------------------------------------------------------
|
|
1858
|
+
/**
|
|
1859
|
+
* Extract structured data from a prompt using JSON mode.
|
|
1860
|
+
* Returns a parsed object matching the provided schema shape.
|
|
1861
|
+
*/
|
|
1862
|
+
async structured(params) {
|
|
1863
|
+
requireToken(this._token, "ai");
|
|
1864
|
+
const schemaStr = JSON.stringify(params.schema, null, 2);
|
|
1865
|
+
const messages = [
|
|
1866
|
+
{
|
|
1867
|
+
role: "system",
|
|
1868
|
+
content: `You are a structured data extraction assistant. Return ONLY valid JSON matching this schema:
|
|
1869
|
+
${schemaStr}
|
|
1870
|
+
Do not include any other text, markdown, or explanation.`
|
|
1871
|
+
},
|
|
1872
|
+
{ role: "user", content: params.prompt }
|
|
1873
|
+
];
|
|
1874
|
+
try {
|
|
1875
|
+
const data = await gatewayFetch(this._gatewayUrl, this._token, "/v1/chat/completions", {
|
|
1876
|
+
messages,
|
|
1877
|
+
model: params.model,
|
|
1878
|
+
temperature: params.temperature ?? 0,
|
|
1879
|
+
response_format: { type: "json_object" }
|
|
1880
|
+
}, "ai");
|
|
1881
|
+
const raw = data.choices?.[0]?.message?.content ?? "{}";
|
|
1882
|
+
try {
|
|
1883
|
+
return JSON.parse(raw);
|
|
1884
|
+
} catch {
|
|
1885
|
+
throw new BrokrError(
|
|
1886
|
+
"[brokr] AI returned invalid JSON for structured extraction.",
|
|
1887
|
+
"AI_STRUCTURED_PARSE_ERROR",
|
|
1888
|
+
"ai"
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
} catch (err) {
|
|
1892
|
+
if (err instanceof BrokrError) err.component = "AIStructured";
|
|
1893
|
+
throw err;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Extract fields from unstructured text.
|
|
1898
|
+
* Semantic alias for structured() — same behavior, clearer intent.
|
|
1899
|
+
*/
|
|
1900
|
+
async extract(prompt, schema) {
|
|
1901
|
+
return this.structured({ prompt, schema });
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Summarize text using AI.
|
|
1905
|
+
*/
|
|
1906
|
+
async summarize(text, options) {
|
|
1907
|
+
try {
|
|
1908
|
+
const lengthHint = options?.maxLength ? ` Keep it under ${options.maxLength}.` : "";
|
|
1909
|
+
const response = await this.chat(
|
|
1910
|
+
[
|
|
1911
|
+
{
|
|
1912
|
+
role: "system",
|
|
1913
|
+
content: `You are a summarization assistant. Provide a concise, accurate summary of the following text.${lengthHint} Return only the summary, no preamble.`
|
|
1914
|
+
},
|
|
1915
|
+
{ role: "user", content: text }
|
|
1916
|
+
],
|
|
1917
|
+
{ model: options?.model }
|
|
1918
|
+
);
|
|
1919
|
+
return { summary: response.content };
|
|
1920
|
+
} catch (err) {
|
|
1921
|
+
if (err instanceof BrokrError) err.component = "AISummarize";
|
|
1922
|
+
throw err;
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Classify text into one of the provided labels.
|
|
1927
|
+
*/
|
|
1928
|
+
async classify(text, labels, options) {
|
|
1929
|
+
try {
|
|
1930
|
+
const labelsStr = labels.map((l) => `"${l}"`).join(", ");
|
|
1931
|
+
const result = await this.structured({
|
|
1932
|
+
prompt: `Classify the following text into exactly one of these labels: [${labelsStr}]
|
|
1933
|
+
|
|
1934
|
+
Text: ${text}`,
|
|
1935
|
+
schema: {
|
|
1936
|
+
type: "object",
|
|
1937
|
+
properties: {
|
|
1938
|
+
label: { type: "string", enum: labels },
|
|
1939
|
+
confidence: { type: "number", minimum: 0, maximum: 1 }
|
|
1940
|
+
},
|
|
1941
|
+
required: ["label", "confidence"]
|
|
1942
|
+
},
|
|
1943
|
+
model: options?.model,
|
|
1944
|
+
temperature: 0
|
|
1945
|
+
});
|
|
1946
|
+
return result;
|
|
1947
|
+
} catch (err) {
|
|
1948
|
+
if (err instanceof BrokrError) err.component = "AIClassify";
|
|
1949
|
+
throw err;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Generate an embedding vector for the given text.
|
|
1954
|
+
*/
|
|
1955
|
+
async embed(text) {
|
|
1956
|
+
requireToken(this._token, "ai");
|
|
1957
|
+
try {
|
|
1958
|
+
const data = await gatewayFetch(this._gatewayUrl, this._token, "/v1/embeddings", {
|
|
1959
|
+
input: text,
|
|
1960
|
+
model: "text-embedding-3-small"
|
|
1961
|
+
}, "ai");
|
|
1962
|
+
return {
|
|
1963
|
+
vector: data.data?.[0]?.embedding ?? []
|
|
1964
|
+
};
|
|
1965
|
+
} catch (err) {
|
|
1966
|
+
if (err instanceof BrokrError) err.component = "AIEmbed";
|
|
1967
|
+
throw err;
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Generate an image from a text prompt.
|
|
1972
|
+
*/
|
|
1973
|
+
async image(prompt, options) {
|
|
1974
|
+
requireToken(this._token, "ai");
|
|
1975
|
+
try {
|
|
1976
|
+
const data = await gatewayFetch(this._gatewayUrl, this._token, "/v1/images/generate", {
|
|
1977
|
+
prompt,
|
|
1978
|
+
size: options?.size ?? "1024x1024",
|
|
1979
|
+
n: options?.n ?? 1,
|
|
1980
|
+
model: options?.model
|
|
1981
|
+
}, "ai");
|
|
1982
|
+
const url = data.data?.[0]?.url;
|
|
1983
|
+
if (!url) {
|
|
1984
|
+
throw new BrokrError("[brokr] Image generation returned no URL.", "AI_IMAGE_FAILED", "ai");
|
|
1985
|
+
}
|
|
1986
|
+
return { url };
|
|
1987
|
+
} catch (err) {
|
|
1988
|
+
if (err instanceof BrokrError) err.component = "AIImage";
|
|
1989
|
+
throw err;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
// ---------------------------------------------------------------------------
|
|
1993
|
+
// OpenAI-SDK compatibility
|
|
1994
|
+
// ---------------------------------------------------------------------------
|
|
1995
|
+
/** OpenAI-SDK compatible base URL. */
|
|
1996
|
+
get baseURL() {
|
|
1997
|
+
return `${this._gatewayUrl}/v1`;
|
|
1998
|
+
}
|
|
1999
|
+
/** Use as `apiKey` with the official OpenAI SDK to route through Brokr's gateway. */
|
|
2000
|
+
get apiKey() {
|
|
2001
|
+
requireToken(this._token, "ai");
|
|
2002
|
+
return this._token;
|
|
2003
|
+
}
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
// src/storage/client.ts
|
|
2007
|
+
var MULTIPART_THRESHOLD = 100 * 1024 * 1024;
|
|
2008
|
+
var DEFAULT_PART_SIZE = 100 * 1024 * 1024;
|
|
2009
|
+
var PART_UPLOAD_RETRIES = 3;
|
|
2010
|
+
var BrokrStorageClient = class {
|
|
2011
|
+
constructor(_token, _gatewayUrl) {
|
|
2012
|
+
this._token = _token;
|
|
2013
|
+
this._gatewayUrl = _gatewayUrl;
|
|
2014
|
+
}
|
|
2015
|
+
async upload(paramsOrData, filename, contentTypeArg) {
|
|
2016
|
+
try {
|
|
2017
|
+
let data;
|
|
2018
|
+
let filePath;
|
|
2019
|
+
let contentType;
|
|
2020
|
+
if (typeof paramsOrData === "object" && paramsOrData !== null && "file" in paramsOrData) {
|
|
2021
|
+
data = paramsOrData.file;
|
|
2022
|
+
filePath = paramsOrData.path ?? `upload-${Date.now()}`;
|
|
2023
|
+
contentType = paramsOrData.contentType ?? "application/octet-stream";
|
|
2024
|
+
} else {
|
|
2025
|
+
data = paramsOrData;
|
|
2026
|
+
filePath = filename ?? `upload-${Date.now()}`;
|
|
2027
|
+
contentType = contentTypeArg ?? "application/octet-stream";
|
|
2028
|
+
}
|
|
2029
|
+
const size = getUploadSize(data);
|
|
2030
|
+
if (size !== void 0 && size > MULTIPART_THRESHOLD) {
|
|
2031
|
+
return await this._uploadMultipart(data, filePath, contentType, size);
|
|
2032
|
+
}
|
|
2033
|
+
const { url, key } = await this.signUpload({ fileName: filePath, contentType });
|
|
2034
|
+
const uploadHeaders = { "Content-Type": contentType };
|
|
2035
|
+
if (typeof window === "undefined") {
|
|
2036
|
+
const origin = process.env.BETTER_AUTH_URL ?? process.env.NEXT_PUBLIC_APP_URL ?? process.env.APP_URL ?? process.env.BROKR_AUTH_URL ?? (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : void 0) ?? "http://localhost:3000";
|
|
2037
|
+
uploadHeaders.Origin = origin;
|
|
2038
|
+
}
|
|
2039
|
+
const putRes = await fetch(url, {
|
|
2040
|
+
method: "PUT",
|
|
2041
|
+
headers: uploadHeaders,
|
|
2042
|
+
body: data
|
|
2043
|
+
});
|
|
2044
|
+
if (!putRes.ok) {
|
|
2045
|
+
throw new BrokrError(
|
|
2046
|
+
`[brokr] Upload failed (HTTP ${putRes.status})`,
|
|
2047
|
+
"STORAGE_UPLOAD_FAILED",
|
|
2048
|
+
"storage"
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
const gatewayBase = this._gatewayUrl.replace(/\/+$/, "");
|
|
2052
|
+
const permanentUrl = `${gatewayBase}/v1/storage/file/${encodeURI(key)}`;
|
|
2053
|
+
return {
|
|
2054
|
+
key,
|
|
2055
|
+
url: permanentUrl,
|
|
2056
|
+
name: filePath,
|
|
2057
|
+
size,
|
|
2058
|
+
type: contentType
|
|
2059
|
+
};
|
|
2060
|
+
} catch (err) {
|
|
2061
|
+
if (err instanceof BrokrError) err.component = "Storage";
|
|
2062
|
+
throw err;
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* Get a presigned download URL for a stored object.
|
|
2067
|
+
*/
|
|
2068
|
+
async signedUrl(key, options) {
|
|
2069
|
+
requireToken(this._token, "storage");
|
|
2070
|
+
try {
|
|
2071
|
+
return await gatewayFetch(
|
|
2072
|
+
this._gatewayUrl,
|
|
2073
|
+
this._token,
|
|
2074
|
+
"/v1/storage/sign-download",
|
|
2075
|
+
{ key, expiresIn: options?.expiresIn },
|
|
2076
|
+
"storage"
|
|
2077
|
+
);
|
|
2078
|
+
} catch (err) {
|
|
2079
|
+
if (err instanceof BrokrError) err.component = "Storage";
|
|
2080
|
+
throw err;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
/** Low-level escape hatch. Most apps should call upload() instead. */
|
|
2084
|
+
async signUpload(params) {
|
|
2085
|
+
requireToken(this._token, "storage");
|
|
2086
|
+
try {
|
|
2087
|
+
return await gatewayFetch(
|
|
2088
|
+
this._gatewayUrl,
|
|
2089
|
+
this._token,
|
|
2090
|
+
"/v1/storage/sign-upload",
|
|
2091
|
+
{ filename: params.fileName, contentType: params.contentType ?? "application/octet-stream" },
|
|
2092
|
+
"storage"
|
|
2093
|
+
);
|
|
2094
|
+
} catch (err) {
|
|
2095
|
+
if (err instanceof BrokrError) err.component = "Storage";
|
|
2096
|
+
throw err;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
// ---------------------------------------------------------------------------
|
|
2100
|
+
// Multipart upload — handles files > 100MB, up to 20GB
|
|
2101
|
+
// ---------------------------------------------------------------------------
|
|
2102
|
+
/**
|
|
2103
|
+
* Initiate a multipart upload via the gateway.
|
|
2104
|
+
*/
|
|
2105
|
+
async _initiateMultipart(params) {
|
|
2106
|
+
requireToken(this._token, "storage");
|
|
2107
|
+
return gatewayFetch(
|
|
2108
|
+
this._gatewayUrl,
|
|
2109
|
+
this._token,
|
|
2110
|
+
"/v1/storage/multipart/initiate",
|
|
2111
|
+
{
|
|
2112
|
+
filename: params.fileName,
|
|
2113
|
+
contentType: params.contentType,
|
|
2114
|
+
totalSize: params.totalSize,
|
|
2115
|
+
partCount: params.partCount
|
|
2116
|
+
},
|
|
2117
|
+
"storage"
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Get a presigned URL for uploading a single part.
|
|
2122
|
+
*/
|
|
2123
|
+
async _signPartUpload(params) {
|
|
2124
|
+
requireToken(this._token, "storage");
|
|
2125
|
+
return gatewayFetch(
|
|
2126
|
+
this._gatewayUrl,
|
|
2127
|
+
this._token,
|
|
2128
|
+
"/v1/storage/multipart/sign-part",
|
|
2129
|
+
params,
|
|
2130
|
+
"storage"
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* Complete a multipart upload with part ETags.
|
|
2135
|
+
*/
|
|
2136
|
+
async _completeMultipart(params) {
|
|
2137
|
+
requireToken(this._token, "storage");
|
|
2138
|
+
return gatewayFetch(
|
|
2139
|
+
this._gatewayUrl,
|
|
2140
|
+
this._token,
|
|
2141
|
+
"/v1/storage/multipart/complete",
|
|
2142
|
+
params,
|
|
2143
|
+
"storage"
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
/**
|
|
2147
|
+
* Abort a multipart upload, cleaning up uploaded parts.
|
|
2148
|
+
*/
|
|
2149
|
+
async _abortMultipart(params) {
|
|
2150
|
+
requireToken(this._token, "storage");
|
|
2151
|
+
await gatewayFetch(
|
|
2152
|
+
this._gatewayUrl,
|
|
2153
|
+
this._token,
|
|
2154
|
+
"/v1/storage/multipart/abort",
|
|
2155
|
+
params,
|
|
2156
|
+
"storage"
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
/**
|
|
2160
|
+
* Upload a large file using multipart upload.
|
|
2161
|
+
* Splits the file into parts, uploads each with retry, then completes.
|
|
2162
|
+
*/
|
|
2163
|
+
async _uploadMultipart(data, filePath, contentType, totalSize) {
|
|
2164
|
+
const bytes = await toUint8Array(data);
|
|
2165
|
+
const partSize = Math.max(DEFAULT_PART_SIZE, Math.ceil(totalSize / MAX_PARTS_PER_UPLOAD));
|
|
2166
|
+
const partCount = Math.ceil(totalSize / partSize);
|
|
2167
|
+
const { uploadId, key } = await this._initiateMultipart({
|
|
2168
|
+
fileName: filePath,
|
|
2169
|
+
contentType,
|
|
2170
|
+
totalSize,
|
|
2171
|
+
partCount
|
|
2172
|
+
});
|
|
2173
|
+
try {
|
|
2174
|
+
const completedParts = [];
|
|
2175
|
+
for (let i = 0; i < partCount; i++) {
|
|
2176
|
+
const partNumber = i + 1;
|
|
2177
|
+
const start = i * partSize;
|
|
2178
|
+
const end = Math.min(start + partSize, totalSize);
|
|
2179
|
+
const partData = bytes.slice(start, end);
|
|
2180
|
+
const { url } = await this._signPartUpload({ key, uploadId, partNumber });
|
|
2181
|
+
let lastError;
|
|
2182
|
+
for (let attempt = 0; attempt < PART_UPLOAD_RETRIES; attempt++) {
|
|
2183
|
+
try {
|
|
2184
|
+
const putRes = await fetch(url, {
|
|
2185
|
+
method: "PUT",
|
|
2186
|
+
body: partData
|
|
2187
|
+
});
|
|
2188
|
+
if (!putRes.ok) {
|
|
2189
|
+
throw new Error(`Part ${partNumber} upload failed (HTTP ${putRes.status})`);
|
|
2190
|
+
}
|
|
2191
|
+
const etag = putRes.headers.get("ETag");
|
|
2192
|
+
if (!etag) {
|
|
2193
|
+
throw new Error(`Part ${partNumber} upload returned no ETag`);
|
|
2194
|
+
}
|
|
2195
|
+
completedParts.push({ partNumber, etag });
|
|
2196
|
+
lastError = void 0;
|
|
2197
|
+
break;
|
|
2198
|
+
} catch (err) {
|
|
2199
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
if (lastError) {
|
|
2203
|
+
throw new BrokrError(
|
|
2204
|
+
`[brokr] Multipart upload failed at part ${partNumber} after ${PART_UPLOAD_RETRIES} retries: ${lastError.message}`,
|
|
2205
|
+
"STORAGE_UPLOAD_FAILED",
|
|
2206
|
+
"storage"
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
completedParts.sort((a, b) => a.partNumber - b.partNumber);
|
|
2211
|
+
await this._completeMultipart({ key, uploadId, parts: completedParts });
|
|
2212
|
+
const gatewayBase = this._gatewayUrl.replace(/\/+$/, "");
|
|
2213
|
+
const permanentUrl = `${gatewayBase}/v1/storage/file/${encodeURI(key)}`;
|
|
2214
|
+
return {
|
|
2215
|
+
key,
|
|
2216
|
+
url: permanentUrl,
|
|
2217
|
+
name: filePath,
|
|
2218
|
+
size: totalSize,
|
|
2219
|
+
type: contentType
|
|
2220
|
+
};
|
|
2221
|
+
} catch (err) {
|
|
2222
|
+
try {
|
|
2223
|
+
await this._abortMultipart({ key, uploadId });
|
|
2224
|
+
} catch {
|
|
2225
|
+
}
|
|
2226
|
+
throw err;
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
* List objects by prefix with pagination.
|
|
2231
|
+
*/
|
|
2232
|
+
async list(params) {
|
|
2233
|
+
requireToken(this._token, "storage");
|
|
2234
|
+
try {
|
|
2235
|
+
return await gatewayFetch(
|
|
2236
|
+
this._gatewayUrl,
|
|
2237
|
+
this._token,
|
|
2238
|
+
"/v1/storage/list",
|
|
2239
|
+
{
|
|
2240
|
+
prefix: params?.prefix,
|
|
2241
|
+
maxKeys: params?.maxKeys,
|
|
2242
|
+
cursor: params?.cursor
|
|
2243
|
+
},
|
|
2244
|
+
"storage"
|
|
2245
|
+
);
|
|
2246
|
+
} catch (err) {
|
|
2247
|
+
if (err instanceof BrokrError) err.component = "Storage";
|
|
2248
|
+
throw err;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
/**
|
|
2252
|
+
* Delete an object by key.
|
|
2253
|
+
*/
|
|
2254
|
+
async delete(key) {
|
|
2255
|
+
requireToken(this._token, "storage");
|
|
2256
|
+
try {
|
|
2257
|
+
await gatewayFetch(
|
|
2258
|
+
this._gatewayUrl,
|
|
2259
|
+
this._token,
|
|
2260
|
+
"/v1/storage/delete",
|
|
2261
|
+
{ key },
|
|
2262
|
+
"storage"
|
|
2263
|
+
);
|
|
2264
|
+
} catch (err) {
|
|
2265
|
+
if (err instanceof BrokrError) err.component = "Storage";
|
|
2266
|
+
throw err;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Copy an object from one key to another.
|
|
2271
|
+
*/
|
|
2272
|
+
async copy(from, to) {
|
|
2273
|
+
requireToken(this._token, "storage");
|
|
2274
|
+
try {
|
|
2275
|
+
return await gatewayFetch(
|
|
2276
|
+
this._gatewayUrl,
|
|
2277
|
+
this._token,
|
|
2278
|
+
"/v1/storage/copy",
|
|
2279
|
+
{ from, to },
|
|
2280
|
+
"storage"
|
|
2281
|
+
);
|
|
2282
|
+
} catch (err) {
|
|
2283
|
+
if (err instanceof BrokrError) err.component = "Storage";
|
|
2284
|
+
throw err;
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Move an object (copy then delete source).
|
|
2289
|
+
*/
|
|
2290
|
+
async move(from, to) {
|
|
2291
|
+
try {
|
|
2292
|
+
const copied = await this.copy(from, to);
|
|
2293
|
+
await this.delete(from);
|
|
2294
|
+
return copied;
|
|
2295
|
+
} catch (err) {
|
|
2296
|
+
if (err instanceof BrokrError) err.component = "Storage";
|
|
2297
|
+
throw err;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
/**
|
|
2301
|
+
* Check if an object exists.
|
|
2302
|
+
*/
|
|
2303
|
+
/**
|
|
2304
|
+
* Check if an object exists.
|
|
2305
|
+
*
|
|
2306
|
+
* Returns `false` on any error (not-found, rate-limit, network).
|
|
2307
|
+
* This preserves backwards compatibility — `if (await brokr.storage.exists(key))`
|
|
2308
|
+
* must never throw in user code that doesn't wrap it in try-catch.
|
|
2309
|
+
*
|
|
2310
|
+
* If you need to distinguish "doesn't exist" from "couldn't check",
|
|
2311
|
+
* use `metadata()` instead and catch the error.
|
|
2312
|
+
*/
|
|
2313
|
+
async exists(key) {
|
|
2314
|
+
requireToken(this._token, "storage");
|
|
2315
|
+
try {
|
|
2316
|
+
const result = await gatewayFetch(
|
|
2317
|
+
this._gatewayUrl,
|
|
2318
|
+
this._token,
|
|
2319
|
+
"/v1/storage/exists",
|
|
2320
|
+
{ key },
|
|
2321
|
+
"storage"
|
|
2322
|
+
);
|
|
2323
|
+
return result.exists;
|
|
2324
|
+
} catch (err) {
|
|
2325
|
+
if (err instanceof BrokrError) err.component = "Storage";
|
|
2326
|
+
return false;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Get file metadata without downloading the file.
|
|
2331
|
+
*/
|
|
2332
|
+
async metadata(key) {
|
|
2333
|
+
requireToken(this._token, "storage");
|
|
2334
|
+
try {
|
|
2335
|
+
return await gatewayFetch(
|
|
2336
|
+
this._gatewayUrl,
|
|
2337
|
+
this._token,
|
|
2338
|
+
"/v1/storage/metadata",
|
|
2339
|
+
{ key },
|
|
2340
|
+
"storage"
|
|
2341
|
+
);
|
|
2342
|
+
} catch (err) {
|
|
2343
|
+
if (err instanceof BrokrError) err.component = "Storage";
|
|
2344
|
+
throw err;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
// ---------------------------------------------------------------------------
|
|
2348
|
+
// Deprecated aliases (backward compat)
|
|
2349
|
+
// ---------------------------------------------------------------------------
|
|
2350
|
+
/** @deprecated Use signedUrl() instead. */
|
|
2351
|
+
async url(key, options) {
|
|
2352
|
+
return this.signedUrl(key, options);
|
|
2353
|
+
}
|
|
2354
|
+
/** @deprecated Use signedUrl() instead. */
|
|
2355
|
+
async getUrl(key, options) {
|
|
2356
|
+
return this.signedUrl(key, options);
|
|
2357
|
+
}
|
|
2358
|
+
/** @deprecated Use signUpload() instead. */
|
|
2359
|
+
async getUploadUrl(filename, contentType) {
|
|
2360
|
+
return this.signUpload({ fileName: filename, contentType });
|
|
2361
|
+
}
|
|
2362
|
+
};
|
|
2363
|
+
var MAX_PARTS_PER_UPLOAD = 1e4;
|
|
2364
|
+
async function toUint8Array(data) {
|
|
2365
|
+
if (data instanceof Uint8Array) return data;
|
|
2366
|
+
if (typeof data === "string") return new TextEncoder().encode(data);
|
|
2367
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
2368
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
2369
|
+
}
|
|
2370
|
+
throw new Error("Unsupported data type for multipart upload");
|
|
2371
|
+
}
|
|
2372
|
+
function getUploadSize(data) {
|
|
2373
|
+
if (typeof data === "string") {
|
|
2374
|
+
return new TextEncoder().encode(data).length;
|
|
2375
|
+
}
|
|
2376
|
+
if (data instanceof Uint8Array) {
|
|
2377
|
+
return data.byteLength;
|
|
2378
|
+
}
|
|
2379
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
2380
|
+
return data.size;
|
|
2381
|
+
}
|
|
2382
|
+
return void 0;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
// src/email/templates.ts
|
|
2386
|
+
function interpolate(template, vars) {
|
|
2387
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? "");
|
|
2388
|
+
}
|
|
2389
|
+
function wrapHtml(body) {
|
|
2390
|
+
return `<!DOCTYPE html>
|
|
2391
|
+
<html>
|
|
2392
|
+
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>
|
|
2393
|
+
<body style="margin:0;padding:0;background:#f5f5f5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
|
2394
|
+
<div style="max-width:560px;margin:40px auto;background:#ffffff;border-radius:8px;border:1px solid #e5e5e5;overflow:hidden;">
|
|
2395
|
+
${body}
|
|
2396
|
+
</div>
|
|
2397
|
+
<div style="text-align:center;padding:20px;color:#999;font-size:12px;">
|
|
2398
|
+
Sent via Brokr
|
|
2399
|
+
</div>
|
|
2400
|
+
</body>
|
|
2401
|
+
</html>`;
|
|
2402
|
+
}
|
|
2403
|
+
function section(content) {
|
|
2404
|
+
return `<div style="padding:32px 40px;">${content}</div>`;
|
|
2405
|
+
}
|
|
2406
|
+
function button(text, urlVar) {
|
|
2407
|
+
return `<a href="{{${urlVar}}}" style="display:inline-block;padding:12px 24px;background:#000;color:#fff;text-decoration:none;border-radius:6px;font-weight:600;font-size:14px;">${text}</a>`;
|
|
2408
|
+
}
|
|
2409
|
+
var welcomeTemplate = {
|
|
2410
|
+
name: "welcome",
|
|
2411
|
+
subject: "Welcome to {{appName}}",
|
|
2412
|
+
html: (vars) => interpolate(wrapHtml(section(`
|
|
2413
|
+
<h1 style="margin:0 0 16px;font-size:24px;color:#111;">Welcome to {{appName}}</h1>
|
|
2414
|
+
<p style="margin:0 0 24px;color:#555;font-size:15px;line-height:1.6;">
|
|
2415
|
+
Your account is ready. You can start using {{appName}} right away.
|
|
2416
|
+
</p>
|
|
2417
|
+
${button("Get Started", "actionUrl")}
|
|
2418
|
+
`)), vars)
|
|
2419
|
+
};
|
|
2420
|
+
var magicLinkTemplate = {
|
|
2421
|
+
name: "magicLink",
|
|
2422
|
+
subject: "Your sign-in link",
|
|
2423
|
+
html: (vars) => interpolate(wrapHtml(section(`
|
|
2424
|
+
<h1 style="margin:0 0 16px;font-size:24px;color:#111;">Sign in to {{appName}}</h1>
|
|
2425
|
+
<p style="margin:0 0 24px;color:#555;font-size:15px;line-height:1.6;">
|
|
2426
|
+
Click the button below to sign in. This link expires in 10 minutes.
|
|
2427
|
+
</p>
|
|
2428
|
+
${button("Sign In", "magicLinkUrl")}
|
|
2429
|
+
<p style="margin:24px 0 0;color:#999;font-size:13px;">
|
|
2430
|
+
If you didn't request this, you can safely ignore this email.
|
|
2431
|
+
</p>
|
|
2432
|
+
`)), vars)
|
|
2433
|
+
};
|
|
2434
|
+
var passwordResetTemplate = {
|
|
2435
|
+
name: "passwordReset",
|
|
2436
|
+
subject: "Reset your password",
|
|
2437
|
+
html: (vars) => interpolate(wrapHtml(section(`
|
|
2438
|
+
<h1 style="margin:0 0 16px;font-size:24px;color:#111;">Reset your password</h1>
|
|
2439
|
+
<p style="margin:0 0 24px;color:#555;font-size:15px;line-height:1.6;">
|
|
2440
|
+
We received a request to reset your password for {{appName}}.
|
|
2441
|
+
Click the button below to choose a new password.
|
|
2442
|
+
</p>
|
|
2443
|
+
${button("Reset Password", "resetUrl")}
|
|
2444
|
+
<p style="margin:24px 0 0;color:#999;font-size:13px;">
|
|
2445
|
+
This link expires in 1 hour. If you didn't request this, ignore this email.
|
|
2446
|
+
</p>
|
|
2447
|
+
`)), vars)
|
|
2448
|
+
};
|
|
2449
|
+
var invoiceTemplate = {
|
|
2450
|
+
name: "invoice",
|
|
2451
|
+
subject: "Invoice #{{invoiceNumber}}",
|
|
2452
|
+
html: (vars) => interpolate(wrapHtml(section(`
|
|
2453
|
+
<h1 style="margin:0 0 16px;font-size:24px;color:#111;">Invoice #{{invoiceNumber}}</h1>
|
|
2454
|
+
<p style="margin:0 0 8px;color:#555;font-size:15px;line-height:1.6;">
|
|
2455
|
+
Amount: <strong>{{amount}}</strong>
|
|
2456
|
+
</p>
|
|
2457
|
+
<p style="margin:0 0 24px;color:#555;font-size:15px;line-height:1.6;">
|
|
2458
|
+
{{description}}
|
|
2459
|
+
</p>
|
|
2460
|
+
${button("View Invoice", "invoiceUrl")}
|
|
2461
|
+
`)), vars)
|
|
2462
|
+
};
|
|
2463
|
+
var notificationTemplate = {
|
|
2464
|
+
name: "notification",
|
|
2465
|
+
subject: "{{title}}",
|
|
2466
|
+
html: (vars) => interpolate(wrapHtml(section(`
|
|
2467
|
+
<h1 style="margin:0 0 16px;font-size:24px;color:#111;">{{title}}</h1>
|
|
2468
|
+
<p style="margin:0 0 24px;color:#555;font-size:15px;line-height:1.6;">
|
|
2469
|
+
{{body}}
|
|
2470
|
+
</p>
|
|
2471
|
+
`)), vars)
|
|
2472
|
+
};
|
|
2473
|
+
var builtinTemplates = {
|
|
2474
|
+
welcome: welcomeTemplate,
|
|
2475
|
+
magicLink: magicLinkTemplate,
|
|
2476
|
+
passwordReset: passwordResetTemplate,
|
|
2477
|
+
invoice: invoiceTemplate,
|
|
2478
|
+
notification: notificationTemplate
|
|
2479
|
+
};
|
|
2480
|
+
|
|
2481
|
+
// src/email/client.ts
|
|
2482
|
+
function interpolate2(template, vars) {
|
|
2483
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? "");
|
|
2484
|
+
}
|
|
2485
|
+
var BrokrEmailClient = class {
|
|
2486
|
+
constructor(_token, _gatewayUrl) {
|
|
2487
|
+
this._token = _token;
|
|
2488
|
+
this._gatewayUrl = _gatewayUrl;
|
|
2489
|
+
}
|
|
2490
|
+
/**
|
|
2491
|
+
* Send an email. The from address and API credentials are resolved server-side.
|
|
2492
|
+
*/
|
|
2493
|
+
async send(params) {
|
|
2494
|
+
requireToken(this._token, "email");
|
|
2495
|
+
try {
|
|
2496
|
+
return await gatewayFetch(
|
|
2497
|
+
this._gatewayUrl,
|
|
2498
|
+
this._token,
|
|
2499
|
+
"/v1/email/send",
|
|
2500
|
+
params,
|
|
2501
|
+
"email"
|
|
2502
|
+
);
|
|
2503
|
+
} catch (err) {
|
|
2504
|
+
if (err instanceof BrokrError) err.component = "Email";
|
|
2505
|
+
throw err;
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
/**
|
|
2509
|
+
* Send an email using a built-in template.
|
|
2510
|
+
*
|
|
2511
|
+
* @example
|
|
2512
|
+
* ```typescript
|
|
2513
|
+
* await brokr.email.sendTemplate({
|
|
2514
|
+
* template: 'welcome',
|
|
2515
|
+
* to: 'user@example.com',
|
|
2516
|
+
* variables: { appName: 'MyApp', actionUrl: 'https://myapp.com/dashboard' },
|
|
2517
|
+
* });
|
|
2518
|
+
* ```
|
|
2519
|
+
*/
|
|
2520
|
+
async sendTemplate(params) {
|
|
2521
|
+
try {
|
|
2522
|
+
const template = builtinTemplates[params.template];
|
|
2523
|
+
if (!template) {
|
|
2524
|
+
throw new BrokrError(
|
|
2525
|
+
`[brokr] Unknown email template "${params.template}". Available: ${Object.keys(builtinTemplates).join(", ")}`,
|
|
2526
|
+
"EMAIL_TEMPLATE_NOT_FOUND",
|
|
2527
|
+
"email"
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2530
|
+
const subject = interpolate2(template.subject, params.variables);
|
|
2531
|
+
const html = template.html(params.variables);
|
|
2532
|
+
return await this.send({
|
|
2533
|
+
to: params.to,
|
|
2534
|
+
subject,
|
|
2535
|
+
html,
|
|
2536
|
+
from: params.from
|
|
2537
|
+
});
|
|
2538
|
+
} catch (err) {
|
|
2539
|
+
if (err instanceof BrokrError) err.component = "Email";
|
|
2540
|
+
throw err;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
};
|
|
2544
|
+
|
|
2545
|
+
// src/files/client.ts
|
|
2546
|
+
var BrokrFilesClient = class {
|
|
2547
|
+
constructor(_token, _gatewayUrl, _storage) {
|
|
2548
|
+
this._token = _token;
|
|
2549
|
+
this._gatewayUrl = _gatewayUrl;
|
|
2550
|
+
this._storage = _storage;
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2553
|
+
* Upload a file and trigger server-side AI processing.
|
|
2554
|
+
* Returns the storage key plus any extracted description/text.
|
|
2555
|
+
*/
|
|
2556
|
+
async process(params) {
|
|
2557
|
+
requireToken(this._token, "files");
|
|
2558
|
+
const uploaded = await this._storage.upload({
|
|
2559
|
+
file: params.file,
|
|
2560
|
+
path: params.fileName,
|
|
2561
|
+
contentType: params.purpose === "text-extraction" ? "application/pdf" : void 0
|
|
2562
|
+
});
|
|
2563
|
+
const result = await gatewayFetch(
|
|
2564
|
+
this._gatewayUrl,
|
|
2565
|
+
this._token,
|
|
2566
|
+
"/v1/files/process",
|
|
2567
|
+
{ key: uploaded.key, purpose: params.purpose ?? "general" },
|
|
2568
|
+
"files"
|
|
2569
|
+
);
|
|
2570
|
+
return {
|
|
2571
|
+
key: uploaded.key,
|
|
2572
|
+
description: result.description,
|
|
2573
|
+
text: result.text
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Get an AI-generated description of an already-uploaded file.
|
|
2578
|
+
*/
|
|
2579
|
+
async describe(key) {
|
|
2580
|
+
requireToken(this._token, "files");
|
|
2581
|
+
return gatewayFetch(
|
|
2582
|
+
this._gatewayUrl,
|
|
2583
|
+
this._token,
|
|
2584
|
+
"/v1/files/describe",
|
|
2585
|
+
{ key },
|
|
2586
|
+
"files"
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* Extract text from a PDF or image (OCR).
|
|
2591
|
+
*/
|
|
2592
|
+
async getText(key) {
|
|
2593
|
+
requireToken(this._token, "files");
|
|
2594
|
+
return gatewayFetch(
|
|
2595
|
+
this._gatewayUrl,
|
|
2596
|
+
this._token,
|
|
2597
|
+
"/v1/files/text",
|
|
2598
|
+
{ key },
|
|
2599
|
+
"files"
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
};
|
|
2603
|
+
|
|
2604
|
+
// src/payments/client.ts
|
|
2605
|
+
var BrokrPaymentsClient = class {
|
|
2606
|
+
constructor(_token, _gatewayUrl) {
|
|
2607
|
+
this._token = _token;
|
|
2608
|
+
this._gatewayUrl = _gatewayUrl;
|
|
2609
|
+
}
|
|
2610
|
+
/**
|
|
2611
|
+
* Create a Stripe Checkout session for a plan.
|
|
2612
|
+
* Returns a URL to redirect the end user to.
|
|
2613
|
+
*
|
|
2614
|
+
* @example
|
|
2615
|
+
* ```ts
|
|
2616
|
+
* const user = await brokr.auth.requireUser(request.headers);
|
|
2617
|
+
* const { checkoutUrl } = await brokr.payments.checkout({
|
|
2618
|
+
* plan: 'pro',
|
|
2619
|
+
* userId: user.id,
|
|
2620
|
+
* });
|
|
2621
|
+
* // Redirect user to checkoutUrl
|
|
2622
|
+
* ```
|
|
2623
|
+
*/
|
|
2624
|
+
/**
|
|
2625
|
+
* Create a Stripe Checkout session for a plan.
|
|
2626
|
+
*
|
|
2627
|
+
* @param params.plan - Plan slug (e.g. 'pro')
|
|
2628
|
+
* @param params.userId - End user's ID from your auth system
|
|
2629
|
+
* @param params.returnUrl - Where to redirect after checkout. Defaults to your stack's URL.
|
|
2630
|
+
* Pass explicitly for localhost or non-Brokr deployments.
|
|
2631
|
+
*/
|
|
2632
|
+
async checkout(params) {
|
|
2633
|
+
requireToken(this._token, "payments");
|
|
2634
|
+
try {
|
|
2635
|
+
return await gatewayFetch(
|
|
2636
|
+
this._gatewayUrl,
|
|
2637
|
+
this._token,
|
|
2638
|
+
"/v1/payments/checkout",
|
|
2639
|
+
{ planSlug: params.plan, appUserId: params.userId, returnUrl: params.returnUrl },
|
|
2640
|
+
"payments"
|
|
2641
|
+
);
|
|
2642
|
+
} catch (err) {
|
|
2643
|
+
if (err instanceof BrokrError) err.component = "Payments";
|
|
2644
|
+
throw err;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* Create a Stripe Customer Portal session.
|
|
2649
|
+
* Returns a URL where the end user can manage their subscription.
|
|
2650
|
+
*/
|
|
2651
|
+
/**
|
|
2652
|
+
* Create a Stripe Customer Portal session.
|
|
2653
|
+
*
|
|
2654
|
+
* @param params.userId - End user's ID
|
|
2655
|
+
* @param params.returnUrl - Where to redirect when they're done. Defaults to your stack's URL.
|
|
2656
|
+
*/
|
|
2657
|
+
async portal(params) {
|
|
2658
|
+
requireToken(this._token, "payments");
|
|
2659
|
+
try {
|
|
2660
|
+
return await gatewayFetch(
|
|
2661
|
+
this._gatewayUrl,
|
|
2662
|
+
this._token,
|
|
2663
|
+
"/v1/payments/portal",
|
|
2664
|
+
{ appUserId: params.userId, returnUrl: params.returnUrl },
|
|
2665
|
+
"payments"
|
|
2666
|
+
);
|
|
2667
|
+
} catch (err) {
|
|
2668
|
+
if (err instanceof BrokrError) err.component = "Payments";
|
|
2669
|
+
throw err;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Get the end user's current plan.
|
|
2674
|
+
* Returns null if the user has no subscription.
|
|
2675
|
+
*/
|
|
2676
|
+
async currentPlan(params) {
|
|
2677
|
+
requireToken(this._token, "payments");
|
|
2678
|
+
try {
|
|
2679
|
+
return await gatewayFetch(
|
|
2680
|
+
this._gatewayUrl,
|
|
2681
|
+
this._token,
|
|
2682
|
+
"/v1/payments/plan",
|
|
2683
|
+
{ appUserId: params.userId },
|
|
2684
|
+
"payments"
|
|
2685
|
+
);
|
|
2686
|
+
} catch (err) {
|
|
2687
|
+
if (err instanceof BrokrError) err.component = "Payments";
|
|
2688
|
+
throw err;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
};
|
|
2692
|
+
|
|
2693
|
+
// src/payments/entitlements.ts
|
|
2694
|
+
var BrokrEntitlementsClient = class {
|
|
2695
|
+
constructor(_token, _gatewayUrl) {
|
|
2696
|
+
this._token = _token;
|
|
2697
|
+
this._gatewayUrl = _gatewayUrl;
|
|
2698
|
+
}
|
|
2699
|
+
/**
|
|
2700
|
+
* Check if an end user is entitled to a feature.
|
|
2701
|
+
* Returns true/false without throwing.
|
|
2702
|
+
*
|
|
2703
|
+
* @example
|
|
2704
|
+
* ```ts
|
|
2705
|
+
* const user = await brokr.auth.requireUser(request.headers);
|
|
2706
|
+
* const canChat = await brokr.entitlements.check({ feature: 'ai.chat', userId: user.id });
|
|
2707
|
+
* ```
|
|
2708
|
+
*/
|
|
2709
|
+
async check(params) {
|
|
2710
|
+
requireToken(this._token, "payments");
|
|
2711
|
+
const result = await gatewayFetch(
|
|
2712
|
+
this._gatewayUrl,
|
|
2713
|
+
this._token,
|
|
2714
|
+
"/v1/payments/entitlements/check",
|
|
2715
|
+
{ feature: params.feature, appUserId: params.userId },
|
|
2716
|
+
"payments"
|
|
2717
|
+
);
|
|
2718
|
+
return result.allowed;
|
|
2719
|
+
}
|
|
2720
|
+
/**
|
|
2721
|
+
* Require that an end user is entitled to a feature.
|
|
2722
|
+
* Throws BrokrError if not entitled.
|
|
2723
|
+
*/
|
|
2724
|
+
async require(params) {
|
|
2725
|
+
const allowed = await this.check(params);
|
|
2726
|
+
if (!allowed) {
|
|
2727
|
+
throw new BrokrError(
|
|
2728
|
+
`[brokr] Feature "${params.feature}" is not available on the user's current plan. Upgrade at the billing portal.`,
|
|
2729
|
+
"ENTITLEMENT_DENIED",
|
|
2730
|
+
"payments"
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2735
|
+
* Get usage stats for a metered feature.
|
|
2736
|
+
* This is a READ — does NOT increment the counter.
|
|
2737
|
+
* Use `increment()` to record usage.
|
|
2738
|
+
*/
|
|
2739
|
+
async usage(params) {
|
|
2740
|
+
requireToken(this._token, "payments");
|
|
2741
|
+
return gatewayFetch(
|
|
2742
|
+
this._gatewayUrl,
|
|
2743
|
+
this._token,
|
|
2744
|
+
"/v1/payments/entitlements/usage",
|
|
2745
|
+
{ feature: params.feature, appUserId: params.userId },
|
|
2746
|
+
"payments"
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
/**
|
|
2750
|
+
* Record a usage event for a metered feature.
|
|
2751
|
+
* This is a WRITE — increments the counter.
|
|
2752
|
+
*/
|
|
2753
|
+
async increment(params) {
|
|
2754
|
+
requireToken(this._token, "payments");
|
|
2755
|
+
await gatewayFetch(
|
|
2756
|
+
this._gatewayUrl,
|
|
2757
|
+
this._token,
|
|
2758
|
+
"/v1/payments/entitlements/increment",
|
|
2759
|
+
{ feature: params.feature, appUserId: params.userId, amount: params.amount ?? 1 },
|
|
2760
|
+
"payments"
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
|
|
2765
|
+
// src/notifications/client.ts
|
|
2766
|
+
var BrokrNotificationsClient = class {
|
|
2767
|
+
constructor(token, gatewayUrl) {
|
|
2768
|
+
this.token = token;
|
|
2769
|
+
this.gatewayUrl = gatewayUrl;
|
|
2770
|
+
}
|
|
2771
|
+
/**
|
|
2772
|
+
* Send a notification to a user. Delivered via WebSocket in real-time
|
|
2773
|
+
* and persisted in the notification history.
|
|
2774
|
+
*/
|
|
2775
|
+
async send(params) {
|
|
2776
|
+
requireToken(this.token, "notifications");
|
|
2777
|
+
try {
|
|
2778
|
+
return await gatewayFetch(
|
|
2779
|
+
this.gatewayUrl,
|
|
2780
|
+
this.token,
|
|
2781
|
+
"/v1/notifications/push",
|
|
2782
|
+
params,
|
|
2783
|
+
"notifications"
|
|
2784
|
+
);
|
|
2785
|
+
} catch (err) {
|
|
2786
|
+
if (err instanceof BrokrError) err.component = "Notifications";
|
|
2787
|
+
throw err;
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
/**
|
|
2791
|
+
* Fetch notification history for a user. Reads from the DO's SQLite
|
|
2792
|
+
* storage via a gateway REST endpoint.
|
|
2793
|
+
*/
|
|
2794
|
+
async list(params) {
|
|
2795
|
+
requireToken(this.token, "notifications");
|
|
2796
|
+
try {
|
|
2797
|
+
return await gatewayFetch(
|
|
2798
|
+
this.gatewayUrl,
|
|
2799
|
+
this.token,
|
|
2800
|
+
"/v1/notifications/list",
|
|
2801
|
+
params,
|
|
2802
|
+
"notifications"
|
|
2803
|
+
);
|
|
2804
|
+
} catch (err) {
|
|
2805
|
+
if (err instanceof BrokrError) err.component = "Notifications";
|
|
2806
|
+
throw err;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
};
|
|
2810
|
+
|
|
23
2811
|
// src/auth.ts
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
2812
|
+
function resolveAppUrl(appUrl) {
|
|
2813
|
+
if (appUrl) return appUrl;
|
|
2814
|
+
throw new BrokrError(
|
|
2815
|
+
"[brokr] BROKR_AUTH_URL is not set. Auth may not be provisioned.",
|
|
2816
|
+
"AUTH_NOT_CONFIGURED",
|
|
2817
|
+
"auth"
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2820
|
+
function mapSessionUser(raw) {
|
|
2821
|
+
return {
|
|
2822
|
+
id: raw.id,
|
|
2823
|
+
email: raw.email,
|
|
2824
|
+
name: raw.name ?? null,
|
|
2825
|
+
image: raw.image ?? null,
|
|
2826
|
+
emailVerified: raw.emailVerified ? /* @__PURE__ */ new Date() : null
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
function pluralizeResource(resource) {
|
|
2830
|
+
if (resource.endsWith("s")) return resource;
|
|
2831
|
+
if (resource.endsWith("y") && !/[aeiou]y$/i.test(resource)) return resource.slice(0, -1) + "ies";
|
|
2832
|
+
return resource + "s";
|
|
2833
|
+
}
|
|
2834
|
+
function validateSqlIdentifier(name, label) {
|
|
2835
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
2836
|
+
throw new BrokrError(
|
|
2837
|
+
`[brokr] Invalid ${label}: "${name}". Must be alphanumeric with underscores only.`,
|
|
2838
|
+
"INVALID_IDENTIFIER",
|
|
2839
|
+
"auth"
|
|
2840
|
+
);
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
var _ownershipPool = null;
|
|
2844
|
+
async function getOwnershipPool(dbUrl) {
|
|
2845
|
+
let Pool;
|
|
2846
|
+
try {
|
|
2847
|
+
const mod = await import("@neondatabase/serverless");
|
|
2848
|
+
Pool = mod.Pool;
|
|
2849
|
+
} catch {
|
|
2850
|
+
throw new BrokrError(
|
|
2851
|
+
"[brokr] @neondatabase/serverless is required for ownership checks. Run: npm install @neondatabase/serverless",
|
|
2852
|
+
"MISSING_DEPENDENCY",
|
|
2853
|
+
"auth"
|
|
2854
|
+
);
|
|
2855
|
+
}
|
|
2856
|
+
if (_ownershipPool && _ownershipPool.dbUrl === dbUrl) {
|
|
2857
|
+
return _ownershipPool.pool;
|
|
2858
|
+
}
|
|
2859
|
+
const pool = new Pool({ connectionString: dbUrl, max: 3 });
|
|
2860
|
+
_ownershipPool = { pool, dbUrl };
|
|
2861
|
+
return pool;
|
|
2862
|
+
}
|
|
2863
|
+
async function authApiFetch(appUrl, path, options) {
|
|
2864
|
+
const headers = { "Content-Type": "application/json" };
|
|
2865
|
+
if (options?.cookies) headers.cookie = options.cookies;
|
|
2866
|
+
if (typeof window === "undefined") {
|
|
2867
|
+
headers.Origin = process.env.BETTER_AUTH_URL ?? process.env.NEXT_PUBLIC_APP_URL ?? process.env.APP_URL ?? process.env.BROKR_AUTH_URL ?? (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : void 0) ?? appUrl;
|
|
2868
|
+
}
|
|
2869
|
+
const res = await fetch(`${appUrl}${path}`, {
|
|
2870
|
+
method: options?.method ?? "GET",
|
|
2871
|
+
headers,
|
|
2872
|
+
body: options?.body ? JSON.stringify(options.body) : void 0
|
|
2873
|
+
});
|
|
2874
|
+
if (!res.ok) {
|
|
2875
|
+
const data = await res.json().catch(() => ({}));
|
|
2876
|
+
throw new BrokrError(
|
|
2877
|
+
data.message ?? `[brokr] Auth API call failed (HTTP ${res.status})`,
|
|
2878
|
+
"AUTH_API_FAILED",
|
|
2879
|
+
"auth"
|
|
2880
|
+
);
|
|
2881
|
+
}
|
|
2882
|
+
return res.json();
|
|
2883
|
+
}
|
|
2884
|
+
var BrokrAuthClient = class {
|
|
2885
|
+
constructor(_token, _gatewayUrl, appUrl) {
|
|
2886
|
+
this._appUrl = appUrl ?? (typeof process !== "undefined" ? process.env.BROKR_AUTH_URL : void 0);
|
|
2887
|
+
}
|
|
2888
|
+
// -------------------------------------------------------------------------
|
|
2889
|
+
// Identity — read user/session from incoming request
|
|
2890
|
+
// -------------------------------------------------------------------------
|
|
2891
|
+
/**
|
|
2892
|
+
* Get user from request headers. Returns null if not authenticated.
|
|
2893
|
+
* Calls the app's own Better Auth API.
|
|
2894
|
+
*/
|
|
2895
|
+
async user(headers) {
|
|
2896
|
+
const session = await this.session(headers);
|
|
2897
|
+
return session?.user ?? null;
|
|
2898
|
+
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Get user from request headers. Throws 401 if not authenticated.
|
|
2901
|
+
*/
|
|
2902
|
+
async requireUser(headers) {
|
|
2903
|
+
const u = await this.user(headers);
|
|
2904
|
+
if (!u) {
|
|
2905
|
+
throw new BrokrError("[brokr] Authentication required. The request has no valid session. Check that cookies are being forwarded.", "UNAUTHORIZED", "auth");
|
|
2906
|
+
}
|
|
2907
|
+
return u;
|
|
2908
|
+
}
|
|
2909
|
+
/**
|
|
2910
|
+
* Get full session from request headers. Returns null if not authenticated.
|
|
2911
|
+
*/
|
|
2912
|
+
async session(headers) {
|
|
2913
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
2914
|
+
const cookieHeader = headers.get("cookie") ?? "";
|
|
2915
|
+
if (!cookieHeader) return null;
|
|
2916
|
+
const res = await fetch(`${appUrl}/api/auth/get-session`, {
|
|
2917
|
+
method: "GET",
|
|
2918
|
+
headers: { cookie: cookieHeader }
|
|
2919
|
+
});
|
|
2920
|
+
if (!res.ok) return null;
|
|
2921
|
+
const data = await res.json();
|
|
2922
|
+
if (!data?.user || !data?.session) return null;
|
|
2923
|
+
return {
|
|
2924
|
+
user: mapSessionUser(data.user),
|
|
2925
|
+
sessionId: data.session.id,
|
|
2926
|
+
expiresAt: new Date(data.session.expiresAt)
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
/**
|
|
2930
|
+
* Get full session. Throws 401 if not authenticated.
|
|
2931
|
+
*/
|
|
2932
|
+
async requireSession(headers) {
|
|
2933
|
+
const s = await this.session(headers);
|
|
2934
|
+
if (!s) {
|
|
2935
|
+
throw new BrokrError("[brokr] Authentication required. The request has no valid session. Check that cookies are being forwarded.", "UNAUTHORIZED", "auth");
|
|
2936
|
+
}
|
|
2937
|
+
return s;
|
|
2938
|
+
}
|
|
2939
|
+
// -------------------------------------------------------------------------
|
|
2940
|
+
// Legacy aliases (backward compat)
|
|
2941
|
+
// -------------------------------------------------------------------------
|
|
2942
|
+
/** @deprecated Use user(request.headers) instead. */
|
|
2943
|
+
async currentUser(request) {
|
|
2944
|
+
return this.user(request.headers);
|
|
2945
|
+
}
|
|
2946
|
+
/** @deprecated Use session(request.headers) instead. */
|
|
2947
|
+
async getSession(request) {
|
|
2948
|
+
return this.session(request.headers);
|
|
2949
|
+
}
|
|
2950
|
+
// -------------------------------------------------------------------------
|
|
2951
|
+
// Auth actions — call Better Auth endpoints on the stack's app
|
|
2952
|
+
// -------------------------------------------------------------------------
|
|
2953
|
+
/**
|
|
2954
|
+
* Sign in with email and password.
|
|
2955
|
+
*/
|
|
2956
|
+
async signIn(params) {
|
|
2957
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
2958
|
+
const data = await authApiFetch(appUrl, "/api/auth/sign-in/email", {
|
|
2959
|
+
method: "POST",
|
|
2960
|
+
body: params
|
|
2961
|
+
});
|
|
2962
|
+
return {
|
|
2963
|
+
user: mapSessionUser(data.user),
|
|
2964
|
+
sessionId: data.session.id,
|
|
2965
|
+
expiresAt: new Date(data.session.expiresAt)
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
/**
|
|
2969
|
+
* Sign in with OAuth provider. Returns redirect URL.
|
|
2970
|
+
*/
|
|
2971
|
+
async signInWithProvider(provider, options) {
|
|
2972
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
2973
|
+
const redirectTo = options?.redirectTo ?? "/";
|
|
2974
|
+
if (!redirectTo.startsWith("/") || redirectTo.startsWith("//")) {
|
|
2975
|
+
throw new BrokrError("[brokr] redirectTo must be a relative path (start with /)", "INVALID_REDIRECT", "auth");
|
|
2976
|
+
}
|
|
2977
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(provider)) {
|
|
2978
|
+
throw new BrokrError(`[brokr] Invalid provider name: "${provider}". Must be lowercase alphanumeric.`, "INVALID_PROVIDER", "auth");
|
|
2979
|
+
}
|
|
2980
|
+
const params = new URLSearchParams({ provider, callbackURL: redirectTo });
|
|
2981
|
+
return { redirectUrl: `${appUrl}/api/auth/sign-in/social?${params}` };
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* Sign up with email and password.
|
|
2985
|
+
*/
|
|
2986
|
+
async signUp(params) {
|
|
2987
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
2988
|
+
const data = await authApiFetch(appUrl, "/api/auth/sign-up/email", {
|
|
2989
|
+
method: "POST",
|
|
2990
|
+
body: params
|
|
2991
|
+
});
|
|
2992
|
+
return {
|
|
2993
|
+
user: mapSessionUser(data.user),
|
|
2994
|
+
sessionId: data.session.id,
|
|
2995
|
+
expiresAt: new Date(data.session.expiresAt)
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Sign out the current user.
|
|
3000
|
+
*/
|
|
3001
|
+
async signOut(headers) {
|
|
3002
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
3003
|
+
const cookies = headers?.get("cookie") ?? "";
|
|
3004
|
+
await authApiFetch(appUrl, "/api/auth/sign-out", {
|
|
3005
|
+
method: "POST",
|
|
3006
|
+
cookies
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
/**
|
|
3010
|
+
* Send a magic link email.
|
|
3011
|
+
*/
|
|
3012
|
+
async sendMagicLink(email, options) {
|
|
3013
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
3014
|
+
await authApiFetch(appUrl, "/api/auth/magic-link/send", {
|
|
3015
|
+
method: "POST",
|
|
3016
|
+
body: { email, callbackURL: options?.redirectTo ?? "/" }
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
/**
|
|
3020
|
+
* Send a password reset email.
|
|
3021
|
+
*/
|
|
3022
|
+
async sendPasswordReset(params) {
|
|
3023
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
3024
|
+
await authApiFetch(appUrl, "/api/auth/forget-password", {
|
|
3025
|
+
method: "POST",
|
|
3026
|
+
body: params
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3029
|
+
/**
|
|
3030
|
+
* Reset password with token from email.
|
|
3031
|
+
*/
|
|
3032
|
+
async resetPassword(params) {
|
|
3033
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
3034
|
+
await authApiFetch(appUrl, "/api/auth/reset-password", {
|
|
3035
|
+
method: "POST",
|
|
3036
|
+
body: { token: params.token, newPassword: params.password }
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
/**
|
|
3040
|
+
* Verify email with token from email.
|
|
3041
|
+
*/
|
|
3042
|
+
async verifyEmail(params) {
|
|
3043
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
3044
|
+
await authApiFetch(appUrl, "/api/auth/verify-email", {
|
|
3045
|
+
method: "POST",
|
|
3046
|
+
body: params
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
3049
|
+
/**
|
|
3050
|
+
* Update the current user's profile.
|
|
3051
|
+
*/
|
|
3052
|
+
async updateUser(params, headers) {
|
|
3053
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
3054
|
+
const cookies = headers?.get("cookie") ?? "";
|
|
3055
|
+
await authApiFetch(appUrl, "/api/auth/update-user", {
|
|
3056
|
+
method: "POST",
|
|
3057
|
+
body: params,
|
|
3058
|
+
cookies
|
|
3059
|
+
});
|
|
3060
|
+
}
|
|
3061
|
+
// -------------------------------------------------------------------------
|
|
3062
|
+
// Session management
|
|
3063
|
+
// -------------------------------------------------------------------------
|
|
3064
|
+
/** Session management sub-client. */
|
|
3065
|
+
get sessions() {
|
|
3066
|
+
if (!this._sessions) {
|
|
3067
|
+
this._sessions = new BrokrSessionsClient(this._appUrl);
|
|
3068
|
+
}
|
|
3069
|
+
return this._sessions;
|
|
3070
|
+
}
|
|
3071
|
+
// -------------------------------------------------------------------------
|
|
3072
|
+
// Ownership — queries the stack's own DB via gateway → server
|
|
3073
|
+
// -------------------------------------------------------------------------
|
|
3074
|
+
/**
|
|
3075
|
+
* Check if the current user owns a resource. Throws 403 if not.
|
|
3076
|
+
*
|
|
3077
|
+
* Queries the developer's own database directly via DATABASE_URL.
|
|
3078
|
+
* Convention: pluralizes resource name → table, assumes `id` PK + `user_id` owner column.
|
|
3079
|
+
* Override with `opts.table` and `opts.ownerColumn` for non-standard schemas.
|
|
3080
|
+
*
|
|
3081
|
+
* @example
|
|
3082
|
+
* ```ts
|
|
3083
|
+
* await brokr.auth.requireOwner('conversation', conversationId, request.headers);
|
|
3084
|
+
* // Under the hood: SELECT 1 FROM conversations WHERE id = $1 AND user_id = $2
|
|
3085
|
+
* ```
|
|
3086
|
+
*/
|
|
3087
|
+
async requireOwner(resource, id, headers, opts) {
|
|
3088
|
+
const isOwner = await this.isOwner(resource, id, headers, opts);
|
|
3089
|
+
if (!isOwner) {
|
|
3090
|
+
throw new BrokrError(
|
|
3091
|
+
`[brokr] Access denied \u2014 current user does not own this ${resource}. Verify the resource ID and that the user is the owner.`,
|
|
3092
|
+
"FORBIDDEN",
|
|
3093
|
+
"auth"
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
/**
|
|
3098
|
+
* Check if the current user owns a resource. Returns boolean.
|
|
3099
|
+
*
|
|
3100
|
+
* Runs a direct query against the developer's database (DATABASE_URL).
|
|
3101
|
+
* No gateway round-trip — this is a local DB operation.
|
|
3102
|
+
*/
|
|
3103
|
+
async isOwner(resource, id, headers, opts) {
|
|
3104
|
+
const user = await this.requireUser(headers);
|
|
3105
|
+
const dbUrl = typeof process !== "undefined" ? process.env.DATABASE_URL : void 0;
|
|
3106
|
+
if (!dbUrl) {
|
|
3107
|
+
throw new BrokrError(
|
|
3108
|
+
"[brokr] DATABASE_URL is not set. Cannot check ownership without a database.",
|
|
3109
|
+
"DB_NOT_CONFIGURED",
|
|
3110
|
+
"auth"
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
3113
|
+
const tableName = opts?.table ?? pluralizeResource(resource);
|
|
3114
|
+
const ownerCol = opts?.ownerColumn ?? "user_id";
|
|
3115
|
+
validateSqlIdentifier(tableName, "table name");
|
|
3116
|
+
validateSqlIdentifier(ownerCol, "owner column");
|
|
3117
|
+
const pool = await getOwnershipPool(dbUrl);
|
|
3118
|
+
try {
|
|
3119
|
+
const result = await pool.query(
|
|
3120
|
+
`SELECT 1 FROM "${tableName}" WHERE id = $1 AND "${ownerCol}" = $2 LIMIT 1`,
|
|
3121
|
+
[id, user.id]
|
|
3122
|
+
);
|
|
3123
|
+
return (result.rowCount ?? 0) > 0;
|
|
3124
|
+
} catch (err) {
|
|
3125
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3126
|
+
console.error("[brokr] Ownership check error:", msg);
|
|
3127
|
+
if (msg.includes("does not exist")) {
|
|
3128
|
+
throw new BrokrError(
|
|
3129
|
+
`[brokr] Table "${tableName}" does not exist. Check your resource name or provide { table: '...' }.`,
|
|
3130
|
+
"TABLE_NOT_FOUND",
|
|
3131
|
+
"auth"
|
|
3132
|
+
);
|
|
3133
|
+
}
|
|
3134
|
+
throw new BrokrError(
|
|
3135
|
+
"[brokr] Ownership check failed. Check your DATABASE_URL and that the database is accessible.",
|
|
3136
|
+
"DB_QUERY_FAILED",
|
|
3137
|
+
"auth"
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
/**
|
|
3142
|
+
* Check if the current user matches a specific userId. Throws 403 if not.
|
|
3143
|
+
*/
|
|
3144
|
+
async requireCurrentUser(userId, headers) {
|
|
3145
|
+
const user = await this.requireUser(headers);
|
|
3146
|
+
if (user.id !== userId) {
|
|
3147
|
+
throw new BrokrError("[brokr] Access denied.", "FORBIDDEN", "auth");
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
// -------------------------------------------------------------------------
|
|
3151
|
+
// Legacy alias
|
|
3152
|
+
// -------------------------------------------------------------------------
|
|
3153
|
+
/** @deprecated Use signInWithProvider() instead. */
|
|
3154
|
+
async getOAuthUrl(provider, options) {
|
|
3155
|
+
return this.signInWithProvider(provider, options);
|
|
3156
|
+
}
|
|
3157
|
+
};
|
|
3158
|
+
var BrokrSessionsClient = class {
|
|
3159
|
+
constructor(appUrl) {
|
|
3160
|
+
this._appUrl = appUrl;
|
|
3161
|
+
}
|
|
3162
|
+
/**
|
|
3163
|
+
* List all active sessions for the current user.
|
|
3164
|
+
*/
|
|
3165
|
+
async list(headers) {
|
|
3166
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
3167
|
+
const cookies = headers.get("cookie") ?? "";
|
|
3168
|
+
const data = await authApiFetch(appUrl, "/api/auth/list-sessions", { cookies });
|
|
3169
|
+
const sessionToken = parseCookieValue(cookies, "better-auth.session_token") ?? parseCookieValue(cookies, "__Secure-better-auth.session_token");
|
|
3170
|
+
return data.map((s) => ({
|
|
3171
|
+
id: s.id,
|
|
3172
|
+
createdAt: new Date(s.createdAt),
|
|
3173
|
+
expiresAt: new Date(s.expiresAt),
|
|
3174
|
+
userAgent: s.userAgent,
|
|
3175
|
+
ipAddress: s.ipAddress,
|
|
3176
|
+
isCurrent: s.token === sessionToken
|
|
3177
|
+
}));
|
|
3178
|
+
}
|
|
3179
|
+
/**
|
|
3180
|
+
* Revoke a specific session by ID.
|
|
3181
|
+
*/
|
|
3182
|
+
async revoke(sessionId, headers) {
|
|
3183
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
3184
|
+
const cookies = headers.get("cookie") ?? "";
|
|
3185
|
+
await authApiFetch(appUrl, "/api/auth/revoke-session", {
|
|
3186
|
+
method: "POST",
|
|
3187
|
+
body: { id: sessionId },
|
|
3188
|
+
cookies
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
/**
|
|
3192
|
+
* Revoke all sessions except the current one.
|
|
3193
|
+
*/
|
|
3194
|
+
async revokeOthers(headers) {
|
|
3195
|
+
const appUrl = resolveAppUrl(this._appUrl);
|
|
3196
|
+
const cookies = headers.get("cookie") ?? "";
|
|
3197
|
+
await authApiFetch(appUrl, "/api/auth/revoke-other-sessions", {
|
|
3198
|
+
method: "POST",
|
|
3199
|
+
cookies
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
};
|
|
29
3203
|
function parseCookies(cookieHeader) {
|
|
30
3204
|
const cookies = /* @__PURE__ */ new Map();
|
|
31
3205
|
for (const pair of cookieHeader.split(";")) {
|
|
@@ -37,6 +3211,9 @@ function parseCookies(cookieHeader) {
|
|
|
37
3211
|
}
|
|
38
3212
|
return cookies;
|
|
39
3213
|
}
|
|
3214
|
+
function parseCookieValue(cookieHeader, name) {
|
|
3215
|
+
return parseCookies(cookieHeader).get(name);
|
|
3216
|
+
}
|
|
40
3217
|
function authMiddleware(options) {
|
|
41
3218
|
const { protectedRoutes = [], publicOnlyRoutes = [] } = options;
|
|
42
3219
|
return async function middleware(request) {
|
|
@@ -47,7 +3224,7 @@ function authMiddleware(options) {
|
|
|
47
3224
|
if (!isProtected && !isPublicOnly) return void 0;
|
|
48
3225
|
const cookieHeader = request.headers.get("cookie") ?? "";
|
|
49
3226
|
const cookies = parseCookies(cookieHeader);
|
|
50
|
-
const hasSession = cookies.has("better-auth.session_token");
|
|
3227
|
+
const hasSession = cookies.has("better-auth.session_token") || cookies.has("__Secure-better-auth.session_token");
|
|
51
3228
|
if (isProtected && !hasSession) {
|
|
52
3229
|
return Response.redirect(new URL("/sign-in", request.url).toString(), 302);
|
|
53
3230
|
}
|
|
@@ -56,513 +3233,951 @@ function authMiddleware(options) {
|
|
|
56
3233
|
}
|
|
57
3234
|
return void 0;
|
|
58
3235
|
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
});
|
|
98
|
-
if (!res.ok) return null;
|
|
99
|
-
const data = await res.json();
|
|
100
|
-
if (!data?.user || !data?.session) return null;
|
|
101
|
-
return {
|
|
102
|
-
user: {
|
|
103
|
-
id: data.user.id,
|
|
104
|
-
email: data.user.email,
|
|
105
|
-
name: data.user.name ?? null,
|
|
106
|
-
avatarUrl: data.user.image ?? null,
|
|
107
|
-
emailVerified: data.user.emailVerified ? /* @__PURE__ */ new Date() : null
|
|
108
|
-
},
|
|
109
|
-
sessionId: data.session.id,
|
|
110
|
-
expiresAt: new Date(data.session.expiresAt)
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Generate an OAuth authorization URL.
|
|
115
|
-
*/
|
|
116
|
-
async getOAuthUrl(provider, options) {
|
|
117
|
-
const appUrl = this._appUrl;
|
|
118
|
-
if (!appUrl) {
|
|
119
|
-
throw new BrokrError("[brokr] BETTER_AUTH_URL is not set.", "AUTH_NOT_CONFIGURED", "auth");
|
|
120
|
-
}
|
|
121
|
-
const redirectTo = options?.redirectTo ?? "/";
|
|
122
|
-
if (!redirectTo.startsWith("/") || redirectTo.startsWith("//")) {
|
|
123
|
-
throw new BrokrError("[brokr] redirectTo must be a relative path (start with /)", "INVALID_REDIRECT", "auth");
|
|
124
|
-
}
|
|
125
|
-
const params = new URLSearchParams({ callbackURL: redirectTo });
|
|
126
|
-
return { redirectUrl: `${appUrl}/api/auth/sign-in/social?provider=${provider}&${params}` };
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Send a magic link email (requires email capability).
|
|
130
|
-
*/
|
|
131
|
-
async sendMagicLink(email, options) {
|
|
132
|
-
const appUrl = this._appUrl;
|
|
133
|
-
if (!appUrl) {
|
|
134
|
-
throw new BrokrError("[brokr] BETTER_AUTH_URL is not set.", "AUTH_NOT_CONFIGURED", "auth");
|
|
135
|
-
}
|
|
136
|
-
const res = await fetch(`${appUrl}/api/auth/magic-link/send`, {
|
|
137
|
-
method: "POST",
|
|
138
|
-
headers: { "Content-Type": "application/json" },
|
|
139
|
-
body: JSON.stringify({ email, callbackURL: options?.redirectTo ?? "/" })
|
|
140
|
-
});
|
|
141
|
-
if (!res.ok) {
|
|
142
|
-
const data = await res.json().catch(() => ({}));
|
|
143
|
-
throw new BrokrError(
|
|
144
|
-
data.message ?? `[brokr] Failed to send magic link (HTTP ${res.status})`,
|
|
145
|
-
"AUTH_MAGIC_LINK_FAILED",
|
|
146
|
-
"auth"
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// src/runtime.ts
|
|
155
|
-
function resolveToken() {
|
|
156
|
-
return typeof process !== "undefined" ? process.env.BROKR_TOKEN : void 0;
|
|
157
|
-
}
|
|
158
|
-
function assertToken(token, capability) {
|
|
159
|
-
if (!token) {
|
|
160
|
-
let hint = "brokr env pull --stack <name>";
|
|
161
|
-
try {
|
|
162
|
-
if (typeof process !== "undefined") {
|
|
163
|
-
const fs = require("fs");
|
|
164
|
-
const path = require("path");
|
|
165
|
-
const brokrFile = path.join(process.cwd(), ".brokr");
|
|
166
|
-
if (fs.existsSync(brokrFile)) {
|
|
167
|
-
const data = JSON.parse(fs.readFileSync(brokrFile, "utf8"));
|
|
168
|
-
if (data?.stackName) hint = `brokr env pull --stack ${data.stackName}`;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
} catch {
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
// src/models.ts
|
|
3239
|
+
var models = {
|
|
3240
|
+
/** Cheapest and fastest model (Deepseek Chat). */
|
|
3241
|
+
FAST: "deepseek-chat",
|
|
3242
|
+
/** Most capable model. */
|
|
3243
|
+
SMART: "claude-sonnet-4-6",
|
|
3244
|
+
/** Default balanced model (Deepseek Chat). */
|
|
3245
|
+
BALANCED: "deepseek-chat"
|
|
3246
|
+
};
|
|
3247
|
+
var providers = [
|
|
3248
|
+
{ id: "deepseek", label: "Deepseek", model: "deepseek-chat", color: "#0EA5E9", free: true, logo: "https://assets.brokr.sh/sdk/deepseek_logo.png" },
|
|
3249
|
+
{ id: "openai", label: "ChatGPT", model: "gpt-5.4-mini", color: "#10B981", free: false, logo: "https://assets.brokr.sh/sdk/gpt_logo.png" },
|
|
3250
|
+
{ id: "anthropic", label: "Claude", model: "claude-sonnet-4-6", color: "#F59E0B", free: false, logo: "https://assets.brokr.sh/sdk/claude_logo.png" },
|
|
3251
|
+
{ id: "kimi", label: "Kimi", model: "fireworks/kimi-k2p5", color: "#8B5CF6", free: false, logo: "https://assets.brokr.sh/sdk/kimi_logo.png" },
|
|
3252
|
+
{ id: "minimax", label: "Minimax", model: "MiniMaxAI/MiniMax-M2.5", color: "#EC4899", free: false, logo: "https://assets.brokr.sh/sdk/minimax_logo.png" }
|
|
3253
|
+
];
|
|
3254
|
+
function resolveProviderByModel(model) {
|
|
3255
|
+
return providers.find((p) => p.model === model) ?? providers.find((p) => model.toLowerCase().includes(p.id));
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
// src/runtime.ts
|
|
3259
|
+
var BrokrRuntime = class {
|
|
3260
|
+
constructor(options) {
|
|
3261
|
+
this._token = options?.token ?? resolveToken();
|
|
3262
|
+
this._gatewayUrl = options?.gatewayUrl ?? GATEWAY_URL;
|
|
3263
|
+
this.ai = new BrokrAIClient(this._token, this._gatewayUrl);
|
|
3264
|
+
this.storage = new BrokrStorageClient(this._token, this._gatewayUrl);
|
|
3265
|
+
this.email = new BrokrEmailClient(this._token, this._gatewayUrl);
|
|
3266
|
+
}
|
|
3267
|
+
// -------------------------------------------------------------------------
|
|
3268
|
+
// Lazy namespace getters
|
|
3269
|
+
// -------------------------------------------------------------------------
|
|
3270
|
+
/** Files client (upload + AI processing). */
|
|
3271
|
+
get files() {
|
|
3272
|
+
if (!this._files) {
|
|
3273
|
+
this._files = new BrokrFilesClient(this._token, this._gatewayUrl, this.storage);
|
|
172
3274
|
}
|
|
173
|
-
|
|
174
|
-
`[brokr] BROKR_TOKEN is not set.
|
|
175
|
-
Run: ${hint}`,
|
|
176
|
-
"BROKR_TOKEN_MISSING"
|
|
177
|
-
);
|
|
3275
|
+
return this._files;
|
|
178
3276
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
186
|
-
body: JSON.stringify(body)
|
|
187
|
-
});
|
|
188
|
-
} catch (err) {
|
|
189
|
-
throw new BrokrNetworkError(
|
|
190
|
-
`[brokr] Could not reach Brokr gateway. Check your network.
|
|
191
|
-
${err instanceof Error ? err.message : String(err)}`,
|
|
192
|
-
capability
|
|
193
|
-
);
|
|
3277
|
+
/** Payments client (checkout, portal, plan queries). */
|
|
3278
|
+
get payments() {
|
|
3279
|
+
if (!this._payments) {
|
|
3280
|
+
this._payments = new BrokrPaymentsClient(this._token, this._gatewayUrl);
|
|
3281
|
+
}
|
|
3282
|
+
return this._payments;
|
|
194
3283
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
capability
|
|
202
|
-
);
|
|
3284
|
+
/** Notifications client (send + list notifications). */
|
|
3285
|
+
get notifications() {
|
|
3286
|
+
if (!this._notifications) {
|
|
3287
|
+
this._notifications = new BrokrNotificationsClient(this._token, this._gatewayUrl);
|
|
3288
|
+
}
|
|
3289
|
+
return this._notifications;
|
|
203
3290
|
}
|
|
204
|
-
|
|
205
|
-
|
|
3291
|
+
/** Entitlements client (feature checks, usage queries). */
|
|
3292
|
+
get entitlements() {
|
|
3293
|
+
if (!this._entitlements) {
|
|
3294
|
+
this._entitlements = new BrokrEntitlementsClient(this._token, this._gatewayUrl);
|
|
3295
|
+
}
|
|
3296
|
+
return this._entitlements;
|
|
206
3297
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
);
|
|
3298
|
+
/** Auth client — lazily initialized. */
|
|
3299
|
+
get auth() {
|
|
3300
|
+
if (!this._auth) {
|
|
3301
|
+
this._auth = new BrokrAuthClient(this._token, this._gatewayUrl);
|
|
3302
|
+
}
|
|
3303
|
+
return this._auth;
|
|
214
3304
|
}
|
|
215
|
-
|
|
216
|
-
|
|
3305
|
+
// -------------------------------------------------------------------------
|
|
3306
|
+
// Root aliases — one-liner DX for the most common operations
|
|
3307
|
+
// -------------------------------------------------------------------------
|
|
3308
|
+
/** Send a chat message. Alias for `brokr.ai.chat()`. */
|
|
3309
|
+
chat(input, options) {
|
|
3310
|
+
return this.ai.chat(input, options);
|
|
3311
|
+
}
|
|
3312
|
+
/** Upload a file. Alias for `brokr.storage.upload()`. */
|
|
3313
|
+
upload(params) {
|
|
3314
|
+
return this.storage.upload(params);
|
|
3315
|
+
}
|
|
3316
|
+
/** Start a checkout flow. Alias for `brokr.payments.checkout()`. */
|
|
3317
|
+
checkout(params) {
|
|
3318
|
+
return this.payments.checkout(params);
|
|
3319
|
+
}
|
|
3320
|
+
/** Start a checkout flow. Alias for `brokr.checkout()` — north star one-liner. */
|
|
3321
|
+
purchase(params) {
|
|
3322
|
+
return this.payments.checkout(params);
|
|
3323
|
+
}
|
|
3324
|
+
};
|
|
217
3325
|
function createBrokr(options) {
|
|
3326
|
+
BrokrDevConsole.install();
|
|
3327
|
+
initCapture({
|
|
3328
|
+
token: options?.token,
|
|
3329
|
+
gatewayUrl: options?.gatewayUrl
|
|
3330
|
+
});
|
|
218
3331
|
return new BrokrRuntime(options);
|
|
219
3332
|
}
|
|
220
|
-
var GATEWAY_URL, BrokrError, BrokrAuthError, BrokrRateLimitError, BrokrNetworkError, BrokrAIClient, BrokrStorageClient, BrokrEmailClient, BrokrRuntime;
|
|
221
|
-
var init_runtime = __esm({
|
|
222
|
-
"src/runtime.ts"() {
|
|
223
|
-
"use strict";
|
|
224
|
-
GATEWAY_URL = "https://api.brokr.sh";
|
|
225
|
-
BrokrError = class extends Error {
|
|
226
|
-
constructor(message, code, capability, retryable = false) {
|
|
227
|
-
super(message);
|
|
228
|
-
this.code = code;
|
|
229
|
-
this.capability = capability;
|
|
230
|
-
this.retryable = retryable;
|
|
231
|
-
this.name = "BrokrError";
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
BrokrAuthError = class extends BrokrError {
|
|
235
|
-
constructor(message, code) {
|
|
236
|
-
super(message, code, "auth", false);
|
|
237
|
-
this.name = "BrokrAuthError";
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
BrokrRateLimitError = class extends BrokrError {
|
|
241
|
-
constructor(message, retryAfter, capability) {
|
|
242
|
-
super(message, "RATE_LIMITED", capability, true);
|
|
243
|
-
this.retryAfter = retryAfter;
|
|
244
|
-
this.name = "BrokrRateLimitError";
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
BrokrNetworkError = class extends BrokrError {
|
|
248
|
-
constructor(message, capability) {
|
|
249
|
-
super(message, "NETWORK_ERROR", capability, true);
|
|
250
|
-
this.name = "BrokrNetworkError";
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
BrokrAIClient = class {
|
|
254
|
-
constructor(_token, _gatewayUrl) {
|
|
255
|
-
this._token = _token;
|
|
256
|
-
this._gatewayUrl = _gatewayUrl;
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Send a chat completion request.
|
|
260
|
-
*
|
|
261
|
-
* @example
|
|
262
|
-
* ```typescript
|
|
263
|
-
* const reply = await brokr.ai.chat([
|
|
264
|
-
* { role: 'user', content: 'Explain quantum computing in one sentence.' }
|
|
265
|
-
* ]);
|
|
266
|
-
* console.log(reply.content);
|
|
267
|
-
* ```
|
|
268
|
-
*/
|
|
269
|
-
async chat(messages, options) {
|
|
270
|
-
assertToken(this._token, "ai");
|
|
271
|
-
const data = await gatewayFetch(this._gatewayUrl, this._token, "/v1/chat/completions", {
|
|
272
|
-
messages,
|
|
273
|
-
model: options?.model,
|
|
274
|
-
max_tokens: options?.maxTokens,
|
|
275
|
-
temperature: options?.temperature
|
|
276
|
-
}, "ai");
|
|
277
|
-
return {
|
|
278
|
-
content: data.choices?.[0]?.message?.content ?? "",
|
|
279
|
-
model: data.model ?? "",
|
|
280
|
-
usage: {
|
|
281
|
-
promptTokens: data.usage?.prompt_tokens ?? 0,
|
|
282
|
-
completionTokens: data.usage?.completion_tokens ?? 0
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Stream a chat completion. Yields text strings directly.
|
|
288
|
-
*
|
|
289
|
-
* @example
|
|
290
|
-
* ```typescript
|
|
291
|
-
* for await (const text of brokr.ai.stream(messages)) {
|
|
292
|
-
* process.stdout.write(text);
|
|
293
|
-
* }
|
|
294
|
-
* ```
|
|
295
|
-
*/
|
|
296
|
-
async *stream(messages, options) {
|
|
297
|
-
assertToken(this._token, "ai");
|
|
298
|
-
let res;
|
|
299
|
-
try {
|
|
300
|
-
res = await fetch(`${this._gatewayUrl}/v1/chat/completions`, {
|
|
301
|
-
method: "POST",
|
|
302
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${this._token}` },
|
|
303
|
-
body: JSON.stringify({ messages, stream: true, model: options?.model, max_tokens: options?.maxTokens })
|
|
304
|
-
});
|
|
305
|
-
} catch (err) {
|
|
306
|
-
throw new BrokrNetworkError(
|
|
307
|
-
`[brokr] Could not reach Brokr gateway.
|
|
308
|
-
${err instanceof Error ? err.message : String(err)}`,
|
|
309
|
-
"ai"
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
if (res.status === 429) {
|
|
313
|
-
const retryAfter = parseInt(res.headers.get("Retry-After") ?? "60", 10);
|
|
314
|
-
throw new BrokrRateLimitError("[brokr] AI rate limited.", retryAfter, "ai");
|
|
315
|
-
}
|
|
316
|
-
if (res.status === 401) {
|
|
317
|
-
throw new BrokrAuthError("[brokr] Invalid or expired BROKR_TOKEN.", "BROKR_TOKEN_INVALID");
|
|
318
|
-
}
|
|
319
|
-
if (!res.ok || !res.body) {
|
|
320
|
-
throw new BrokrError(`[brokr] AI stream failed (HTTP ${res.status})`, "AI_STREAM_FAILED", "ai");
|
|
321
|
-
}
|
|
322
|
-
const reader = res.body.getReader();
|
|
323
|
-
const decoder = new TextDecoder();
|
|
324
|
-
let buffer = "";
|
|
325
|
-
while (true) {
|
|
326
|
-
const { done, value } = await reader.read();
|
|
327
|
-
if (done) break;
|
|
328
|
-
buffer += decoder.decode(value, { stream: true });
|
|
329
|
-
const lines = buffer.split("\n");
|
|
330
|
-
buffer = lines.pop() ?? "";
|
|
331
|
-
for (const line of lines) {
|
|
332
|
-
if (!line.startsWith("data: ")) continue;
|
|
333
|
-
const payload = line.slice(6).trim();
|
|
334
|
-
if (payload === "[DONE]") return;
|
|
335
|
-
try {
|
|
336
|
-
const parsed = JSON.parse(payload);
|
|
337
|
-
const delta = parsed.choices?.[0]?.delta?.content ?? "";
|
|
338
|
-
if (delta) yield delta;
|
|
339
|
-
} catch {
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* OpenAI-SDK compatible base URL.
|
|
346
|
-
*
|
|
347
|
-
* @example
|
|
348
|
-
* ```typescript
|
|
349
|
-
* const openai = new OpenAI({ baseURL: brokr.ai.baseURL, apiKey: brokr.ai.apiKey });
|
|
350
|
-
* ```
|
|
351
|
-
*/
|
|
352
|
-
get baseURL() {
|
|
353
|
-
return `${this._gatewayUrl}/v1`;
|
|
354
|
-
}
|
|
355
|
-
/** Use as `apiKey` with the official OpenAI SDK to route through Brokr's gateway. */
|
|
356
|
-
get apiKey() {
|
|
357
|
-
assertToken(this._token, "ai");
|
|
358
|
-
return this._token;
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
BrokrStorageClient = class {
|
|
362
|
-
constructor(_token, _gatewayUrl) {
|
|
363
|
-
this._token = _token;
|
|
364
|
-
this._gatewayUrl = _gatewayUrl;
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Get a presigned upload URL for browser-direct or streaming uploads.
|
|
368
|
-
*
|
|
369
|
-
* @example
|
|
370
|
-
* ```typescript
|
|
371
|
-
* const { url, key } = await brokr.storage.getUploadUrl('avatar.png', 'image/png');
|
|
372
|
-
* await fetch(url, { method: 'PUT', body: file });
|
|
373
|
-
* ```
|
|
374
|
-
*/
|
|
375
|
-
async getUploadUrl(filename, contentType = "application/octet-stream") {
|
|
376
|
-
assertToken(this._token, "storage");
|
|
377
|
-
return gatewayFetch(
|
|
378
|
-
this._gatewayUrl,
|
|
379
|
-
this._token,
|
|
380
|
-
"/v1/storage/sign-upload",
|
|
381
|
-
{ filename, contentType },
|
|
382
|
-
"storage"
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Upload data to R2. Returns the stable object key.
|
|
387
|
-
*
|
|
388
|
-
* @example
|
|
389
|
-
* ```typescript
|
|
390
|
-
* const { key } = await brokr.storage.upload(fileBuffer, 'photo.jpg', 'image/jpeg');
|
|
391
|
-
* ```
|
|
392
|
-
*/
|
|
393
|
-
async upload(data, filename, contentType = "application/octet-stream") {
|
|
394
|
-
const { url, key } = await this.getUploadUrl(filename, contentType);
|
|
395
|
-
const putRes = await fetch(url, {
|
|
396
|
-
method: "PUT",
|
|
397
|
-
headers: { "Content-Type": contentType },
|
|
398
|
-
body: data
|
|
399
|
-
});
|
|
400
|
-
if (!putRes.ok) {
|
|
401
|
-
throw new BrokrError(`[brokr] Upload failed (HTTP ${putRes.status})`, "STORAGE_UPLOAD_FAILED", "storage");
|
|
402
|
-
}
|
|
403
|
-
return { key };
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Get a presigned download URL for a stored object.
|
|
407
|
-
*
|
|
408
|
-
* @example
|
|
409
|
-
* ```typescript
|
|
410
|
-
* const { url } = await brokr.storage.url('photos/avatar.jpg');
|
|
411
|
-
* // use url in <img src={url} /> or redirect
|
|
412
|
-
* ```
|
|
413
|
-
*/
|
|
414
|
-
async url(key, options) {
|
|
415
|
-
assertToken(this._token, "storage");
|
|
416
|
-
return gatewayFetch(
|
|
417
|
-
this._gatewayUrl,
|
|
418
|
-
this._token,
|
|
419
|
-
"/v1/storage/sign-download",
|
|
420
|
-
{ key, expiresIn: options?.expiresIn },
|
|
421
|
-
"storage"
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
/** @deprecated Use `url()` instead. */
|
|
425
|
-
async getUrl(key, options) {
|
|
426
|
-
return this.url(key, options);
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
BrokrEmailClient = class {
|
|
430
|
-
constructor(_token, _gatewayUrl) {
|
|
431
|
-
this._token = _token;
|
|
432
|
-
this._gatewayUrl = _gatewayUrl;
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* Send an email. The from address and API credentials are resolved server-side.
|
|
436
|
-
*
|
|
437
|
-
* @example
|
|
438
|
-
* ```typescript
|
|
439
|
-
* await brokr.email.send({
|
|
440
|
-
* to: 'user@example.com',
|
|
441
|
-
* subject: 'Welcome!',
|
|
442
|
-
* html: '<h1>Welcome to the app</h1>',
|
|
443
|
-
* });
|
|
444
|
-
* ```
|
|
445
|
-
*/
|
|
446
|
-
async send(params) {
|
|
447
|
-
assertToken(this._token, "email");
|
|
448
|
-
return gatewayFetch(
|
|
449
|
-
this._gatewayUrl,
|
|
450
|
-
this._token,
|
|
451
|
-
"/v1/email/send",
|
|
452
|
-
params,
|
|
453
|
-
"email"
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
BrokrRuntime = class {
|
|
458
|
-
constructor(options) {
|
|
459
|
-
this._token = options?.token ?? resolveToken();
|
|
460
|
-
this._gatewayUrl = options?.gatewayUrl ?? GATEWAY_URL;
|
|
461
|
-
this.ai = new BrokrAIClient(this._token, this._gatewayUrl);
|
|
462
|
-
this.storage = new BrokrStorageClient(this._token, this._gatewayUrl);
|
|
463
|
-
this.email = new BrokrEmailClient(this._token, this._gatewayUrl);
|
|
464
|
-
}
|
|
465
|
-
/** Auth client — lazily initialized to avoid pulling in auth deps when not needed. */
|
|
466
|
-
get auth() {
|
|
467
|
-
if (!this._auth) {
|
|
468
|
-
const { BrokrAuthClient: BrokrAuthClient2 } = (init_auth(), __toCommonJS(auth_exports));
|
|
469
|
-
this._auth = new BrokrAuthClient2(this._token, this._gatewayUrl);
|
|
470
|
-
}
|
|
471
|
-
return this._auth;
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
3333
|
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
3334
|
+
// src/ai/conversation-title.ts
|
|
3335
|
+
function titleCaseWord(word) {
|
|
3336
|
+
if (!word) return word;
|
|
3337
|
+
if (/[A-Z]{2,}/.test(word) || /\d/.test(word)) return word;
|
|
3338
|
+
return `${word.charAt(0).toUpperCase()}${word.slice(1).toLowerCase()}`;
|
|
3339
|
+
}
|
|
3340
|
+
function buildFallbackConversationTitle(message) {
|
|
3341
|
+
const words = message.replace(/\s+/g, " ").trim().split(" ").filter(Boolean).slice(0, 5).map((word) => word.replace(/^[^\w]+|[^\w]+$/g, "")).filter(Boolean).map(titleCaseWord);
|
|
3342
|
+
return words.length > 0 ? words.join(" ") : "New Chat";
|
|
3343
|
+
}
|
|
3344
|
+
function sanitizeConversationTitle(input, fallback = "New Chat") {
|
|
3345
|
+
const cleaned = input.replace(/^['"`]+|['"`]+$/g, "").replace(/[.!?]+$/g, "").replace(/[:/\\]+/g, " ").replace(/\s+/g, " ").trim();
|
|
3346
|
+
if (!cleaned) return fallback;
|
|
3347
|
+
const words = cleaned.split(" ").filter(Boolean).slice(0, 5).map(titleCaseWord);
|
|
3348
|
+
if (words.length === 0) return fallback;
|
|
3349
|
+
const title = words.join(" ").slice(0, 80).trim();
|
|
3350
|
+
return title || fallback;
|
|
3351
|
+
}
|
|
3352
|
+
async function generateConversationTitle(ai, firstUserMessage, options = {}) {
|
|
3353
|
+
const fallback = sanitizeConversationTitle(
|
|
3354
|
+
options.fallback ?? buildFallbackConversationTitle(firstUserMessage),
|
|
3355
|
+
"New Chat"
|
|
3356
|
+
);
|
|
3357
|
+
const prompt = [
|
|
3358
|
+
"Generate a short conversation title for an AI chat thread.",
|
|
3359
|
+
"The title must describe the user intent from the first user message, not the assistant response.",
|
|
3360
|
+
'Return JSON with one field: "title".',
|
|
3361
|
+
"Rules:",
|
|
3362
|
+
"- 2 to 5 words.",
|
|
3363
|
+
"- Plain Title Case.",
|
|
3364
|
+
"- Specific and concrete.",
|
|
3365
|
+
"- No quotes, emojis, colons, slashes, or trailing punctuation.",
|
|
3366
|
+
"- Do not use filler like New Chat, Help, Question, Support, General, or Conversation unless there is truly no better option.",
|
|
3367
|
+
"- Preserve product names, framework names, and acronyms when they matter.",
|
|
3368
|
+
"",
|
|
3369
|
+
"First user message:",
|
|
3370
|
+
firstUserMessage.trim()
|
|
3371
|
+
].join("\n");
|
|
3372
|
+
try {
|
|
3373
|
+
const result = await ai.structured({
|
|
3374
|
+
prompt,
|
|
3375
|
+
schema: {
|
|
3376
|
+
type: "object",
|
|
3377
|
+
additionalProperties: false,
|
|
3378
|
+
properties: {
|
|
3379
|
+
title: { type: "string" }
|
|
3380
|
+
},
|
|
3381
|
+
required: ["title"]
|
|
3382
|
+
},
|
|
3383
|
+
model: options.model,
|
|
3384
|
+
temperature: 0.15
|
|
3385
|
+
});
|
|
3386
|
+
return sanitizeConversationTitle(result.title ?? "", fallback);
|
|
3387
|
+
} catch {
|
|
3388
|
+
return fallback;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
494
3391
|
|
|
495
3392
|
// src/management.ts
|
|
496
|
-
async function
|
|
497
|
-
const
|
|
3393
|
+
async function orpcRequest(config2, procedure, input) {
|
|
3394
|
+
const path = procedure.replace(/^v1\./, "").replace(/\./g, "/");
|
|
3395
|
+
const url = new URL(`${config2.apiUrl}/api/orpc/${path}`);
|
|
498
3396
|
const response = await fetch(url.toString(), {
|
|
499
3397
|
method: "POST",
|
|
500
3398
|
headers: {
|
|
501
3399
|
"Content-Type": "application/json",
|
|
502
|
-
"Authorization": `Bearer ${
|
|
503
|
-
...
|
|
3400
|
+
"Authorization": `Bearer ${config2.accessToken}`,
|
|
3401
|
+
...config2.headers
|
|
504
3402
|
},
|
|
505
|
-
body: JSON.stringify(input),
|
|
506
|
-
signal:
|
|
3403
|
+
body: JSON.stringify({ json: input }),
|
|
3404
|
+
signal: config2.timeout ? AbortSignal.timeout(config2.timeout) : void 0
|
|
507
3405
|
});
|
|
508
3406
|
if (!response.ok) {
|
|
509
3407
|
const error = await response.json().catch(() => ({ message: "Request failed" }));
|
|
510
|
-
throw new Error(error
|
|
3408
|
+
throw new Error(error?.json?.message || error?.message || `HTTP ${response.status}`);
|
|
511
3409
|
}
|
|
512
|
-
const
|
|
513
|
-
return
|
|
3410
|
+
const envelope = await response.json();
|
|
3411
|
+
return envelope.json;
|
|
514
3412
|
}
|
|
515
3413
|
var BrokrClient = class {
|
|
516
|
-
constructor(
|
|
517
|
-
this.config =
|
|
3414
|
+
constructor(config2) {
|
|
3415
|
+
this.config = config2;
|
|
518
3416
|
}
|
|
519
3417
|
/** Create a new stack with default providers. */
|
|
520
3418
|
async create(input) {
|
|
521
|
-
return
|
|
3419
|
+
return orpcRequest(this.config, "v1.stack.create", input);
|
|
522
3420
|
}
|
|
523
3421
|
/** List all stacks for the current user. */
|
|
524
3422
|
async listStacks() {
|
|
525
|
-
return
|
|
3423
|
+
return orpcRequest(this.config, "v1.stack.list", {});
|
|
526
3424
|
}
|
|
527
3425
|
/** Get a specific stack by ID. */
|
|
528
3426
|
async getStack(id) {
|
|
529
|
-
return
|
|
3427
|
+
return orpcRequest(this.config, "v1.stack.get", { id });
|
|
530
3428
|
}
|
|
531
3429
|
/** Update a stack. */
|
|
532
3430
|
async updateStack(id, input) {
|
|
533
|
-
return
|
|
3431
|
+
return orpcRequest(this.config, "v1.stack.update", { id, ...input });
|
|
534
3432
|
}
|
|
535
3433
|
/** Delete a stack. */
|
|
536
3434
|
async deleteStack(id) {
|
|
537
|
-
return
|
|
3435
|
+
return orpcRequest(this.config, "v1.stack.delete", { id });
|
|
538
3436
|
}
|
|
539
3437
|
/** Add a provider to a stack. */
|
|
540
3438
|
async add(provider, input) {
|
|
541
|
-
return
|
|
3439
|
+
return orpcRequest(this.config, "v1.provider.add", { provider, ...input });
|
|
542
3440
|
}
|
|
543
3441
|
/** Refresh the access token. */
|
|
544
3442
|
async refreshToken(refreshToken) {
|
|
545
|
-
return
|
|
3443
|
+
return orpcRequest(this.config, "v1.auth.refresh", { refreshToken });
|
|
546
3444
|
}
|
|
547
3445
|
/** Update the access token. */
|
|
548
3446
|
setAccessToken(accessToken) {
|
|
549
3447
|
this.config.accessToken = accessToken;
|
|
550
3448
|
}
|
|
551
3449
|
};
|
|
552
|
-
function createBrokrClient(
|
|
553
|
-
return new BrokrClient(
|
|
3450
|
+
function createBrokrClient(config2) {
|
|
3451
|
+
return new BrokrClient(config2);
|
|
554
3452
|
}
|
|
555
3453
|
var create = createBrokrClient;
|
|
3454
|
+
|
|
3455
|
+
// src/next/notifications.ts
|
|
3456
|
+
var DEFAULT_TABLE = "brokr_notifications";
|
|
3457
|
+
async function getNeonSql() {
|
|
3458
|
+
const url = process.env.DATABASE_URL;
|
|
3459
|
+
if (!url) {
|
|
3460
|
+
throw new Error(
|
|
3461
|
+
"[brokr] DATABASE_URL is not set. Notification persistence requires a Postgres database.\nRun: brokr add database"
|
|
3462
|
+
);
|
|
3463
|
+
}
|
|
3464
|
+
try {
|
|
3465
|
+
const { neon } = await import("@neondatabase/serverless");
|
|
3466
|
+
return neon(url);
|
|
3467
|
+
} catch {
|
|
3468
|
+
throw new Error(
|
|
3469
|
+
"[brokr] @neondatabase/serverless is not installed.\nRun: npm install @neondatabase/serverless"
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
var _sql = null;
|
|
3474
|
+
var _ensuredTables = /* @__PURE__ */ new Set();
|
|
3475
|
+
async function getDb() {
|
|
3476
|
+
if (!_sql) _sql = await getNeonSql();
|
|
3477
|
+
return _sql;
|
|
3478
|
+
}
|
|
3479
|
+
function validateTableName(name) {
|
|
3480
|
+
if (!/^[a-z_][a-z0-9_]{0,62}$/.test(name)) {
|
|
3481
|
+
throw new Error(`[brokr] Invalid table name: "${name}". Use lowercase letters, digits, and underscores only.`);
|
|
3482
|
+
}
|
|
3483
|
+
return name;
|
|
3484
|
+
}
|
|
3485
|
+
async function ensureNotificationTable(tableName = DEFAULT_TABLE) {
|
|
3486
|
+
const table = validateTableName(tableName);
|
|
3487
|
+
if (_ensuredTables.has(table)) return;
|
|
3488
|
+
const sql = await getDb();
|
|
3489
|
+
await sql`
|
|
3490
|
+
CREATE TABLE IF NOT EXISTS brokr_notifications (
|
|
3491
|
+
id TEXT PRIMARY KEY,
|
|
3492
|
+
user_id TEXT NOT NULL,
|
|
3493
|
+
type TEXT NOT NULL DEFAULT 'default',
|
|
3494
|
+
title TEXT NOT NULL,
|
|
3495
|
+
message TEXT NOT NULL,
|
|
3496
|
+
variant TEXT NOT NULL DEFAULT 'default',
|
|
3497
|
+
href TEXT,
|
|
3498
|
+
image_url TEXT,
|
|
3499
|
+
image_alt TEXT,
|
|
3500
|
+
read BOOLEAN NOT NULL DEFAULT false,
|
|
3501
|
+
read_at TIMESTAMPTZ,
|
|
3502
|
+
data JSONB,
|
|
3503
|
+
expires_at TIMESTAMPTZ,
|
|
3504
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
3505
|
+
)
|
|
3506
|
+
`;
|
|
3507
|
+
await sql`ALTER TABLE brokr_notifications ADD COLUMN IF NOT EXISTS type TEXT NOT NULL DEFAULT 'default'`.catch(() => {
|
|
3508
|
+
});
|
|
3509
|
+
await sql`ALTER TABLE brokr_notifications ADD COLUMN IF NOT EXISTS href TEXT`.catch(() => {
|
|
3510
|
+
});
|
|
3511
|
+
await sql`ALTER TABLE brokr_notifications ADD COLUMN IF NOT EXISTS image_url TEXT`.catch(() => {
|
|
3512
|
+
});
|
|
3513
|
+
await sql`ALTER TABLE brokr_notifications ADD COLUMN IF NOT EXISTS image_alt TEXT`.catch(() => {
|
|
3514
|
+
});
|
|
3515
|
+
await sql`ALTER TABLE brokr_notifications ADD COLUMN IF NOT EXISTS read_at TIMESTAMPTZ`.catch(() => {
|
|
3516
|
+
});
|
|
3517
|
+
await sql`ALTER TABLE brokr_notifications ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ`.catch(() => {
|
|
3518
|
+
});
|
|
3519
|
+
await sql`
|
|
3520
|
+
CREATE INDEX IF NOT EXISTS idx_brokr_notifications_user
|
|
3521
|
+
ON brokr_notifications(user_id, created_at DESC)
|
|
3522
|
+
`;
|
|
3523
|
+
await sql`
|
|
3524
|
+
CREATE INDEX IF NOT EXISTS idx_brokr_notifications_type
|
|
3525
|
+
ON brokr_notifications(user_id, type, created_at DESC)
|
|
3526
|
+
`;
|
|
3527
|
+
if (table !== DEFAULT_TABLE) {
|
|
3528
|
+
const exists = await sql`
|
|
3529
|
+
SELECT 1 FROM information_schema.tables
|
|
3530
|
+
WHERE table_schema = 'public' AND table_name = ${table}
|
|
3531
|
+
`;
|
|
3532
|
+
if (exists.length === 0) {
|
|
3533
|
+
throw new Error(
|
|
3534
|
+
`[brokr] Custom notification table "${table}" does not exist.
|
|
3535
|
+
Create it with the same schema as brokr_notifications, or remove the tableName config.`
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
_ensuredTables.add(table);
|
|
3540
|
+
}
|
|
3541
|
+
async function persistNotification(params, opts) {
|
|
3542
|
+
await ensureNotificationTable(opts?.tableName);
|
|
3543
|
+
const sql = await getDb();
|
|
3544
|
+
await sql`
|
|
3545
|
+
INSERT INTO brokr_notifications (id, user_id, type, title, message, variant, href, image_url, image_alt, data, expires_at, created_at)
|
|
3546
|
+
VALUES (
|
|
3547
|
+
${params.id},
|
|
3548
|
+
${params.userId},
|
|
3549
|
+
${params.type ?? "default"},
|
|
3550
|
+
${params.title},
|
|
3551
|
+
${params.message},
|
|
3552
|
+
${params.variant},
|
|
3553
|
+
${params.href ?? null},
|
|
3554
|
+
${params.imageUrl ?? null},
|
|
3555
|
+
${params.imageAlt ?? null},
|
|
3556
|
+
${params.data ? JSON.stringify(params.data) : null}::jsonb,
|
|
3557
|
+
${params.expiresAt?.toISOString() ?? null},
|
|
3558
|
+
NOW()
|
|
3559
|
+
)
|
|
3560
|
+
ON CONFLICT (id) DO NOTHING
|
|
3561
|
+
`;
|
|
3562
|
+
}
|
|
3563
|
+
async function listPersistedNotifications(userId, params, opts) {
|
|
3564
|
+
await ensureNotificationTable(opts?.tableName);
|
|
3565
|
+
const sql = await getDb();
|
|
3566
|
+
const limit = Math.min(params?.limit ?? 50, 100);
|
|
3567
|
+
const typeFilter = params?.type ?? null;
|
|
3568
|
+
const skipUnreadFilter = !(params?.unreadOnly ?? false);
|
|
3569
|
+
const rows = await sql`
|
|
3570
|
+
SELECT id, user_id, type, title, message, variant, href, image_url, image_alt, read, read_at, data, expires_at, created_at
|
|
3571
|
+
FROM brokr_notifications
|
|
3572
|
+
WHERE user_id = ${userId}
|
|
3573
|
+
AND (expires_at IS NULL OR expires_at > NOW())
|
|
3574
|
+
AND (${typeFilter}::text IS NULL OR type = ${typeFilter})
|
|
3575
|
+
AND (${skipUnreadFilter}::boolean OR read = false)
|
|
3576
|
+
ORDER BY created_at DESC
|
|
3577
|
+
LIMIT ${limit}
|
|
3578
|
+
`;
|
|
3579
|
+
return rows.map((r) => ({
|
|
3580
|
+
id: r.id,
|
|
3581
|
+
userId: r.user_id,
|
|
3582
|
+
type: r.type ?? "default",
|
|
3583
|
+
title: r.title,
|
|
3584
|
+
message: r.message,
|
|
3585
|
+
variant: r.variant,
|
|
3586
|
+
href: r.href ?? null,
|
|
3587
|
+
imageUrl: r.image_url ?? null,
|
|
3588
|
+
imageAlt: r.image_alt ?? null,
|
|
3589
|
+
read: r.read,
|
|
3590
|
+
readAt: r.read_at ? r.read_at.toISOString() : null,
|
|
3591
|
+
data: r.data,
|
|
3592
|
+
expiresAt: r.expires_at ? r.expires_at.toISOString() : null,
|
|
3593
|
+
createdAt: r.created_at.toISOString()
|
|
3594
|
+
}));
|
|
3595
|
+
}
|
|
3596
|
+
async function markNotificationRead(notificationId, userId, opts) {
|
|
3597
|
+
await ensureNotificationTable(opts?.tableName);
|
|
3598
|
+
const sql = await getDb();
|
|
3599
|
+
await sql`
|
|
3600
|
+
UPDATE brokr_notifications SET read = true, read_at = NOW()
|
|
3601
|
+
WHERE id = ${notificationId} AND user_id = ${userId}
|
|
3602
|
+
`;
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
// src/ai/types.ts
|
|
3606
|
+
function contentToText(content) {
|
|
3607
|
+
if (typeof content === "string") return content;
|
|
3608
|
+
return content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
// src/next/chat.ts
|
|
3612
|
+
initCapture();
|
|
3613
|
+
var CHARS_PER_TOKEN = 4;
|
|
3614
|
+
var DEFAULT_TOKEN_BUDGET = 2e4;
|
|
3615
|
+
function estimateTokens(text) {
|
|
3616
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
3617
|
+
}
|
|
3618
|
+
function trimToTokenBudget(msgs, budget = DEFAULT_TOKEN_BUDGET) {
|
|
3619
|
+
if (msgs.length === 0) return msgs;
|
|
3620
|
+
const system = msgs.filter((m) => m.role === "system");
|
|
3621
|
+
const rest = msgs.filter((m) => m.role !== "system");
|
|
3622
|
+
if (rest.length === 0) return system;
|
|
3623
|
+
const systemCost = system.reduce((s, m) => s + estimateTokens(contentToText(m.content)), 0);
|
|
3624
|
+
const latest = rest[rest.length - 1];
|
|
3625
|
+
const latestCost = estimateTokens(contentToText(latest.content));
|
|
3626
|
+
const remaining = budget - systemCost - latestCost;
|
|
3627
|
+
if (remaining <= 0) return [...system, latest];
|
|
3628
|
+
const kept = [];
|
|
3629
|
+
let used = 0;
|
|
3630
|
+
for (let i = rest.length - 2; i >= 0; i--) {
|
|
3631
|
+
const cost = estimateTokens(contentToText(rest[i].content));
|
|
3632
|
+
if (used + cost > remaining) break;
|
|
3633
|
+
used += cost;
|
|
3634
|
+
kept.unshift(rest[i]);
|
|
3635
|
+
}
|
|
3636
|
+
return [...system, ...kept, latest];
|
|
3637
|
+
}
|
|
3638
|
+
async function getNeonSql2() {
|
|
3639
|
+
const url = process.env.DATABASE_URL;
|
|
3640
|
+
if (!url) {
|
|
3641
|
+
throw new Error(
|
|
3642
|
+
"[brokr] DATABASE_URL is not set. Thread persistence requires a Postgres database.\nSet DATABASE_URL in your environment variables."
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
try {
|
|
3646
|
+
const { neon } = await import("@neondatabase/serverless");
|
|
3647
|
+
return neon(url);
|
|
3648
|
+
} catch {
|
|
3649
|
+
throw new Error(
|
|
3650
|
+
"[brokr] @neondatabase/serverless is not installed.\nRun: npm install @neondatabase/serverless"
|
|
3651
|
+
);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
var _sql2 = null;
|
|
3655
|
+
var _tablesEnsured = false;
|
|
3656
|
+
async function getDb2() {
|
|
3657
|
+
if (!_sql2) _sql2 = await getNeonSql2();
|
|
3658
|
+
return _sql2;
|
|
3659
|
+
}
|
|
3660
|
+
async function ensureTables(sql) {
|
|
3661
|
+
if (_tablesEnsured) return;
|
|
3662
|
+
await sql`CREATE SCHEMA IF NOT EXISTS brokr_ai`;
|
|
3663
|
+
const oldTablesExist = await sql`
|
|
3664
|
+
SELECT EXISTS (
|
|
3665
|
+
SELECT 1 FROM information_schema.tables
|
|
3666
|
+
WHERE table_schema = 'public' AND table_name = 'brokr_ai_threads'
|
|
3667
|
+
) AS exists
|
|
3668
|
+
`;
|
|
3669
|
+
if (oldTablesExist[0]?.exists) {
|
|
3670
|
+
await sql`ALTER TABLE public.brokr_ai_threads SET SCHEMA brokr_ai`.catch(() => {
|
|
3671
|
+
});
|
|
3672
|
+
await sql`ALTER TABLE public.brokr_ai_messages SET SCHEMA brokr_ai`.catch(() => {
|
|
3673
|
+
});
|
|
3674
|
+
await sql`ALTER TABLE brokr_ai.brokr_ai_threads RENAME TO threads`.catch(() => {
|
|
3675
|
+
});
|
|
3676
|
+
await sql`ALTER TABLE brokr_ai.brokr_ai_messages RENAME TO messages`.catch(() => {
|
|
3677
|
+
});
|
|
3678
|
+
}
|
|
3679
|
+
await sql`
|
|
3680
|
+
CREATE TABLE IF NOT EXISTS brokr_ai.threads (
|
|
3681
|
+
id TEXT PRIMARY KEY,
|
|
3682
|
+
surface TEXT NOT NULL DEFAULT 'default',
|
|
3683
|
+
subject TEXT,
|
|
3684
|
+
title TEXT NOT NULL DEFAULT 'New chat',
|
|
3685
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
3686
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
3687
|
+
)
|
|
3688
|
+
`;
|
|
3689
|
+
await sql`
|
|
3690
|
+
CREATE TABLE IF NOT EXISTS brokr_ai.messages (
|
|
3691
|
+
id TEXT PRIMARY KEY,
|
|
3692
|
+
thread_id TEXT NOT NULL REFERENCES brokr_ai.threads(id) ON DELETE CASCADE,
|
|
3693
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
|
|
3694
|
+
content TEXT NOT NULL,
|
|
3695
|
+
model TEXT,
|
|
3696
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
3697
|
+
)
|
|
3698
|
+
`;
|
|
3699
|
+
await sql`
|
|
3700
|
+
ALTER TABLE brokr_ai.messages ADD COLUMN IF NOT EXISTS model TEXT
|
|
3701
|
+
`.catch(() => {
|
|
3702
|
+
});
|
|
3703
|
+
await sql`
|
|
3704
|
+
ALTER TABLE brokr_ai.messages ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'complete'
|
|
3705
|
+
`.catch(() => {
|
|
3706
|
+
});
|
|
3707
|
+
await sql`
|
|
3708
|
+
ALTER TABLE brokr_ai.messages ADD COLUMN IF NOT EXISTS provider TEXT
|
|
3709
|
+
`.catch(() => {
|
|
3710
|
+
});
|
|
3711
|
+
await sql`
|
|
3712
|
+
CREATE INDEX IF NOT EXISTS brokr_ai_threads_surface_subject
|
|
3713
|
+
ON brokr_ai.threads(surface, subject, updated_at DESC)
|
|
3714
|
+
`;
|
|
3715
|
+
await sql`
|
|
3716
|
+
CREATE INDEX IF NOT EXISTS brokr_ai_messages_thread_id
|
|
3717
|
+
ON brokr_ai.messages(thread_id, created_at ASC)
|
|
3718
|
+
`;
|
|
3719
|
+
_tablesEnsured = true;
|
|
3720
|
+
}
|
|
3721
|
+
function parsePath(url) {
|
|
3722
|
+
const segments = new URL(url).pathname.split("/").filter(Boolean);
|
|
3723
|
+
const last = segments[segments.length - 1];
|
|
3724
|
+
const secondLast = segments[segments.length - 2];
|
|
3725
|
+
if (last === "chat") return { action: "chat" };
|
|
3726
|
+
if (last === "threads") return { action: "threads" };
|
|
3727
|
+
if (last === "ws-token") return { action: "ws-token" };
|
|
3728
|
+
if (last === "notifications") return { action: "notifications" };
|
|
3729
|
+
if (last === "read" && secondLast === "notifications") return { action: "notifications-read" };
|
|
3730
|
+
if (last === "messages" && secondLast) return { action: "messages", id: secondLast };
|
|
3731
|
+
if (last === "rename" && secondLast) return { action: "rename", id: secondLast };
|
|
3732
|
+
if (secondLast === "threads" && last) return { action: "thread", id: last };
|
|
3733
|
+
return { action: "unknown" };
|
|
3734
|
+
}
|
|
3735
|
+
function makeThreadTitle(content) {
|
|
3736
|
+
const cleaned = content.trim().replace(/\s+/g, " ");
|
|
3737
|
+
return cleaned.length > 48 ? `${cleaned.slice(0, 48).trimEnd()}\u2026` : cleaned || "New chat";
|
|
3738
|
+
}
|
|
3739
|
+
async function handleListThreads(request, opts) {
|
|
3740
|
+
if (!opts.persist) {
|
|
3741
|
+
return Response.json({ threads: [] });
|
|
3742
|
+
}
|
|
3743
|
+
const url = new URL(request.url);
|
|
3744
|
+
const surface = url.searchParams.get("surface") ?? "default";
|
|
3745
|
+
const subject = url.searchParams.get("subject");
|
|
3746
|
+
const sql = await getDb2();
|
|
3747
|
+
await ensureTables(sql);
|
|
3748
|
+
const rows = subject ? await sql`
|
|
3749
|
+
SELECT t.id, t.surface, t.subject, t.title, t.created_at, t.updated_at,
|
|
3750
|
+
(SELECT COUNT(*) FROM brokr_ai.messages WHERE thread_id = t.id) AS message_count
|
|
3751
|
+
FROM brokr_ai.threads t
|
|
3752
|
+
WHERE t.surface = ${surface} AND t.subject = ${subject}
|
|
3753
|
+
ORDER BY t.updated_at DESC
|
|
3754
|
+
LIMIT 50
|
|
3755
|
+
` : await sql`
|
|
3756
|
+
SELECT t.id, t.surface, t.subject, t.title, t.created_at, t.updated_at,
|
|
3757
|
+
(SELECT COUNT(*) FROM brokr_ai.messages WHERE thread_id = t.id) AS message_count
|
|
3758
|
+
FROM brokr_ai.threads t
|
|
3759
|
+
WHERE t.surface = ${surface}
|
|
3760
|
+
ORDER BY t.updated_at DESC
|
|
3761
|
+
LIMIT 50
|
|
3762
|
+
`;
|
|
3763
|
+
return Response.json({
|
|
3764
|
+
threads: rows.map((r) => ({
|
|
3765
|
+
id: r.id,
|
|
3766
|
+
surface: r.surface,
|
|
3767
|
+
subject: r.subject,
|
|
3768
|
+
title: r.title,
|
|
3769
|
+
createdAt: r.created_at,
|
|
3770
|
+
updatedAt: r.updated_at,
|
|
3771
|
+
messageCount: Number(r.message_count)
|
|
3772
|
+
}))
|
|
3773
|
+
});
|
|
3774
|
+
}
|
|
3775
|
+
async function handleGetMessages(request, threadId, opts) {
|
|
3776
|
+
if (!opts.persist) {
|
|
3777
|
+
return Response.json({ messages: [], hasMore: false });
|
|
3778
|
+
}
|
|
3779
|
+
const url = new URL(request.url);
|
|
3780
|
+
const limit = Math.min(Number(url.searchParams.get("limit")) || 30, 100);
|
|
3781
|
+
const offset = Math.max(Number(url.searchParams.get("offset")) || 0, 0);
|
|
3782
|
+
const sql = await getDb2();
|
|
3783
|
+
const countResult = await sql`
|
|
3784
|
+
SELECT COUNT(*) AS total FROM brokr_ai.messages WHERE thread_id = ${threadId}
|
|
3785
|
+
`;
|
|
3786
|
+
const total = Number(countResult[0]?.total ?? 0);
|
|
3787
|
+
const rows = await sql`
|
|
3788
|
+
SELECT id, role, content, model, status, provider FROM (
|
|
3789
|
+
SELECT id, role, content, model, status, provider, created_at
|
|
3790
|
+
FROM brokr_ai.messages
|
|
3791
|
+
WHERE thread_id = ${threadId}
|
|
3792
|
+
ORDER BY created_at DESC, id DESC
|
|
3793
|
+
LIMIT ${limit} OFFSET ${offset}
|
|
3794
|
+
) sub ORDER BY created_at ASC, id ASC
|
|
3795
|
+
`;
|
|
3796
|
+
return Response.json({
|
|
3797
|
+
messages: rows.map((r) => ({
|
|
3798
|
+
id: r.id,
|
|
3799
|
+
role: r.role,
|
|
3800
|
+
content: r.content,
|
|
3801
|
+
model: r.model ?? void 0,
|
|
3802
|
+
status: r.status ?? "complete",
|
|
3803
|
+
provider: r.provider ?? void 0
|
|
3804
|
+
})),
|
|
3805
|
+
hasMore: offset + limit < total
|
|
3806
|
+
});
|
|
3807
|
+
}
|
|
3808
|
+
async function handleRenameThread(request, threadId, opts) {
|
|
3809
|
+
if (!opts.persist) {
|
|
3810
|
+
return Response.json({ success: false }, { status: 400 });
|
|
3811
|
+
}
|
|
3812
|
+
const body = await request.json();
|
|
3813
|
+
const title = (body.title ?? "").trim();
|
|
3814
|
+
if (!title) {
|
|
3815
|
+
return Response.json({ error: "Title is required" }, { status: 400 });
|
|
3816
|
+
}
|
|
3817
|
+
const safeTitle = title.length > 100 ? `${title.slice(0, 100).trimEnd()}\u2026` : title;
|
|
3818
|
+
const sql = await getDb2();
|
|
3819
|
+
await ensureTables(sql);
|
|
3820
|
+
await sql`UPDATE brokr_ai.threads SET title = ${safeTitle}, updated_at = NOW() WHERE id = ${threadId}`;
|
|
3821
|
+
return Response.json({ success: true, title: safeTitle });
|
|
3822
|
+
}
|
|
3823
|
+
async function handleDeleteThread(_request, threadId, opts) {
|
|
3824
|
+
if (!opts.persist) {
|
|
3825
|
+
return Response.json({ success: true });
|
|
3826
|
+
}
|
|
3827
|
+
const sql = await getDb2();
|
|
3828
|
+
await sql`DELETE FROM brokr_ai.threads WHERE id = ${threadId}`;
|
|
3829
|
+
return Response.json({ success: true });
|
|
3830
|
+
}
|
|
3831
|
+
async function backgroundTask(promise) {
|
|
3832
|
+
try {
|
|
3833
|
+
const mod = await Promise.resolve().then(() => __toESM(require_functions()));
|
|
3834
|
+
if (typeof mod.waitUntil === "function") {
|
|
3835
|
+
mod.waitUntil(promise);
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
} catch {
|
|
3839
|
+
}
|
|
3840
|
+
promise.catch((err) => console.error("[brokr] Background task failed:", err));
|
|
3841
|
+
}
|
|
3842
|
+
async function handleChat(request, opts) {
|
|
3843
|
+
const body = await request.json();
|
|
3844
|
+
const gatewayUrl = process.env.BROKR_GATEWAY_URL ?? GATEWAY_URL;
|
|
3845
|
+
const token = process.env.BROKR_TOKEN;
|
|
3846
|
+
if (!token) {
|
|
3847
|
+
return Response.json(
|
|
3848
|
+
{ error: "BROKR_TOKEN is not configured. Run: brokr env pull --stack <name>" },
|
|
3849
|
+
{ status: 500 }
|
|
3850
|
+
);
|
|
3851
|
+
}
|
|
3852
|
+
const ai = new BrokrAIClient(token, gatewayUrl);
|
|
3853
|
+
const model = body.model;
|
|
3854
|
+
const systemPrompt = opts.systemPrompt ?? body.systemPrompt;
|
|
3855
|
+
const resolvedProvider = model ? resolveProviderByModel(model) : void 0;
|
|
3856
|
+
let messages;
|
|
3857
|
+
let threadId;
|
|
3858
|
+
let isNewThread = false;
|
|
3859
|
+
let userMsgId;
|
|
3860
|
+
let assistantMsgId;
|
|
3861
|
+
let threadTitle;
|
|
3862
|
+
if (opts.persist && body.content !== void 0) {
|
|
3863
|
+
threadId = body.thread_id ?? void 0;
|
|
3864
|
+
const sql = await getDb2();
|
|
3865
|
+
await ensureTables(sql);
|
|
3866
|
+
let history = [];
|
|
3867
|
+
if (threadId) {
|
|
3868
|
+
const [historyRows, titleRows] = await Promise.all([
|
|
3869
|
+
sql`
|
|
3870
|
+
SELECT role, content FROM brokr_ai.messages
|
|
3871
|
+
WHERE thread_id = ${threadId} AND status = 'complete'
|
|
3872
|
+
ORDER BY created_at ASC
|
|
3873
|
+
`,
|
|
3874
|
+
sql`SELECT title FROM brokr_ai.threads WHERE id = ${threadId} LIMIT 1`
|
|
3875
|
+
]);
|
|
3876
|
+
history = historyRows;
|
|
3877
|
+
threadTitle = titleRows[0]?.title || void 0;
|
|
3878
|
+
} else {
|
|
3879
|
+
threadId = crypto.randomUUID();
|
|
3880
|
+
isNewThread = true;
|
|
3881
|
+
}
|
|
3882
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3883
|
+
const assistantNow = new Date(Date.parse(now) + 1).toISOString();
|
|
3884
|
+
if (!threadTitle) {
|
|
3885
|
+
threadTitle = makeThreadTitle(body.content ?? "");
|
|
3886
|
+
}
|
|
3887
|
+
userMsgId = crypto.randomUUID();
|
|
3888
|
+
assistantMsgId = crypto.randomUUID();
|
|
3889
|
+
if (isNewThread) {
|
|
3890
|
+
await sql`
|
|
3891
|
+
INSERT INTO brokr_ai.threads (id, surface, subject, title, created_at, updated_at)
|
|
3892
|
+
VALUES (
|
|
3893
|
+
${threadId},
|
|
3894
|
+
${body.surface ?? "default"},
|
|
3895
|
+
${body.subject ?? null},
|
|
3896
|
+
${threadTitle},
|
|
3897
|
+
${now},
|
|
3898
|
+
${now}
|
|
3899
|
+
)
|
|
3900
|
+
`;
|
|
3901
|
+
} else {
|
|
3902
|
+
await sql`
|
|
3903
|
+
UPDATE brokr_ai.threads SET updated_at = ${now} WHERE id = ${threadId}
|
|
3904
|
+
`;
|
|
3905
|
+
}
|
|
3906
|
+
await sql`
|
|
3907
|
+
INSERT INTO brokr_ai.messages (id, thread_id, role, content, status, created_at)
|
|
3908
|
+
VALUES (${userMsgId}, ${threadId}, 'user', ${body.content ?? ""}, 'complete', ${now})
|
|
3909
|
+
`;
|
|
3910
|
+
await sql`
|
|
3911
|
+
INSERT INTO brokr_ai.messages (id, thread_id, role, content, model, provider, status, created_at)
|
|
3912
|
+
VALUES (${assistantMsgId}, ${threadId}, 'assistant', '', ${model ?? null}, ${resolvedProvider?.id ?? null}, 'pending', ${assistantNow})
|
|
3913
|
+
`;
|
|
3914
|
+
messages = trimToTokenBudget([
|
|
3915
|
+
...systemPrompt ? [{ role: "system", content: systemPrompt }] : [],
|
|
3916
|
+
...history.map((h) => ({ role: h.role, content: h.content })),
|
|
3917
|
+
{ role: "user", content: body.content }
|
|
3918
|
+
]);
|
|
3919
|
+
} else {
|
|
3920
|
+
const clientMessages = body.messages ?? [];
|
|
3921
|
+
messages = systemPrompt ? [{ role: "system", content: systemPrompt }, ...clientMessages] : clientMessages;
|
|
3922
|
+
}
|
|
3923
|
+
const chunks = [];
|
|
3924
|
+
let chunkIndex = 0;
|
|
3925
|
+
let generationDone = false;
|
|
3926
|
+
let generationError = null;
|
|
3927
|
+
let fullResponse = "";
|
|
3928
|
+
let metadataSent = false;
|
|
3929
|
+
let resolveChunk = null;
|
|
3930
|
+
function notifyChunk() {
|
|
3931
|
+
if (resolveChunk) {
|
|
3932
|
+
const fn = resolveChunk;
|
|
3933
|
+
resolveChunk = null;
|
|
3934
|
+
fn();
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
function waitForChunk() {
|
|
3938
|
+
if (generationDone || generationError || chunkIndex < chunks.length) {
|
|
3939
|
+
return Promise.resolve();
|
|
3940
|
+
}
|
|
3941
|
+
return new Promise((resolve) => {
|
|
3942
|
+
resolveChunk = resolve;
|
|
3943
|
+
});
|
|
3944
|
+
}
|
|
3945
|
+
const generationTask = (async () => {
|
|
3946
|
+
try {
|
|
3947
|
+
const aiStream = ai.stream(messages, { model });
|
|
3948
|
+
for await (const delta of aiStream) {
|
|
3949
|
+
fullResponse += delta;
|
|
3950
|
+
chunks.push(delta);
|
|
3951
|
+
notifyChunk();
|
|
3952
|
+
}
|
|
3953
|
+
if (opts.persist && threadId && assistantMsgId) {
|
|
3954
|
+
const sql = await getDb2();
|
|
3955
|
+
await sql`
|
|
3956
|
+
UPDATE brokr_ai.messages
|
|
3957
|
+
SET content = ${fullResponse}, status = 'complete'
|
|
3958
|
+
WHERE id = ${assistantMsgId}
|
|
3959
|
+
`;
|
|
3960
|
+
}
|
|
3961
|
+
if (opts.persist && body.userId && threadId && assistantMsgId) {
|
|
3962
|
+
const notifId = crypto.randomUUID();
|
|
3963
|
+
const preview = fullResponse.length > 120 ? `${fullResponse.slice(0, 120)}...` : fullResponse;
|
|
3964
|
+
const notifData = {
|
|
3965
|
+
type: "ai_response",
|
|
3966
|
+
threadId,
|
|
3967
|
+
assistantMessageId: assistantMsgId,
|
|
3968
|
+
model: model ?? null,
|
|
3969
|
+
provider: resolvedProvider?.id ?? null,
|
|
3970
|
+
providerLogo: resolvedProvider?.logo ?? null,
|
|
3971
|
+
surface: body.surface ?? "default",
|
|
3972
|
+
href: `/chat?thread=${threadId}`
|
|
3973
|
+
};
|
|
3974
|
+
await Promise.allSettled([
|
|
3975
|
+
persistNotification({
|
|
3976
|
+
id: notifId,
|
|
3977
|
+
userId: body.userId,
|
|
3978
|
+
type: "ai_response",
|
|
3979
|
+
title: threadTitle ?? "AI Response",
|
|
3980
|
+
message: preview,
|
|
3981
|
+
variant: "default",
|
|
3982
|
+
href: `/chat?thread=${threadId}`,
|
|
3983
|
+
imageUrl: resolvedProvider?.logo ?? void 0,
|
|
3984
|
+
imageAlt: resolvedProvider?.label ?? void 0,
|
|
3985
|
+
data: notifData
|
|
3986
|
+
}, { tableName: opts.notifications?.tableName }).catch((err) => {
|
|
3987
|
+
console.error("[brokr] Notification DB persist failed:", err);
|
|
3988
|
+
}),
|
|
3989
|
+
new BrokrNotificationsClient(token, gatewayUrl).send({
|
|
3990
|
+
userId: body.userId,
|
|
3991
|
+
title: threadTitle ?? "AI Response",
|
|
3992
|
+
message: preview,
|
|
3993
|
+
variant: "default",
|
|
3994
|
+
data: notifData,
|
|
3995
|
+
dedup: `ai_response:${assistantMsgId}`
|
|
3996
|
+
}).catch((err) => {
|
|
3997
|
+
console.error("[brokr] Notification push failed:", err);
|
|
3998
|
+
})
|
|
3999
|
+
]);
|
|
4000
|
+
}
|
|
4001
|
+
} catch (err) {
|
|
4002
|
+
console.error("[brokr] AI generation error:", err);
|
|
4003
|
+
generationError = "Something went wrong generating a reply.";
|
|
4004
|
+
if (opts.persist && threadId && assistantMsgId) {
|
|
4005
|
+
try {
|
|
4006
|
+
const sql = await getDb2();
|
|
4007
|
+
await sql`
|
|
4008
|
+
UPDATE brokr_ai.messages
|
|
4009
|
+
SET status = 'error', content = ''
|
|
4010
|
+
WHERE id = ${assistantMsgId}
|
|
4011
|
+
`;
|
|
4012
|
+
} catch (dbErr) {
|
|
4013
|
+
console.error("[brokr] Failed to mark message as error:", dbErr);
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
} finally {
|
|
4017
|
+
generationDone = true;
|
|
4018
|
+
notifyChunk();
|
|
4019
|
+
}
|
|
4020
|
+
})();
|
|
4021
|
+
void backgroundTask(generationTask);
|
|
4022
|
+
const encoder = new TextEncoder();
|
|
4023
|
+
const stream = new ReadableStream({
|
|
4024
|
+
async pull(controller) {
|
|
4025
|
+
const enqueue = (event) => {
|
|
4026
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}
|
|
4027
|
+
|
|
4028
|
+
`));
|
|
4029
|
+
};
|
|
4030
|
+
await waitForChunk();
|
|
4031
|
+
if (!metadataSent && opts.persist && threadId) {
|
|
4032
|
+
metadataSent = true;
|
|
4033
|
+
enqueue({
|
|
4034
|
+
type: "conversation",
|
|
4035
|
+
id: threadId,
|
|
4036
|
+
assistantMessageId: assistantMsgId,
|
|
4037
|
+
isNewThread
|
|
4038
|
+
});
|
|
4039
|
+
}
|
|
4040
|
+
while (chunkIndex < chunks.length) {
|
|
4041
|
+
enqueue({ type: "delta", delta: chunks[chunkIndex] });
|
|
4042
|
+
chunkIndex++;
|
|
4043
|
+
}
|
|
4044
|
+
if (generationDone || generationError) {
|
|
4045
|
+
if (generationError) {
|
|
4046
|
+
enqueue({ type: "error", message: generationError });
|
|
4047
|
+
} else {
|
|
4048
|
+
enqueue({ type: "done" });
|
|
4049
|
+
}
|
|
4050
|
+
controller.close();
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
});
|
|
4054
|
+
return new Response(stream, {
|
|
4055
|
+
headers: {
|
|
4056
|
+
"Content-Type": "text/event-stream",
|
|
4057
|
+
"Cache-Control": "no-cache",
|
|
4058
|
+
Connection: "keep-alive"
|
|
4059
|
+
}
|
|
4060
|
+
});
|
|
4061
|
+
}
|
|
4062
|
+
async function handleListNotifications(request, opts) {
|
|
4063
|
+
const url = new URL(request.url);
|
|
4064
|
+
const userId = url.searchParams.get("userId");
|
|
4065
|
+
if (!userId) {
|
|
4066
|
+
return Response.json({ error: "userId is required" }, { status: 400 });
|
|
4067
|
+
}
|
|
4068
|
+
const tableName = opts.notifications?.tableName;
|
|
4069
|
+
try {
|
|
4070
|
+
const notifications = await listPersistedNotifications(
|
|
4071
|
+
userId,
|
|
4072
|
+
{
|
|
4073
|
+
limit: Math.min(Number(url.searchParams.get("limit")) || 50, 100),
|
|
4074
|
+
unreadOnly: url.searchParams.get("unreadOnly") === "true",
|
|
4075
|
+
type: url.searchParams.get("type") ?? void 0
|
|
4076
|
+
},
|
|
4077
|
+
{ tableName }
|
|
4078
|
+
);
|
|
4079
|
+
return Response.json({ notifications });
|
|
4080
|
+
} catch (err) {
|
|
4081
|
+
console.error("[brokr] List notifications failed:", err);
|
|
4082
|
+
return Response.json({ notifications: [] });
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
async function handleMarkNotificationRead(request, opts) {
|
|
4086
|
+
const body = await request.json();
|
|
4087
|
+
if (!body.notificationId || !body.userId) {
|
|
4088
|
+
return Response.json({ error: "notificationId and userId are required" }, { status: 400 });
|
|
4089
|
+
}
|
|
4090
|
+
const tableName = opts.notifications?.tableName;
|
|
4091
|
+
try {
|
|
4092
|
+
await markNotificationRead(body.notificationId, body.userId, { tableName });
|
|
4093
|
+
return Response.json({ success: true });
|
|
4094
|
+
} catch (err) {
|
|
4095
|
+
console.error("[brokr] Mark notification read failed:", err);
|
|
4096
|
+
return Response.json({ error: "Failed to mark as read" }, { status: 500 });
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
function handleWsToken() {
|
|
4100
|
+
const token = process.env.BROKR_TOKEN;
|
|
4101
|
+
if (!token) {
|
|
4102
|
+
return Response.json(
|
|
4103
|
+
{ error: "BROKR_TOKEN not configured" },
|
|
4104
|
+
{ status: 500 }
|
|
4105
|
+
);
|
|
4106
|
+
}
|
|
4107
|
+
return Response.json({ token });
|
|
4108
|
+
}
|
|
4109
|
+
function createBrokrHandlers(options) {
|
|
4110
|
+
const opts = { persist: false, ...options };
|
|
4111
|
+
function withLogging(method, handler) {
|
|
4112
|
+
return async (request) => {
|
|
4113
|
+
const start = Date.now();
|
|
4114
|
+
const urlPath = new URL(request.url).pathname;
|
|
4115
|
+
try {
|
|
4116
|
+
const response = await handler(request);
|
|
4117
|
+
const dur = Date.now() - start;
|
|
4118
|
+
captureRequest(method, urlPath, response.status, dur);
|
|
4119
|
+
return response;
|
|
4120
|
+
} catch (err) {
|
|
4121
|
+
const dur = Date.now() - start;
|
|
4122
|
+
capture("error", `${method} ${urlPath} FAILED ${dur}ms \u2014 ${err instanceof Error ? err.message : "unknown"}`, urlPath);
|
|
4123
|
+
throw err;
|
|
4124
|
+
}
|
|
4125
|
+
};
|
|
4126
|
+
}
|
|
4127
|
+
return {
|
|
4128
|
+
GET: withLogging("GET", async (request) => {
|
|
4129
|
+
const path = parsePath(request.url);
|
|
4130
|
+
if (path.action === "threads") return handleListThreads(request, opts);
|
|
4131
|
+
if (path.action === "messages" && path.id) return handleGetMessages(request, path.id, opts);
|
|
4132
|
+
if (path.action === "ws-token") return handleWsToken();
|
|
4133
|
+
if (path.action === "notifications") return handleListNotifications(request, opts);
|
|
4134
|
+
return new Response("Not found", { status: 404 });
|
|
4135
|
+
}),
|
|
4136
|
+
POST: withLogging("POST", async (request) => {
|
|
4137
|
+
const path = parsePath(request.url);
|
|
4138
|
+
if (path.action === "chat") return handleChat(request, opts);
|
|
4139
|
+
if (path.action === "notifications-read") return handleMarkNotificationRead(request, opts);
|
|
4140
|
+
return new Response("Not found", { status: 404 });
|
|
4141
|
+
}),
|
|
4142
|
+
PATCH: withLogging("PATCH", async (request) => {
|
|
4143
|
+
const path = parsePath(request.url);
|
|
4144
|
+
if (path.action === "rename" && path.id) return handleRenameThread(request, path.id, opts);
|
|
4145
|
+
if (path.action === "thread" && path.id) return handleRenameThread(request, path.id, opts);
|
|
4146
|
+
return new Response("Not found", { status: 404 });
|
|
4147
|
+
}),
|
|
4148
|
+
DELETE: withLogging("DELETE", async (request) => {
|
|
4149
|
+
const path = parsePath(request.url);
|
|
4150
|
+
if (path.action === "thread" && path.id) return handleDeleteThread(request, path.id, opts);
|
|
4151
|
+
return new Response("Not found", { status: 404 });
|
|
4152
|
+
})
|
|
4153
|
+
};
|
|
4154
|
+
}
|
|
556
4155
|
// Annotate the CommonJS export names for ESM import in node:
|
|
557
4156
|
0 && (module.exports = {
|
|
558
4157
|
BrokrAIClient,
|
|
4158
|
+
BrokrAuthError,
|
|
559
4159
|
BrokrClient,
|
|
560
4160
|
BrokrEmailClient,
|
|
4161
|
+
BrokrEntitlementsClient,
|
|
561
4162
|
BrokrError,
|
|
4163
|
+
BrokrFilesClient,
|
|
4164
|
+
BrokrNetworkError,
|
|
4165
|
+
BrokrNotFoundError,
|
|
4166
|
+
BrokrPaymentsClient,
|
|
4167
|
+
BrokrRateLimitError,
|
|
562
4168
|
BrokrRuntime,
|
|
563
4169
|
BrokrStorageClient,
|
|
4170
|
+
BrokrTimeoutError,
|
|
4171
|
+
BrokrValidationError,
|
|
564
4172
|
authMiddleware,
|
|
4173
|
+
buildFallbackConversationTitle,
|
|
565
4174
|
create,
|
|
566
4175
|
createBrokr,
|
|
567
|
-
createBrokrClient
|
|
4176
|
+
createBrokrClient,
|
|
4177
|
+
createBrokrHandlers,
|
|
4178
|
+
generateConversationTitle,
|
|
4179
|
+
models,
|
|
4180
|
+
providers,
|
|
4181
|
+
resolveProviderByModel,
|
|
4182
|
+
sanitizeConversationTitle
|
|
568
4183
|
});
|