@edge-base/server 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin-build/.gitkeep +0 -0
- package/admin-build/_app/env.js +1 -0
- package/admin-build/_app/immutable/assets/0.Bm6cF078.css +1 -0
- package/admin-build/_app/immutable/assets/1.BfW3pUNa.css +1 -0
- package/admin-build/_app/immutable/assets/11.CVmQOewb.css +1 -0
- package/admin-build/_app/immutable/assets/12.B1EhbRZT.css +1 -0
- package/admin-build/_app/immutable/assets/13.BvwYeuwE.css +1 -0
- package/admin-build/_app/immutable/assets/14.CdVfcO0R.css +1 -0
- package/admin-build/_app/immutable/assets/15.2yeZ66b-.css +1 -0
- package/admin-build/_app/immutable/assets/17.BVg0JEVu.css +1 -0
- package/admin-build/_app/immutable/assets/18.Rwnl3x_i.css +1 -0
- package/admin-build/_app/immutable/assets/20.DsPWA9AV.css +1 -0
- package/admin-build/_app/immutable/assets/21.Dz2RJ56c.css +1 -0
- package/admin-build/_app/immutable/assets/22.DwNLk5Ai.css +1 -0
- package/admin-build/_app/immutable/assets/23.CFpu0gOO.css +1 -0
- package/admin-build/_app/immutable/assets/24.Cy5LBeoJ.css +1 -0
- package/admin-build/_app/immutable/assets/25.pUyLVf-h.css +1 -0
- package/admin-build/_app/immutable/assets/26.DBcGrlXa.css +1 -0
- package/admin-build/_app/immutable/assets/27.BswYyAJD.css +1 -0
- package/admin-build/_app/immutable/assets/28.B4ueB1Kf.css +1 -0
- package/admin-build/_app/immutable/assets/29.B-qU6PdF.css +1 -0
- package/admin-build/_app/immutable/assets/3.Dg81Pgmd.css +1 -0
- package/admin-build/_app/immutable/assets/30.CsdWum94.css +1 -0
- package/admin-build/_app/immutable/assets/31.U6OwIp50.css +1 -0
- package/admin-build/_app/immutable/assets/4.CyawCCux.css +1 -0
- package/admin-build/_app/immutable/assets/5.C0YO2HTk.css +1 -0
- package/admin-build/_app/immutable/assets/8.Br5jd6kD.css +1 -0
- package/admin-build/_app/immutable/assets/Badge.EMYLHBxE.css +1 -0
- package/admin-build/_app/immutable/assets/Button.DpzMRTjK.css +1 -0
- package/admin-build/_app/immutable/assets/ConfirmDialog.DAnaWRRk.css +1 -0
- package/admin-build/_app/immutable/assets/EmptyState.CwKsu57Y.css +1 -0
- package/admin-build/_app/immutable/assets/Input.BDUSenmU.css +1 -0
- package/admin-build/_app/immutable/assets/Modal.Dm5B0Xie.css +1 -0
- package/admin-build/_app/immutable/assets/PageShell.CmU-Xh-b.css +1 -0
- package/admin-build/_app/immutable/assets/SchemaFieldEditor.g4NsCdno.css +1 -0
- package/admin-build/_app/immutable/assets/Select.BW4Keufm.css +1 -0
- package/admin-build/_app/immutable/assets/Skeleton.KWUulTKJ.css +1 -0
- package/admin-build/_app/immutable/assets/Tabs.CniGYb67.css +1 -0
- package/admin-build/_app/immutable/assets/TimeChart.BTCDAvmT.css +1 -0
- package/admin-build/_app/immutable/assets/Toggle.Cy_K12OM.css +1 -0
- package/admin-build/_app/immutable/assets/TopList.ClFzmPlA.css +1 -0
- package/admin-build/_app/immutable/chunks/7B47DvSx.js +1 -0
- package/admin-build/_app/immutable/chunks/7f08Id8e.js +1 -0
- package/admin-build/_app/immutable/chunks/8wJeQ7LN.js +1 -0
- package/admin-build/_app/immutable/chunks/B-h2afW5.js +1 -0
- package/admin-build/_app/immutable/chunks/B8vJP3wz.js +1 -0
- package/admin-build/_app/immutable/chunks/BR_fL5Yv.js +1 -0
- package/admin-build/_app/immutable/chunks/BY92tFS2.js +1 -0
- package/admin-build/_app/immutable/chunks/BcR-Rdj9.js +1 -0
- package/admin-build/_app/immutable/chunks/BdrwyZv8.js +1 -0
- package/admin-build/_app/immutable/chunks/Bh56EfQ_.js +1 -0
- package/admin-build/_app/immutable/chunks/BkrCkgYp.js +1 -0
- package/admin-build/_app/immutable/chunks/BmRjiP5k.js +1 -0
- package/admin-build/_app/immutable/chunks/BsokvhWC.js +1 -0
- package/admin-build/_app/immutable/chunks/C4D51vTW.js +1 -0
- package/admin-build/_app/immutable/chunks/C6puvcoR.js +2 -0
- package/admin-build/_app/immutable/chunks/CCKNu7m7.js +1 -0
- package/admin-build/_app/immutable/chunks/CWj6FrbW.js +1 -0
- package/admin-build/_app/immutable/chunks/Ce-ngf4p.js +5 -0
- package/admin-build/_app/immutable/chunks/Cs0GwzJA.js +1 -0
- package/admin-build/_app/immutable/chunks/CwROoZK0.js +1 -0
- package/admin-build/_app/immutable/chunks/CxCPv_Ut.js +1 -0
- package/admin-build/_app/immutable/chunks/CxbRue-5.js +1 -0
- package/admin-build/_app/immutable/chunks/CyqB6g-D.js +1 -0
- package/admin-build/_app/immutable/chunks/D5h5A1cc.js +2 -0
- package/admin-build/_app/immutable/chunks/DnyL7Zq-.js +1 -0
- package/admin-build/_app/immutable/chunks/DoPXzH7F.js +1 -0
- package/admin-build/_app/immutable/chunks/DrQSgw-f.js +1 -0
- package/admin-build/_app/immutable/chunks/DttM2zNO.js +1 -0
- package/admin-build/_app/immutable/chunks/DuXuUBWN.js +1 -0
- package/admin-build/_app/immutable/chunks/MdeqaOQx.js +10 -0
- package/admin-build/_app/immutable/chunks/NuUjtcO2.js +1 -0
- package/admin-build/_app/immutable/chunks/Q2nPFxS6.js +1 -0
- package/admin-build/_app/immutable/chunks/R6arueIl.js +1 -0
- package/admin-build/_app/immutable/chunks/UUazaC_N.js +1 -0
- package/admin-build/_app/immutable/chunks/cOYbrQxx.js +1 -0
- package/admin-build/_app/immutable/chunks/eFQHTGwA.js +1 -0
- package/admin-build/_app/immutable/chunks/ehbppgYb.js +1 -0
- package/admin-build/_app/immutable/chunks/glwixJlP.js +1 -0
- package/admin-build/_app/immutable/chunks/vApWTCBs.js +1 -0
- package/admin-build/_app/immutable/chunks/w89G9Xpi.js +1 -0
- package/admin-build/_app/immutable/chunks/wJsUhbfZ.js +1 -0
- package/admin-build/_app/immutable/chunks/zfauFM8P.js +1 -0
- package/admin-build/_app/immutable/entry/app.CcO-Uos3.js +2 -0
- package/admin-build/_app/immutable/entry/start.COebYq3I.js +1 -0
- package/admin-build/_app/immutable/nodes/0.CjtHKU-6.js +1 -0
- package/admin-build/_app/immutable/nodes/1.DEisjlM0.js +1 -0
- package/admin-build/_app/immutable/nodes/10.CvhdyWVB.js +1 -0
- package/admin-build/_app/immutable/nodes/11.DjHqcOvy.js +1 -0
- package/admin-build/_app/immutable/nodes/12.mQLz4Mj_.js +1 -0
- package/admin-build/_app/immutable/nodes/13.CBonZZyP.js +110 -0
- package/admin-build/_app/immutable/nodes/14.d-oiZL0j.js +3 -0
- package/admin-build/_app/immutable/nodes/15.CKPQsUYF.js +1 -0
- package/admin-build/_app/immutable/nodes/16.wPzAPQGx.js +1 -0
- package/admin-build/_app/immutable/nodes/17.DayhKyEZ.js +1 -0
- package/admin-build/_app/immutable/nodes/18.DKwS0Ir0.js +1 -0
- package/admin-build/_app/immutable/nodes/19.wPzAPQGx.js +1 -0
- package/admin-build/_app/immutable/nodes/2.BKoKrw1i.js +1 -0
- package/admin-build/_app/immutable/nodes/20.BvIkkkrW.js +1 -0
- package/admin-build/_app/immutable/nodes/21.DMaFhdHk.js +128 -0
- package/admin-build/_app/immutable/nodes/22.3xdgwuK1.js +1 -0
- package/admin-build/_app/immutable/nodes/23.8Bvgjbsl.js +112 -0
- package/admin-build/_app/immutable/nodes/24.DzSSzRhG.js +2 -0
- package/admin-build/_app/immutable/nodes/25.9KKYBnAE.js +2 -0
- package/admin-build/_app/immutable/nodes/26.Bhn9dfhY.js +1 -0
- package/admin-build/_app/immutable/nodes/27.kRLiC24G.js +1 -0
- package/admin-build/_app/immutable/nodes/28.BVIN1-7N.js +1 -0
- package/admin-build/_app/immutable/nodes/29.3yabZWj4.js +1 -0
- package/admin-build/_app/immutable/nodes/3.BFtSOkX7.js +2 -0
- package/admin-build/_app/immutable/nodes/30.CyCQlwaP.js +1 -0
- package/admin-build/_app/immutable/nodes/31.C4LDXjES.js +1 -0
- package/admin-build/_app/immutable/nodes/4.CvbiMlCa.js +1 -0
- package/admin-build/_app/immutable/nodes/5.C6BLv2eM.js +1 -0
- package/admin-build/_app/immutable/nodes/6.BcXvfl2P.js +1 -0
- package/admin-build/_app/immutable/nodes/7.CIuqhPiK.js +1 -0
- package/admin-build/_app/immutable/nodes/8.BQOR_JfO.js +1 -0
- package/admin-build/_app/immutable/nodes/9.NZqXQxPy.js +1 -0
- package/admin-build/_app/version.json +1 -0
- package/admin-build/favicon.svg +26 -0
- package/admin-build/index.html +45 -0
- package/openapi.json +19543 -0
- package/package.json +66 -0
- package/src/__tests__/admin-assets.test.ts +55 -0
- package/src/__tests__/admin-data-routes.test.ts +488 -0
- package/src/__tests__/admin-db-target.test.ts +103 -0
- package/src/__tests__/admin-routing.test.ts +31 -0
- package/src/__tests__/admin-user-management.test.ts +311 -0
- package/src/__tests__/analytics-query.test.ts +75 -0
- package/src/__tests__/auth-d1.test.ts +749 -0
- package/src/__tests__/auth-db-adapter.test.ts +73 -0
- package/src/__tests__/auth-jwt.test.ts +440 -0
- package/src/__tests__/auth-oauth.test.ts +389 -0
- package/src/__tests__/auth-password.test.ts +367 -0
- package/src/__tests__/auth-redirect.test.ts +87 -0
- package/src/__tests__/backup-restore.test.ts +711 -0
- package/src/__tests__/broadcast.test.ts +128 -0
- package/src/__tests__/cli.test.ts +178 -0
- package/src/__tests__/cloudflare-realtime.test.ts +113 -0
- package/src/__tests__/config.test.ts +469 -0
- package/src/__tests__/cors.test.ts +154 -0
- package/src/__tests__/cron.test.ts +302 -0
- package/src/__tests__/d1-handler.test.ts +402 -0
- package/src/__tests__/d1-sql.test.ts +120 -0
- package/src/__tests__/database-live-config.test.ts +42 -0
- package/src/__tests__/database-live-emitter.test.ts +56 -0
- package/src/__tests__/database-live-filters.test.ts +63 -0
- package/src/__tests__/database-live-route.test.ts +113 -0
- package/src/__tests__/db-sql.test.ts +163 -0
- package/src/__tests__/do-lifecycle.test.ts +263 -0
- package/src/__tests__/do-router.test.ts +729 -0
- package/src/__tests__/email-provider.test.ts +128 -0
- package/src/__tests__/email-templates.test.ts +528 -0
- package/src/__tests__/error-format.test.ts +250 -0
- package/src/__tests__/field-ops.test.ts +242 -0
- package/src/__tests__/functions-context.test.ts +334 -0
- package/src/__tests__/functions-d1-proxy.test.ts +229 -0
- package/src/__tests__/functions-registry-runtime-config.test.ts +17 -0
- package/src/__tests__/functions-route.test.ts +139 -0
- package/src/__tests__/internal-request.test.ts +77 -0
- package/src/__tests__/log-writer.test.ts +44 -0
- package/src/__tests__/logger.test.ts +58 -0
- package/src/__tests__/meta-admin-proxy.test.ts +48 -0
- package/src/__tests__/meta-export-coverage.test.ts +191 -0
- package/src/__tests__/meta-route-registration.test.ts +47 -0
- package/src/__tests__/namespace-dump.test.ts +28 -0
- package/src/__tests__/oauth-providers.test.ts +337 -0
- package/src/__tests__/openapi-coverage.test.ts +144 -0
- package/src/__tests__/pagination.test.ts +59 -0
- package/src/__tests__/password-policy.test.ts +191 -0
- package/src/__tests__/plugin-migrations.test.ts +379 -0
- package/src/__tests__/postgres-batch-compat.test.ts +133 -0
- package/src/__tests__/postgres-dialect.test.ts +328 -0
- package/src/__tests__/postgres-executor.test.ts +79 -0
- package/src/__tests__/postgres-field-ops-compat.test.ts +222 -0
- package/src/__tests__/postgres-schema-init.test.ts +105 -0
- package/src/__tests__/postgres-table-utils.test.ts +107 -0
- package/src/__tests__/presence.test.ts +199 -0
- package/src/__tests__/provider.test.ts +550 -0
- package/src/__tests__/public-user-profile.test.ts +339 -0
- package/src/__tests__/push-handlers.test.ts +179 -0
- package/src/__tests__/push-provider.test.ts +80 -0
- package/src/__tests__/push-token.test.ts +418 -0
- package/src/__tests__/query.test.ts +771 -0
- package/src/__tests__/rate-limit.test.ts +260 -0
- package/src/__tests__/room-access-policy.test.ts +101 -0
- package/src/__tests__/room-handler-context.test.ts +130 -0
- package/src/__tests__/room-monitoring.test.ts +138 -0
- package/src/__tests__/room-runtime-routing.test.ts +222 -0
- package/src/__tests__/room.test.ts +254 -0
- package/src/__tests__/route-parser.test.ts +490 -0
- package/src/__tests__/rules.test.ts +234 -0
- package/src/__tests__/runtime-surface-accounting.test.ts +120 -0
- package/src/__tests__/scheduled.test.ts +80 -0
- package/src/__tests__/schema.test.ts +1273 -0
- package/src/__tests__/security-hardening.test.ts +312 -0
- package/src/__tests__/server.unit.test.ts +333 -0
- package/src/__tests__/service-key-db-proxy.test.ts +650 -0
- package/src/__tests__/service-key-provider-bypass.test.ts +138 -0
- package/src/__tests__/service-key.test.ts +757 -0
- package/src/__tests__/smoke-skip-report.test.ts +72 -0
- package/src/__tests__/sms-provider.test.ts +39 -0
- package/src/__tests__/sql-route.test.ts +218 -0
- package/src/__tests__/storage-hook-context.test.ts +115 -0
- package/src/__tests__/totp.test.ts +200 -0
- package/src/__tests__/uuid.test.ts +144 -0
- package/src/__tests__/validation.test.ts +773 -0
- package/src/__tests__/websocket-pending.test.ts +163 -0
- package/src/_functions-registry.ts +51 -0
- package/src/bench-entry.ts +9 -0
- package/src/cloudflare-test.d.ts +1 -0
- package/src/durable-objects/auth-do.ts +49 -0
- package/src/durable-objects/database-do.ts +2240 -0
- package/src/durable-objects/database-live-do.ts +949 -0
- package/src/durable-objects/logs-do.ts +1200 -0
- package/src/durable-objects/room-runtime-base.ts +1604 -0
- package/src/durable-objects/rooms-do.ts +2191 -0
- package/src/generated-config.ts +6 -0
- package/src/index.ts +382 -0
- package/src/lib/admin-assets.ts +54 -0
- package/src/lib/admin-db-target.ts +301 -0
- package/src/lib/admin-routing.ts +35 -0
- package/src/lib/admin-user-management.ts +464 -0
- package/src/lib/analytics-adapter.ts +103 -0
- package/src/lib/analytics-query.ts +579 -0
- package/src/lib/auth-d1-service.ts +1193 -0
- package/src/lib/auth-d1.ts +1056 -0
- package/src/lib/auth-db-adapter.ts +289 -0
- package/src/lib/auth-redirect.ts +116 -0
- package/src/lib/cidr.ts +115 -0
- package/src/lib/client-ip.ts +51 -0
- package/src/lib/cloudflare-realtime.ts +251 -0
- package/src/lib/control-db.ts +36 -0
- package/src/lib/cron.ts +163 -0
- package/src/lib/d1-handler.ts +1425 -0
- package/src/lib/d1-schema-init.ts +255 -0
- package/src/lib/d1-sql.ts +33 -0
- package/src/lib/database-live-config.ts +24 -0
- package/src/lib/database-live-emitter.ts +111 -0
- package/src/lib/db-sql.ts +66 -0
- package/src/lib/do-retry.ts +36 -0
- package/src/lib/do-router.ts +270 -0
- package/src/lib/do-sql.ts +73 -0
- package/src/lib/email-provider.ts +379 -0
- package/src/lib/email-templates.ts +285 -0
- package/src/lib/email-translations.ts +422 -0
- package/src/lib/errors.ts +151 -0
- package/src/lib/functions.ts +2091 -0
- package/src/lib/hono.ts +56 -0
- package/src/lib/internal-request.ts +56 -0
- package/src/lib/jwt.ts +354 -0
- package/src/lib/log-writer.ts +272 -0
- package/src/lib/namespace-dump.ts +125 -0
- package/src/lib/oauth-providers.ts +1225 -0
- package/src/lib/op-parser.ts +99 -0
- package/src/lib/openapi.ts +146 -0
- package/src/lib/pagination.ts +19 -0
- package/src/lib/password-policy.ts +102 -0
- package/src/lib/password.ts +145 -0
- package/src/lib/plugin-migrations.ts +612 -0
- package/src/lib/postgres-executor.ts +203 -0
- package/src/lib/postgres-handler.ts +1102 -0
- package/src/lib/postgres-schema-init.ts +341 -0
- package/src/lib/postgres-table-utils.ts +87 -0
- package/src/lib/public-user-profile.ts +187 -0
- package/src/lib/push-provider.ts +409 -0
- package/src/lib/push-token.ts +294 -0
- package/src/lib/query-engine.ts +768 -0
- package/src/lib/room-monitoring.ts +97 -0
- package/src/lib/room-runtime.ts +14 -0
- package/src/lib/route-parser.ts +434 -0
- package/src/lib/schema.ts +538 -0
- package/src/lib/schemas.ts +152 -0
- package/src/lib/service-key.ts +419 -0
- package/src/lib/sms-provider.ts +230 -0
- package/src/lib/startup-config.ts +99 -0
- package/src/lib/totp.ts +242 -0
- package/src/lib/uuid.ts +87 -0
- package/src/lib/validation.ts +205 -0
- package/src/lib/version.ts +2 -0
- package/src/lib/websocket-pending.ts +40 -0
- package/src/middleware/auth.ts +169 -0
- package/src/middleware/captcha-verify.ts +217 -0
- package/src/middleware/cors.ts +159 -0
- package/src/middleware/error-handler.ts +54 -0
- package/src/middleware/internal-guard.ts +26 -0
- package/src/middleware/logger.ts +126 -0
- package/src/middleware/rate-limit.ts +283 -0
- package/src/middleware/rules.ts +475 -0
- package/src/routes/admin-auth.ts +447 -0
- package/src/routes/admin.ts +3501 -0
- package/src/routes/analytics-api.ts +290 -0
- package/src/routes/auth.ts +4222 -0
- package/src/routes/backup.ts +1466 -0
- package/src/routes/config.ts +53 -0
- package/src/routes/d1.ts +109 -0
- package/src/routes/database-live.ts +281 -0
- package/src/routes/functions.ts +155 -0
- package/src/routes/health.ts +32 -0
- package/src/routes/kv.ts +167 -0
- package/src/routes/oauth.ts +1055 -0
- package/src/routes/push.ts +1465 -0
- package/src/routes/room.ts +639 -0
- package/src/routes/schema-endpoint.ts +76 -0
- package/src/routes/sql.ts +176 -0
- package/src/routes/storage.ts +1674 -0
- package/src/routes/tables.ts +699 -0
- package/src/routes/users.ts +21 -0
- package/src/routes/vectorize.ts +372 -0
- package/src/types.ts +99 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgreSQL lazy schema initializer.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors database-do.ts initializeSchema() but for PostgreSQL:
|
|
5
|
+
* 1. Creates _meta table if not exists
|
|
6
|
+
* 2. For each table: compute schema hash, compare with stored hash
|
|
7
|
+
* 3. If new: CREATE TABLE + indexes + FTS
|
|
8
|
+
* 4. If changed: ADD COLUMN for new fields (non-destructive)
|
|
9
|
+
* 5. Stores schema hash in _meta
|
|
10
|
+
*
|
|
11
|
+
* Called once per Worker lifetime per namespace/config signature.
|
|
12
|
+
*/
|
|
13
|
+
import type { TableConfig, MigrationConfig, SchemaField } from '@edge-base/shared';
|
|
14
|
+
import {
|
|
15
|
+
type PostgresExecutor,
|
|
16
|
+
withPostgresConnection,
|
|
17
|
+
} from './postgres-executor.js';
|
|
18
|
+
import {
|
|
19
|
+
PG_META_TABLE_DDL,
|
|
20
|
+
generatePgTableDDL,
|
|
21
|
+
generatePgAddColumnDDL,
|
|
22
|
+
generatePgFTSDDL,
|
|
23
|
+
generatePgIndexDDL,
|
|
24
|
+
buildEffectiveSchema,
|
|
25
|
+
computeSchemaHashSync,
|
|
26
|
+
} from './schema.js';
|
|
27
|
+
|
|
28
|
+
// Track schema initialization promises so CRUD requests do not re-run the full
|
|
29
|
+
// schema/meta scan on every query in the same Worker process.
|
|
30
|
+
const _schemaInitCache = new Map<string, Promise<void>>();
|
|
31
|
+
|
|
32
|
+
function extractReferenceTable(reference: SchemaField['references']): string | null {
|
|
33
|
+
if (!reference) return null;
|
|
34
|
+
if (typeof reference === 'string') {
|
|
35
|
+
const match = reference.trim().match(/^(\w+)(?:\((\w+)\))?$/);
|
|
36
|
+
return match?.[1] ?? null;
|
|
37
|
+
}
|
|
38
|
+
return reference.table;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isLogicalOnlyReference(reference: SchemaField['references']): boolean {
|
|
42
|
+
const table = extractReferenceTable(reference);
|
|
43
|
+
return table !== null && ['users', '_users', '_users_public'].includes(table);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolvePgInitOrder(
|
|
47
|
+
tables: Record<string, TableConfig>,
|
|
48
|
+
): Array<[string, TableConfig]> {
|
|
49
|
+
const entries = Object.entries(tables);
|
|
50
|
+
const tableNames = new Set(entries.map(([tableName]) => tableName));
|
|
51
|
+
const dependencies = new Map<string, Set<string>>();
|
|
52
|
+
|
|
53
|
+
for (const [tableName, config] of entries) {
|
|
54
|
+
const deps = new Set<string>();
|
|
55
|
+
const schema = buildEffectiveSchema(config.schema);
|
|
56
|
+
|
|
57
|
+
for (const field of Object.values(schema)) {
|
|
58
|
+
const refTable = extractReferenceTable(field.references);
|
|
59
|
+
if (!refTable || refTable === tableName) continue;
|
|
60
|
+
if (isLogicalOnlyReference(field.references)) continue;
|
|
61
|
+
if (!tableNames.has(refTable)) continue;
|
|
62
|
+
deps.add(refTable);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dependencies.set(tableName, deps);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ordered: string[] = [];
|
|
69
|
+
const visited = new Set<string>();
|
|
70
|
+
const visiting = new Set<string>();
|
|
71
|
+
|
|
72
|
+
function visit(tableName: string): void {
|
|
73
|
+
if (visited.has(tableName)) return;
|
|
74
|
+
if (visiting.has(tableName)) {
|
|
75
|
+
// Cycles are preserved in original relative order; PostgreSQL will still
|
|
76
|
+
// reject impossible FK cycles, but we avoid infinite recursion here.
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
visiting.add(tableName);
|
|
81
|
+
for (const dependency of dependencies.get(tableName) ?? []) {
|
|
82
|
+
visit(dependency);
|
|
83
|
+
}
|
|
84
|
+
visiting.delete(tableName);
|
|
85
|
+
visited.add(tableName);
|
|
86
|
+
ordered.push(tableName);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const [tableName] of entries) {
|
|
90
|
+
visit(tableName);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return ordered.map((tableName) => [tableName, tables[tableName]!]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Ensure PostgreSQL schema is up-to-date for a given namespace.
|
|
98
|
+
* Called once per Worker lifetime per namespace (cached in memory).
|
|
99
|
+
*/
|
|
100
|
+
export async function ensurePgSchema(
|
|
101
|
+
connectionString: string,
|
|
102
|
+
namespace: string,
|
|
103
|
+
tables: Record<string, TableConfig>,
|
|
104
|
+
queryExecutor?: PostgresExecutor,
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
const cacheKey = buildPgSchemaCacheKey(connectionString, namespace, tables);
|
|
107
|
+
const cached = _schemaInitCache.get(cacheKey);
|
|
108
|
+
if (cached) {
|
|
109
|
+
await cached;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const promise = (async () => {
|
|
114
|
+
if (queryExecutor) {
|
|
115
|
+
await ensurePgSchemaInternal(connectionString, tables, queryExecutor);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await withPostgresConnection(connectionString, async (query) => {
|
|
120
|
+
await ensurePgSchemaInternal(connectionString, tables, query);
|
|
121
|
+
});
|
|
122
|
+
})();
|
|
123
|
+
|
|
124
|
+
_schemaInitCache.set(cacheKey, promise);
|
|
125
|
+
try {
|
|
126
|
+
await promise;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
_schemaInitCache.delete(cacheKey);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function ensurePgSchemaInternal(
|
|
134
|
+
connectionString: string,
|
|
135
|
+
tables: Record<string, TableConfig>,
|
|
136
|
+
query: PostgresExecutor,
|
|
137
|
+
): Promise<void> {
|
|
138
|
+
await query(PG_META_TABLE_DDL, []);
|
|
139
|
+
|
|
140
|
+
for (const [tableName, config] of resolvePgInitOrder(tables)) {
|
|
141
|
+
await initPgTable(connectionString, tableName, config, query);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function buildPgSchemaCacheKey(
|
|
146
|
+
connectionString: string,
|
|
147
|
+
namespace: string,
|
|
148
|
+
tables: Record<string, TableConfig>,
|
|
149
|
+
): string {
|
|
150
|
+
const signature = Object.entries(tables)
|
|
151
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
152
|
+
.map(([tableName, config]) => {
|
|
153
|
+
const migrations = (config.migrations ?? [])
|
|
154
|
+
.map((migration) => `${migration.version}:${migration.upPg ?? migration.up}`)
|
|
155
|
+
.join('|');
|
|
156
|
+
const indexes = JSON.stringify(config.indexes ?? []);
|
|
157
|
+
const fts = JSON.stringify(config.fts ?? []);
|
|
158
|
+
return `${tableName}:${computeSchemaHashSync(config)}:${indexes}:${fts}:${migrations}`;
|
|
159
|
+
})
|
|
160
|
+
.join('||');
|
|
161
|
+
|
|
162
|
+
return `${namespace}:${connectionString}:${signature}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Initialize or update a single PostgreSQL table.
|
|
167
|
+
*/
|
|
168
|
+
async function initPgTable(
|
|
169
|
+
connectionString: string,
|
|
170
|
+
tableName: string,
|
|
171
|
+
config: TableConfig,
|
|
172
|
+
query: PostgresExecutor,
|
|
173
|
+
): Promise<void> {
|
|
174
|
+
const currentHash = computeSchemaHashSync(config);
|
|
175
|
+
|
|
176
|
+
// Check stored hash
|
|
177
|
+
const storedHash = await getMeta(connectionString, `schemaHash:${tableName}`, query);
|
|
178
|
+
|
|
179
|
+
if (storedHash === currentHash) {
|
|
180
|
+
// No schema change — still check migrations (user may add new migrations
|
|
181
|
+
// without changing the schema, same as database-do.ts)
|
|
182
|
+
await ensurePgFTSAndIndexes(connectionString, tableName, config, query);
|
|
183
|
+
await runPgMigrations(connectionString, tableName, config, query);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!storedHash) {
|
|
188
|
+
// First time — create table + indexes + FTS
|
|
189
|
+
const ddls = generatePgTableDDL(tableName, config);
|
|
190
|
+
for (const ddl of ddls) {
|
|
191
|
+
await query(ddl, []);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Set initial migration version if migrations exist (skip running them —
|
|
195
|
+
// fresh table already has the latest schema)
|
|
196
|
+
if (config.migrations?.length) {
|
|
197
|
+
const maxVersion = Math.max(...config.migrations.map((m: MigrationConfig) => m.version));
|
|
198
|
+
await setMeta(connectionString, `migration_version:${tableName}`, String(maxVersion), query);
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
// Schema changed — detect new columns and add them (non-destructive)
|
|
202
|
+
await handlePgSchemaUpdate(connectionString, tableName, config, query);
|
|
203
|
+
// Re-apply FTS and indexes to pick up new field additions
|
|
204
|
+
await ensurePgFTSAndIndexes(connectionString, tableName, config, query);
|
|
205
|
+
// Run pending migrations after schema update
|
|
206
|
+
await runPgMigrations(connectionString, tableName, config, query);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Store new hash
|
|
210
|
+
await setMeta(connectionString, `schemaHash:${tableName}`, currentHash, query);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Non-destructive schema update: detect new columns and ADD COLUMN.
|
|
215
|
+
* Does NOT drop columns (data safety) — mirrors database-do.ts handleSchemaUpdate().
|
|
216
|
+
*/
|
|
217
|
+
async function handlePgSchemaUpdate(
|
|
218
|
+
connectionString: string,
|
|
219
|
+
tableName: string,
|
|
220
|
+
config: TableConfig,
|
|
221
|
+
query: PostgresExecutor,
|
|
222
|
+
): Promise<void> {
|
|
223
|
+
// Get existing columns from information_schema
|
|
224
|
+
const colResult = await query(
|
|
225
|
+
`SELECT column_name FROM information_schema.columns WHERE table_name = $1`,
|
|
226
|
+
[tableName],
|
|
227
|
+
);
|
|
228
|
+
const existingCols = new Set(
|
|
229
|
+
colResult.rows.map(r => (r as Record<string, unknown>).column_name as string),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Build effective schema with auto-fields
|
|
233
|
+
const effectiveSchema = buildEffectiveSchema(config.schema);
|
|
234
|
+
|
|
235
|
+
// Add missing columns
|
|
236
|
+
for (const [colName, field] of Object.entries(effectiveSchema)) {
|
|
237
|
+
if (!existingCols.has(colName)) {
|
|
238
|
+
const ddl = generatePgAddColumnDDL(tableName, colName, field);
|
|
239
|
+
await query(ddl, []);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Ensure FTS and indexes are up-to-date after schema changes.
|
|
246
|
+
*/
|
|
247
|
+
async function ensurePgFTSAndIndexes(
|
|
248
|
+
connectionString: string,
|
|
249
|
+
tableName: string,
|
|
250
|
+
config: TableConfig,
|
|
251
|
+
query: PostgresExecutor,
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
// Re-apply indexes (CREATE IF NOT EXISTS is idempotent)
|
|
254
|
+
if (config.indexes?.length) {
|
|
255
|
+
const indexDDLs = generatePgIndexDDL(tableName, config.indexes);
|
|
256
|
+
for (const ddl of indexDDLs) {
|
|
257
|
+
await query(ddl, []);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Re-apply FTS (uses CREATE OR REPLACE and DROP TRIGGER IF EXISTS)
|
|
262
|
+
if (config.fts?.length) {
|
|
263
|
+
const ftsDDLs = generatePgFTSDDL(tableName, config.fts);
|
|
264
|
+
for (const ddl of ftsDDLs) {
|
|
265
|
+
await query(ddl, []);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ─── Migration Engine ───
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Run pending migrations for a PostgreSQL table.
|
|
274
|
+
* Mirrors database-do.ts runMigrations() with upPg → up fallback.
|
|
275
|
+
*
|
|
276
|
+
* Migration version tracked in `_meta` as `migration_version:{tableName}`.
|
|
277
|
+
* Migrations are sorted by version (ascending) and executed sequentially.
|
|
278
|
+
* If `upPg` is provided, it is used instead of `up` for PostgreSQL.
|
|
279
|
+
*/
|
|
280
|
+
async function runPgMigrations(
|
|
281
|
+
connectionString: string,
|
|
282
|
+
tableName: string,
|
|
283
|
+
config: TableConfig,
|
|
284
|
+
query: PostgresExecutor,
|
|
285
|
+
): Promise<void> {
|
|
286
|
+
if (!config.migrations?.length) return;
|
|
287
|
+
|
|
288
|
+
const versionKey = `migration_version:${tableName}`;
|
|
289
|
+
const currentVersionStr = await getMeta(connectionString, versionKey, query);
|
|
290
|
+
const currentVersion = parseInt(currentVersionStr || '1', 10);
|
|
291
|
+
|
|
292
|
+
const pending = config.migrations
|
|
293
|
+
.filter((m: MigrationConfig) => m.version > currentVersion)
|
|
294
|
+
.sort((a: MigrationConfig, b: MigrationConfig) => a.version - b.version);
|
|
295
|
+
|
|
296
|
+
for (const migration of pending) {
|
|
297
|
+
try {
|
|
298
|
+
// Use upPg if available, otherwise fall back to up
|
|
299
|
+
const sql = migration.upPg ?? migration.up;
|
|
300
|
+
await query(sql, []);
|
|
301
|
+
await setMeta(connectionString, versionKey, String(migration.version), query);
|
|
302
|
+
} catch (err) {
|
|
303
|
+
// Migration failed — stop here, throw so the request gets a 503
|
|
304
|
+
console.error(`PG Migration v${migration.version} failed for ${tableName}:`, err);
|
|
305
|
+
throw new Error(`PG Migration v${migration.version} failed: ${(err as Error).message}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ─── _meta Helpers ───
|
|
311
|
+
|
|
312
|
+
async function getMeta(
|
|
313
|
+
connectionString: string,
|
|
314
|
+
key: string,
|
|
315
|
+
query: PostgresExecutor,
|
|
316
|
+
): Promise<string | null> {
|
|
317
|
+
const result = await query(
|
|
318
|
+
`SELECT "value" FROM "_meta" WHERE "key" = $1`,
|
|
319
|
+
[key],
|
|
320
|
+
);
|
|
321
|
+
return result.rows.length > 0
|
|
322
|
+
? (result.rows[0] as Record<string, unknown>).value as string
|
|
323
|
+
: null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function setMeta(
|
|
327
|
+
connectionString: string,
|
|
328
|
+
key: string,
|
|
329
|
+
value: string,
|
|
330
|
+
query: PostgresExecutor,
|
|
331
|
+
): Promise<void> {
|
|
332
|
+
await query(
|
|
333
|
+
`INSERT INTO "_meta" ("key", "value") VALUES ($1, $2) ON CONFLICT ("key") DO UPDATE SET "value" = $2`,
|
|
334
|
+
[key, value],
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Reset initialized state (for testing). */
|
|
339
|
+
export function _resetPgSchemaCache(): void {
|
|
340
|
+
_schemaInitCache.clear();
|
|
341
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { SchemaField, TableConfig } from '@edge-base/shared';
|
|
2
|
+
import { buildEffectiveSchema } from './schema.js';
|
|
3
|
+
import { generateId } from './uuid.js';
|
|
4
|
+
|
|
5
|
+
export function escapePgIdentifier(name: string): string {
|
|
6
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function stripInternalPgFields(row: Record<string, unknown>): Record<string, unknown> {
|
|
10
|
+
const cleaned = { ...row };
|
|
11
|
+
delete cleaned._fts;
|
|
12
|
+
return cleaned;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function serializePgJsonFields(
|
|
16
|
+
data: Record<string, unknown>,
|
|
17
|
+
schema: Record<string, SchemaField>,
|
|
18
|
+
): void {
|
|
19
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
20
|
+
if (field.type === 'json' && data[key] !== undefined && data[key] !== null) {
|
|
21
|
+
if (typeof data[key] !== 'string') {
|
|
22
|
+
data[key] = JSON.stringify(data[key]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function filterToPgSchemaColumns(
|
|
29
|
+
data: Record<string, unknown>,
|
|
30
|
+
effectiveSchema: Record<string, SchemaField>,
|
|
31
|
+
): Record<string, unknown> {
|
|
32
|
+
const filtered: Record<string, unknown> = {};
|
|
33
|
+
for (const key of Object.keys(data)) {
|
|
34
|
+
if (key in effectiveSchema) {
|
|
35
|
+
filtered[key] = data[key];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return filtered;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function preparePgInsertData(
|
|
42
|
+
body: Record<string, unknown>,
|
|
43
|
+
tableConfig: TableConfig,
|
|
44
|
+
): {
|
|
45
|
+
data: Record<string, unknown>;
|
|
46
|
+
effectiveSchema: Record<string, SchemaField>;
|
|
47
|
+
} {
|
|
48
|
+
const effectiveSchema = buildEffectiveSchema(tableConfig.schema);
|
|
49
|
+
const prepared = { ...body };
|
|
50
|
+
|
|
51
|
+
if (!prepared.id) prepared.id = generateId();
|
|
52
|
+
const now = new Date().toISOString();
|
|
53
|
+
if (effectiveSchema.createdAt) prepared.createdAt = now;
|
|
54
|
+
if (effectiveSchema.updatedAt) prepared.updatedAt = now;
|
|
55
|
+
|
|
56
|
+
for (const [name, field] of Object.entries(effectiveSchema)) {
|
|
57
|
+
if (prepared[name] === undefined && field.default !== undefined) {
|
|
58
|
+
prepared[name] = field.default;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const data = filterToPgSchemaColumns(prepared, effectiveSchema);
|
|
63
|
+
serializePgJsonFields(data, effectiveSchema);
|
|
64
|
+
return { data, effectiveSchema };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function preparePgUpdateData(
|
|
68
|
+
body: Record<string, unknown>,
|
|
69
|
+
tableConfig: TableConfig,
|
|
70
|
+
): {
|
|
71
|
+
data: Record<string, unknown>;
|
|
72
|
+
effectiveSchema: Record<string, SchemaField>;
|
|
73
|
+
} {
|
|
74
|
+
const effectiveSchema = buildEffectiveSchema(tableConfig.schema);
|
|
75
|
+
const prepared = { ...body };
|
|
76
|
+
|
|
77
|
+
if (effectiveSchema.updatedAt) {
|
|
78
|
+
prepared.updatedAt = new Date().toISOString();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
delete prepared.id;
|
|
82
|
+
delete prepared.createdAt;
|
|
83
|
+
|
|
84
|
+
const data = filterToPgSchemaColumns(prepared, effectiveSchema);
|
|
85
|
+
serializePgJsonFields(data, effectiveSchema);
|
|
86
|
+
return { data, effectiveSchema };
|
|
87
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { AuthDb } from './auth-db-adapter.js';
|
|
2
|
+
import { batchDeleteUserPublic, upsertUserPublic } from './auth-d1.js';
|
|
3
|
+
import type { UserPublicData } from './auth-d1.js';
|
|
4
|
+
|
|
5
|
+
const L1_TTL_MS = 60_000;
|
|
6
|
+
const L1_MAX = 500;
|
|
7
|
+
const KV_TTL_SECONDS = 3600;
|
|
8
|
+
|
|
9
|
+
type PublicProfile = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
interface CacheEntry {
|
|
12
|
+
data: PublicProfile;
|
|
13
|
+
expiresAt: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface PublicProfileOptions {
|
|
17
|
+
executionCtx?: ExecutionContext;
|
|
18
|
+
kv?: KVNamespace;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SyncPublicProfileOptions extends PublicProfileOptions {
|
|
22
|
+
awaitCacheWrites?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const l1Cache = new Map<string, CacheEntry>();
|
|
26
|
+
|
|
27
|
+
function kvKey(userId: string): string {
|
|
28
|
+
return `kv:users_public:${userId}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function l1Get(userId: string): PublicProfile | null {
|
|
32
|
+
const entry = l1Cache.get(userId);
|
|
33
|
+
if (!entry) return null;
|
|
34
|
+
if (Date.now() > entry.expiresAt) {
|
|
35
|
+
l1Cache.delete(userId);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return entry.data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function l1Set(userId: string, data: PublicProfile): void {
|
|
42
|
+
if (l1Cache.size >= L1_MAX) {
|
|
43
|
+
const firstKey = l1Cache.keys().next().value;
|
|
44
|
+
if (firstKey !== undefined) l1Cache.delete(firstKey);
|
|
45
|
+
}
|
|
46
|
+
l1Cache.set(userId, { data, expiresAt: Date.now() + L1_TTL_MS });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function l1Delete(userId: string): void {
|
|
50
|
+
l1Cache.delete(userId);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function runOrSchedule(
|
|
54
|
+
promiseFactory: () => Promise<void>,
|
|
55
|
+
options: SyncPublicProfileOptions,
|
|
56
|
+
errorMessage: string,
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
const task = Promise.resolve().then(promiseFactory);
|
|
59
|
+
if (options.awaitCacheWrites) {
|
|
60
|
+
await task;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const handledTask = task.catch((err) => {
|
|
65
|
+
console.error(errorMessage, err);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (options.executionCtx) {
|
|
69
|
+
options.executionCtx.waitUntil(handledTask);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
void handledTask;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function getPublicProfileWithCache(
|
|
77
|
+
authDb: AuthDb,
|
|
78
|
+
userId: string,
|
|
79
|
+
options: PublicProfileOptions,
|
|
80
|
+
): Promise<PublicProfile | null> {
|
|
81
|
+
const cached = l1Get(userId);
|
|
82
|
+
if (cached) return cached;
|
|
83
|
+
|
|
84
|
+
if (options.kv) {
|
|
85
|
+
const kvRaw = await options.kv.get(kvKey(userId), 'json').catch(() => null);
|
|
86
|
+
if (kvRaw) {
|
|
87
|
+
const profile = kvRaw as PublicProfile;
|
|
88
|
+
l1Set(userId, profile);
|
|
89
|
+
return profile;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const profile = await authDb.first<PublicProfile>(
|
|
94
|
+
'SELECT id, email, displayName, avatarUrl, role, isAnonymous, createdAt, updatedAt FROM _users_public WHERE id = ?',
|
|
95
|
+
[userId],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (!profile) return null;
|
|
99
|
+
|
|
100
|
+
l1Set(userId, profile);
|
|
101
|
+
|
|
102
|
+
if (options.kv) {
|
|
103
|
+
await runOrSchedule(
|
|
104
|
+
() =>
|
|
105
|
+
options.kv!.put(kvKey(userId), JSON.stringify(profile), {
|
|
106
|
+
expirationTtl: KV_TTL_SECONDS,
|
|
107
|
+
}),
|
|
108
|
+
{ ...options, awaitCacheWrites: false },
|
|
109
|
+
`[EdgeBase] Failed to populate public profile cache for ${userId}:`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return profile;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function syncPublicUserProjection(
|
|
117
|
+
authDb: AuthDb,
|
|
118
|
+
userId: string,
|
|
119
|
+
profile: PublicProfile,
|
|
120
|
+
options: SyncPublicProfileOptions = {},
|
|
121
|
+
): Promise<void> {
|
|
122
|
+
await upsertUserPublic(authDb, userId, profile as unknown as UserPublicData);
|
|
123
|
+
const cachedProfile: PublicProfile = { id: userId, ...profile };
|
|
124
|
+
l1Set(userId, cachedProfile);
|
|
125
|
+
|
|
126
|
+
if (!options.kv) return;
|
|
127
|
+
|
|
128
|
+
await runOrSchedule(
|
|
129
|
+
() =>
|
|
130
|
+
options.kv!.put(kvKey(userId), JSON.stringify(cachedProfile), {
|
|
131
|
+
expirationTtl: KV_TTL_SECONDS,
|
|
132
|
+
}),
|
|
133
|
+
options,
|
|
134
|
+
`[EdgeBase] Failed to write public profile cache for ${userId}:`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function invalidatePublicUserCache(
|
|
139
|
+
userId: string,
|
|
140
|
+
options: SyncPublicProfileOptions = {},
|
|
141
|
+
): Promise<void> {
|
|
142
|
+
l1Delete(userId);
|
|
143
|
+
|
|
144
|
+
if (!options.kv) return;
|
|
145
|
+
|
|
146
|
+
await runOrSchedule(
|
|
147
|
+
() => options.kv!.delete(kvKey(userId)),
|
|
148
|
+
options,
|
|
149
|
+
`[EdgeBase] Failed to invalidate public profile cache for ${userId}:`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function queuePublicUserProjectionSync(
|
|
154
|
+
authDb: AuthDb,
|
|
155
|
+
userId: string,
|
|
156
|
+
profile: PublicProfile,
|
|
157
|
+
options: PublicProfileOptions,
|
|
158
|
+
): void {
|
|
159
|
+
void syncPublicUserProjection(authDb, userId, profile, {
|
|
160
|
+
...options,
|
|
161
|
+
awaitCacheWrites: false,
|
|
162
|
+
}).catch((err) => {
|
|
163
|
+
console.error(`[EdgeBase] Failed to sync public user projection for ${userId}:`, err);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function deletePublicUserProjection(
|
|
168
|
+
authDb: AuthDb,
|
|
169
|
+
userId: string,
|
|
170
|
+
options: SyncPublicProfileOptions = {},
|
|
171
|
+
): Promise<void> {
|
|
172
|
+
await batchDeleteUserPublic(authDb, [userId]);
|
|
173
|
+
await invalidatePublicUserCache(userId, options);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function queuePublicUserProjectionDelete(
|
|
177
|
+
authDb: AuthDb,
|
|
178
|
+
userId: string,
|
|
179
|
+
options: PublicProfileOptions,
|
|
180
|
+
): void {
|
|
181
|
+
void deletePublicUserProjection(authDb, userId, {
|
|
182
|
+
...options,
|
|
183
|
+
awaitCacheWrites: false,
|
|
184
|
+
}).catch((err) => {
|
|
185
|
+
console.error(`[EdgeBase] Failed to delete public user projection for ${userId}:`, err);
|
|
186
|
+
});
|
|
187
|
+
}
|