@sudobility/shapeshyft_service 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CLAUDE.md +35 -0
  2. package/README.md +17 -0
  3. package/dist/context.d.ts +62 -0
  4. package/dist/context.d.ts.map +1 -0
  5. package/dist/context.js +55 -0
  6. package/dist/context.js.map +1 -0
  7. package/dist/contracts.d.ts +110 -0
  8. package/dist/contracts.d.ts.map +1 -0
  9. package/dist/contracts.js +7 -0
  10. package/dist/contracts.js.map +1 -0
  11. package/dist/index.d.ts +17 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +16 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/lib/api-key.d.ts +22 -0
  16. package/dist/lib/api-key.d.ts.map +1 -0
  17. package/dist/lib/api-key.js +87 -0
  18. package/dist/lib/api-key.js.map +1 -0
  19. package/dist/lib/encryption.d.ts +22 -0
  20. package/dist/lib/encryption.d.ts.map +1 -0
  21. package/dist/lib/encryption.js +41 -0
  22. package/dist/lib/encryption.js.map +1 -0
  23. package/dist/lib/entity-api-key.d.ts +21 -0
  24. package/dist/lib/entity-api-key.d.ts.map +1 -0
  25. package/dist/lib/entity-api-key.js +54 -0
  26. package/dist/lib/entity-api-key.js.map +1 -0
  27. package/dist/lib/entity-helpers.d.ts +90 -0
  28. package/dist/lib/entity-helpers.d.ts.map +1 -0
  29. package/dist/lib/entity-helpers.js +136 -0
  30. package/dist/lib/entity-helpers.js.map +1 -0
  31. package/dist/lib/money.d.ts +7 -0
  32. package/dist/lib/money.d.ts.map +1 -0
  33. package/dist/lib/money.js +9 -0
  34. package/dist/lib/money.js.map +1 -0
  35. package/dist/lib/public-project.d.ts +14 -0
  36. package/dist/lib/public-project.d.ts.map +1 -0
  37. package/dist/lib/public-project.js +17 -0
  38. package/dist/lib/public-project.js.map +1 -0
  39. package/dist/lib/user-api-key-cache.d.ts +35 -0
  40. package/dist/lib/user-api-key-cache.d.ts.map +1 -0
  41. package/dist/lib/user-api-key-cache.js +99 -0
  42. package/dist/lib/user-api-key-cache.js.map +1 -0
  43. package/dist/lib/user-api-key.d.ts +46 -0
  44. package/dist/lib/user-api-key.d.ts.map +1 -0
  45. package/dist/lib/user-api-key.js +102 -0
  46. package/dist/lib/user-api-key.js.map +1 -0
  47. package/dist/middleware/firebaseAuth.d.ts +34 -0
  48. package/dist/middleware/firebaseAuth.d.ts.map +1 -0
  49. package/dist/middleware/firebaseAuth.js +133 -0
  50. package/dist/middleware/firebaseAuth.js.map +1 -0
  51. package/dist/middleware/rateLimit.d.ts +29 -0
  52. package/dist/middleware/rateLimit.d.ts.map +1 -0
  53. package/dist/middleware/rateLimit.js +85 -0
  54. package/dist/middleware/rateLimit.js.map +1 -0
  55. package/dist/middleware/subscription.d.ts +8 -0
  56. package/dist/middleware/subscription.d.ts.map +1 -0
  57. package/dist/middleware/subscription.js +19 -0
  58. package/dist/middleware/subscription.js.map +1 -0
  59. package/dist/routes/ai.d.ts +10 -0
  60. package/dist/routes/ai.d.ts.map +1 -0
  61. package/dist/routes/ai.js +698 -0
  62. package/dist/routes/ai.js.map +1 -0
  63. package/dist/routes/analytics.d.ts +9 -0
  64. package/dist/routes/analytics.d.ts.map +1 -0
  65. package/dist/routes/analytics.js +192 -0
  66. package/dist/routes/analytics.js.map +1 -0
  67. package/dist/routes/endpoints.d.ts +10 -0
  68. package/dist/routes/endpoints.d.ts.map +1 -0
  69. package/dist/routes/endpoints.js +255 -0
  70. package/dist/routes/endpoints.js.map +1 -0
  71. package/dist/routes/entities.d.ts +15 -0
  72. package/dist/routes/entities.d.ts.map +1 -0
  73. package/dist/routes/entities.js +332 -0
  74. package/dist/routes/entities.js.map +1 -0
  75. package/dist/routes/entity-api-keys.d.ts +17 -0
  76. package/dist/routes/entity-api-keys.d.ts.map +1 -0
  77. package/dist/routes/entity-api-keys.js +142 -0
  78. package/dist/routes/entity-api-keys.js.map +1 -0
  79. package/dist/routes/invitations.d.ts +15 -0
  80. package/dist/routes/invitations.d.ts.map +1 -0
  81. package/dist/routes/invitations.js +58 -0
  82. package/dist/routes/invitations.js.map +1 -0
  83. package/dist/routes/projects.d.ts +9 -0
  84. package/dist/routes/projects.d.ts.map +1 -0
  85. package/dist/routes/projects.js +241 -0
  86. package/dist/routes/projects.js.map +1 -0
  87. package/dist/routes/providers.d.ts +9 -0
  88. package/dist/routes/providers.d.ts.map +1 -0
  89. package/dist/routes/providers.js +89 -0
  90. package/dist/routes/providers.js.map +1 -0
  91. package/dist/routes/ratelimits.d.ts +9 -0
  92. package/dist/routes/ratelimits.d.ts.map +1 -0
  93. package/dist/routes/ratelimits.js +171 -0
  94. package/dist/routes/ratelimits.js.map +1 -0
  95. package/dist/routes/settings.d.ts +9 -0
  96. package/dist/routes/settings.d.ts.map +1 -0
  97. package/dist/routes/settings.js +138 -0
  98. package/dist/routes/settings.js.map +1 -0
  99. package/dist/routes/storage.d.ts +9 -0
  100. package/dist/routes/storage.d.ts.map +1 -0
  101. package/dist/routes/storage.js +180 -0
  102. package/dist/routes/storage.js.map +1 -0
  103. package/dist/routes/user-api-keys.d.ts +14 -0
  104. package/dist/routes/user-api-keys.d.ts.map +1 -0
  105. package/dist/routes/user-api-keys.js +237 -0
  106. package/dist/routes/user-api-keys.js.map +1 -0
  107. package/dist/routes/users.d.ts +7 -0
  108. package/dist/routes/users.d.ts.map +1 -0
  109. package/dist/routes/users.js +102 -0
  110. package/dist/routes/users.js.map +1 -0
  111. package/dist/schema/init.d.ts +12 -0
  112. package/dist/schema/init.d.ts.map +1 -0
  113. package/dist/schema/init.js +404 -0
  114. package/dist/schema/init.js.map +1 -0
  115. package/dist/schema/tables.d.ts +2110 -0
  116. package/dist/schema/tables.d.ts.map +1 -0
  117. package/dist/schema/tables.js +251 -0
  118. package/dist/schema/tables.js.map +1 -0
  119. package/dist/schemas/index.d.ts +671 -0
  120. package/dist/schemas/index.d.ts.map +1 -0
  121. package/dist/schemas/index.js +299 -0
  122. package/dist/schemas/index.js.map +1 -0
  123. package/dist/service.d.ts +41 -0
  124. package/dist/service.d.ts.map +1 -0
  125. package/dist/service.js +70 -0
  126. package/dist/service.js.map +1 -0
  127. package/package.json +85 -0
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @fileoverview User routes
3
+ */
4
+ import { Hono } from "hono";
5
+ import { NONE_ENTITLEMENT, } from "@sudobility/types";
6
+ import { successResponse, errorResponse, } from "@sudobility/shapeshyft_engine/types";
7
+ export function createUsersRouter(ctx) {
8
+ const getUserInfo = (...args) => ctx.auth.getUserInfo(...args);
9
+ const { getSubscriptionHelper, getTestMode } = ctx.subscription;
10
+ const usersRouter = new Hono();
11
+ /**
12
+ * GET /users/:userId
13
+ *
14
+ * Get user information including siteAdmin status.
15
+ * Requires the Firebase token to match the requested userId.
16
+ * Returns 403 if token doesn't match or user not found.
17
+ *
18
+ * Note: This route is under adminRoutes which applies firebaseAuthMiddleware,
19
+ * so c.get("userId") is already available.
20
+ */
21
+ /**
22
+ * GET /users/me
23
+ *
24
+ * Identify the authenticated caller. Works with either auth method, and is the
25
+ * only way an API-key client can learn its own Firebase UID — which the
26
+ * /users/:userId/* routes require. Registered before "/:userId" so the literal
27
+ * "me" is not captured as a user id.
28
+ */
29
+ usersRouter.get("/me", async (c) => {
30
+ const userId = c.get("userId");
31
+ const userEmail = c.get("userEmail");
32
+ // The display name is a nicety fetched from Firebase. Identity is already
33
+ // established by the middleware, so a Firebase outage (or an API-key caller on
34
+ // a deployment without Firebase reachable) must not fail this route.
35
+ let displayName = null;
36
+ try {
37
+ const userInfo = await getUserInfo(userId);
38
+ displayName = userInfo?.displayName ?? null;
39
+ }
40
+ catch (error) {
41
+ console.error("Could not load Firebase profile for /users/me:", error);
42
+ }
43
+ return c.json(successResponse({
44
+ firebase_uid: userId,
45
+ email: userEmail,
46
+ siteAdmin: c.get("siteAdmin") ?? false,
47
+ auth_method: c.get("authMethod") ?? "firebase",
48
+ display_name: displayName,
49
+ }));
50
+ });
51
+ usersRouter.get("/:userId", async (c) => {
52
+ const requestedUserId = c.req.param("userId");
53
+ const tokenUserId = c.get("userId");
54
+ // Verify the token belongs to the requested user
55
+ if (requestedUserId !== tokenUserId) {
56
+ return c.json(errorResponse("Token does not match requested user"), 403);
57
+ }
58
+ const userInfo = await getUserInfo(requestedUserId);
59
+ if (!userInfo) {
60
+ return c.json(errorResponse("User not found"), 403);
61
+ }
62
+ return c.json(successResponse(userInfo));
63
+ });
64
+ /**
65
+ * GET /users/:userId/subscriptions
66
+ *
67
+ * Get user subscription status (requires Firebase auth).
68
+ */
69
+ usersRouter.get("/:userId/subscriptions", async (c) => {
70
+ const requestedUserId = c.req.param("userId");
71
+ const tokenUserId = c.get("userId");
72
+ if (requestedUserId !== tokenUserId) {
73
+ return c.json(errorResponse("You can only access your own subscription"), 403);
74
+ }
75
+ const subHelper = getSubscriptionHelper();
76
+ if (!subHelper) {
77
+ return c.json(errorResponse("Subscription service not configured"), 500);
78
+ }
79
+ try {
80
+ const testMode = getTestMode(c);
81
+ const subscriptionInfo = await subHelper.getSubscriptionInfo(requestedUserId, testMode);
82
+ const subscriptionResult = {
83
+ hasSubscription: subscriptionInfo.entitlements.length > 0 &&
84
+ !subscriptionInfo.entitlements.includes(NONE_ENTITLEMENT),
85
+ entitlements: subscriptionInfo.entitlements,
86
+ subscriptionStartedAt: subscriptionInfo.subscriptionStartedAt?.toISOString() ?? null,
87
+ platform: subscriptionInfo.platform,
88
+ productIdentifier: subscriptionInfo.productIdentifier,
89
+ expiresDate: subscriptionInfo.expiresDate?.toISOString() ?? null,
90
+ willRenew: subscriptionInfo.willRenew,
91
+ managementUrl: subscriptionInfo.managementUrl,
92
+ };
93
+ return c.json(successResponse(subscriptionResult));
94
+ }
95
+ catch (error) {
96
+ console.error("Error fetching subscription:", error);
97
+ return c.json(errorResponse("Failed to fetch subscription status"), 500);
98
+ }
99
+ });
100
+ return usersRouter;
101
+ }
102
+ //# sourceMappingURL=users.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/routes/users.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @fileoverview Idempotent DDL for the shared tables
3
+ * @description Runs on every server boot. Every statement is IF NOT EXISTS or
4
+ * guarded, so re-running is safe. Identifiers cannot be bound as parameters,
5
+ * so schema and prefix are validated and interpolated into `client.unsafe`.
6
+ */
7
+ import type { Sql } from "postgres";
8
+ export declare function initServiceTables(client: Sql, opts: {
9
+ schemaName: string;
10
+ indexPrefix: string;
11
+ }): Promise<void>;
12
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/schema/init.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAapC,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,GAAG,EACX,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAChD,OAAO,CAAC,IAAI,CAAC,CAgbf"}
@@ -0,0 +1,404 @@
1
+ /**
2
+ * @fileoverview Idempotent DDL for the shared tables
3
+ * @description Runs on every server boot. Every statement is IF NOT EXISTS or
4
+ * guarded, so re-running is safe. Identifiers cannot be bound as parameters,
5
+ * so schema and prefix are validated and interpolated into `client.unsafe`.
6
+ */
7
+ import { initRateLimitTable } from "@sudobility/ratelimit_service";
8
+ import { runEntityMigration } from "@sudobility/entity_service";
9
+ const IDENTIFIER = /^[a-z_][a-z0-9_]*$/;
10
+ function identifier(value, what) {
11
+ if (!IDENTIFIER.test(value)) {
12
+ throw new Error(`Invalid ${what} "${value}": must match ${IDENTIFIER}`);
13
+ }
14
+ return value;
15
+ }
16
+ export async function initServiceTables(client, opts) {
17
+ const s = identifier(opts.schemaName, "schemaName");
18
+ const p = identifier(opts.indexPrefix, "indexPrefix");
19
+ // Create schema if it doesn't exist
20
+ await client.unsafe(`CREATE SCHEMA IF NOT EXISTS ${s}`);
21
+ // Create enums (if they don't exist)
22
+ await client.unsafe(`
23
+ DO $$ BEGIN
24
+ CREATE TYPE ${s}.llm_provider AS ENUM ('openai', 'anthropic', 'gemini', 'mistral', 'cohere', 'groq', 'xai', 'deepseek', 'perplexity', 'lm_studio');
25
+ EXCEPTION
26
+ WHEN duplicate_object THEN null;
27
+ END $$;
28
+ `);
29
+ // Add new enum values if they don't exist (migration for existing databases)
30
+ await client.unsafe(`
31
+ DO $$
32
+ BEGIN
33
+ -- Add mistral if not exists
34
+ IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'mistral' AND enumtypid = '${s}.llm_provider'::regtype) THEN
35
+ ALTER TYPE ${s}.llm_provider ADD VALUE 'mistral';
36
+ END IF;
37
+ -- Add cohere if not exists
38
+ IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'cohere' AND enumtypid = '${s}.llm_provider'::regtype) THEN
39
+ ALTER TYPE ${s}.llm_provider ADD VALUE 'cohere';
40
+ END IF;
41
+ -- Add groq if not exists
42
+ IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'groq' AND enumtypid = '${s}.llm_provider'::regtype) THEN
43
+ ALTER TYPE ${s}.llm_provider ADD VALUE 'groq';
44
+ END IF;
45
+ -- Add xai if not exists
46
+ IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'xai' AND enumtypid = '${s}.llm_provider'::regtype) THEN
47
+ ALTER TYPE ${s}.llm_provider ADD VALUE 'xai';
48
+ END IF;
49
+ -- Add deepseek if not exists
50
+ IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'deepseek' AND enumtypid = '${s}.llm_provider'::regtype) THEN
51
+ ALTER TYPE ${s}.llm_provider ADD VALUE 'deepseek';
52
+ END IF;
53
+ -- Add perplexity if not exists
54
+ IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'perplexity' AND enumtypid = '${s}.llm_provider'::regtype) THEN
55
+ ALTER TYPE ${s}.llm_provider ADD VALUE 'perplexity';
56
+ END IF;
57
+ -- Add lm_studio if not exists
58
+ IF NOT EXISTS (SELECT 1 FROM pg_enum WHERE enumlabel = 'lm_studio' AND enumtypid = '${s}.llm_provider'::regtype) THEN
59
+ ALTER TYPE ${s}.llm_provider ADD VALUE 'lm_studio';
60
+ END IF;
61
+ END $$;
62
+ `);
63
+ await client.unsafe(`
64
+ DO $$ BEGIN
65
+ CREATE TYPE ${s}.http_method AS ENUM ('GET', 'POST');
66
+ EXCEPTION
67
+ WHEN duplicate_object THEN null;
68
+ END $$;
69
+ `);
70
+ // =============================================================================
71
+ // Step 1: Create users and user_settings tables
72
+ // firebase_uid is now the primary key
73
+ // =============================================================================
74
+ await client.unsafe(`
75
+ CREATE TABLE IF NOT EXISTS ${s}.users (
76
+ firebase_uid VARCHAR(128) PRIMARY KEY,
77
+ email VARCHAR(255),
78
+ display_name VARCHAR(255),
79
+ created_at TIMESTAMP DEFAULT NOW(),
80
+ updated_at TIMESTAMP DEFAULT NOW()
81
+ )
82
+ `);
83
+ await client.unsafe(`
84
+ CREATE TABLE IF NOT EXISTS ${s}.user_settings (
85
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
86
+ firebase_uid VARCHAR(128) NOT NULL UNIQUE REFERENCES ${s}.users(firebase_uid) ON DELETE CASCADE,
87
+ organization_name VARCHAR(255),
88
+ organization_path VARCHAR(255) NOT NULL UNIQUE,
89
+ created_at TIMESTAMP DEFAULT NOW(),
90
+ updated_at TIMESTAMP DEFAULT NOW()
91
+ )
92
+ `);
93
+ // =============================================================================
94
+ // Step 2: Run entity migration (creates entities, entity_members tables)
95
+ // This must happen BEFORE tables that reference entities.id
96
+ // =============================================================================
97
+ await runEntityMigration({
98
+ client: client, // postgres type versions can differ under bun link
99
+ schemaName: s,
100
+ indexPrefix: p,
101
+ migrateProjects: false, // Tables are created fresh with entity_id
102
+ migrateUsers: false, // Personal entities created on-demand via EntityHelper
103
+ });
104
+ // =============================================================================
105
+ // Step 2b: Create user_api_keys table (references users.firebase_uid)
106
+ // Personal API keys used as an alternative to a Firebase ID token.
107
+ // =============================================================================
108
+ await client.unsafe(`
109
+ CREATE TABLE IF NOT EXISTS ${s}.user_api_keys (
110
+ uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
111
+ firebase_uid VARCHAR(128) NOT NULL REFERENCES ${s}.users(firebase_uid) ON DELETE CASCADE,
112
+ key_name VARCHAR(255) NOT NULL,
113
+ key_hash VARCHAR(64) NOT NULL UNIQUE,
114
+ key_prefix VARCHAR(20) NOT NULL,
115
+ encrypted_key TEXT NOT NULL,
116
+ encryption_iv VARCHAR(32) NOT NULL,
117
+ is_active BOOLEAN NOT NULL DEFAULT true,
118
+ last_used_at TIMESTAMP,
119
+ created_at TIMESTAMP DEFAULT NOW(),
120
+ updated_at TIMESTAMP DEFAULT NOW()
121
+ )
122
+ `);
123
+ await client.unsafe(`
124
+ CREATE INDEX IF NOT EXISTS ${p}_user_api_keys_user_idx
125
+ ON ${s}.user_api_keys(firebase_uid)
126
+ `);
127
+ await client.unsafe(`
128
+ CREATE UNIQUE INDEX IF NOT EXISTS ${p}_user_api_keys_hash_idx
129
+ ON ${s}.user_api_keys(key_hash)
130
+ `);
131
+ // =============================================================================
132
+ // Step 2c: Create entity_api_keys table (references entities.id)
133
+ // Entity-scoped keys used by CI, scripts, and MCP clients.
134
+ // =============================================================================
135
+ await client.unsafe(`
136
+ CREATE TABLE IF NOT EXISTS ${s}.entity_api_keys (
137
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
138
+ entity_id UUID NOT NULL REFERENCES ${s}.entities(id) ON DELETE CASCADE,
139
+ key_name VARCHAR(255) NOT NULL,
140
+ key_hash VARCHAR(64) NOT NULL UNIQUE,
141
+ key_prefix VARCHAR(20) NOT NULL,
142
+ created_by_user_id VARCHAR(128) NOT NULL,
143
+ is_active BOOLEAN NOT NULL DEFAULT true,
144
+ last_used_at TIMESTAMPTZ,
145
+ created_at TIMESTAMPTZ DEFAULT NOW(),
146
+ updated_at TIMESTAMPTZ DEFAULT NOW()
147
+ )
148
+ `);
149
+ await client.unsafe(`
150
+ CREATE UNIQUE INDEX IF NOT EXISTS ${p}_entity_api_keys_hash_idx
151
+ ON ${s}.entity_api_keys(key_hash)
152
+ `);
153
+ await client.unsafe(`
154
+ CREATE INDEX IF NOT EXISTS ${p}_entity_api_keys_entity_idx
155
+ ON ${s}.entity_api_keys(entity_id)
156
+ `);
157
+ await client.unsafe(`
158
+ CREATE INDEX IF NOT EXISTS ${p}_entity_api_keys_active_idx
159
+ ON ${s}.entity_api_keys(is_active)
160
+ `);
161
+ // =============================================================================
162
+ // Step 4: Create projects table (references entities.id)
163
+ // =============================================================================
164
+ await client.unsafe(`
165
+ CREATE TABLE IF NOT EXISTS ${s}.projects (
166
+ uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
167
+ entity_id UUID NOT NULL REFERENCES ${s}.entities(id) ON DELETE CASCADE,
168
+ project_name VARCHAR(255) NOT NULL,
169
+ display_name VARCHAR(255) NOT NULL,
170
+ description TEXT,
171
+ is_active BOOLEAN DEFAULT true,
172
+ encrypted_api_key TEXT,
173
+ api_key_iv VARCHAR(32),
174
+ api_key_prefix VARCHAR(20),
175
+ api_key_created_at TIMESTAMP,
176
+ created_at TIMESTAMP DEFAULT NOW(),
177
+ updated_at TIMESTAMP DEFAULT NOW()
178
+ )
179
+ `);
180
+ // Create unique index for project_name per entity
181
+ await client.unsafe(`
182
+ CREATE UNIQUE INDEX IF NOT EXISTS unique_project_per_entity
183
+ ON ${s}.projects(entity_id, project_name)
184
+ `);
185
+ // Create index for entity_id lookups
186
+ await client.unsafe(`
187
+ CREATE INDEX IF NOT EXISTS ${p}_projects_entity_idx
188
+ ON ${s}.projects(entity_id)
189
+ `);
190
+ // =============================================================================
191
+ // Step 5: Create endpoints table (references projects.uuid)
192
+ // =============================================================================
193
+ await client.unsafe(`
194
+ CREATE TABLE IF NOT EXISTS ${s}.endpoints (
195
+ uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
196
+ project_id UUID NOT NULL REFERENCES ${s}.projects(uuid) ON DELETE CASCADE,
197
+ endpoint_name VARCHAR(255) NOT NULL,
198
+ display_name VARCHAR(255) NOT NULL,
199
+ http_method ${s}.http_method NOT NULL DEFAULT 'POST',
200
+ llm_key_id UUID,
201
+ provider ${s}.llm_provider,
202
+ model VARCHAR(255),
203
+ input_schema JSONB,
204
+ output_schema JSONB,
205
+ instructions TEXT,
206
+ context TEXT,
207
+ is_active BOOLEAN DEFAULT true,
208
+ ip_allowlist JSONB,
209
+ call_count INTEGER NOT NULL DEFAULT 0,
210
+ created_at TIMESTAMP DEFAULT NOW(),
211
+ updated_at TIMESTAMP DEFAULT NOW(),
212
+ UNIQUE(project_id, endpoint_name)
213
+ )
214
+ `);
215
+ // Add model column if it doesn't exist (migration for existing tables)
216
+ await client.unsafe(`
217
+ DO $$
218
+ BEGIN
219
+ IF NOT EXISTS (
220
+ SELECT 1 FROM information_schema.columns
221
+ WHERE table_schema = '${s}'
222
+ AND table_name = 'endpoints'
223
+ AND column_name = 'model'
224
+ ) THEN
225
+ ALTER TABLE ${s}.endpoints ADD COLUMN model VARCHAR(255);
226
+ END IF;
227
+ END $$;
228
+ `);
229
+ // Add call_count column if it doesn't exist (migration for existing tables).
230
+ // Existing endpoints start at 0 rather than backfilling from usage_analytics:
231
+ // the counter is a forward-looking tally, and a backfill would silently
232
+ // disagree with any analytics rows that have since been pruned.
233
+ await client.unsafe(`
234
+ DO $$
235
+ BEGIN
236
+ IF NOT EXISTS (
237
+ SELECT 1 FROM information_schema.columns
238
+ WHERE table_schema = '${s}'
239
+ AND table_name = 'endpoints'
240
+ AND column_name = 'call_count'
241
+ ) THEN
242
+ ALTER TABLE ${s}.endpoints
243
+ ADD COLUMN call_count INTEGER NOT NULL DEFAULT 0;
244
+ END IF;
245
+ END $$;
246
+ `);
247
+ // Create index for project_id lookups
248
+ await client.unsafe(`
249
+ CREATE INDEX IF NOT EXISTS ${p}_endpoints_project_idx
250
+ ON ${s}.endpoints(project_id)
251
+ `);
252
+ // =============================================================================
253
+ // Step 6: Create usage_analytics table (references endpoints.uuid)
254
+ // =============================================================================
255
+ await client.unsafe(`
256
+ CREATE TABLE IF NOT EXISTS ${s}.usage_analytics (
257
+ uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
258
+ endpoint_id UUID NOT NULL REFERENCES ${s}.endpoints(uuid) ON DELETE CASCADE,
259
+ timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
260
+ success BOOLEAN NOT NULL,
261
+ error_message TEXT,
262
+ tokens_input INTEGER,
263
+ tokens_output INTEGER,
264
+ latency_ms INTEGER,
265
+ estimated_cost_cents INTEGER,
266
+ request_metadata JSONB
267
+ )
268
+ `);
269
+ // Create indexes for analytics queries
270
+ await client.unsafe(`
271
+ CREATE INDEX IF NOT EXISTS idx_usage_endpoint_timestamp
272
+ ON ${s}.usage_analytics(endpoint_id, timestamp DESC)
273
+ `);
274
+ // =============================================================================
275
+ // Step 7: Rate limit counters table
276
+ // =============================================================================
277
+ await initRateLimitTable(client, s, p);
278
+ // =============================================================================
279
+ // Step 8: Entity Storage Configs table (for generated media uploads)
280
+ // =============================================================================
281
+ await client.unsafe(`
282
+ CREATE TABLE IF NOT EXISTS ${s}.entity_storage_configs (
283
+ uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
284
+ entity_id UUID NOT NULL UNIQUE REFERENCES ${s}.entities(id) ON DELETE CASCADE,
285
+ provider VARCHAR(20) NOT NULL,
286
+ bucket VARCHAR(255) NOT NULL,
287
+ path_prefix VARCHAR(500),
288
+ encrypted_credentials TEXT NOT NULL,
289
+ encryption_iv VARCHAR(32) NOT NULL,
290
+ created_at TIMESTAMP DEFAULT NOW(),
291
+ updated_at TIMESTAMP DEFAULT NOW(),
292
+ created_by VARCHAR(128) NOT NULL
293
+ )
294
+ `);
295
+ // Create index for entity_id lookups
296
+ await client.unsafe(`
297
+ CREATE INDEX IF NOT EXISTS ${p}_entity_storage_configs_entity_idx
298
+ ON ${s}.entity_storage_configs(entity_id)
299
+ `);
300
+ // =============================================================================
301
+ // Step 9: Add multimodal columns to endpoints table (migration for existing DBs)
302
+ // =============================================================================
303
+ // Add expects_media_output column
304
+ await client.unsafe(`
305
+ DO $$
306
+ BEGIN
307
+ IF NOT EXISTS (
308
+ SELECT 1 FROM information_schema.columns
309
+ WHERE table_schema = '${s}'
310
+ AND table_name = 'endpoints'
311
+ AND column_name = 'expects_media_output'
312
+ ) THEN
313
+ ALTER TABLE ${s}.endpoints ADD COLUMN expects_media_output JSONB;
314
+ END IF;
315
+ END $$;
316
+ `);
317
+ // Add output_media_format column
318
+ await client.unsafe(`
319
+ DO $$
320
+ BEGIN
321
+ IF NOT EXISTS (
322
+ SELECT 1 FROM information_schema.columns
323
+ WHERE table_schema = '${s}'
324
+ AND table_name = 'endpoints'
325
+ AND column_name = 'output_media_format'
326
+ ) THEN
327
+ ALTER TABLE ${s}.endpoints ADD COLUMN output_media_format VARCHAR(20);
328
+ END IF;
329
+ END $$;
330
+ `);
331
+ // Add web_search column
332
+ await client.unsafe(`
333
+ DO $$
334
+ BEGIN
335
+ IF NOT EXISTS (
336
+ SELECT 1 FROM information_schema.columns
337
+ WHERE table_schema = '${s}'
338
+ AND table_name = 'endpoints'
339
+ AND column_name = 'web_search'
340
+ ) THEN
341
+ ALTER TABLE ${s}.endpoints ADD COLUMN web_search BOOLEAN DEFAULT false;
342
+ END IF;
343
+ END $$;
344
+ `);
345
+ // Add temperature column
346
+ await client.unsafe(`
347
+ DO $$
348
+ BEGIN
349
+ IF NOT EXISTS (
350
+ SELECT 1 FROM information_schema.columns
351
+ WHERE table_schema = '${s}'
352
+ AND table_name = 'endpoints'
353
+ AND column_name = 'temperature'
354
+ ) THEN
355
+ ALTER TABLE ${s}.endpoints ADD COLUMN temperature REAL;
356
+ END IF;
357
+ END $$;
358
+ `);
359
+ // Add transcription_extraction_model column
360
+ await client.unsafe(`
361
+ DO $$
362
+ BEGIN
363
+ IF NOT EXISTS (
364
+ SELECT 1 FROM information_schema.columns
365
+ WHERE table_schema = '${s}'
366
+ AND table_name = 'endpoints'
367
+ AND column_name = 'transcription_extraction_model'
368
+ ) THEN
369
+ ALTER TABLE ${s}.endpoints ADD COLUMN transcription_extraction_model VARCHAR(255);
370
+ END IF;
371
+ END $$;
372
+ `);
373
+ // Add max_output_tokens column.
374
+ // Intentionally nullable with NO default and NO backfill: NULL means "no
375
+ // runaway protection", and existing endpoints must keep behaving exactly as
376
+ // they did. Adding a default here would silently cap live endpoints whose
377
+ // callers depend on longer answers.
378
+ await client.unsafe(`
379
+ DO $$
380
+ BEGIN
381
+ IF NOT EXISTS (
382
+ SELECT 1 FROM information_schema.columns
383
+ WHERE table_schema = '${s}'
384
+ AND table_name = 'endpoints'
385
+ AND column_name = 'max_output_tokens'
386
+ ) THEN
387
+ ALTER TABLE ${s}.endpoints ADD COLUMN max_output_tokens INTEGER;
388
+ END IF;
389
+ END $$;
390
+ `);
391
+ // =============================================================================
392
+ // Step 10: Provider binding (service extraction)
393
+ // provider: which LLM the endpoint calls, written by bindEndpoint.
394
+ // llm_key_id: relaxed to nullable; products without per-entity keys leave it
395
+ // null. Apps that keep a key table add their own FK and backfill provider.
396
+ // =============================================================================
397
+ await client.unsafe(`
398
+ ALTER TABLE ${s}.endpoints ADD COLUMN IF NOT EXISTS provider ${s}.llm_provider
399
+ `);
400
+ await client.unsafe(`
401
+ ALTER TABLE ${s}.endpoints ALTER COLUMN llm_key_id DROP NOT NULL
402
+ `);
403
+ }
404
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/schema/init.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE,MAAM,UAAU,GAAG,oBAAoB,CAAC;AAExC,SAAS,UAAU,CAAC,KAAa,EAAE,IAAY;IAC7C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,KAAK,iBAAiB,UAAU,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAW,EACX,IAAiD;IAEjD,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAEtD,oCAAoC;IACpC,MAAM,MAAM,CAAC,MAAM,CAAC,+BAA+B,CAAC,EAAE,CAAC,CAAC;IAExD,qCAAqC;IACrC,MAAM,MAAM,CAAC,MAAM,CAAC;;oBAEF,CAAC;;;;GAIlB,CAAC,CAAC;IAEH,6EAA6E;IAC7E,MAAM,MAAM,CAAC,MAAM,CAAC;;;;0FAIoE,CAAC;qBACtE,CAAC;;;yFAGmE,CAAC;qBACrE,CAAC;;;uFAGiE,CAAC;qBACnE,CAAC;;;sFAGgE,CAAC;qBAClE,CAAC;;;2FAGqE,CAAC;qBACvE,CAAC;;;6FAGuE,CAAC;qBACzE,CAAC;;;4FAGsE,CAAC;qBACxE,CAAC;;;GAGnB,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,MAAM,CAAC;;oBAEF,CAAC;;;;GAIlB,CAAC,CAAC;IAEH,gFAAgF;IAChF,gDAAgD;IAChD,sCAAsC;IACtC,gFAAgF;IAEhF,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;;;;;;;GAO/B,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;;6DAE2B,CAAC;;;;;;GAM3D,CAAC,CAAC;IAEH,gFAAgF;IAChF,yEAAyE;IACzE,4DAA4D;IAC5D,gFAAgF;IAEhF,MAAM,kBAAkB,CAAC;QACvB,MAAM,EAAE,MAAa,EAAE,mDAAmD;QAC1E,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,CAAC;QACd,eAAe,EAAE,KAAK,EAAE,0CAA0C;QAClE,YAAY,EAAE,KAAK,EAAE,uDAAuD;KAC7E,CAAC,CAAC;IAEH,gFAAgF;IAChF,sEAAsE;IACtE,mEAAmE;IACnE,gFAAgF;IAEhF,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;;sDAEoB,CAAC;;;;;;;;;;;GAWpD,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;WACvB,CAAC;GACT,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,MAAM,CAAC;wCACkB,CAAC;WAC9B,CAAC;GACT,CAAC,CAAC;IAEH,gFAAgF;IAChF,iEAAiE;IACjE,2DAA2D;IAC3D,gFAAgF;IAEhF,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;;2CAES,CAAC;;;;;;;;;;GAUzC,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,MAAM,CAAC;wCACkB,CAAC;WAC9B,CAAC;GACT,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;WACvB,CAAC;GACT,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;WACvB,CAAC;GACT,CAAC,CAAC;IAEH,gFAAgF;IAChF,yDAAyD;IACzD,gFAAgF;IAEhF,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;;2CAES,CAAC;;;;;;;;;;;;GAYzC,CAAC,CAAC;IAEH,kDAAkD;IAClD,MAAM,MAAM,CAAC,MAAM,CAAC;;SAEb,CAAC;GACP,CAAC,CAAC;IAEH,qCAAqC;IACrC,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;SACzB,CAAC;GACP,CAAC,CAAC;IAEH,gFAAgF;IAChF,4DAA4D;IAC5D,gFAAgF;IAEhF,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;;4CAEU,CAAC;;;oBAGzB,CAAC;;iBAEJ,CAAC;;;;;;;;;;;;;GAaf,CAAC,CAAC;IAEH,uEAAuE;IACvE,MAAM,MAAM,CAAC,MAAM,CAAC;;;;;gCAKU,CAAC;;;;sBAIX,CAAC;;;GAGpB,CAAC,CAAC;IAEH,6EAA6E;IAC7E,8EAA8E;IAC9E,wEAAwE;IACxE,gEAAgE;IAChE,MAAM,MAAM,CAAC,MAAM,CAAC;;;;;gCAKU,CAAC;;;;sBAIX,CAAC;;;;GAIpB,CAAC,CAAC;IAEH,sCAAsC;IACtC,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;SACzB,CAAC;GACP,CAAC,CAAC;IAEH,gFAAgF;IAChF,mEAAmE;IACnE,gFAAgF;IAEhF,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;;6CAEW,CAAC;;;;;;;;;;GAU3C,CAAC,CAAC;IAEH,uCAAuC;IACvC,MAAM,MAAM,CAAC,MAAM,CAAC;;SAEb,CAAC;GACP,CAAC,CAAC;IAEH,gFAAgF;IAChF,oCAAoC;IACpC,gFAAgF;IAEhF,MAAM,kBAAkB,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAEvC,gFAAgF;IAChF,qEAAqE;IACrE,gFAAgF;IAEhF,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;;kDAEgB,CAAC;;;;;;;;;;GAUhD,CAAC,CAAC;IAEH,qCAAqC;IACrC,MAAM,MAAM,CAAC,MAAM,CAAC;iCACW,CAAC;SACzB,CAAC;GACP,CAAC,CAAC;IAEH,gFAAgF;IAChF,iFAAiF;IACjF,gFAAgF;IAEhF,kCAAkC;IAClC,MAAM,MAAM,CAAC,MAAM,CAAC;;;;;gCAKU,CAAC;;;;sBAIX,CAAC;;;GAGpB,CAAC,CAAC;IAEH,iCAAiC;IACjC,MAAM,MAAM,CAAC,MAAM,CAAC;;;;;gCAKU,CAAC;;;;sBAIX,CAAC;;;GAGpB,CAAC,CAAC;IAEH,wBAAwB;IACxB,MAAM,MAAM,CAAC,MAAM,CAAC;;;;;gCAKU,CAAC;;;;sBAIX,CAAC;;;GAGpB,CAAC,CAAC;IAEH,yBAAyB;IACzB,MAAM,MAAM,CAAC,MAAM,CAAC;;;;;gCAKU,CAAC;;;;sBAIX,CAAC;;;GAGpB,CAAC,CAAC;IAEH,4CAA4C;IAC5C,MAAM,MAAM,CAAC,MAAM,CAAC;;;;;gCAKU,CAAC;;;;sBAIX,CAAC;;;GAGpB,CAAC,CAAC;IAEH,gCAAgC;IAChC,yEAAyE;IACzE,4EAA4E;IAC5E,0EAA0E;IAC1E,oCAAoC;IACpC,MAAM,MAAM,CAAC,MAAM,CAAC;;;;;gCAKU,CAAC;;;;sBAIX,CAAC;;;GAGpB,CAAC,CAAC;IAEH,gFAAgF;IAChF,iDAAiD;IACjD,mEAAmE;IACnE,6EAA6E;IAC7E,2EAA2E;IAC3E,gFAAgF;IAEhF,MAAM,MAAM,CAAC,MAAM,CAAC;kBACJ,CAAC,gDAAgD,CAAC;GACjE,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,MAAM,CAAC;kBACJ,CAAC;GAChB,CAAC,CAAC;AACL,CAAC"}