@supatype/cli 0.2.1 → 0.3.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +180 -143
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/api-config-cache.d.ts +34 -0
- package/dist/api-config-cache.d.ts.map +1 -0
- package/dist/api-config-cache.js +123 -0
- package/dist/api-config-cache.js.map +1 -0
- package/dist/cache-identity-scope.d.ts +38 -0
- package/dist/cache-identity-scope.d.ts.map +1 -0
- package/dist/cache-identity-scope.js +70 -0
- package/dist/cache-identity-scope.js.map +1 -0
- package/dist/cache-provider.d.ts +30 -0
- package/dist/cache-provider.d.ts.map +1 -0
- package/dist/cache-provider.js +60 -0
- package/dist/cache-provider.js.map +1 -0
- package/dist/cli-version-embedded.js +1 -1
- package/dist/commands/add.js +1 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/cache.d.ts +1 -1
- package/dist/commands/cache.js +3 -3
- package/dist/commands/cache.js.map +1 -1
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +2 -1
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +2 -1
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +61 -8
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +2 -1
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +6 -2
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +13 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +2 -1
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +52 -1
- package/dist/commands/push.js.map +1 -1
- package/dist/compose-services.d.ts.map +1 -1
- package/dist/compose-services.js +2 -1
- package/dist/compose-services.js.map +1 -1
- package/dist/dev-compose.d.ts.map +1 -1
- package/dist/dev-compose.js +18 -7
- package/dist/dev-compose.js.map +1 -1
- package/dist/engine-floor.d.ts +11 -1
- package/dist/engine-floor.d.ts.map +1 -1
- package/dist/engine-floor.js +49 -16
- package/dist/engine-floor.js.map +1 -1
- package/dist/functions-deno-types.d.ts +9 -0
- package/dist/functions-deno-types.d.ts.map +1 -1
- package/dist/functions-deno-types.js +45 -1
- package/dist/functions-deno-types.js.map +1 -1
- package/dist/functions-router-gen.d.ts.map +1 -1
- package/dist/functions-router-gen.js +113 -81
- package/dist/functions-router-gen.js.map +1 -1
- package/dist/kong-config.d.ts +1 -1
- package/dist/model-cache.d.ts +17 -0
- package/dist/model-cache.d.ts.map +1 -0
- package/dist/model-cache.js +45 -0
- package/dist/model-cache.js.map +1 -0
- package/dist/model-hooks.d.ts +5 -0
- package/dist/model-hooks.d.ts.map +1 -1
- package/dist/model-hooks.js +29 -1
- package/dist/model-hooks.js.map +1 -1
- package/dist/model-versioning.d.ts +80 -0
- package/dist/model-versioning.d.ts.map +1 -0
- package/dist/model-versioning.js +140 -0
- package/dist/model-versioning.js.map +1 -0
- package/dist/postgres-ctl.d.ts +55 -0
- package/dist/postgres-ctl.d.ts.map +1 -1
- package/dist/postgres-ctl.js +84 -8
- package/dist/postgres-ctl.js.map +1 -1
- package/dist/preview-config-check.d.ts +21 -0
- package/dist/preview-config-check.d.ts.map +1 -0
- package/dist/preview-config-check.js +47 -0
- package/dist/preview-config-check.js.map +1 -0
- package/dist/project-config.d.ts +182 -5
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +125 -8
- package/dist/project-config.js.map +1 -1
- package/dist/resolve-target.d.ts +9 -0
- package/dist/resolve-target.d.ts.map +1 -1
- package/dist/resolve-target.js.map +1 -1
- package/dist/rest-cache-admin.d.ts.map +1 -1
- package/dist/rest-cache-admin.js +6 -2
- package/dist/rest-cache-admin.js.map +1 -1
- package/dist/schema-ast-v2.d.ts +32 -1
- package/dist/schema-ast-v2.d.ts.map +1 -1
- package/dist/schema-ast-v2.js +14 -1
- package/dist/schema-ast-v2.js.map +1 -1
- package/dist/self-host-compose.d.ts +20 -1
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +181 -27
- package/dist/self-host-compose.js.map +1 -1
- package/dist/studio-admin-roles.d.ts +8 -1
- package/dist/studio-admin-roles.d.ts.map +1 -1
- package/dist/studio-admin-roles.js +16 -2
- package/dist/studio-admin-roles.js.map +1 -1
- package/dist/type-extractor.d.ts +7 -0
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +234 -3
- package/dist/type-extractor.js.map +1 -1
- package/package.json +2 -2
- package/src/api-config-cache.ts +142 -0
- package/src/cache-identity-scope.ts +92 -0
- package/src/cache-provider.ts +61 -0
- package/src/cli-version-embedded.ts +1 -1
- package/src/commands/add.ts +1 -1
- package/src/commands/cache.ts +3 -3
- package/src/commands/cloud.ts +2 -1
- package/src/commands/deploy.ts +2 -1
- package/src/commands/dev.ts +68 -7
- package/src/commands/diff.ts +2 -1
- package/src/commands/functions.ts +6 -2
- package/src/commands/init.ts +13 -9
- package/src/commands/migrate.ts +2 -1
- package/src/commands/push.ts +57 -1
- package/src/compose-services.ts +2 -1
- package/src/dev-compose.ts +18 -7
- package/src/engine-floor.ts +73 -16
- package/src/functions-deno-types.ts +48 -1
- package/src/functions-router-gen.ts +113 -81
- package/src/kong-config.ts +1 -1
- package/src/model-cache.ts +74 -0
- package/src/model-hooks.ts +26 -2
- package/src/model-versioning.ts +167 -0
- package/src/postgres-ctl.ts +103 -9
- package/src/preview-config-check.ts +52 -0
- package/src/project-config.ts +273 -11
- package/src/resolve-target.ts +11 -1
- package/src/rest-cache-admin.ts +8 -2
- package/src/schema-ast-v2.ts +47 -0
- package/src/self-host-compose.ts +204 -27
- package/src/studio-admin-roles.ts +16 -2
- package/src/type-extractor.ts +281 -2
- package/tests/api-config-cache-seed.test.ts +180 -0
- package/tests/cache-identity-scope.test.ts +61 -0
- package/tests/cache-provider-config.test.ts +132 -0
- package/tests/engine-floor.test.ts +43 -0
- package/tests/external-database-compose.test.ts +74 -3
- package/tests/field-masking-tier.test.ts +3 -3
- package/tests/fixtures/identity-scope-corpus.json +42 -0
- package/tests/functions-concurrency.test.ts +71 -0
- package/tests/init.test.ts +36 -2
- package/tests/model-cache-declaration.test.ts +229 -0
- package/tests/model-cache-manifest.test.ts +99 -0
- package/tests/model-hooks.test.ts +55 -0
- package/tests/model-versions.test.ts +229 -0
- package/tests/native-keyspace.test.ts +130 -0
- package/tests/preview-config-check.test.ts +104 -0
- package/tests/rest-cache-admin.test.ts +36 -0
- package/tests/runtime-contract.test.ts +176 -22
- package/tests/studio-admin-roles.test.ts +32 -0
- package/tests/type-extractor.test.ts +113 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/self-host-compose.ts
CHANGED
|
@@ -16,8 +16,10 @@ import {
|
|
|
16
16
|
} from "./project-config.js"
|
|
17
17
|
import { hasEngineOverride, hasStudioOverride, pinnedVersion, fetchLatestVersion, VERSION_PIN_LOCAL } from "./binary-cache.js"
|
|
18
18
|
import { buildKongDeclarative } from "./kong-config.js"
|
|
19
|
+
import { keyspaceInPostgres } from "./cache-provider.js"
|
|
19
20
|
import { readEnvFile } from "./env-file.js"
|
|
20
21
|
import { fieldMaskingTierFromProject, type FieldMaskingTier } from "./field-masking-tier.js"
|
|
22
|
+
import { projectHasVersionedModels } from "./model-versioning.js"
|
|
21
23
|
|
|
22
24
|
/** Env keys written when `versions` pins exist in supatype.config.ts. */
|
|
23
25
|
export const COMPOSE_PINNED_IMAGE_ENV_KEYS = [
|
|
@@ -34,8 +36,31 @@ export const COMPOSE_IMAGE_ENV_KEYS = [
|
|
|
34
36
|
"SUPATYPE_STUDIO_IMAGE",
|
|
35
37
|
"SUPATYPE_STORAGE_IMAGE",
|
|
36
38
|
"SUPATYPE_FUNCTIONS_WORKER_IMAGE",
|
|
39
|
+
"SUPATYPE_SEAWEEDFS_IMAGE",
|
|
37
40
|
] as const
|
|
38
41
|
|
|
42
|
+
/** Pinned by digest, because the registry this replaced proved a tag can be taken away. */
|
|
43
|
+
const SEAWEEDFS_IMAGE =
|
|
44
|
+
"chrislusf/seaweedfs:4.46@sha256:08d516132314207d10c8e37cbffc1f32b147d870169688734cc61c6231625b62"
|
|
45
|
+
|
|
46
|
+
/** Credentials the object store is configured with and the storage service is handed. */
|
|
47
|
+
/**
|
|
48
|
+
* The prefix Kong's ACME plugin stores its certificates under.
|
|
49
|
+
*
|
|
50
|
+
* Not a guess and not configurable: the plugin prefixes every key it writes
|
|
51
|
+
* with this and enforces it in its own `reserved_words.lua`, so it is the one
|
|
52
|
+
* durable thing in a keyspace that is otherwise a cache. Verified on a live
|
|
53
|
+
* instance — both ACME keys survived `kill -9` with only this prefix named
|
|
54
|
+
* durable, and a response-cache key beside them did not.
|
|
55
|
+
*/
|
|
56
|
+
const ACME_KEY_PREFIX = "kong_acme:"
|
|
57
|
+
|
|
58
|
+
const OBJECT_STORE_ACCESS_KEY = "supatype"
|
|
59
|
+
const OBJECT_STORE_SECRET_KEY = "supatype-secret"
|
|
60
|
+
|
|
61
|
+
/** Where the rendered SeaweedFS identity file sits, resolved from the project root like kong.yml. */
|
|
62
|
+
const SEAWEED_CONFIG_MOUNT = ".supatype/self-host/s3.json"
|
|
63
|
+
|
|
39
64
|
type DockerPinComponent = "engine" | "server" | "postgres"
|
|
40
65
|
|
|
41
66
|
/** Map a config version pin to a Docker Hub image reference. */
|
|
@@ -142,11 +167,36 @@ export async function schemaEngineImageForPush(
|
|
|
142
167
|
return dockerImageRef("engine", version)
|
|
143
168
|
}
|
|
144
169
|
|
|
170
|
+
/**
|
|
171
|
+
* SeaweedFS reads its identities from a file, so the stack's credentials are a generated artifact
|
|
172
|
+
* beside the compose file rather than two env vars inside it.
|
|
173
|
+
*
|
|
174
|
+
* There is deliberately no `anonymous` identity. Declaring one with `Read` would make every bucket
|
|
175
|
+
* world-readable and the per-bucket policy decorative, which is the opposite of what
|
|
176
|
+
* `applyPublicPolicy` is for: a bucket is public because its policy says so.
|
|
177
|
+
*/
|
|
178
|
+
export function renderSeaweedIdentities(): string {
|
|
179
|
+
const identities = {
|
|
180
|
+
identities: [
|
|
181
|
+
{
|
|
182
|
+
name: "supatype",
|
|
183
|
+
credentials: [
|
|
184
|
+
{ accessKey: OBJECT_STORE_ACCESS_KEY, secretKey: OBJECT_STORE_SECRET_KEY },
|
|
185
|
+
],
|
|
186
|
+
actions: ["Admin", "Read", "List", "Tagging", "Write"],
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
}
|
|
190
|
+
return `${JSON.stringify(identities, null, 2)}\n`
|
|
191
|
+
}
|
|
192
|
+
|
|
145
193
|
export interface SelfHostComposePaths {
|
|
146
194
|
dir: string
|
|
147
195
|
composePath: string
|
|
148
196
|
kongPath: string
|
|
149
197
|
nginxPath: string
|
|
198
|
+
/** SeaweedFS identities, rendered beside the compose file the server reads them through. */
|
|
199
|
+
s3ConfigPath: string
|
|
150
200
|
}
|
|
151
201
|
|
|
152
202
|
export function selfHostComposePaths(cwd: string): SelfHostComposePaths {
|
|
@@ -156,6 +206,7 @@ export function selfHostComposePaths(cwd: string): SelfHostComposePaths {
|
|
|
156
206
|
composePath: join(dir, "docker-compose.yml"),
|
|
157
207
|
kongPath: join(dir, "kong.yml"),
|
|
158
208
|
nginxPath: join(dir, "nginx.conf"),
|
|
209
|
+
s3ConfigPath: join(dir, "s3.json"),
|
|
159
210
|
}
|
|
160
211
|
}
|
|
161
212
|
|
|
@@ -286,6 +337,14 @@ export interface SelfHostComposeOptions {
|
|
|
286
337
|
* rules and for every project on `supatype/postgres`.
|
|
287
338
|
*/
|
|
288
339
|
fieldMaskingTier?: FieldMaskingTier
|
|
340
|
+
/**
|
|
341
|
+
* Whether any model declares `versions`, which puts the generated `draft` schema on the exposed
|
|
342
|
+
* list.
|
|
343
|
+
*
|
|
344
|
+
* Same reasoning as the tier above: only a caller that has loaded the schema can say, and left
|
|
345
|
+
* unset the list is today's, which is correct for every project with no versioned model.
|
|
346
|
+
*/
|
|
347
|
+
drafts?: boolean
|
|
289
348
|
}
|
|
290
349
|
|
|
291
350
|
export function renderSelfHostCompose(
|
|
@@ -296,6 +355,12 @@ export function renderSelfHostCompose(
|
|
|
296
355
|
const projectMount = projectMountPath(cwd)
|
|
297
356
|
const kongMount = kongMountPath(cwd)
|
|
298
357
|
const external = usesExternalDatabase(config)
|
|
358
|
+
// Both halves come from the caller because both need the schema loaded, which this renderer does
|
|
359
|
+
// not do. See `SelfHostComposeOptions`.
|
|
360
|
+
const restSchemas = apiSchemaList(config, {
|
|
361
|
+
...(options?.fieldMaskingTier !== undefined && { tier: options.fieldMaskingTier }),
|
|
362
|
+
drafts: options?.drafts === true,
|
|
363
|
+
})
|
|
299
364
|
const ownerUrl = ownerDatabaseUrl(config)
|
|
300
365
|
// the auth driver wants the `postgres://` spelling; an external URL is used as given.
|
|
301
366
|
const authUrl = external ? ownerUrl : ownerDatabaseUrl(config, "postgres")
|
|
@@ -345,12 +410,23 @@ ${studioService}
|
|
|
345
410
|
: ` ports:
|
|
346
411
|
- "9999:9999"
|
|
347
412
|
`
|
|
348
|
-
const
|
|
413
|
+
const seaweedPorts = devLocal
|
|
349
414
|
? ""
|
|
350
415
|
: ` ports:
|
|
351
|
-
- "
|
|
352
|
-
- "9001:9001"
|
|
416
|
+
- "8333:8333"
|
|
353
417
|
`
|
|
418
|
+
// One source for the credentials: the server is configured with them and the storage service is
|
|
419
|
+
// handed them, and a mismatch does not fail at start, it fails at the first upload.
|
|
420
|
+
const objectStoreBlock = ` seaweedfs:
|
|
421
|
+
# Identities come from a config file rather than env vars, and the anonymous identity is
|
|
422
|
+
# deliberately absent: a bucket is public because of its policy, never because the server is
|
|
423
|
+
# open. Written beside this file by the same generator, so the two cannot drift.
|
|
424
|
+
image: \${SUPATYPE_SEAWEEDFS_IMAGE:-${SEAWEEDFS_IMAGE}}
|
|
425
|
+
command: server -dir=/data -s3 -s3.port=8333 -s3.config=/etc/seaweedfs/s3.json
|
|
426
|
+
volumes:
|
|
427
|
+
- storage-data:/data
|
|
428
|
+
- ${SEAWEED_CONFIG_MOUNT}:/etc/seaweedfs/s3.json:ro
|
|
429
|
+
${seaweedPorts}`
|
|
354
430
|
const kongTlsEnv = tlsEnabled
|
|
355
431
|
? ` KONG_PROXY_LISTEN: "0.0.0.0:8000, 0.0.0.0:8443 ssl"
|
|
356
432
|
KONG_LUA_SSL_TRUSTED_CERTIFICATE: system
|
|
@@ -361,7 +437,16 @@ ${studioService}
|
|
|
361
437
|
? ` - "80:8000"
|
|
362
438
|
- "443:8443"`
|
|
363
439
|
: ` - "\${SUPATYPE_KONG_PORT:-18473}:8000"`
|
|
364
|
-
|
|
440
|
+
// The RESP server the response cache and Kong's ACME certificates use.
|
|
441
|
+
// Either a Valkey sidecar, or pg_keyspace inside the Postgres container --
|
|
442
|
+
// one stateful service instead of two.
|
|
443
|
+
const keyspaceInPg = keyspaceInPostgres(config)
|
|
444
|
+
const respHost = keyspaceInPg ? "db" : "valkey"
|
|
445
|
+
const valkeyBlock = keyspaceInPg
|
|
446
|
+
? ` # No \`valkey\` service: cache.provider is "pg_keyspace", so the RESP keyspace is
|
|
447
|
+
# served by the \`db\` container on :6379. See extensions/pg_keyspace in supatype/postgres.
|
|
448
|
+
`
|
|
449
|
+
: `
|
|
365
450
|
valkey:
|
|
366
451
|
image: \${SUPATYPE_VALKEY_IMAGE:-valkey/valkey:8-alpine}
|
|
367
452
|
command: ["valkey-server", "--appendonly", "yes"]
|
|
@@ -370,7 +455,9 @@ ${studioService}
|
|
|
370
455
|
volumes:
|
|
371
456
|
- valkey-data:/data
|
|
372
457
|
`
|
|
373
|
-
|
|
458
|
+
// With the keyspace inside Postgres, waiting on `db` is already what every
|
|
459
|
+
// other service does, so Kong needs no extra dependency of its own.
|
|
460
|
+
const kongValkeyDepends = keyspaceInPg ? "" : "\n - valkey"
|
|
374
461
|
const tlsHintComment = tlsEnabled
|
|
375
462
|
? ""
|
|
376
463
|
: ` # HTTPS is off. To enable automatic TLS (Let's Encrypt) for production, set in supatype.config.ts:
|
|
@@ -379,9 +466,8 @@ ${studioService}
|
|
|
379
466
|
`
|
|
380
467
|
// An external database is not ours to declare a volume for.
|
|
381
468
|
const volumesBlock = `volumes:
|
|
382
|
-
${external ? "" : " db-data:\n"}
|
|
383
|
-
valkey-data
|
|
384
|
-
`
|
|
469
|
+
${external ? "" : " db-data:\n"} storage-data:
|
|
470
|
+
${keyspaceInPg ? "" : " valkey-data:\n"}`
|
|
385
471
|
|
|
386
472
|
// `depends_on` for the services that talk to Postgres. With an external database there is no
|
|
387
473
|
// container to wait on, so the clause disappears entirely and each service retries on connect
|
|
@@ -426,6 +512,66 @@ ${dbDependency}`
|
|
|
426
512
|
: ""
|
|
427
513
|
const realtimeServerEnv = realtime ? " SUPATYPE_REALTIME_URL: http://realtime:4000" : ""
|
|
428
514
|
|
|
515
|
+
// pg_keyspace registers background workers and requests shared memory at
|
|
516
|
+
// postmaster start, so it has to be in `shared_preload_libraries` -- there is
|
|
517
|
+
// no runtime toggle. The image turns it on from the environment
|
|
518
|
+
// (SUPATYPE_KEYSPACE_ENABLED) and writes the configuration itself, which is
|
|
519
|
+
// why this does not restate the preload list: the entrypoint appends
|
|
520
|
+
// pg_keyspace to the image's own list, in the right place (before
|
|
521
|
+
// supatype_mask, so the mask stays outermost and the image's
|
|
522
|
+
// pg_keyspace.require_mask = on is satisfied rather than refusing to serve).
|
|
523
|
+
// Restating it here would be a copy that goes stale the first time the image
|
|
524
|
+
// adds a library.
|
|
525
|
+
//
|
|
526
|
+
// Durability is `ephemeral` for everything, with ACME the one exception.
|
|
527
|
+
// A response cache that went through the WAL would put this stack's busiest
|
|
528
|
+
// write on disk to keep a copy of something that expires in seconds; a
|
|
529
|
+
// certificate that did not survive `docker compose restart db` would be
|
|
530
|
+
// re-issued against Let's Encrypt's rate limits, or fail. So the default is
|
|
531
|
+
// the cheap one and the certificates are named.
|
|
532
|
+
//
|
|
533
|
+
// `kong_acme:` is not a guess. Kong's ACME plugin prefixes every key it
|
|
534
|
+
// stores with it and enforces that prefix in its own reserved_words.lua;
|
|
535
|
+
// proven on a live instance, where both ACME keys survived `kill -9` under
|
|
536
|
+
// this exact configuration and the response-cache key did not.
|
|
537
|
+
//
|
|
538
|
+
// It is set whether or not TLS is on today. The override costs nothing when
|
|
539
|
+
// no key matches it, and a stack that turns TLS on later should not need a
|
|
540
|
+
// database restart before its certificates are safe to store.
|
|
541
|
+
const durablePrefixes = config.cache?.durablePrefixes ?? []
|
|
542
|
+
const durabilityOverrides = [ACME_KEY_PREFIX, ...durablePrefixes]
|
|
543
|
+
.map((prefix) => `${prefix}=durable`)
|
|
544
|
+
.join(", ")
|
|
545
|
+
|
|
546
|
+
// Sizing is shared memory, reserved at postmaster start whether or not the
|
|
547
|
+
// cache is used, so the defaults are a self-hosted stack's floor rather than
|
|
548
|
+
// the extension's own (1M keys x 512 B reserves ~689 MiB per worker before
|
|
549
|
+
// rings and row cache). These are measured on a live PG16 build, not derived:
|
|
550
|
+
// this profile reserves ~235 MiB, of which ~16 MiB is a fixed row-cache
|
|
551
|
+
// directory that rowcache_mb does not describe and cannot be declined.
|
|
552
|
+
const keyspaceDbEnv = keyspaceInPg
|
|
553
|
+
? ` # The RESP keyspace, in place of a Valkey sidecar. The image writes
|
|
554
|
+
# /etc/postgresql-custom/pg_keyspace.conf from these before any server starts.
|
|
555
|
+
SUPATYPE_KEYSPACE_ENABLED: "1"
|
|
556
|
+
# Durable keys are rows in this database; without it they land in \`postgres\`.
|
|
557
|
+
SUPATYPE_KEYSPACE_DATABASE: \${POSTGRES_DB:-supatype}
|
|
558
|
+
SUPATYPE_KEYSPACE_DURABILITY: ephemeral
|
|
559
|
+
SUPATYPE_KEYSPACE_DURABILITY_OVERRIDES: "${durabilityOverrides}"
|
|
560
|
+
SUPATYPE_KEYSPACE_KEYS: "200000"
|
|
561
|
+
SUPATYPE_KEYSPACE_RING_MB: "16"
|
|
562
|
+
SUPATYPE_KEYSPACE_ROWCACHE_MB: "64"
|
|
563
|
+
`
|
|
564
|
+
: ""
|
|
565
|
+
|
|
566
|
+
// Not published to the host: the keyspace is reachable to the compose network
|
|
567
|
+
// (server, Kong) and nothing else. A RESP port on a public interface is an
|
|
568
|
+
// unauthenticated read of every cached response.
|
|
569
|
+
const keyspaceExpose = keyspaceInPg
|
|
570
|
+
? ` expose:
|
|
571
|
+
- "6379"
|
|
572
|
+
`
|
|
573
|
+
: ""
|
|
574
|
+
|
|
429
575
|
const dbServiceBlock = external
|
|
430
576
|
? ` # No \`db\` service: database.external points this stack at a Postgres it does not manage.
|
|
431
577
|
# Every service reads \${DATABASE_URL} from .env, and \`supatype db check\` reports what that
|
|
@@ -439,7 +585,7 @@ ${dbDependency}`
|
|
|
439
585
|
POSTGRES_DB: \${POSTGRES_DB:-supatype}
|
|
440
586
|
# Read by the image's init to password the \`authenticator\` role PostgREST connects as.
|
|
441
587
|
AUTHENTICATOR_PASSWORD: \${AUTHENTICATOR_PASSWORD:?AUTHENTICATOR_PASSWORD is missing from .env}
|
|
442
|
-
${dbPorts} volumes:
|
|
588
|
+
${keyspaceDbEnv}${dbPorts}${keyspaceExpose} volumes:
|
|
443
589
|
- db-data:/var/lib/postgresql/data
|
|
444
590
|
healthcheck:
|
|
445
591
|
# -h 127.0.0.1 forces TCP. Without it \`pg_isready\` uses the Unix socket, which the
|
|
@@ -477,7 +623,7 @@ ${dbServiceBlock} postgrest:
|
|
|
477
623
|
# Derived from schema.pg_schema (or schema.api_schemas). Hardcoding this is why choosing a
|
|
478
624
|
# non-public pg_schema used to give a correct push and an API that answered PGRST106 for
|
|
479
625
|
# everything: the engine moved and PostgREST was never told.
|
|
480
|
-
PGRST_DB_SCHEMA: "${
|
|
626
|
+
PGRST_DB_SCHEMA: "${restSchemas}"
|
|
481
627
|
PGRST_DB_ANON_ROLE: anon
|
|
482
628
|
PGRST_JWT_SECRET: \${JWT_SECRET:?JWT_SECRET is missing from .env}
|
|
483
629
|
PGRST_DB_EXTRA_SEARCH_PATH: public,extensions
|
|
@@ -491,10 +637,10 @@ ${dbDependency}
|
|
|
491
637
|
PORT: 5000
|
|
492
638
|
DATABASE_URL: "${ownerUrl}"
|
|
493
639
|
JWT_SECRET: \${JWT_SECRET:?JWT_SECRET is missing from .env}
|
|
494
|
-
S3_ENDPOINT: http://
|
|
640
|
+
S3_ENDPOINT: http://seaweedfs:8333
|
|
495
641
|
S3_REGION: us-east-1
|
|
496
|
-
S3_ACCESS_KEY:
|
|
497
|
-
S3_SECRET_KEY:
|
|
642
|
+
S3_ACCESS_KEY: ${OBJECT_STORE_ACCESS_KEY}
|
|
643
|
+
S3_SECRET_KEY: ${OBJECT_STORE_SECRET_KEY}
|
|
498
644
|
S3_FORCE_PATH_STYLE: "true"
|
|
499
645
|
${dbDependency}
|
|
500
646
|
functions-worker:
|
|
@@ -510,6 +656,10 @@ ${dbDependency}
|
|
|
510
656
|
# outside. One worker rather than two: a second container would cost a pod per project on
|
|
511
657
|
# cloud, for isolation the route boundary already provides.
|
|
512
658
|
SUPATYPE_HOOKS_ROOT: /project/hooks
|
|
659
|
+
# Every log line the worker writes carries this, and on cloud it is the label Loki indexes
|
|
660
|
+
# each project's logs under. Without it a line is collected and filed under no project, which
|
|
661
|
+
# is the same as losing it.
|
|
662
|
+
SUPATYPE_PROJECT_REF: ${JSON.stringify(config.project.name)}
|
|
513
663
|
PORT: "8001"
|
|
514
664
|
# In-compose loopback to Kong (not API_EXTERNAL_URL / localhost, unreachable from this container).
|
|
515
665
|
SUPATYPE_URL: http://kong:8000
|
|
@@ -573,7 +723,7 @@ ${serverPorts} volumes:
|
|
|
573
723
|
SUPATYPE_FUNCTIONS_WORKER_URL: http://functions-worker:8001
|
|
574
724
|
${realtimeServerEnv}
|
|
575
725
|
SUPATYPE_CONTROL_PLANE_URL: http://control-plane:8080
|
|
576
|
-
SUPATYPE_VALKEY_ADDR:
|
|
726
|
+
SUPATYPE_VALKEY_ADDR: ${respHost}:6379
|
|
577
727
|
${appEnv}
|
|
578
728
|
SUPATYPE_API_HOST: 0.0.0.0
|
|
579
729
|
SUPATYPE_API_PORT: 9999
|
|
@@ -606,9 +756,7 @@ ${appEnv}
|
|
|
606
756
|
SUPATYPE_DISABLE_SIGNUP: \${DISABLE_SIGNUP:-false}
|
|
607
757
|
${devLocal ? " STUDIO_OPEN_DEV: \"1\"\n" : ""}
|
|
608
758
|
depends_on:
|
|
609
|
-
${dbDependencyClause} valkey:
|
|
610
|
-
condition: service_started
|
|
611
|
-
postgrest:
|
|
759
|
+
${dbDependencyClause}${keyspaceInPg ? "" : " valkey:\n condition: service_started\n"} postgrest:
|
|
612
760
|
condition: service_started
|
|
613
761
|
storage:
|
|
614
762
|
condition: service_started
|
|
@@ -617,15 +765,7 @@ ${dbDependencyClause} valkey:
|
|
|
617
765
|
${realtimeDependency} control-plane:
|
|
618
766
|
condition: service_started
|
|
619
767
|
|
|
620
|
-
|
|
621
|
-
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
|
|
622
|
-
command: server /data --console-address ":9001"
|
|
623
|
-
environment:
|
|
624
|
-
MINIO_ROOT_USER: supatype
|
|
625
|
-
MINIO_ROOT_PASSWORD: supatype-secret
|
|
626
|
-
${minioPorts} volumes:
|
|
627
|
-
- minio-data:/data
|
|
628
|
-
|
|
768
|
+
${objectStoreBlock}
|
|
629
769
|
schema-engine:
|
|
630
770
|
image: \${SUPATYPE_ENGINE_IMAGE:-supatype/schema-engine:latest}
|
|
631
771
|
profiles: ["tools"]
|
|
@@ -642,6 +782,16 @@ ${dbDependency}${studioBlock}${valkeyBlock}${tlsHintComment} kong:
|
|
|
642
782
|
KONG_ADMIN_ACCESS_LOG: /dev/stdout
|
|
643
783
|
KONG_PROXY_ERROR_LOG: /dev/stderr
|
|
644
784
|
KONG_ADMIN_ERROR_LOG: /dev/stderr
|
|
785
|
+
# Cookies are not scoped by port, so every project a developer runs on localhost shares one
|
|
786
|
+
# cookie jar and every request through this gateway carries the lot, which overruns nginx's
|
|
787
|
+
# default buffers and answers "Request header or cookie too large" about cookies belonging to
|
|
788
|
+
# something else entirely.
|
|
789
|
+
#
|
|
790
|
+
# Kept equal to large_client_header_buffers in packages/studio/nginx.conf, where the reason
|
|
791
|
+
# for the two numbers is written down. Both hops need it and they need the same value: this
|
|
792
|
+
# one carries API calls, that one the page, and a request that clears one and fails the other
|
|
793
|
+
# is worse to diagnose than one that fails outright.
|
|
794
|
+
KONG_NGINX_HTTP_LARGE_CLIENT_HEADER_BUFFERS: "4 32k"
|
|
645
795
|
${kongTlsEnv} volumes:
|
|
646
796
|
- ${kongMount}:/etc/kong/kong.yml:ro
|
|
647
797
|
ports:
|
|
@@ -821,6 +971,21 @@ function resolveFieldMaskingTier(
|
|
|
821
971
|
return fieldMaskingTierFromProject(cwd, config)
|
|
822
972
|
}
|
|
823
973
|
|
|
974
|
+
/**
|
|
975
|
+
* Whether this project has a versioned model, unless the caller already knows.
|
|
976
|
+
*
|
|
977
|
+
* Same seam as the tier above, and the same reason: the renderer does not load the schema, so a
|
|
978
|
+
* caller that has can say, and `writeSelfHostCompose` answers for the callers that have not.
|
|
979
|
+
*/
|
|
980
|
+
function resolveDrafts(
|
|
981
|
+
cwd: string,
|
|
982
|
+
config: SupatypeProjectConfig,
|
|
983
|
+
options?: SelfHostComposeOptions,
|
|
984
|
+
): boolean {
|
|
985
|
+
if (options?.drafts !== undefined) return options.drafts
|
|
986
|
+
return projectHasVersionedModels(cwd, config)
|
|
987
|
+
}
|
|
988
|
+
|
|
824
989
|
export function writeSelfHostCompose(
|
|
825
990
|
cwd: string,
|
|
826
991
|
config: SupatypeProjectConfig,
|
|
@@ -831,6 +996,7 @@ export function writeSelfHostCompose(
|
|
|
831
996
|
const resolved: SelfHostComposeOptions = {
|
|
832
997
|
...options,
|
|
833
998
|
...(tier !== undefined && { fieldMaskingTier: tier }),
|
|
999
|
+
drafts: resolveDrafts(cwd, config, options),
|
|
834
1000
|
}
|
|
835
1001
|
assertExternalUrlReachableFromContainers(config)
|
|
836
1002
|
const paths = selfHostComposePaths(cwd)
|
|
@@ -838,6 +1004,7 @@ export function writeSelfHostCompose(
|
|
|
838
1004
|
ensureProjectFunctionsDir(cwd, config)
|
|
839
1005
|
ensureComposeManifest(cwd, config)
|
|
840
1006
|
writeFileSync(paths.composePath, renderSelfHostCompose(config, cwd, resolved), "utf8")
|
|
1007
|
+
writeFileSync(paths.s3ConfigPath, renderSeaweedIdentities(), "utf8")
|
|
841
1008
|
const studioHostDev = options?.devLocal === true && hasStudioOverride(config)
|
|
842
1009
|
const tlsEnabled = selfHostTlsEnabled(config, options?.devLocal === true)
|
|
843
1010
|
const domain = config.server.domain?.trim()
|
|
@@ -850,8 +1017,18 @@ export function writeSelfHostCompose(
|
|
|
850
1017
|
studioServiceUrl: COMPOSE_STUDIO_HOST_URL,
|
|
851
1018
|
studioStripPath: false,
|
|
852
1019
|
}),
|
|
1020
|
+
// Kong stores its certificates wherever the RESP server is. With
|
|
1021
|
+
// pg_keyspace that is the database container, and they are durable
|
|
1022
|
+
// there because the keyspace defaults to durable and nothing names
|
|
1023
|
+
// Kong's keys as an exception.
|
|
853
1024
|
...(tlsEnabled && domain && acmeEmail
|
|
854
|
-
? {
|
|
1025
|
+
? {
|
|
1026
|
+
acme: {
|
|
1027
|
+
email: acmeEmail,
|
|
1028
|
+
domain,
|
|
1029
|
+
redisHost: keyspaceInPostgres(config) ? "db" : "valkey",
|
|
1030
|
+
},
|
|
1031
|
+
}
|
|
855
1032
|
: {}),
|
|
856
1033
|
}),
|
|
857
1034
|
"utf8",
|
|
@@ -9,8 +9,22 @@ export function studioAdminRoles(cfg: SupatypeProjectConfig): string[] {
|
|
|
9
9
|
return [...DEFAULT_STUDIO_ADMIN_ROLES]
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Merge the project's own settings into engine admin-config JSON for Studio and supatype-server.
|
|
14
|
+
*
|
|
15
|
+
* The engine derives everything it can from the schema. These two cannot be derived from it: which
|
|
16
|
+
* JWT roles may open Studio, and where the project's front end renders each model. Both are facts
|
|
17
|
+
* about a deployment rather than about a shape, so they live in `supatype.config.ts` and are folded
|
|
18
|
+
* in here, at the one place the file is written.
|
|
19
|
+
*/
|
|
13
20
|
export function withAdminRoles(admin: unknown, cfg: SupatypeProjectConfig): Record<string, unknown> {
|
|
14
21
|
const base = typeof admin === "object" && admin !== null ? (admin as Record<string, unknown>) : {}
|
|
15
|
-
|
|
22
|
+
const livePreview = cfg.admin?.livePreview
|
|
23
|
+
return {
|
|
24
|
+
...base,
|
|
25
|
+
adminRoles: studioAdminRoles(cfg),
|
|
26
|
+
// Omitted rather than written empty: Studio treats an absent entry as "this project has not
|
|
27
|
+
// said", which is what drives the message asking for it.
|
|
28
|
+
...(livePreview !== undefined && Object.keys(livePreview).length > 0 && { livePreview }),
|
|
29
|
+
}
|
|
16
30
|
}
|