@starci/hfs 4.9.9 → 4.10.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/CHANGELOG.md +33 -0
- package/README.md +20 -8
- package/db/backup-receipt.mjs +10 -47
- package/db/drift.mjs +289 -0
- package/db/dump.mjs +12 -11
- package/db/entity-list.mjs +9 -0
- package/db/migrate.mjs +84 -260
- package/db/new.mjs +46 -0
- package/db/post-cutover.mjs +13 -17
- package/db/postgres.mjs +80 -0
- package/emit/contracts.mjs +5 -9
- package/lint/run.mjs +12 -2
- package/package.json +5 -3
- package/release/gate.mjs +82 -31
- package/release/index.mjs +19 -8
- package/release/push.mjs +4 -4
- package/release/records.mjs +54 -0
- package/release/urgent.mjs +129 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +54 -24
- package/runtime/knowledge/hfs/design-truths.yaml +1 -1
- package/runtime/knowledge/hfs/fe-stack.yaml +4 -11
- package/runtime/knowledge/hfs/rules.yaml +250 -157
- package/runtime/knowledge/hfs/slots.yaml +199 -174
- package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
- package/runtime/knowledge/patterns/be/idempotency.yaml +3 -3
- package/runtime/knowledge/patterns/be/jobs.yaml +4 -4
- package/runtime/knowledge/patterns/be/projections.yaml +7 -5
- package/runtime/knowledge/patterns/be/queues.yaml +8 -8
- package/runtime/knowledge/patterns/be/reactors.yaml +2 -2
- package/runtime/knowledge/patterns/be/realtime.yaml +6 -6
- package/runtime/knowledge/patterns/be/saga.yaml +3 -3
- package/runtime/knowledge/patterns/be/webhooks.yaml +1 -1
- package/runtime/modules/kernel/command-policy.yaml +1 -1
- package/runtime/modules/kernel/failure-codes.yaml +86 -115
- package/runtime/modules/schemas/application-stacks.schema.yaml +5 -5
- package/runtime/scripts/hfs/app-wiring.mjs +23 -2
- package/runtime/scripts/hfs/architecture/canon-clones.mjs +1 -1
- package/runtime/scripts/hfs/architecture/{supabase-be.mjs → identity.mjs} +45 -15
- package/runtime/scripts/hfs/architecture/index.mjs +2 -3
- package/runtime/scripts/hfs/architecture/{supabase-ast.mjs → jwt-ast.mjs} +2 -103
- package/runtime/scripts/hfs/architecture/register-once.mjs +1 -1
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +29 -4
- package/runtime/scripts/hfs/architecture/server-action.mjs +3 -4
- package/runtime/scripts/hfs/architecture/side-boundary.mjs +1 -1
- package/runtime/scripts/hfs/architecture/{supabase-keys.mjs → sql-keys.mjs} +1 -1
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +25 -27
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +7 -3
- package/runtime/scripts/hfs/check.mjs +8 -8
- package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
- package/runtime/scripts/hfs/db-baseline-verb.mjs +48 -30
- package/runtime/scripts/hfs/db-plan-verb.mjs +17 -106
- package/runtime/scripts/hfs/db-restamp-verb.mjs +11 -10
- package/runtime/scripts/hfs/declaration-shape.mjs +15 -18
- package/runtime/scripts/hfs/manifest-shape.mjs +6 -5
- package/runtime/scripts/hfs/rule-params-shape.mjs +4 -1
- package/runtime/scripts/hfs/rules/a15-findings.mjs +1 -1
- package/runtime/scripts/hfs/rules/cli.mjs +1 -1
- package/runtime/scripts/hfs/rules/database-access.mjs +8 -74
- package/runtime/scripts/hfs/rules/database-baseline.mjs +3 -3
- package/runtime/scripts/hfs/rules/database-config.mjs +6 -214
- package/runtime/scripts/hfs/rules/database-constants.mjs +8 -10
- package/runtime/scripts/hfs/rules/database-migrations.mjs +3 -3
- package/runtime/scripts/hfs/rules/database-queues.mjs +4 -5
- package/runtime/scripts/hfs/rules/database-sql.mjs +1 -1
- package/runtime/scripts/hfs/rules/database.mjs +36 -39
- package/runtime/scripts/hfs/rules/edition.mjs +1 -1
- package/runtime/scripts/hfs/rules/{supabase-secrets.mjs → lite-secrets.mjs} +6 -6
- package/runtime/scripts/hfs/rules/script-hygiene.mjs +1 -10
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +5 -4
- package/runtime/scripts/hfs/rules/stacks-release.mjs +3 -2
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +2 -2
- package/runtime/scripts/hfs/rules/stacks.mjs +1 -1
- package/runtime/scripts/hfs/slot-app-view.mjs +1 -1
- package/runtime/scripts/hfs/slots.mjs +8 -2
- package/runtime/scripts/hfs/sql/pg-parse.mjs +2 -2
- package/runtime/scripts/lib/secret-patterns.mjs +2 -2
- package/runtime/scripts/lib/swarm-stack.mjs +7 -4
- package/scaffold/add-app.mjs +2 -10
- package/scaffold/add-cli-lite.mjs +6 -6
- package/scaffold/add-compose.mjs +1 -1
- package/scaffold/add-inbox-lite.mjs +4 -8
- package/scaffold/add-plan.mjs +1 -1
- package/scaffold/add-queue-lite.mjs +16 -24
- package/scaffold/add-table.mjs +34 -26
- package/scaffold/add.mjs +2 -2
- package/scaffold/app.mjs +19 -86
- package/scaffold/lite-exports.mjs +1 -1
- package/src/main.mjs +24 -22
- package/stack/cli.mjs +10 -2
- package/stack/deploy.mjs +21 -4
- package/stack/target-binding.mjs +0 -60
- package/stack/urgent-floor.mjs +147 -0
- package/sync/ci-commands.mjs +3 -1
- package/sync/index.mjs +32 -8
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +4 -1
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -17
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +6 -8
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +4 -1
- package/templates/app/codegen-lite/scripts/codegen.mjs +2 -39
- package/templates/app/db-bootstrap/db/bootstrap.sql +139 -0
- package/templates/app/gitignore-lite +0 -3
- package/templates/app/package-scripts/db.json +9 -0
- package/templates/app/package-scripts/package.json +0 -1
- package/templates/app/package-scripts-lite/package.json +1 -1
- package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
- package/templates/app/skeleton/.starcistacks/production/README.md +1 -1
- package/templates/app/skeleton/README.md +1 -1
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +84 -19
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +5 -5
- package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +1 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +69 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -7
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +6 -6
- package/templates/app/skeleton-lite/.starcistacks/production/environment.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +2 -2
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +69 -6
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +17 -9
- package/templates/app/skeleton-lite/README.md +11 -11
- package/templates/app/skeleton-lite/{supabase → db}/migrations/__timestamp___baseline.sql +21 -1
- package/templates/app/tool-config-lite/prettierignore +0 -1
- package/templates/be/inbox/database-client.spec.ts.tpl +9 -5
- package/templates/be/inbox/database-client.ts.tpl +13 -4
- package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +47 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +3 -1
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +2 -2
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +6 -1
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +39 -7
- package/templates/be/inbox/platform/migration.sql.tpl +22 -0
- package/templates/be/inbox/service.spec.ts.tpl +2 -2
- package/templates/be/inbox/service.ts.tpl +1 -2
- package/templates/be/patterns/jobs/platform-lite/job-claim.service.spec.ts.tpl +156 -0
- package/templates/be/patterns/jobs/platform-lite/job-claim.service.ts.tpl +114 -0
- package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +51 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.service.spec.ts.tpl +62 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.service.ts.tpl +20 -0
- package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +35 -0
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +4 -1
- package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +16 -5
- package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +8 -8
- package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +3 -3
- package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +2 -2
- package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -7
- package/templates/be/skeleton/src/modules/platform/composition/composition.decorators.ts +3 -0
- package/templates/be/skeleton/src/modules/platform/composition/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +15 -1
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.ts +10 -9
- package/templates/be/skeleton/src/modules/platform/http-security/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +34 -18
- package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.ts +29 -8
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +80 -4
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.ts +39 -11
- package/templates/be/skeleton-lite/apps/api/Dockerfile +0 -1
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +9 -5
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +7 -4
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +4 -4
- package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +3 -3
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +9 -13
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +4 -2
- package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +2 -2
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +3 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +4 -4
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -13
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +25 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +9 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +35 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +23 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +29 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +63 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +11 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +32 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +69 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +37 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +36 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +59 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.functions.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +4 -2
- package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +19 -6
- package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +2 -2
- package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +2 -2
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +1 -1
- package/templates/be/table/entities.index.ts.tpl +4 -0
- package/templates/be/table/entity.ts.tpl +21 -0
- package/templates/be/table/index.ts.tpl +1 -0
- package/templates/be/table/module.ts.tpl +1 -1
- package/templates/be/table/service.api.ts.tpl +10 -11
- package/templates/be/table/service.ts.tpl +5 -6
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +30 -0
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +46 -14
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +31 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +2 -5
- package/db/local-migrate.mjs +0 -194
- package/db/reset.mjs +0 -125
- package/emit/db-types.mjs +0 -271
- package/runtime/scripts/api/supabase/database-dump.mjs +0 -5
- package/runtime/scripts/api/supabase/database-push.mjs +0 -5
- package/runtime/scripts/api/supabase/database-query.mjs +0 -5
- package/runtime/scripts/api/supabase/lib.mjs +0 -55
- package/runtime/scripts/api/supabase/migration-repair.mjs +0 -5
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +0 -50
- package/runtime/scripts/hfs/architecture/supabase.mjs +0 -226
- package/scaffold/supabase-ports.mjs +0 -26
- package/templates/app/package-scripts/supabase.json +0 -7
- package/templates/app/skeleton-lite/supabase/config.toml +0 -48
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +0 -5
- package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +0 -33
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +0 -33
- package/templates/be/inbox/sql.ts.tpl +0 -16
- package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +0 -33
- package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +0 -12
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -16
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +0 -6
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +0 -27
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +0 -24
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +0 -8
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +0 -17
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +0 -32
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +0 -24
- package/templates/be/table/rows.ts.tpl +0 -4
- package/templates/be/table/sql.api.ts.tpl +0 -7
- package/templates/be/table/sql.ts.tpl +0 -4
- package/templates/fe/auth/supabase-client.ts.tpl +0 -38
- package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +0 -11
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +0 -18
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +0 -19
- /package/templates/app/skeleton-lite/{supabase → db}/seed.sql +0 -0
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// supabase-ports.mjs - the local Supabase ports of one lite app, derived from its project name.
|
|
2
|
-
// The Supabase CLI default ports (54321-54329) belong to whichever stack starts first, so a scaffolded app never uses them: it
|
|
3
|
-
// writes its own port block into supabase/config.toml, one block of ten consecutive ports per project name, so two apps (and a
|
|
4
|
-
// proof run beside another stack) never contend. The same name always gets the same block.
|
|
5
|
-
|
|
6
|
-
/** The first port of the block range and how many blocks it holds (41000-44999); clear of the CLI defaults (543xx) and of the Next dev port. */
|
|
7
|
-
const RANGE_START = 41000;
|
|
8
|
-
const BLOCK_SIZE = 10;
|
|
9
|
-
const BLOCK_COUNT = 400;
|
|
10
|
-
/** The offsets inside one block, by config.toml section. */
|
|
11
|
-
const OFFSETS = Object.freeze({ shadow: 0, api: 1, db: 2, studio: 3, inbucket: 4, analytics: 7, pooler: 9 });
|
|
12
|
-
|
|
13
|
-
/** The template variables of the port block: `supabasePort<Name>` for every section above. */
|
|
14
|
-
export const SUPABASE_PORT_VARIABLES = Object.freeze(Object.keys(OFFSETS).map((key) => `supabasePort${key[0].toUpperCase()}${key.slice(1)}`));
|
|
15
|
-
|
|
16
|
-
/** FNV-1a over the project name: stable, dependency-free. */
|
|
17
|
-
const hash = (text) => [...text].reduce((value, char) => Math.imul(value ^ char.charCodeAt(0), 16777619) >>> 0, 2166136261);
|
|
18
|
-
|
|
19
|
-
/** The first port of the project's block. */
|
|
20
|
-
const supabasePortBase = (project) => RANGE_START + (hash(String(project)) % BLOCK_COUNT) * BLOCK_SIZE;
|
|
21
|
-
|
|
22
|
-
/** { supabasePortApi: '41231', ... }: the variables a skeleton file fills. */
|
|
23
|
-
export function supabasePortVars(project) {
|
|
24
|
-
const base = supabasePortBase(project);
|
|
25
|
-
return Object.fromEntries(Object.entries(OFFSETS).map(([key, offset]) => [`supabasePort${key[0].toUpperCase()}${key.slice(1)}`, String(base + offset)]));
|
|
26
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
project_id = "{{project}}"
|
|
2
|
-
|
|
3
|
-
[api]
|
|
4
|
-
enabled = true
|
|
5
|
-
port = {{supabasePortApi}}
|
|
6
|
-
schemas = []
|
|
7
|
-
extra_search_path = ["public", "extensions"]
|
|
8
|
-
max_rows = 1000
|
|
9
|
-
|
|
10
|
-
[db]
|
|
11
|
-
port = {{supabasePortDb}}
|
|
12
|
-
shadow_port = {{supabasePortShadow}}
|
|
13
|
-
major_version = 17
|
|
14
|
-
|
|
15
|
-
[db.pooler]
|
|
16
|
-
enabled = false
|
|
17
|
-
port = {{supabasePortPooler}}
|
|
18
|
-
|
|
19
|
-
[studio]
|
|
20
|
-
port = {{supabasePortStudio}}
|
|
21
|
-
|
|
22
|
-
[inbucket]
|
|
23
|
-
port = {{supabasePortInbucket}}
|
|
24
|
-
|
|
25
|
-
[analytics]
|
|
26
|
-
port = {{supabasePortAnalytics}}
|
|
27
|
-
|
|
28
|
-
[edge_runtime]
|
|
29
|
-
enabled = false
|
|
30
|
-
|
|
31
|
-
[storage]
|
|
32
|
-
enabled = true
|
|
33
|
-
file_size_limit = "50MiB"
|
|
34
|
-
|
|
35
|
-
[auth]
|
|
36
|
-
enabled = true
|
|
37
|
-
site_url = "http://127.0.0.1:3000"
|
|
38
|
-
additional_redirect_urls = ["http://127.0.0.1:3000/"]
|
|
39
|
-
enable_signup = false
|
|
40
|
-
jwt_expiry = 3600
|
|
41
|
-
|
|
42
|
-
[auth.email]
|
|
43
|
-
enable_signup = false
|
|
44
|
-
|
|
45
|
-
[auth.external.google]
|
|
46
|
-
enabled = false
|
|
47
|
-
client_id = "env(SUPABASE_AUTH_GOOGLE_CLIENT_ID)"
|
|
48
|
-
secret = "env(SUPABASE_AUTH_GOOGLE_SECRET)"
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { sql } from "@modules/platform/database"
|
|
2
|
-
|
|
3
|
-
/** Writes only the typed envelope; a reactor or job reads the committed row afterward. */
|
|
4
|
-
export const INSERT_OUTBOX_EVENT = sql`INSERT INTO public.event_bus (event_id, event_name, payload, correlation_id)
|
|
5
|
-
VALUES ($1, $2, $3::jsonb, $4)`
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/** The JSON envelope makes a completed null value distinct from a pending SQL-null outcome. */
|
|
2
|
-
export interface ClientOutcomeRow<T> {
|
|
3
|
-
/** The operation's complete JSON result, including expected refusals. */
|
|
4
|
-
readonly value: T
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
/** The row returned by the atomic client claim. */
|
|
8
|
-
export interface ClaimClientRow<T> {
|
|
9
|
-
/** The digest recorded by the first operation under this scoped key. */
|
|
10
|
-
readonly request_fingerprint: string | null
|
|
11
|
-
/** The saved result, or null only for a claim inserted by this transaction. */
|
|
12
|
-
readonly outcome: ClientOutcomeRow<T> | null
|
|
13
|
-
/** An existing row had no completed outcome, so it must never run an effect again. */
|
|
14
|
-
readonly incomplete: boolean
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** The persisted outcome returned to the first caller. */
|
|
18
|
-
export interface SavedClientOutcomeRow<T> {
|
|
19
|
-
/** The normalized JSON result as the database stores it. */
|
|
20
|
-
readonly outcome: ClientOutcomeRow<T>
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** A claimed or deleted delivery key returned by inbox persistence. */
|
|
24
|
-
export interface InboxEventRow {
|
|
25
|
-
/** The event or client key owned by the row. */
|
|
26
|
-
readonly event_id: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** The bounded retention batch's aggregate result. */
|
|
30
|
-
export interface InboxSweepRow {
|
|
31
|
-
/** Number of completed client claims removed by this transaction. */
|
|
32
|
-
readonly deleted_count: number
|
|
33
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { sql } from "@modules/platform/database"
|
|
2
|
-
|
|
3
|
-
/** The unique source and delivery id let only one transaction record or process each delivery. */
|
|
4
|
-
export const CLAIM_EVENT = sql`INSERT INTO public.inbox (source, event_id)
|
|
5
|
-
VALUES ($1, $2)
|
|
6
|
-
ON CONFLICT (source, event_id) DO NOTHING
|
|
7
|
-
RETURNING event_id`
|
|
8
|
-
|
|
9
|
-
/** A conflicting insert locks the existing row and returns its committed fingerprint and outcome after the winner commits. */
|
|
10
|
-
export const CLAIM_CLIENT = sql`INSERT INTO public.inbox (source, event_id, request_fingerprint, claimed_at)
|
|
11
|
-
VALUES ($1, $2, $3, $4)
|
|
12
|
-
ON CONFLICT (source, event_id) DO UPDATE SET outcome = COALESCE(inbox.outcome, '{}'::jsonb)
|
|
13
|
-
RETURNING request_fingerprint, outcome, (outcome IS NOT NULL AND (jsonb_typeof(outcome) <> 'object' OR NOT (outcome ? 'value'))) AS incomplete`
|
|
14
|
-
|
|
15
|
-
/** The JSON envelope distinguishes a completed null result from an uncompleted claim in this transaction. */
|
|
16
|
-
export const SAVE_CLIENT_OUTCOME = sql`WITH saved AS (
|
|
17
|
-
UPDATE public.inbox SET outcome = $3::jsonb
|
|
18
|
-
WHERE source = $1 AND event_id = $2
|
|
19
|
-
RETURNING outcome
|
|
20
|
-
)
|
|
21
|
-
SELECT outcome FROM saved LIMIT 1`
|
|
22
|
-
|
|
23
|
-
/** Retention never deletes delivery rows or an incomplete claim; its bounded batch skips rows held by active retries. */
|
|
24
|
-
export const SWEEP_CLIENTS = sql`WITH expired AS (
|
|
25
|
-
SELECT source, event_id FROM public.inbox
|
|
26
|
-
WHERE source LIKE 'client:%' AND request_fingerprint IS NOT NULL AND outcome IS NOT NULL AND jsonb_typeof(outcome) = 'object' AND outcome ? 'value' AND claimed_at < $1
|
|
27
|
-
ORDER BY claimed_at, source, event_id LIMIT $2 FOR UPDATE SKIP LOCKED
|
|
28
|
-
), deleted AS (
|
|
29
|
-
DELETE FROM public.inbox AS claims USING expired
|
|
30
|
-
WHERE claims.source = expired.source AND claims.event_id = expired.event_id
|
|
31
|
-
RETURNING claims.event_id
|
|
32
|
-
)
|
|
33
|
-
SELECT count(*)::int AS deleted_count FROM deleted`
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { sql } from "@modules/platform/database"
|
|
2
|
-
|
|
3
|
-
/** Records one pending @@provider@@ delivery and returns its durable inbox identity only to the winning transaction. */
|
|
4
|
-
export const INSERT_@@providerUpper@@_INBOX = sql`INSERT INTO public.@@table@@ (provider, delivery_id, payload)
|
|
5
|
-
VALUES ($1, $2, $3::jsonb)
|
|
6
|
-
ON CONFLICT (provider, delivery_id) DO NOTHING
|
|
7
|
-
RETURNING id`
|
|
8
|
-
|
|
9
|
-
/** The reactor or job marks a recorded delivery complete in the transaction that applies its effect. Never called by the intake. */
|
|
10
|
-
export const COMPLETE_@@providerUpper@@_INBOX = sql`WITH completed AS (
|
|
11
|
-
UPDATE public.@@table@@
|
|
12
|
-
SET processed_at = now()
|
|
13
|
-
WHERE id = $1
|
|
14
|
-
RETURNING id
|
|
15
|
-
)
|
|
16
|
-
SELECT id FROM completed`
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { sql } from "@modules/platform/database"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Claims a delivery in one statement ($1 kind, $2 job key, $3 worker, $4 lease end, $5 payload, $6 instant). A new key inserts the row
|
|
5
|
-
* with token 1; an existing key is claimed again only when the job failed or its lease expired, and then the token is bumped by the
|
|
6
|
-
* store. A done job and a live claim match nothing, so no row comes back.
|
|
7
|
-
*/
|
|
8
|
-
export const CLAIM_JOB = sql`INSERT INTO public.jobs (kind, job_key, status, fencing_token, claimed_by, lease_expires_at, payload, created_at, updated_at)
|
|
9
|
-
VALUES ($1, $2, 'running', 1, $3, $4, $5::jsonb, $6, $6)
|
|
10
|
-
ON CONFLICT (job_key) DO UPDATE
|
|
11
|
-
SET fencing_token = jobs.fencing_token + 1, claimed_by = $3, lease_expires_at = $4, status = 'running', error = NULL, updated_at = $6
|
|
12
|
-
WHERE jobs.status = 'failed' OR (jobs.status = 'running' AND jobs.lease_expires_at < $6)
|
|
13
|
-
RETURNING id, kind, fencing_token, current_step, payload`
|
|
14
|
-
|
|
15
|
-
/** The guarded writes wrap their UPDATE in a CTE: TypeORM returns a bare `UPDATE ... RETURNING` as `[rows, count]`, a SELECT as rows. */
|
|
16
|
-
|
|
17
|
-
/** Records a step ($1 job id, $2 expected token, $3 step, $4 instant); no row comes back when the token is stale. */
|
|
18
|
-
export const ADVANCE_JOB = sql`WITH changed AS (UPDATE public.jobs SET current_step = $3, updated_at = $4
|
|
19
|
-
WHERE id = $1 AND fencing_token = $2 AND status = 'running'
|
|
20
|
-
RETURNING id)
|
|
21
|
-
SELECT id FROM changed`
|
|
22
|
-
|
|
23
|
-
/** Completes the job ($1 job id, $2 expected token, $3 instant). */
|
|
24
|
-
export const COMPLETE_JOB = sql`WITH changed AS (UPDATE public.jobs SET status = 'done', lease_expires_at = NULL, updated_at = $3
|
|
25
|
-
WHERE id = $1 AND fencing_token = $2 AND status = 'running'
|
|
26
|
-
RETURNING id)
|
|
27
|
-
SELECT id FROM changed`
|
|
28
|
-
|
|
29
|
-
/** Fails the job ($1 job id, $2 expected token, $3 reason, $4 instant). */
|
|
30
|
-
export const FAIL_JOB = sql`WITH changed AS (UPDATE public.jobs SET status = 'failed', error = $3, lease_expires_at = NULL, updated_at = $4
|
|
31
|
-
WHERE id = $1 AND fencing_token = $2 AND status = 'running'
|
|
32
|
-
RETURNING id)
|
|
33
|
-
SELECT id FROM changed`
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { sql } from "@modules/platform/database"
|
|
2
|
-
|
|
3
|
-
/** Writes one job in the outbox ($1 queue, $2 payload, $3 instant, $4 correlation id); the relay hands it to BullMQ after the commit. */
|
|
4
|
-
export const INSERT_QUEUE_ROW = sql`INSERT INTO public.queue (queue, payload, created_at, correlation_id)
|
|
5
|
-
VALUES ($1, $2::jsonb, $3, $4)`
|
|
6
|
-
|
|
7
|
-
/** Locks and reads the oldest rows that wait ($1 batch size); a second relay skips the rows this one holds. */
|
|
8
|
-
export const SELECT_WAITING_ROWS = sql`SELECT id, queue, payload, coalesce(correlation_id, id::text) AS "correlationId" FROM public.queue
|
|
9
|
-
WHERE sent_at IS NULL ORDER BY created_at, id LIMIT $1 FOR UPDATE SKIP LOCKED`
|
|
10
|
-
|
|
11
|
-
/** Marks the rows as handed to BullMQ ($1 ids, $2 instant). */
|
|
12
|
-
export const MARK_ROWS_SENT = sql`UPDATE public.queue SET sent_at = $2 WHERE id = ANY($1::uuid[])`
|
package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { DomainError } from "@modules/platform/errors"
|
|
2
|
-
import type { ErrorKind } from "@modules/platform/errors"
|
|
3
|
-
|
|
4
|
-
/** Codes of the Supabase integration. */
|
|
5
|
-
export enum SupabaseErrorCode {
|
|
6
|
-
/** The access token failed signature or required-claim verification. */
|
|
7
|
-
AccessTokenInvalid = "SUPABASE_ACCESS_TOKEN_INVALID",
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** How each Supabase integration code travels. */
|
|
11
|
-
export const SUPABASE_ERROR_KINDS: Record<SupabaseErrorCode, ErrorKind> = {
|
|
12
|
-
[SupabaseErrorCode.AccessTokenInvalid]: "unauthenticated",
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** The one error class of the Supabase integration. */
|
|
16
|
-
export class SupabaseError extends DomainError<SupabaseErrorCode> {}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { createSupabaseAdminClient } from "./supabase.client"
|
|
2
|
-
export { parseSupabaseConfig } from "./supabase.config"
|
|
3
|
-
export { SUPABASE_ACCESS_TOKEN_VERIFIER, SUPABASE_ADMIN_CLIENT } from "./supabase.decorators"
|
|
4
|
-
export { SUPABASE_ERROR_KINDS, SupabaseErrorCode } from "./errors/supabase.error"
|
|
5
|
-
export { createSupabaseAccessTokenVerifier } from "./supabase.jwks"
|
|
6
|
-
export type { SupabaseOptions, VerifySupabaseAccessToken } from "./supabase.options"
|
package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Test } from "@nestjs/testing"
|
|
2
|
-
import { Secret } from "@modules/platform/config"
|
|
3
|
-
import { createSupabaseAdminClient } from "./supabase.client"
|
|
4
|
-
import type { SupabaseAdminClient } from "./supabase.client"
|
|
5
|
-
import { SUPABASE_ADMIN_CLIENT } from "./supabase.decorators"
|
|
6
|
-
|
|
7
|
-
describe("createSupabaseAdminClient", () => {
|
|
8
|
-
it("exposes only the Auth administration and Storage surfaces of a session-less service client", async () => {
|
|
9
|
-
const moduleRef = await Test.createTestingModule({
|
|
10
|
-
providers: [
|
|
11
|
-
{
|
|
12
|
-
provide: SUPABASE_ADMIN_CLIENT,
|
|
13
|
-
useFactory: () =>
|
|
14
|
-
createSupabaseAdminClient({
|
|
15
|
-
url: "http://127.0.0.1:54321",
|
|
16
|
-
serviceRoleKey: new Secret("service-role"),
|
|
17
|
-
}),
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
}).compile()
|
|
21
|
-
const client = moduleRef.get<SupabaseAdminClient>(SUPABASE_ADMIN_CLIENT)
|
|
22
|
-
|
|
23
|
-
expect(Object.keys(client).sort()).toEqual(["auth", "storage"])
|
|
24
|
-
expect(typeof client.auth.createUser).toBe("function")
|
|
25
|
-
expect(typeof client.storage.from).toBe("function")
|
|
26
|
-
})
|
|
27
|
-
})
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { createClient } from "@supabase/supabase-js"
|
|
2
|
-
import type { SupabaseClient } from "@supabase/supabase-js"
|
|
3
|
-
import type { Database } from "../../../../../supabase/types/database.types"
|
|
4
|
-
import type { SupabaseOptions } from "./supabase.options"
|
|
5
|
-
|
|
6
|
-
/** The privileged surface exposed by this integration: Auth administration and Storage, never database queries. */
|
|
7
|
-
export interface SupabaseAdminClient {
|
|
8
|
-
/** Supabase Auth administration. */
|
|
9
|
-
readonly auth: SupabaseClient<Database>["auth"]["admin"]
|
|
10
|
-
/** Supabase Storage administration. */
|
|
11
|
-
readonly storage: SupabaseClient<Database>["storage"]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Creates the back-end-only client and narrows it to Auth and Storage administration. */
|
|
15
|
-
export const createSupabaseAdminClient = (options: SupabaseOptions): SupabaseAdminClient => {
|
|
16
|
-
const client: SupabaseClient<Database> = createClient<Database>(options.url, options.serviceRoleKey.reveal(), {
|
|
17
|
-
auth: {
|
|
18
|
-
autoRefreshToken: false,
|
|
19
|
-
detectSessionInUrl: false,
|
|
20
|
-
persistSession: false,
|
|
21
|
-
},
|
|
22
|
-
})
|
|
23
|
-
return { auth: client.auth.admin, storage: client.storage }
|
|
24
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { EnvSource } from "@modules/platform/config"
|
|
2
|
-
import type { SupabaseOptions } from "./supabase.options"
|
|
3
|
-
|
|
4
|
-
/** Reads Supabase configuration with no credential or localhost defaults. */
|
|
5
|
-
export const parseSupabaseConfig = (env: EnvSource): SupabaseOptions => ({
|
|
6
|
-
url: env.url("SUPABASE_URL"),
|
|
7
|
-
serviceRoleKey: env.secret("SUPABASE_SERVICE_ROLE_KEY"),
|
|
8
|
-
})
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { injector } from "@modules/platform/composition"
|
|
2
|
-
import type { TypedParameterDecorator } from "@modules/platform/composition"
|
|
3
|
-
import type { SupabaseAdminClient } from "./supabase.client"
|
|
4
|
-
import type { VerifySupabaseAccessToken } from "./supabase.options"
|
|
5
|
-
|
|
6
|
-
/** Token of the integration's Auth and Storage administrator. */
|
|
7
|
-
export const SUPABASE_ADMIN_CLIENT: unique symbol = Symbol("integrations.supabase.admin-client")
|
|
8
|
-
/** Token of the cryptographic Supabase access-token verifier. */
|
|
9
|
-
export const SUPABASE_ACCESS_TOKEN_VERIFIER: unique symbol = Symbol("integrations.supabase.access-token-verifier")
|
|
10
|
-
|
|
11
|
-
/** Injects the Auth and Storage administrator. Parameter type: SupabaseAdminClient. */
|
|
12
|
-
export const InjectSupabaseAdminClient = (): TypedParameterDecorator<SupabaseAdminClient> =>
|
|
13
|
-
injector<SupabaseAdminClient>(SUPABASE_ADMIN_CLIENT)
|
|
14
|
-
|
|
15
|
-
/** Injects the access-token verifier. Parameter type: VerifySupabaseAccessToken. */
|
|
16
|
-
export const InjectSupabaseAccessTokenVerifier = (): TypedParameterDecorator<VerifySupabaseAccessToken> =>
|
|
17
|
-
injector<VerifySupabaseAccessToken>(SUPABASE_ACCESS_TOKEN_VERIFIER)
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { createRemoteJWKSet, jwtVerify } from "jose"
|
|
2
|
-
import { ok, refused } from "@modules/platform/primitives"
|
|
3
|
-
import { SupabaseErrorCode } from "./errors/supabase.error"
|
|
4
|
-
import type { SupabaseOptions, SupabasePrincipal, VerifySupabaseAccessToken } from "./supabase.options"
|
|
5
|
-
|
|
6
|
-
const AUDIENCE = "authenticated"
|
|
7
|
-
|
|
8
|
-
/** Creates the verifier for this project's remote JWKS, exact issuer, authenticated audience and pinned algorithm. */
|
|
9
|
-
export const createSupabaseAccessTokenVerifier = (options: SupabaseOptions): VerifySupabaseAccessToken => {
|
|
10
|
-
const issuer = `${options.url.replace(/\/$/, "")}/auth/v1`
|
|
11
|
-
const keys = createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`))
|
|
12
|
-
return async (token) => {
|
|
13
|
-
try {
|
|
14
|
-
const { payload } = await jwtVerify(token, keys, {
|
|
15
|
-
algorithms: ["ES256"],
|
|
16
|
-
audience: AUDIENCE,
|
|
17
|
-
issuer,
|
|
18
|
-
})
|
|
19
|
-
if (typeof payload.sub !== "string" || typeof payload.exp !== "number" || payload.role !== AUDIENCE) {
|
|
20
|
-
return refused(SupabaseErrorCode.AccessTokenInvalid)
|
|
21
|
-
}
|
|
22
|
-
const principal: SupabasePrincipal = {
|
|
23
|
-
id: payload.sub,
|
|
24
|
-
role: AUDIENCE,
|
|
25
|
-
...(typeof payload.email === "string" ? { email: payload.email } : {}),
|
|
26
|
-
}
|
|
27
|
-
return ok(principal)
|
|
28
|
-
} catch (cause) {
|
|
29
|
-
return refused(SupabaseErrorCode.AccessTokenInvalid, { cause: String(cause) })
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { Secret } from "@modules/platform/config"
|
|
2
|
-
import type { Outcome } from "@modules/platform/primitives"
|
|
3
|
-
import type { SupabaseErrorCode } from "./errors/supabase.error"
|
|
4
|
-
|
|
5
|
-
/** The verified identity claims admitted from a Supabase access token. */
|
|
6
|
-
export interface SupabasePrincipal {
|
|
7
|
-
/** The Auth user identifier from the verified `sub` claim. */
|
|
8
|
-
readonly id: string
|
|
9
|
-
/** The verified email claim when the provider issued one. */
|
|
10
|
-
readonly email?: string
|
|
11
|
-
/** Supabase's authenticated role, required by the verifier. */
|
|
12
|
-
readonly role: "authenticated"
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Verifies one bearer token and returns only claims that passed every cryptographic and semantic check. */
|
|
16
|
-
export type VerifySupabaseAccessToken = (token: string) => Promise<Outcome<SupabasePrincipal, SupabaseErrorCode>>
|
|
17
|
-
|
|
18
|
-
/** Supabase configuration parsed once at API boot. */
|
|
19
|
-
export interface SupabaseOptions {
|
|
20
|
-
/** The project origin, used for Auth, Storage, issuer and JWKS endpoints. */
|
|
21
|
-
readonly url: string
|
|
22
|
-
/** The service-role credential, held only by the back-end integration. */
|
|
23
|
-
readonly serviceRoleKey: Secret
|
|
24
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { sql } from "@modules/platform/database"
|
|
2
|
-
|
|
3
|
-
/** Reads one public.{{table}} row by id and owner through a branded SQL statement. */
|
|
4
|
-
export const FIND_{{upper}} = sql`SELECT id FROM public.{{tableSql}} WHERE id = $1 AND owner_id = $2 LIMIT 1`
|
|
5
|
-
|
|
6
|
-
/** Inserts one public.{{table}} row owned by the principal and returns its id. */
|
|
7
|
-
export const INSERT_{{upper}} = sql`INSERT INTO public.{{tableSql}} (owner_id) VALUES ($1) RETURNING id`
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
const sessionOf = (session: Session | null): AuthSession | undefined =>
|
|
2
|
-
session === null
|
|
3
|
-
? undefined
|
|
4
|
-
: { userId: session.user.id, ...(session.user.email === undefined ? {} : { email: session.user.email }) }
|
|
5
|
-
|
|
6
|
-
/** A Supabase Auth error as the provider reports it: its HTTP status, its code and its message. */
|
|
7
|
-
type ProviderError = {
|
|
8
|
-
readonly status: number | undefined
|
|
9
|
-
readonly code: string | undefined
|
|
10
|
-
readonly message: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** The failure kind of a Supabase Auth error: refused credentials, rejected input, or the provider unavailable. */
|
|
14
|
-
const kindOf = (status: number | undefined): string => {
|
|
15
|
-
if (status === 400 || status === 401 || status === 403) return "refused"
|
|
16
|
-
return status === 422 ? "invalid" : "unavailable"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** The failure of a Supabase Auth call, or undefined when it succeeded. */
|
|
20
|
-
const failureOf = (error: ProviderError | null): AuthFailure | undefined =>
|
|
21
|
-
error === null
|
|
22
|
-
? undefined
|
|
23
|
-
: { code: error.code ?? String(error.status ?? "auth"), kind: kindOf(error.status), message: error.message }
|
|
24
|
-
|
|
25
|
-
/** The Supabase Auth adapter shared by browser and cookie-backed PKCE flows. */
|
|
26
|
-
export const authClient: AuthClient = {
|
|
27
|
-
session: async () => sessionOf((await authOf().getSession()).data.session),
|
|
28
|
-
accessToken: async () => (await authOf().getSession()).data.session?.access_token,
|
|
29
|
-
refreshAccessToken: async () => (await authOf().refreshSession()).data.session?.access_token,
|
|
30
|
-
signInWithPassword: async (email, password) =>
|
|
31
|
-
failureOf((await authOf().signInWithPassword({ email, password })).error),
|
|
32
|
-
signOut: async () => failureOf((await authOf().signOut()).error),
|
|
33
|
-
// Supabase reports INITIAL_SESSION on subscribe, then every change: the subscription alone keeps the session current.
|
|
34
|
-
onChange: (listener) => {
|
|
35
|
-
const { data } = authOf().onAuthStateChange((_event, session) => listener(sessionOf(session)))
|
|
36
|
-
return () => data.subscription.unsubscribe()
|
|
37
|
-
},
|
|
38
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from "next/server"
|
|
2
|
-
import { exchangeAuthCode } from "@/modules/auth/server"
|
|
3
|
-
import { APP_ROUTES } from "@/modules/routes"
|
|
4
|
-
|
|
5
|
-
/** The opt-in Supabase PKCE callback only exchanges the code and redirects home. */
|
|
6
|
-
export const GET = async (request: Request): Promise<NextResponse> => {
|
|
7
|
-
const url = new URL(request.url)
|
|
8
|
-
const code = url.searchParams.get("code")
|
|
9
|
-
if (code) await exchangeAuthCode(code)
|
|
10
|
-
return NextResponse.redirect(new URL(APP_ROUTES.home, url.origin))
|
|
11
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { createBrowserClient } from "@supabase/ssr"
|
|
2
|
-
import type { Session, SupabaseClient } from "@supabase/supabase-js"
|
|
3
|
-
import { supabaseAnonKey, supabaseUrl } from "@/modules/config"
|
|
4
|
-
import type { AuthClient, AuthFailure, AuthSession } from "./contracts"
|
|
5
|
-
|
|
6
|
-
let client: SupabaseClient | undefined
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* The cookie-backed browser client of the opt-in server PKCE flow, used for Supabase Auth only.
|
|
10
|
-
* The callback exchanges the code; this client shares its verifier and session cookies. It never calls `.from()`, `.rpc()` or storage:
|
|
11
|
-
* data goes through modules/api to the back end, which verifies the access token.
|
|
12
|
-
*/
|
|
13
|
-
const supabase = (): SupabaseClient =>
|
|
14
|
-
(client ??= createBrowserClient(supabaseUrl(), supabaseAnonKey(), {
|
|
15
|
-
auth: { flowType: "pkce", detectSessionInUrl: false, persistSession: true, autoRefreshToken: true },
|
|
16
|
-
}))
|
|
17
|
-
|
|
18
|
-
// {{> fe/auth/supabase-client.ts.tpl}}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import "server-only"
|
|
2
|
-
import { createServerClient } from "@supabase/ssr"
|
|
3
|
-
import { cookies } from "next/headers"
|
|
4
|
-
import { supabaseAnonKey, supabaseUrl } from "@/modules/config"
|
|
5
|
-
|
|
6
|
-
/** The server auth owner reads the PKCE verifier and persists the exchanged session in cookies. */
|
|
7
|
-
export const exchangeAuthCode = async (code: string): Promise<void> => {
|
|
8
|
-
const cookieStore = await cookies()
|
|
9
|
-
const client = createServerClient(supabaseUrl(), supabaseAnonKey(), {
|
|
10
|
-
cookies: {
|
|
11
|
-
getAll: () => cookieStore.getAll(),
|
|
12
|
-
setAll: (values) => {
|
|
13
|
-
for (const { name, value, options } of values) cookieStore.set(name, value, options)
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
})
|
|
17
|
-
const { error } = await client.auth.exchangeCodeForSession(code)
|
|
18
|
-
if (error) throw error
|
|
19
|
-
}
|
|
File without changes
|