@ankhorage/infra 1.1.0 → 2.0.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/CHANGELOG.md +12 -0
- package/README.md +4 -1
- package/dist/adapters/minikube/auth/oauthRuntime.js +6 -12
- package/dist/adapters/minikube/auth/oauthRuntime.js.map +1 -1
- package/dist/adapters/minikube/index.d.ts.map +1 -1
- package/dist/adapters/minikube/index.js +13 -5
- package/dist/adapters/minikube/index.js.map +1 -1
- package/dist/generatedApiDatabase.d.ts +20 -0
- package/dist/generatedApiDatabase.d.ts.map +1 -0
- package/dist/generatedApiDatabase.js +343 -0
- package/dist/generatedApiDatabase.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/project.d.ts +1 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +3 -0
- package/dist/project.js.map +1 -1
- package/dist/testSupport.d.ts.map +1 -1
- package/dist/testSupport.js +1 -0
- package/dist/testSupport.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/minikube/auth/oauthRuntime.ts +6 -12
- package/src/adapters/minikube/auth/supabase/oauthRuntime.test.ts +17 -6
- package/src/adapters/minikube/index.ts +18 -5
- package/src/generatedApiDatabase.test.ts +355 -0
- package/src/generatedApiDatabase.ts +558 -0
- package/src/index.ts +6 -2
- package/src/project.ts +3 -1
- package/src/testSupport.ts +1 -0
- package/src/types.ts +1 -1
- package/dist/apiArtifacts.d.ts +0 -8
- package/dist/apiArtifacts.d.ts.map +0 -1
- package/dist/apiArtifacts.js +0 -35
- package/dist/apiArtifacts.js.map +0 -1
- package/dist/apis.d.ts +0 -11
- package/dist/apis.d.ts.map +0 -1
- package/dist/apis.js +0 -354
- package/dist/apis.js.map +0 -1
- package/src/apiArtifacts.test.ts +0 -54
- package/src/apiArtifacts.ts +0 -107
- package/src/apis.test.ts +0 -212
- package/src/apis.ts +0 -534
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @ankhorage/infra
|
|
2
2
|
|
|
3
|
+
## 2.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6ff65a4: Keep Supabase Auth runtime environment assignments declarative across OAuth reconciliation so repeated Minikube Infra Up runs can safely reapply the generated deployment.
|
|
8
|
+
|
|
9
|
+
## 2.0.0
|
|
10
|
+
|
|
11
|
+
### Major Changes
|
|
12
|
+
|
|
13
|
+
- a4dda40: Consume the canonical generated API desired-state contract, generate deterministic Supabase/Postgres database artifacts, and remove the obsolete generated API handler/OpenAPI artifact surface.
|
|
14
|
+
|
|
3
15
|
## 1.1.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# INFRA
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Executable infra provider and standalone CLI for Ankhorage project workflows.
|
|
9
9
|
|
|
@@ -57,3 +57,6 @@ bunx @ankhorage/infra
|
|
|
57
57
|
- [Export graph](././paradox/diagrams/export-graph.mmd)
|
|
58
58
|
- [ankhorage-infra sequence](././paradox/diagrams/sequences/ankhorage-infra.mmd)
|
|
59
59
|
- [createInfraSecretStoreAdapter sequence](././paradox/diagrams/sequences/create-infra-secret-store-adapter.mmd)
|
|
60
|
+
- [getLocalAuthRedirectPatterns sequence](././paradox/diagrams/sequences/get-local-auth-redirect-patterns.mmd)
|
|
61
|
+
- [normalizeAuthCallbackRoute sequence](././paradox/diagrams/sequences/normalize-auth-callback-route.mmd)
|
|
62
|
+
- [resolveAuthRedirectConfiguration sequence](././paradox/diagrams/sequences/resolve-auth-redirect-configuration.mmd)
|
|
@@ -78,9 +78,6 @@ function getOAuthProviderLifecycle(oauthRuntime) {
|
|
|
78
78
|
}
|
|
79
79
|
function getOAuthRuntimeReconciliationCommand(oauthRuntime) {
|
|
80
80
|
const localPatterns = oauthRuntime.localRedirectPatterns.join(',');
|
|
81
|
-
const providerRedirectAssignments = oauthRuntime.envPrefixes
|
|
82
|
-
.map((prefix) => `GOTRUE_EXTERNAL_${prefix}_REDIRECT_URI="\${oauth_provider_callback}"`)
|
|
83
|
-
.join(' ');
|
|
84
81
|
const providerExports = oauthRuntime.envPrefixes
|
|
85
82
|
.map((prefix) => `export GOTRUE_EXTERNAL_${prefix}_REDIRECT_URI="\${oauth_provider_callback}"
|
|
86
83
|
write_env_value GOTRUE_EXTERNAL_${prefix}_REDIRECT_URI "\${oauth_provider_callback}"`)
|
|
@@ -96,12 +93,7 @@ fi
|
|
|
96
93
|
export ADDITIONAL_REDIRECT_URLS="\${oauth_redirect_allow_list}"
|
|
97
94
|
write_env_value ADDITIONAL_REDIRECT_URLS "\${oauth_redirect_allow_list}"
|
|
98
95
|
${providerExports}
|
|
99
|
-
|
|
100
|
-
API_EXTERNAL_URL="\${API_EXTERNAL_URL}" \\
|
|
101
|
-
GOTRUE_SITE_URL="\${SITE_URL}" \\
|
|
102
|
-
GOTRUE_URI_ALLOW_LIST="\${oauth_redirect_allow_list}" \\
|
|
103
|
-
GOTRUE_JWT_ISSUER="\${API_EXTERNAL_URL}" \\
|
|
104
|
-
${providerRedirectAssignments} >/dev/null
|
|
96
|
+
sync_supabase_secrets
|
|
105
97
|
kubectl --context "\${PROFILE}" -n supabase rollout restart deployment/auth >/dev/null
|
|
106
98
|
kubectl --context "\${PROFILE}" -n supabase rollout status deployment/auth --timeout=600s`;
|
|
107
99
|
}
|
|
@@ -136,9 +128,11 @@ Local Minikube reconciliation adds only callback-scoped loopback patterns:
|
|
|
136
128
|
${localPatterns}
|
|
137
129
|
|
|
138
130
|
Native callback URIs may be supplied through \`OAUTH_NATIVE_REDIRECT_URLS\`. The generated
|
|
139
|
-
provider lifecycle
|
|
140
|
-
|
|
141
|
-
seconds for the rollout.
|
|
131
|
+
provider lifecycle persists redirect metadata, re-syncs the canonical Supabase runtime
|
|
132
|
+
ConfigMap and Secret sources, then forces \`deployment/auth\` to restart and waits up to 600
|
|
133
|
+
seconds for the rollout. OAuth credential values remain sourced from trusted secret resolution
|
|
134
|
+
and are never embedded in generated files. A failed rollout stops Infra Up before its success
|
|
135
|
+
message.
|
|
142
136
|
`;
|
|
143
137
|
}
|
|
144
138
|
//# sourceMappingURL=oauthRuntime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauthRuntime.js","sourceRoot":"","sources":["../../../../src/adapters/minikube/auth/oauthRuntime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAUlG,MAAM,UAAU,qCAAqC,CACnD,QAA4B;IAE5B,MAAM,YAAY,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IACxD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO;YACL,KAAK,EAAE,EAAE;YACT,SAAS,EAAE,EAAE;YACb,iBAAiB,EAAE,EAAE;YACrB,UAAU,EAAE,EAAE;YACd,QAAQ,EAAE,EAAE;SACb,CAAC;IACJ,CAAC;IAED,OAAO;QACL,KAAK,EAAE;YACL;gBACE,IAAI,EAAE,sCAAsC;gBAC5C,OAAO,EAAE,oBAAoB,CAAC,YAAY,CAAC;aAC5C;SACF;QACD,SAAS,EAAE,EAAE;QACb,iBAAiB,EAAE,CAAC,yBAAyB,CAAC,YAAY,CAAC,CAAC;QAC5D,UAAU,EAAE,EAAE;QACd,QAAQ,EAAE,EAAE;KACb,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAAC,QAA4B;IAC5D,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACnC,IAAI,CAAC,KAAK,EAAE,OAAO;QAAE,OAAO,IAAI,CAAC;IAEjC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC;IACnF,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAExC,MAAM,aAAa,GAAG,0BAA0B,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACtE,MAAM,WAAW,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC7C,MAAM,MAAM,GAAG,QAAQ,CAAC,EAAE;aACvB,IAAI,EAAE;aACN,WAAW,EAAE;aACb,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;aAC5B,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,mBAAmB,QAAQ,CAAC,EAAE,gDAAgD,CAC/E,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,aAAa;QACb,WAAW;QACX,qBAAqB,EAAE,4BAA4B,CAAC,aAAa,CAAC;KACnE,CAAC;AACJ,CAAC;AAED,SAAS,yBAAyB,CAChC,YAAuC;IAEvC,OAAO;QACL,EAAE,EAAE,eAAe;QACnB,SAAS,EAAE,UAAU;QACrB,SAAS,EAAE,EAAE;QACb,eAAe,EAAE;YACf;gBACE,KAAK,EAAE,QAAQ;gBACf,SAAS,EAAE,UAAU;gBACrB,QAAQ,EAAE,iBAAiB;gBAC3B,cAAc,EAAE,GAAG;aACpB;SACF;QACD,iBAAiB,EAAE,EAAE;QACrB,sBAAsB,EAAE;YACtB;gBACE,KAAK,EAAE,mDAAmD;gBAC1D,OAAO,EAAE,oCAAoC,CAAC,YAAY,CAAC;aAC5D;SACF;QACD,YAAY,EAAE;YACZ;gBACE,KAAK,EAAE,8BAA8B;gBACrC,OAAO,EAAE,4BAA4B,CAAC,YAAY,CAAC;aACpD;SACF;KACF,CAAC;AACJ,CAAC;AAED,SAAS,oCAAoC,CAAC,YAAuC;IACnF,MAAM,aAAa,GAAG,YAAY,CAAC,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnE,MAAM,
|
|
1
|
+
{"version":3,"file":"oauthRuntime.js","sourceRoot":"","sources":["../../../../src/adapters/minikube/auth/oauthRuntime.ts"],"names":[],"mappings":"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","sourcesContent":["import { getLocalAuthRedirectPatterns, normalizeAuthCallbackRoute } from '../../../authRedirects';\nimport type { InfraManifestInput } from '../../../types';\nimport type { MinikubeAdapterArtifacts, MinikubeProviderLifecycle } from '../contracts';\n\ninterface SupabaseOAuthRuntimeModel {\n callbackRoute: string;\n envPrefixes: readonly string[];\n localRedirectPatterns: readonly string[];\n}\n\nexport function generateSupabaseOAuthRuntimeArtifacts(\n manifest: InfraManifestInput,\n): MinikubeAdapterArtifacts {\n const oauthRuntime = resolveOAuthRuntimeModel(manifest);\n if (!oauthRuntime) {\n return {\n files: [],\n resources: [],\n providerLifecycle: [],\n envEntries: [],\n warnings: [],\n };\n }\n\n return {\n files: [\n {\n path: 'infra/minikube/auth/oauth-runtime.md',\n content: getOAuthRuntimeGuide(oauthRuntime),\n },\n ],\n resources: [],\n providerLifecycle: [getOAuthProviderLifecycle(oauthRuntime)],\n envEntries: [],\n warnings: [],\n };\n}\n\nfunction resolveOAuthRuntimeModel(manifest: InfraManifestInput): SupabaseOAuthRuntimeModel | null {\n const oauth = manifest.auth?.oauth;\n if (!oauth?.enabled) return null;\n\n const providers = oauth.providers.filter((provider) => provider.enabled !== false);\n if (providers.length === 0) return null;\n\n const callbackRoute = normalizeAuthCallbackRoute(oauth.callbackRoute);\n const envPrefixes = providers.map((provider) => {\n const prefix = provider.id\n .trim()\n .toUpperCase()\n .replace(/[^A-Z0-9]+/gu, '_')\n .replace(/^_+|_+$/gu, '');\n if (!prefix) {\n throw new Error(\n `OAuth provider \"${provider.id}\" cannot be mapped to GoTrue environment keys.`,\n );\n }\n return prefix;\n });\n\n return {\n callbackRoute,\n envPrefixes,\n localRedirectPatterns: getLocalAuthRedirectPatterns(callbackRoute),\n };\n}\n\nfunction getOAuthProviderLifecycle(\n oauthRuntime: SupabaseOAuthRuntimeModel,\n): MinikubeProviderLifecycle {\n return {\n id: 'supabase-auth',\n namespace: 'supabase',\n endpoints: [],\n readinessChecks: [\n {\n label: 'GoTrue',\n namespace: 'supabase',\n resource: 'deployment/auth',\n timeoutSeconds: 600,\n },\n ],\n migrationCommands: [],\n reconciliationCommands: [\n {\n label: 'OAuth redirect and runtime rollout reconciliation',\n command: getOAuthRuntimeReconciliationCommand(oauthRuntime),\n },\n ],\n statusChecks: [\n {\n label: 'OAuth redirect configuration',\n command: getOAuthRuntimeStatusCommand(oauthRuntime),\n },\n ],\n };\n}\n\nfunction getOAuthRuntimeReconciliationCommand(oauthRuntime: SupabaseOAuthRuntimeModel): string {\n const localPatterns = oauthRuntime.localRedirectPatterns.join(',');\n const providerExports = oauthRuntime.envPrefixes\n .map(\n (prefix) => `export GOTRUE_EXTERNAL_${prefix}_REDIRECT_URI=\"\\${oauth_provider_callback}\"\nwrite_env_value GOTRUE_EXTERNAL_${prefix}_REDIRECT_URI \"\\${oauth_provider_callback}\"`,\n )\n .join('\\n');\n\n return `oauth_callback_route='${oauthRuntime.callbackRoute}'\noauth_callback_path=\"\\${oauth_callback_route#/}\"\noauth_site_url=\"\\${SITE_URL%/}\"\noauth_provider_callback=\"\\${API_EXTERNAL_URL%/}/callback\"\noauth_redirect_allow_list=\"\\${oauth_site_url},\\${oauth_site_url}/\\${oauth_callback_path},${localPatterns}\"\nif [[ -n \"\\${OAUTH_NATIVE_REDIRECT_URLS:-}\" ]]; then\n oauth_redirect_allow_list=\"\\${oauth_redirect_allow_list},\\${OAUTH_NATIVE_REDIRECT_URLS}\"\nfi\nexport ADDITIONAL_REDIRECT_URLS=\"\\${oauth_redirect_allow_list}\"\nwrite_env_value ADDITIONAL_REDIRECT_URLS \"\\${oauth_redirect_allow_list}\"\n${providerExports}\nsync_supabase_secrets\nkubectl --context \"\\${PROFILE}\" -n supabase rollout restart deployment/auth >/dev/null\nkubectl --context \"\\${PROFILE}\" -n supabase rollout status deployment/auth --timeout=600s`;\n}\n\nfunction getOAuthRuntimeStatusCommand(oauthRuntime: SupabaseOAuthRuntimeModel): string {\n return `oauth_callback_route='${oauthRuntime.callbackRoute}'\noauth_callback_path=\"\\${oauth_callback_route#/}\"\nif [[ -n \"\\${API_EXTERNAL_URL:-}\" ]]; then\n echo \"- provider supabase-auth/provider-callback: \\${API_EXTERNAL_URL%/}/callback\"\nelse\n echo \"- provider supabase-auth/provider-callback: unavailable\"\nfi\nif [[ -n \"\\${SITE_URL:-}\" ]]; then\n echo \"- provider supabase-auth/app-callback: \\${SITE_URL%/}/\\${oauth_callback_path}\"\nelse\n echo \"- provider supabase-auth/app-callback: unavailable\"\nfi\necho \"- provider supabase-auth/local-callback-patterns: ${oauthRuntime.localRedirectPatterns.join(',')}\"`;\n}\n\nfunction getOAuthRuntimeGuide(oauthRuntime: SupabaseOAuthRuntimeModel): string {\n const localPatterns = oauthRuntime.localRedirectPatterns\n .map((pattern) => `- \\`${pattern}\\``)\n .join('\\n');\n\n return `# Supabase OAuth Runtime Reconciliation\n\nThe provider callback is derived from the active project gateway as\n\\`\\${API_EXTERNAL_URL%/}/callback\\`, which resolves to the project-owned\n\\`/auth/v1/callback\\` endpoint. GoTrue-to-app redirects use the configured callback route\n\\`${oauthRuntime.callbackRoute}\\`.\n\nLocal Minikube reconciliation adds only callback-scoped loopback patterns:\n\n${localPatterns}\n\nNative callback URIs may be supplied through \\`OAUTH_NATIVE_REDIRECT_URLS\\`. The generated\nprovider lifecycle persists redirect metadata, re-syncs the canonical Supabase runtime\nConfigMap and Secret sources, then forces \\`deployment/auth\\` to restart and waits up to 600\nseconds for the rollout. OAuth credential values remain sourced from trusted secret resolution\nand are never embedded in generated files. A failed rollout stops Infra Up before its success\nmessage.\n`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/minikube/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,kBAAkB,EAClB,+BAA+B,EAC/B,8BAA8B,EAC/B,MAAM,aAAa,CAAC;AAUrB,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,GAAE,+BAAoC,GAC5C,8BAA8B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/minikube/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,kBAAkB,EAClB,+BAA+B,EAC/B,8BAA8B,EAC/B,MAAM,aAAa,CAAC;AAUrB,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,GAAE,+BAAoC,GAC5C,8BAA8B,CAkGhC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { generateGeneratedApiDatabaseArtifacts } from '../../generatedApiDatabase';
|
|
2
2
|
import { generateAuthProviderArtifacts } from './auth';
|
|
3
3
|
import { generateAuthorizationArtifacts } from './authz';
|
|
4
4
|
import { APP_NAMESPACE, generateMinikubeBaseArtifacts } from './base';
|
|
@@ -23,10 +23,16 @@ export function generateMinikubeInfra(manifest, options = {}) {
|
|
|
23
23
|
});
|
|
24
24
|
const storageArtifacts = generateStorageArtifacts({ manifest, namespace });
|
|
25
25
|
const secretStoreArtifacts = generateSecretStoreArtifacts({ manifest, namespace });
|
|
26
|
-
const
|
|
27
|
-
|
|
26
|
+
const generatedApiDatabaseArtifacts = generateGeneratedApiDatabaseArtifacts({
|
|
27
|
+
generatedApis: options.appManifest?.generatedApis,
|
|
28
28
|
databaseProvider: manifest.database?.provider,
|
|
29
29
|
});
|
|
30
|
+
const generatedApiErrors = generatedApiDatabaseArtifacts.diagnostics.filter((diagnostic) => diagnostic.severity === 'error');
|
|
31
|
+
if (generatedApiErrors.length > 0) {
|
|
32
|
+
throw new Error(`Generated API database definition is invalid:\n${generatedApiErrors
|
|
33
|
+
.map((diagnostic) => `- ${diagnostic.path ?? diagnostic.apiId}: ${diagnostic.message}`)
|
|
34
|
+
.join('\n')}`);
|
|
35
|
+
}
|
|
30
36
|
const extraResources = unique([
|
|
31
37
|
...authArtifacts.resources,
|
|
32
38
|
...authzArtifacts.resources,
|
|
@@ -62,7 +68,9 @@ export function generateMinikubeInfra(manifest, options = {}) {
|
|
|
62
68
|
...authzArtifacts.warnings,
|
|
63
69
|
...storageArtifacts.warnings,
|
|
64
70
|
...secretStoreArtifacts.warnings,
|
|
65
|
-
...
|
|
71
|
+
...generatedApiDatabaseArtifacts.diagnostics
|
|
72
|
+
.filter((diagnostic) => diagnostic.severity === 'warning')
|
|
73
|
+
.map((diagnostic) => diagnostic.message),
|
|
66
74
|
]);
|
|
67
75
|
return {
|
|
68
76
|
files: [
|
|
@@ -71,7 +79,7 @@ export function generateMinikubeInfra(manifest, options = {}) {
|
|
|
71
79
|
...authzArtifacts.files,
|
|
72
80
|
...storageArtifacts.files,
|
|
73
81
|
...secretStoreArtifacts.files,
|
|
74
|
-
...
|
|
82
|
+
...generatedApiDatabaseArtifacts.files,
|
|
75
83
|
],
|
|
76
84
|
warnings,
|
|
77
85
|
meta: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/minikube/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { generateApiInfrastructureArtifacts } from '../../apiArtifacts';\nimport type {\n GeneratedPackageDependency,\n InfraManifestInput,\n InfrastructureGenerationOptions,\n InfrastructureGenerationResult,\n} from '../../types';\nimport { generateAuthProviderArtifacts } from './auth';\nimport { generateAuthorizationArtifacts } from './authz';\nimport { APP_NAMESPACE, generateMinikubeBaseArtifacts } from './base';\nimport { generateSecretStoreArtifacts } from './secrets';\nimport { generateStorageArtifacts } from './storage';\n\nconst CANONICAL_PROJECT_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/;\nconst BUILT_IN_NAMESPACES = new Set([APP_NAMESPACE, 'supabase']);\n\nexport function generateMinikubeInfra(\n manifest: InfraManifestInput,\n options: InfrastructureGenerationOptions = {},\n): InfrastructureGenerationResult {\n if (manifest.deployment?.target !== 'minikube') {\n throw new Error(\n `Minikube adapter only supports deployment target \"minikube\" (received \"${manifest.deployment?.target ?? 'undefined'}\").`,\n );\n }\n\n if (manifest.database?.provider && manifest.database.provider !== 'supabase') {\n throw new Error(\n `Unsupported database provider for minikube adapter: \"${manifest.database.provider}\". Only \"supabase\" is implemented.`,\n );\n }\n\n const appSlug = validateCanonicalAppSlug(options.appManifest?.metadata.slug);\n const namespace = APP_NAMESPACE;\n\n const authArtifacts = generateAuthProviderArtifacts({ manifest, namespace });\n const authzArtifacts = generateAuthorizationArtifacts({\n manifest,\n namespace,\n appManifest: options.appManifest,\n });\n const storageArtifacts = generateStorageArtifacts({ manifest, namespace });\n const secretStoreArtifacts = generateSecretStoreArtifacts({ manifest, namespace });\n const apiArtifacts = generateApiInfrastructureArtifacts({\n data: options.appManifest?.data,\n databaseProvider: manifest.database?.provider,\n });\n\n const extraResources = unique([\n ...authArtifacts.resources,\n ...authzArtifacts.resources,\n ...storageArtifacts.resources,\n ...secretStoreArtifacts.resources,\n ]);\n const providerLifecycle = [\n ...authArtifacts.providerLifecycle,\n ...authzArtifacts.providerLifecycle,\n ...storageArtifacts.providerLifecycle,\n ...secretStoreArtifacts.providerLifecycle,\n ];\n const providerNamespaces = unique([\n ...providerLifecycle.map((contribution) => contribution.namespace),\n ]).filter((providerNamespace) => !BUILT_IN_NAMESPACES.has(providerNamespace));\n const extraEnvEntries = unique([\n ...authArtifacts.envEntries,\n ...authzArtifacts.envEntries,\n ...storageArtifacts.envEntries,\n ...secretStoreArtifacts.envEntries,\n ]);\n\n const baseFiles = generateMinikubeBaseArtifacts({\n manifest,\n appSlug,\n extraResources,\n providerNamespaces,\n providerLifecycle,\n extraEnvEntries,\n });\n\n const warnings = unique([\n ...collectWarnings(manifest),\n ...authArtifacts.warnings,\n ...authzArtifacts.warnings,\n ...storageArtifacts.warnings,\n ...secretStoreArtifacts.warnings,\n ...apiArtifacts.warnings,\n ]);\n\n return {\n files: [\n ...baseFiles,\n ...authArtifacts.files,\n ...authzArtifacts.files,\n ...storageArtifacts.files,\n ...secretStoreArtifacts.files,\n ...apiArtifacts.files,\n ],\n warnings,\n meta: {\n target: 'minikube',\n providers: collectProviders(manifest),\n },\n dependencies: collectDependencies(manifest),\n };\n}\n\nfunction collectProviders(manifest: InfraManifestInput): string[] {\n const providers = new Set<string>();\n\n if (manifest.auth?.provider) providers.add(manifest.auth.provider);\n if (manifest.auth?.authorization?.engine) {\n providers.add(manifest.auth.authorization.engine);\n }\n if (manifest.database?.provider) providers.add(manifest.database.provider);\n if (manifest.storage?.provider) providers.add(manifest.storage.provider);\n if (manifest.secretStore?.provider) providers.add(manifest.secretStore.provider);\n if (manifest.state?.provider) providers.add(manifest.state.provider);\n\n return [...providers].sort();\n}\n\nfunction collectDependencies(manifest: InfraManifestInput): readonly GeneratedPackageDependency[] {\n if (manifest.state?.provider !== 'legend') {\n return [];\n }\n\n return [\n {\n name: '@ankhorage/state-legend',\n version: '^0.1.0',\n reason: 'Selected by infra.state.provider=legend.',\n },\n ];\n}\n\nfunction collectWarnings(manifest: InfraManifestInput): string[] {\n const warnings: string[] = [];\n\n if (manifest.deployment?.monitoring) {\n warnings.push(\n 'Monitoring is enabled in manifest, but monitoring stack resources are not generated yet.',\n );\n }\n\n if (manifest.networking?.cdn) {\n warnings.push('CDN is enabled in manifest, but CDN resources are not generated for minikube.');\n }\n\n return warnings;\n}\n\nfunction validateCanonicalAppSlug(slug: string | undefined): string {\n if (!slug) {\n throw new Error(\n 'Cannot generate Minikube infrastructure: appManifest.metadata.slug is required.',\n );\n }\n\n if (slug.trim() !== slug || !CANONICAL_PROJECT_SLUG_RE.test(slug)) {\n throw new Error(\n 'Cannot generate Minikube infrastructure: appManifest.metadata.slug must be a canonical lowercase slug up to 40 characters using only a-z, 0-9, and hyphens, without leading or trailing hyphens.',\n );\n }\n\n return slug;\n}\n\nfunction unique(values: string[]): string[] {\n return [...new Set(values)];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/minikube/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { generateGeneratedApiDatabaseArtifacts } from '../../generatedApiDatabase';\nimport type {\n GeneratedPackageDependency,\n InfraManifestInput,\n InfrastructureGenerationOptions,\n InfrastructureGenerationResult,\n} from '../../types';\nimport { generateAuthProviderArtifacts } from './auth';\nimport { generateAuthorizationArtifacts } from './authz';\nimport { APP_NAMESPACE, generateMinikubeBaseArtifacts } from './base';\nimport { generateSecretStoreArtifacts } from './secrets';\nimport { generateStorageArtifacts } from './storage';\n\nconst CANONICAL_PROJECT_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/;\nconst BUILT_IN_NAMESPACES = new Set([APP_NAMESPACE, 'supabase']);\n\nexport function generateMinikubeInfra(\n manifest: InfraManifestInput,\n options: InfrastructureGenerationOptions = {},\n): InfrastructureGenerationResult {\n if (manifest.deployment?.target !== 'minikube') {\n throw new Error(\n `Minikube adapter only supports deployment target \"minikube\" (received \"${manifest.deployment?.target ?? 'undefined'}\").`,\n );\n }\n\n if (manifest.database?.provider && manifest.database.provider !== 'supabase') {\n throw new Error(\n `Unsupported database provider for minikube adapter: \"${manifest.database.provider}\". Only \"supabase\" is implemented.`,\n );\n }\n\n const appSlug = validateCanonicalAppSlug(options.appManifest?.metadata.slug);\n const namespace = APP_NAMESPACE;\n\n const authArtifacts = generateAuthProviderArtifacts({ manifest, namespace });\n const authzArtifacts = generateAuthorizationArtifacts({\n manifest,\n namespace,\n appManifest: options.appManifest,\n });\n const storageArtifacts = generateStorageArtifacts({ manifest, namespace });\n const secretStoreArtifacts = generateSecretStoreArtifacts({ manifest, namespace });\n const generatedApiDatabaseArtifacts = generateGeneratedApiDatabaseArtifacts({\n generatedApis: options.appManifest?.generatedApis,\n databaseProvider: manifest.database?.provider,\n });\n const generatedApiErrors = generatedApiDatabaseArtifacts.diagnostics.filter(\n (diagnostic) => diagnostic.severity === 'error',\n );\n\n if (generatedApiErrors.length > 0) {\n throw new Error(\n `Generated API database definition is invalid:\\n${generatedApiErrors\n .map((diagnostic) => `- ${diagnostic.path ?? diagnostic.apiId}: ${diagnostic.message}`)\n .join('\\n')}`,\n );\n }\n\n const extraResources = unique([\n ...authArtifacts.resources,\n ...authzArtifacts.resources,\n ...storageArtifacts.resources,\n ...secretStoreArtifacts.resources,\n ]);\n const providerLifecycle = [\n ...authArtifacts.providerLifecycle,\n ...authzArtifacts.providerLifecycle,\n ...storageArtifacts.providerLifecycle,\n ...secretStoreArtifacts.providerLifecycle,\n ];\n const providerNamespaces = unique([\n ...providerLifecycle.map((contribution) => contribution.namespace),\n ]).filter((providerNamespace) => !BUILT_IN_NAMESPACES.has(providerNamespace));\n const extraEnvEntries = unique([\n ...authArtifacts.envEntries,\n ...authzArtifacts.envEntries,\n ...storageArtifacts.envEntries,\n ...secretStoreArtifacts.envEntries,\n ]);\n\n const baseFiles = generateMinikubeBaseArtifacts({\n manifest,\n appSlug,\n extraResources,\n providerNamespaces,\n providerLifecycle,\n extraEnvEntries,\n });\n\n const warnings = unique([\n ...collectWarnings(manifest),\n ...authArtifacts.warnings,\n ...authzArtifacts.warnings,\n ...storageArtifacts.warnings,\n ...secretStoreArtifacts.warnings,\n ...generatedApiDatabaseArtifacts.diagnostics\n .filter((diagnostic) => diagnostic.severity === 'warning')\n .map((diagnostic) => diagnostic.message),\n ]);\n\n return {\n files: [\n ...baseFiles,\n ...authArtifacts.files,\n ...authzArtifacts.files,\n ...storageArtifacts.files,\n ...secretStoreArtifacts.files,\n ...generatedApiDatabaseArtifacts.files,\n ],\n warnings,\n meta: {\n target: 'minikube',\n providers: collectProviders(manifest),\n },\n dependencies: collectDependencies(manifest),\n };\n}\n\nfunction collectProviders(manifest: InfraManifestInput): string[] {\n const providers = new Set<string>();\n\n if (manifest.auth?.provider) providers.add(manifest.auth.provider);\n if (manifest.auth?.authorization?.engine) {\n providers.add(manifest.auth.authorization.engine);\n }\n if (manifest.database?.provider) providers.add(manifest.database.provider);\n if (manifest.storage?.provider) providers.add(manifest.storage.provider);\n if (manifest.secretStore?.provider) providers.add(manifest.secretStore.provider);\n if (manifest.state?.provider) providers.add(manifest.state.provider);\n\n return [...providers].sort();\n}\n\nfunction collectDependencies(manifest: InfraManifestInput): readonly GeneratedPackageDependency[] {\n if (manifest.state?.provider !== 'legend') {\n return [];\n }\n\n return [\n {\n name: '@ankhorage/state-legend',\n version: '^0.1.0',\n reason: 'Selected by infra.state.provider=legend.',\n },\n ];\n}\n\nfunction collectWarnings(manifest: InfraManifestInput): string[] {\n const warnings: string[] = [];\n\n if (manifest.deployment?.monitoring) {\n warnings.push(\n 'Monitoring is enabled in manifest, but monitoring stack resources are not generated yet.',\n );\n }\n\n if (manifest.networking?.cdn) {\n warnings.push('CDN is enabled in manifest, but CDN resources are not generated for minikube.');\n }\n\n return warnings;\n}\n\nfunction validateCanonicalAppSlug(slug: string | undefined): string {\n if (!slug) {\n throw new Error(\n 'Cannot generate Minikube infrastructure: appManifest.metadata.slug is required.',\n );\n }\n\n if (slug.trim() !== slug || !CANONICAL_PROJECT_SLUG_RE.test(slug)) {\n throw new Error(\n 'Cannot generate Minikube infrastructure: appManifest.metadata.slug must be a canonical lowercase slug up to 40 characters using only a-z, 0-9, and hyphens, without leading or trailing hyphens.',\n );\n }\n\n return slug;\n}\n\nfunction unique(values: string[]): string[] {\n return [...new Set(values)];\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { GeneratedApiRegistry } from '@ankhorage/contracts';
|
|
2
|
+
import type { GeneratedInfrastructureFile } from './types';
|
|
3
|
+
export type GeneratedApiDatabaseDiagnosticCode = 'duplicate-field' | 'duplicate-resource-id' | 'duplicate-target' | 'invalid-default' | 'invalid-identifier' | 'invalid-primary-key' | 'invalid-seed' | 'registry-id-mismatch' | 'unsupported-policy' | 'unsupported-provider';
|
|
4
|
+
export interface GeneratedApiDatabaseDiagnostic {
|
|
5
|
+
readonly code: GeneratedApiDatabaseDiagnosticCode;
|
|
6
|
+
readonly severity: 'error' | 'warning';
|
|
7
|
+
readonly apiId: string;
|
|
8
|
+
readonly resourceId?: string;
|
|
9
|
+
readonly path?: string;
|
|
10
|
+
readonly message: string;
|
|
11
|
+
}
|
|
12
|
+
export interface GeneratedApiDatabaseArtifacts {
|
|
13
|
+
readonly files: readonly GeneratedInfrastructureFile[];
|
|
14
|
+
readonly diagnostics: readonly GeneratedApiDatabaseDiagnostic[];
|
|
15
|
+
}
|
|
16
|
+
export declare function generateGeneratedApiDatabaseArtifacts(args: {
|
|
17
|
+
readonly generatedApis: GeneratedApiRegistry | undefined;
|
|
18
|
+
readonly databaseProvider: string | undefined;
|
|
19
|
+
}): GeneratedApiDatabaseArtifacts;
|
|
20
|
+
//# sourceMappingURL=generatedApiDatabase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generatedApiDatabase.d.ts","sourceRoot":"","sources":["../src/generatedApiDatabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,oBAAoB,EAGrB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,MAAM,kCAAkC,GAC1C,iBAAiB,GACjB,uBAAuB,GACvB,kBAAkB,GAClB,iBAAiB,GACjB,oBAAoB,GACpB,qBAAqB,GACrB,cAAc,GACd,sBAAsB,GACtB,oBAAoB,GACpB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,IAAI,EAAE,kCAAkC,CAAC;IAClD,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,KAAK,EAAE,SAAS,2BAA2B,EAAE,CAAC;IACvD,QAAQ,CAAC,WAAW,EAAE,SAAS,8BAA8B,EAAE,CAAC;CACjE;AAmBD,wBAAgB,qCAAqC,CAAC,IAAI,EAAE;IAC1D,QAAQ,CAAC,aAAa,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C,GAAG,6BAA6B,CA2ChC"}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
const DEFAULT_DATABASE_SCHEMA = 'public';
|
|
2
|
+
const SQL_IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
|
+
export function generateGeneratedApiDatabaseArtifacts(args) {
|
|
5
|
+
const contexts = listGeneratedResources(args.generatedApis);
|
|
6
|
+
if (contexts.length === 0)
|
|
7
|
+
return { files: [], diagnostics: [] };
|
|
8
|
+
const diagnostics = validateGeneratedApiDatabaseState(args.generatedApis ?? {}, contexts, args.databaseProvider);
|
|
9
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === 'error')) {
|
|
10
|
+
return { files: [], diagnostics };
|
|
11
|
+
}
|
|
12
|
+
const seedEntries = contexts.map(createSeedEntry);
|
|
13
|
+
const metadata = contexts.map((context) => ({
|
|
14
|
+
apiId: context.api.id,
|
|
15
|
+
database: context.api.database,
|
|
16
|
+
auth: context.api.auth ?? null,
|
|
17
|
+
resourceId: context.resource.id,
|
|
18
|
+
operations: context.resource.operations,
|
|
19
|
+
collection: context.resource.collection,
|
|
20
|
+
policies: context.resource.policies ?? [],
|
|
21
|
+
}));
|
|
22
|
+
return {
|
|
23
|
+
files: [
|
|
24
|
+
jsonFile('infra/minikube/db/generated-api-resources.json', metadata),
|
|
25
|
+
jsonFile('infra/minikube/db/generated-api-seed.json', seedEntries),
|
|
26
|
+
{
|
|
27
|
+
path: 'infra/minikube/db/migrations/001_generated_api_resources.sql',
|
|
28
|
+
content: generateMigrationSql(contexts),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
path: 'infra/minikube/db/seeds/001_generated_api_seed.sql',
|
|
32
|
+
content: generateSeedSql(seedEntries),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
path: 'infra/minikube/db/README.md',
|
|
36
|
+
content: generateReadme(contexts),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
diagnostics,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function listGeneratedResources(registry) {
|
|
43
|
+
return Object.values(registry ?? {})
|
|
44
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
45
|
+
.flatMap((api) => [...api.resources]
|
|
46
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
47
|
+
.map((resource) => ({ api, resource, seed: resource.seed ?? [] })));
|
|
48
|
+
}
|
|
49
|
+
function validateGeneratedApiDatabaseState(registry, contexts, databaseProvider) {
|
|
50
|
+
const diagnostics = [];
|
|
51
|
+
const targets = new Map();
|
|
52
|
+
const resourceIdsByApi = new Map();
|
|
53
|
+
if (databaseProvider !== 'supabase') {
|
|
54
|
+
diagnostics.push({
|
|
55
|
+
code: 'unsupported-provider',
|
|
56
|
+
severity: 'error',
|
|
57
|
+
apiId: contexts[0]?.api.id ?? 'generated-api',
|
|
58
|
+
path: 'infra.database.provider',
|
|
59
|
+
message: 'Generated API database resources currently require the supabase provider.',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
for (const [registryId, api] of Object.entries(registry)) {
|
|
63
|
+
if (registryId !== api.id) {
|
|
64
|
+
diagnostics.push({
|
|
65
|
+
code: 'registry-id-mismatch',
|
|
66
|
+
severity: 'error',
|
|
67
|
+
apiId: api.id,
|
|
68
|
+
path: `generatedApis.${registryId}.id`,
|
|
69
|
+
message: `Generated API registry key '${registryId}' does not match definition id '${api.id}'.`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const context of contexts) {
|
|
74
|
+
const { api, resource } = context;
|
|
75
|
+
const resourceIds = resourceIdsByApi.get(api.id) ?? new Set();
|
|
76
|
+
if (resourceIds.has(resource.id)) {
|
|
77
|
+
diagnostics.push(resourceDiagnostic(context, 'duplicate-resource-id', 'id', `Resource ID '${resource.id}' is duplicated.`));
|
|
78
|
+
}
|
|
79
|
+
resourceIds.add(resource.id);
|
|
80
|
+
resourceIdsByApi.set(api.id, resourceIds);
|
|
81
|
+
validateIdentifier(context, 'collection.name', resource.collection.name, diagnostics);
|
|
82
|
+
validateIdentifier(context, 'collection.schema', resource.collection.schema ?? DEFAULT_DATABASE_SCHEMA, diagnostics);
|
|
83
|
+
validateFields(context, diagnostics);
|
|
84
|
+
validatePrimaryKey(context, diagnostics);
|
|
85
|
+
validateSeeds(context, diagnostics);
|
|
86
|
+
const target = `${resource.collection.schema ?? DEFAULT_DATABASE_SCHEMA}.${resource.collection.name}`;
|
|
87
|
+
const previous = targets.get(target);
|
|
88
|
+
if (previous !== undefined) {
|
|
89
|
+
diagnostics.push(resourceDiagnostic(context, 'duplicate-target', 'collection', `Database target '${target}' is already owned by ${previous.api.id}/${previous.resource.id}.`));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
targets.set(target, context);
|
|
93
|
+
}
|
|
94
|
+
if ((resource.policies?.length ?? 0) > 0) {
|
|
95
|
+
diagnostics.push(resourceDiagnostic(context, 'unsupported-policy', 'policies', 'Policy references enable RLS, but the canonical contract does not yet contain provider-owned SQL policy expressions; no CREATE POLICY statement is emitted.', 'warning'));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return diagnostics;
|
|
99
|
+
}
|
|
100
|
+
function validateFields(context, diagnostics) {
|
|
101
|
+
const fieldNames = new Set();
|
|
102
|
+
for (const field of context.resource.collection.fields) {
|
|
103
|
+
validateIdentifier(context, `collection.fields.${field.name}.name`, field.name, diagnostics);
|
|
104
|
+
if (fieldNames.has(field.name)) {
|
|
105
|
+
diagnostics.push(resourceDiagnostic(context, 'duplicate-field', `collection.fields.${field.name}`, `Field '${field.name}' is duplicated.`));
|
|
106
|
+
}
|
|
107
|
+
fieldNames.add(field.name);
|
|
108
|
+
if (!isCompatibleDefault(field)) {
|
|
109
|
+
diagnostics.push(resourceDiagnostic(context, 'invalid-default', `collection.fields.${field.name}.defaultValue`, `Default value for '${field.name}' is incompatible with field type '${field.type}'.`));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function validatePrimaryKey(context, diagnostics) {
|
|
114
|
+
const { primaryKey } = context.resource.collection;
|
|
115
|
+
if (primaryKey === undefined)
|
|
116
|
+
return;
|
|
117
|
+
validateIdentifier(context, 'collection.primaryKey', primaryKey, diagnostics);
|
|
118
|
+
if (!context.resource.collection.fields.some((field) => field.name === primaryKey)) {
|
|
119
|
+
diagnostics.push(resourceDiagnostic(context, 'invalid-primary-key', 'collection.primaryKey', `Primary key '${primaryKey}' must reference a declared field.`));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function validateSeeds(context, diagnostics) {
|
|
123
|
+
const fields = new Map(context.resource.collection.fields.map((field) => [field.name, field]));
|
|
124
|
+
const { primaryKey } = context.resource.collection;
|
|
125
|
+
context.seed.forEach((record, index) => {
|
|
126
|
+
for (const key of Object.keys(record)) {
|
|
127
|
+
if (!fields.has(key)) {
|
|
128
|
+
diagnostics.push(resourceDiagnostic(context, 'invalid-seed', `seed.${index}.${key}`, `Seed field '${key}' is not declared by the collection.`));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const field of fields.values()) {
|
|
132
|
+
const value = record[field.name];
|
|
133
|
+
const generatedUuidPrimaryKey = field.name === primaryKey && field.type === 'uuid' && value === undefined;
|
|
134
|
+
const requiresNonNullValue = field.required === true || field.name === primaryKey;
|
|
135
|
+
if (value === undefined) {
|
|
136
|
+
if (requiresNonNullValue && field.defaultValue === undefined && !generatedUuidPrimaryKey) {
|
|
137
|
+
diagnostics.push(resourceDiagnostic(context, 'invalid-seed', `seed.${index}.${field.name}`, `Seed record is missing required field '${field.name}'.`));
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (value === null) {
|
|
142
|
+
if (requiresNonNullValue) {
|
|
143
|
+
diagnostics.push(resourceDiagnostic(context, 'invalid-seed', `seed.${index}.${field.name}`, `Seed field '${field.name}' cannot be null.`));
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!isCompatibleFieldValue(field, value)) {
|
|
148
|
+
diagnostics.push(resourceDiagnostic(context, 'invalid-seed', `seed.${index}.${field.name}`, `Seed value for '${field.name}' is incompatible with field type '${field.type}'.`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function validateIdentifier(context, path, value, diagnostics) {
|
|
154
|
+
if (SQL_IDENTIFIER_RE.test(value))
|
|
155
|
+
return;
|
|
156
|
+
diagnostics.push(resourceDiagnostic(context, 'invalid-identifier', path, `Database identifier '${value}' must match ${SQL_IDENTIFIER_RE.source}.`));
|
|
157
|
+
}
|
|
158
|
+
function resourceDiagnostic(context, code, path, message, severity = 'error') {
|
|
159
|
+
return {
|
|
160
|
+
code,
|
|
161
|
+
severity,
|
|
162
|
+
apiId: context.api.id,
|
|
163
|
+
resourceId: context.resource.id,
|
|
164
|
+
path: `generatedApis.${context.api.id}.resources.${context.resource.id}.${path}`,
|
|
165
|
+
message,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function isCompatibleDefault(field) {
|
|
169
|
+
const value = field.defaultValue;
|
|
170
|
+
if (value === undefined || value === null)
|
|
171
|
+
return true;
|
|
172
|
+
return isCompatibleFieldValue(field, value);
|
|
173
|
+
}
|
|
174
|
+
function isCompatibleFieldValue(field, value) {
|
|
175
|
+
switch (field.type) {
|
|
176
|
+
case 'boolean':
|
|
177
|
+
return typeof value === 'boolean';
|
|
178
|
+
case 'number':
|
|
179
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
180
|
+
case 'text':
|
|
181
|
+
return typeof value === 'string';
|
|
182
|
+
case 'datetime':
|
|
183
|
+
return typeof value === 'string' && Number.isFinite(Date.parse(value));
|
|
184
|
+
case 'uuid':
|
|
185
|
+
return typeof value === 'string' && UUID_RE.test(value);
|
|
186
|
+
case 'json':
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function createSeedEntry(context) {
|
|
191
|
+
return {
|
|
192
|
+
apiId: context.api.id,
|
|
193
|
+
resourceId: context.resource.id,
|
|
194
|
+
collection: context.resource.collection,
|
|
195
|
+
records: context.seed.map((record, index) => materializeSeedRecord(context, record, index)),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function materializeSeedRecord(context, record, index) {
|
|
199
|
+
const { primaryKey } = context.resource.collection;
|
|
200
|
+
if (primaryKey === undefined || record[primaryKey] !== undefined)
|
|
201
|
+
return record;
|
|
202
|
+
const primaryKeyField = context.resource.collection.fields.find((field) => field.name === primaryKey);
|
|
203
|
+
if (primaryKeyField?.type !== 'uuid')
|
|
204
|
+
return record;
|
|
205
|
+
return {
|
|
206
|
+
...record,
|
|
207
|
+
[primaryKey]: createDeterministicSeedUuid(context.api.id, context.resource.id, index),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function generateMigrationSql(contexts) {
|
|
211
|
+
const statements = contexts.flatMap((context) => {
|
|
212
|
+
const schema = quoteIdentifier(context.resource.collection.schema ?? DEFAULT_DATABASE_SCHEMA);
|
|
213
|
+
const table = quoteIdentifier(context.resource.collection.name);
|
|
214
|
+
const columns = context.resource.collection.fields.map((field) => formatColumn(field, field.name === context.resource.collection.primaryKey));
|
|
215
|
+
const rls = (context.resource.policies?.length ?? 0) > 0
|
|
216
|
+
? [`alter table ${schema}.${table} enable row level security;`]
|
|
217
|
+
: [];
|
|
218
|
+
return [
|
|
219
|
+
`create schema if not exists ${schema};`,
|
|
220
|
+
`create table if not exists ${schema}.${table} (\n ${columns.join(',\n ')}\n);`,
|
|
221
|
+
...rls,
|
|
222
|
+
];
|
|
223
|
+
});
|
|
224
|
+
return [
|
|
225
|
+
'-- Generated from canonical GeneratedApiDefinition resources.',
|
|
226
|
+
'-- No HTTP/GraphQL service or Kubernetes API workload is generated here.',
|
|
227
|
+
'create extension if not exists pgcrypto;',
|
|
228
|
+
'',
|
|
229
|
+
...statements,
|
|
230
|
+
'',
|
|
231
|
+
].join('\n');
|
|
232
|
+
}
|
|
233
|
+
function generateSeedSql(entries) {
|
|
234
|
+
const statements = entries.flatMap((entry) => createSeedStatements(entry));
|
|
235
|
+
return [
|
|
236
|
+
'-- Generated from canonical generated API seed records.',
|
|
237
|
+
...(statements.length === 0 ? ['-- No generated API seed records configured.'] : statements),
|
|
238
|
+
'',
|
|
239
|
+
].join('\n');
|
|
240
|
+
}
|
|
241
|
+
function createSeedStatements(entry) {
|
|
242
|
+
if (entry.records.length === 0)
|
|
243
|
+
return [];
|
|
244
|
+
const fields = entry.collection.fields.filter((field) => entry.records.some((record) => record[field.name] !== undefined));
|
|
245
|
+
if (fields.length === 0)
|
|
246
|
+
return [];
|
|
247
|
+
const schema = quoteIdentifier(entry.collection.schema ?? DEFAULT_DATABASE_SCHEMA);
|
|
248
|
+
const table = quoteIdentifier(entry.collection.name);
|
|
249
|
+
const values = entry.records
|
|
250
|
+
.map((record) => `(${fields.map((field) => formatSeedValue(field, record[field.name])).join(', ')})`)
|
|
251
|
+
.join(',\n ');
|
|
252
|
+
const conflict = entry.collection.primaryKey === undefined
|
|
253
|
+
? 'on conflict do nothing'
|
|
254
|
+
: `on conflict (${quoteIdentifier(entry.collection.primaryKey)}) do nothing`;
|
|
255
|
+
return [
|
|
256
|
+
`insert into ${schema}.${table} (${fields.map((field) => quoteIdentifier(field.name)).join(', ')}) values\n ${values}\n${conflict};`,
|
|
257
|
+
];
|
|
258
|
+
}
|
|
259
|
+
function formatSeedValue(field, value) {
|
|
260
|
+
if (value === undefined)
|
|
261
|
+
return 'default';
|
|
262
|
+
return formatFieldValue(field, value);
|
|
263
|
+
}
|
|
264
|
+
function formatColumn(field, primaryKey) {
|
|
265
|
+
const defaultValue = formatDefault(field, primaryKey);
|
|
266
|
+
const primaryKeyClause = primaryKey ? ' primary key' : '';
|
|
267
|
+
const required = field.required === true && !primaryKey ? ' not null' : '';
|
|
268
|
+
const unique = field.unique === true && !primaryKey ? ' unique' : '';
|
|
269
|
+
return `${quoteIdentifier(field.name)} ${mapFieldType(field.type)}${defaultValue}${primaryKeyClause}${required}${unique}`;
|
|
270
|
+
}
|
|
271
|
+
function mapFieldType(type) {
|
|
272
|
+
if (type === 'uuid')
|
|
273
|
+
return 'uuid';
|
|
274
|
+
if (type === 'text')
|
|
275
|
+
return 'text';
|
|
276
|
+
if (type === 'number')
|
|
277
|
+
return 'numeric';
|
|
278
|
+
if (type === 'boolean')
|
|
279
|
+
return 'boolean';
|
|
280
|
+
if (type === 'datetime')
|
|
281
|
+
return 'timestamptz';
|
|
282
|
+
return 'jsonb';
|
|
283
|
+
}
|
|
284
|
+
function formatDefault(field, primaryKey) {
|
|
285
|
+
if (primaryKey && field.type === 'uuid' && field.defaultValue === undefined) {
|
|
286
|
+
return ' default gen_random_uuid()';
|
|
287
|
+
}
|
|
288
|
+
if (field.defaultValue === undefined)
|
|
289
|
+
return '';
|
|
290
|
+
return ` default ${formatFieldValue(field, field.defaultValue)}`;
|
|
291
|
+
}
|
|
292
|
+
function formatFieldValue(field, value) {
|
|
293
|
+
if (value === null)
|
|
294
|
+
return 'null';
|
|
295
|
+
if (field.type === 'json') {
|
|
296
|
+
return `'${escapeString(JSON.stringify(value))}'::jsonb`;
|
|
297
|
+
}
|
|
298
|
+
return formatValue(value);
|
|
299
|
+
}
|
|
300
|
+
function formatValue(value) {
|
|
301
|
+
if (value === null)
|
|
302
|
+
return 'null';
|
|
303
|
+
if (typeof value === 'string')
|
|
304
|
+
return `'${escapeString(value)}'`;
|
|
305
|
+
if (typeof value === 'number')
|
|
306
|
+
return Number.isFinite(value) ? String(value) : 'null';
|
|
307
|
+
if (typeof value === 'boolean')
|
|
308
|
+
return value ? 'true' : 'false';
|
|
309
|
+
return `'${escapeString(JSON.stringify(value))}'::jsonb`;
|
|
310
|
+
}
|
|
311
|
+
function generateReadme(contexts) {
|
|
312
|
+
const resources = contexts
|
|
313
|
+
.map((context) => {
|
|
314
|
+
const { collection } = context.resource;
|
|
315
|
+
return `- \`${context.api.id}/${context.resource.id}\` -> \`${collection.schema ?? DEFAULT_DATABASE_SCHEMA}.${collection.name}\``;
|
|
316
|
+
})
|
|
317
|
+
.join('\n');
|
|
318
|
+
return `# Generated API Database Artifacts\n\nThese files materialize canonical generated API resources into Supabase/Postgres database infrastructure.\n\n${resources}\n\nNo HTTP/GraphQL server, Docker image, application handler, OpenAPI document, Kubernetes Deployment or Service is generated by this bridge.\n`;
|
|
319
|
+
}
|
|
320
|
+
function jsonFile(path, value) {
|
|
321
|
+
return { path, content: `${JSON.stringify(value, null, 2)}\n` };
|
|
322
|
+
}
|
|
323
|
+
function quoteIdentifier(identifier) {
|
|
324
|
+
return `"${identifier.replaceAll('"', '""')}"`;
|
|
325
|
+
}
|
|
326
|
+
function escapeString(value) {
|
|
327
|
+
return value.replaceAll("'", "''");
|
|
328
|
+
}
|
|
329
|
+
function createDeterministicSeedUuid(apiId, resourceId, index) {
|
|
330
|
+
const input = `${apiId}:${resourceId}:${index}`;
|
|
331
|
+
const hex = `${hashHex(input, 0)}${hashHex(input, 1)}${hashHex(input, 2)}${hashHex(input, 3)}`;
|
|
332
|
+
const variant = ((Number.parseInt(hex.charAt(16), 16) & 0x3) | 0x8).toString(16);
|
|
333
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-${variant}${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
334
|
+
}
|
|
335
|
+
function hashHex(input, salt) {
|
|
336
|
+
let hash = 2166136261 ^ salt;
|
|
337
|
+
for (const char of input) {
|
|
338
|
+
hash ^= char.charCodeAt(0);
|
|
339
|
+
hash = Math.imul(hash, 16777619);
|
|
340
|
+
}
|
|
341
|
+
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=generatedApiDatabase.js.map
|