@ankhorage/infra 0.0.0 → 0.1.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 +7 -0
- package/README.md +1 -1
- package/dist/adapters/minikube/index.js +4 -4
- package/dist/adapters/minikube/index.js.map +1 -1
- package/dist/{datasets.d.ts → apis.d.ts} +4 -4
- package/dist/apis.d.ts.map +1 -0
- package/dist/apis.js +354 -0
- package/dist/apis.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/minikube/index.ts +4 -4
- package/src/apis.test.ts +211 -0
- package/src/apis.ts +534 -0
- package/src/index.ts +2 -2
- package/dist/datasets.d.ts.map +0 -1
- package/dist/datasets.js +0 -397
- package/dist/datasets.js.map +0 -1
- package/src/datasets.test.ts +0 -178
- package/src/datasets.ts +0 -562
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# INFRA
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Provider-neutral infrastructure generation: turns app manifests, datasets, auth, state, storage, and deployment config into generated infra artifacts, migrations, seeds, APIs, and runtime dependencies.
|
|
9
9
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { generateApiInfrastructureArtifacts } from '../../apis';
|
|
2
2
|
import { generateAuthProviderArtifacts } from './auth';
|
|
3
3
|
import { generateAuthorizationArtifacts } from './authz';
|
|
4
4
|
import { generateMinikubeBaseArtifacts } from './base';
|
|
@@ -19,7 +19,7 @@ export function generateMinikubeInfra(manifest, options = {}) {
|
|
|
19
19
|
appManifest: options.appManifest,
|
|
20
20
|
});
|
|
21
21
|
const storageArtifacts = generateStorageArtifacts({ manifest, namespace });
|
|
22
|
-
const
|
|
22
|
+
const apiArtifacts = generateApiInfrastructureArtifacts({
|
|
23
23
|
data: options.appManifest?.data,
|
|
24
24
|
databaseProvider: manifest.database?.provider,
|
|
25
25
|
});
|
|
@@ -44,7 +44,7 @@ export function generateMinikubeInfra(manifest, options = {}) {
|
|
|
44
44
|
...authArtifacts.warnings,
|
|
45
45
|
...authzArtifacts.warnings,
|
|
46
46
|
...storageArtifacts.warnings,
|
|
47
|
-
...
|
|
47
|
+
...apiArtifacts.warnings,
|
|
48
48
|
]);
|
|
49
49
|
return {
|
|
50
50
|
files: [
|
|
@@ -52,7 +52,7 @@ export function generateMinikubeInfra(manifest, options = {}) {
|
|
|
52
52
|
...authArtifacts.files,
|
|
53
53
|
...authzArtifacts.files,
|
|
54
54
|
...storageArtifacts.files,
|
|
55
|
-
...
|
|
55
|
+
...apiArtifacts.files,
|
|
56
56
|
],
|
|
57
57
|
warnings,
|
|
58
58
|
meta: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/minikube/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/minikube/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { generateApiInfrastructureArtifacts } from '../../apis';\nimport type {\n GeneratedPackageDependency,\n InfraManifestInput,\n InfrastructureGenerationOptions,\n InfrastructureGenerationResult,\n} from '../../types';\nimport { generateAuthProviderArtifacts } from './auth';\nimport { generateAuthorizationArtifacts } from './authz';\nimport { generateMinikubeBaseArtifacts } from './base';\nimport { generateStorageArtifacts } from './storage';\n\nconst DEFAULT_NAMESPACE = 'ankh-app';\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 namespace = resolveNamespace(manifest, options);\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 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 ]);\n const extraEnvEntries = unique([\n ...authArtifacts.envEntries,\n ...authzArtifacts.envEntries,\n ...storageArtifacts.envEntries,\n ]);\n\n const baseFiles = generateMinikubeBaseArtifacts({\n manifest,\n namespace,\n extraResources,\n extraEnvEntries,\n });\n\n const warnings = unique([\n ...collectWarnings(manifest),\n ...authArtifacts.warnings,\n ...authzArtifacts.warnings,\n ...storageArtifacts.warnings,\n ...apiArtifacts.warnings,\n ]);\n\n return {\n files: [\n ...baseFiles,\n ...authArtifacts.files,\n ...authzArtifacts.files,\n ...storageArtifacts.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) providers.add(manifest.auth.authorization.engine);\n if (manifest.database?.provider) providers.add(manifest.database.provider);\n if (manifest.storage?.provider) providers.add(manifest.storage.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 resolveNamespace(\n manifest: InfraManifestInput,\n options: InfrastructureGenerationOptions,\n): string {\n const source =\n manifest.networking?.domain?.trim() ?? options.namespaceHint?.trim() ?? DEFAULT_NAMESPACE;\n\n const normalized = source\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n\n const safe = normalized.slice(0, 63).replace(/-+$/g, '');\n return safe || DEFAULT_NAMESPACE;\n}\n\nfunction unique(values: string[]): string[] {\n return [...new Set(values)];\n}\n"]}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { AppDataManifest } from '@ankhorage/contracts';
|
|
2
2
|
import type { GeneratedInfrastructureFile } from './types';
|
|
3
|
-
export interface
|
|
3
|
+
export interface ApiInfrastructureArtifacts {
|
|
4
4
|
readonly files: readonly GeneratedInfrastructureFile[];
|
|
5
5
|
readonly warnings: readonly string[];
|
|
6
6
|
}
|
|
7
|
-
export declare function
|
|
7
|
+
export declare function generateApiInfrastructureArtifacts(args: {
|
|
8
8
|
readonly data: AppDataManifest | undefined;
|
|
9
9
|
readonly databaseProvider: string | undefined;
|
|
10
|
-
}):
|
|
11
|
-
//# sourceMappingURL=
|
|
10
|
+
}): ApiInfrastructureArtifacts;
|
|
11
|
+
//# sourceMappingURL=apis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apis.d.ts","sourceRoot":"","sources":["../src/apis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,eAAe,EAGhB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,2BAA2B,EAAE,CAAC;IACvD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AA0DD,wBAAgB,kCAAkC,CAAC,IAAI,EAAE;IACvD,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C,GAAG,0BAA0B,CA4D7B"}
|
package/dist/apis.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
const DEFAULT_DATABASE_SCHEMA = 'public';
|
|
2
|
+
export function generateApiInfrastructureArtifacts(args) {
|
|
3
|
+
const generatedApis = listGeneratedApis(args.data);
|
|
4
|
+
if (generatedApis.length === 0)
|
|
5
|
+
return { files: [], warnings: [] };
|
|
6
|
+
if (args.databaseProvider !== undefined && args.databaseProvider !== 'supabase') {
|
|
7
|
+
return {
|
|
8
|
+
files: [],
|
|
9
|
+
warnings: [
|
|
10
|
+
`API generation is only implemented for the supabase database provider; received ${args.databaseProvider}.`,
|
|
11
|
+
],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const seedManifest = createSeedManifest(generatedApis);
|
|
15
|
+
return {
|
|
16
|
+
files: [
|
|
17
|
+
{
|
|
18
|
+
path: 'infra/minikube/db/apis.json',
|
|
19
|
+
content: `${JSON.stringify(generatedApis.map((resource) => resource.collection), null, 2)}\n`,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: 'infra/minikube/db/api-seed.json',
|
|
23
|
+
content: `${JSON.stringify(seedManifest, null, 2)}\n`,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
path: 'infra/minikube/db/migrations/001_apis.sql',
|
|
27
|
+
content: generateSupabaseMigrationSql(generatedApis),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
path: 'infra/minikube/db/seeds/001_api_seed.sql',
|
|
31
|
+
content: generateSupabaseSeedSql(seedManifest),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
path: 'infra/minikube/db/README.md',
|
|
35
|
+
content: generateReadme(generatedApis),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
path: 'src/generated/apis/resources.ts',
|
|
39
|
+
content: generateResourcesTs(generatedApis),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
path: 'src/generated/apis/appApi.ts',
|
|
43
|
+
content: generateApiTs(generatedApis),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
path: 'src/generated/apis/apiHandlers.ts',
|
|
47
|
+
content: generateApiHandlersTs(generatedApis, seedManifest),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
path: 'src/generated/apis/openapi.json',
|
|
51
|
+
content: `${JSON.stringify(createOpenApi(generatedApis), null, 2)}\n`,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
warnings: createSeedWarnings(generatedApis),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function listGeneratedApis(data) {
|
|
58
|
+
return Object.values(data?.apis ?? {})
|
|
59
|
+
.filter(isGeneratedApiWithCollectionResource)
|
|
60
|
+
.map((api) => ({
|
|
61
|
+
api,
|
|
62
|
+
collection: api.resource.collection,
|
|
63
|
+
seed: materializeSeedRecords(api),
|
|
64
|
+
}))
|
|
65
|
+
.sort((left, right) => left.api.id.localeCompare(right.api.id));
|
|
66
|
+
}
|
|
67
|
+
function isGeneratedApiWithCollectionResource(api) {
|
|
68
|
+
return api.kind === 'generated' && api.resource?.kind === 'collection';
|
|
69
|
+
}
|
|
70
|
+
function createSeedManifest(resources) {
|
|
71
|
+
return resources.map((resource) => ({
|
|
72
|
+
apiId: resource.api.id,
|
|
73
|
+
collection: resource.collection,
|
|
74
|
+
records: resource.seed,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
function materializeSeedRecords(api) {
|
|
78
|
+
const { primaryKey } = api.resource.collection;
|
|
79
|
+
const primaryKeyField = findPrimaryKeyField(api);
|
|
80
|
+
return (api.resource.seed ?? []).map((record, index) => {
|
|
81
|
+
if (primaryKey === undefined || record[primaryKey] !== undefined) {
|
|
82
|
+
return record;
|
|
83
|
+
}
|
|
84
|
+
if (primaryKeyField?.type !== 'uuid') {
|
|
85
|
+
return record;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
...record,
|
|
89
|
+
[primaryKey]: createDeterministicSeedUuid(api.id, index),
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function createSeedWarnings(resources) {
|
|
94
|
+
return resources.flatMap((resource) => {
|
|
95
|
+
const { primaryKey } = resource.collection;
|
|
96
|
+
const primaryKeyField = findPrimaryKeyField(resource.api);
|
|
97
|
+
if (primaryKey === undefined || primaryKeyField?.type === 'uuid') {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const hasMissingPrimaryKey = resource.seed.some((record) => record[primaryKey] === undefined);
|
|
101
|
+
if (!hasMissingPrimaryKey) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
return [
|
|
105
|
+
`API ${resource.api.id} has seed records without primary key ${primaryKey}; seed SQL cannot fully guarantee idempotence for those records.`,
|
|
106
|
+
];
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function generateReadme(resources) {
|
|
110
|
+
const rows = resources
|
|
111
|
+
.map((resource) => {
|
|
112
|
+
const seedCount = resource.seed.length;
|
|
113
|
+
return `- \`${resource.api.id}\` maps to collection \`${formatCollectionName(resource)}\` with ${resource.collection.fields.length} fields and ${seedCount} starter records.`;
|
|
114
|
+
})
|
|
115
|
+
.join('\n');
|
|
116
|
+
return `# Generated API Artifacts\n\nThese files are generated from API definitions.\n\n## APIs\n\n${rows}\n\n## Files\n\n- \`apis.json\` stores provider-neutral collection definitions for database adapters.\n- \`api-seed.json\` stores starter records configured for generated APIs, with deterministic UUID primary keys added when needed.\n- \`migrations/001_apis.sql\` creates Supabase/Postgres schemas and tables from generated API resource definitions.\n- \`seeds/001_api_seed.sql\` inserts starter records using idempotent conflict handling where possible.\n- \`src/generated/apis/appApi.ts\` exposes bindable API operation metadata.\n- \`src/generated/apis/apiHandlers.ts\` exposes app-owned handler helpers for generated API routes.\n- \`src/generated/apis/openapi.json\` exposes the generated API surface.\n`;
|
|
117
|
+
}
|
|
118
|
+
function generateResourcesTs(resources) {
|
|
119
|
+
return `import type { AppGeneratedApiDefinition } from '@ankhorage/contracts';\n\nexport const GENERATED_APIS = ${JSON.stringify(resources.map((resource) => resource.api), null, 2)} as const satisfies readonly AppGeneratedApiDefinition[];\n`;
|
|
120
|
+
}
|
|
121
|
+
function generateApiTs(resources) {
|
|
122
|
+
return `export interface GeneratedApiOperation {\n readonly apiId: string;\n readonly operationId: string;\n readonly intent?: 'create' | 'custom' | 'delete' | 'list' | 'read' | 'update';\n readonly method: 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT';\n readonly path: string;\n}\n\nexport const GENERATED_API_OPERATIONS = ${JSON.stringify(resources.flatMap(createApiOperations), null, 2)} as const satisfies readonly GeneratedApiOperation[];\n`;
|
|
123
|
+
}
|
|
124
|
+
function generateApiHandlersTs(resources, seedManifest) {
|
|
125
|
+
return `export type GeneratedApiRecord = Readonly<Record<string, unknown>>;\n\nexport interface GeneratedApiSeedManifestEntry {\n readonly apiId: string;\n readonly records: readonly GeneratedApiRecord[];\n}\n\nexport interface GeneratedApiStore {\n list(apiId: string): Promise<readonly GeneratedApiRecord[]>;\n read(apiId: string, id: string): Promise<GeneratedApiRecord | null>;\n create(apiId: string, values: GeneratedApiRecord): Promise<GeneratedApiRecord>;\n update(apiId: string, id: string, values: GeneratedApiRecord): Promise<GeneratedApiRecord | null>;\n delete(apiId: string, id: string): Promise<GeneratedApiRecord | null>;\n}\n\nexport interface GeneratedApiRequest {\n readonly apiId: string;\n readonly id?: string;\n readonly body?: GeneratedApiRecord;\n}\n\nexport interface GeneratedApiResponse<TData = unknown> {\n readonly status: number;\n readonly data: TData;\n}\n\nconst GENERATED_API_PRIMARY_KEYS = ${JSON.stringify(createPrimaryKeyRegistry(resources), null, 2)} as const satisfies Readonly<Record<string, string>>;\n\nexport const GENERATED_API_SEED = ${JSON.stringify(seedManifest.map((entry) => ({ apiId: entry.apiId, records: entry.records })), null, 2)} as const satisfies readonly GeneratedApiSeedManifestEntry[];\n\nexport function isGeneratedApiId(apiId: string): apiId is Extract<keyof typeof GENERATED_API_PRIMARY_KEYS, string> {\n return Object.prototype.hasOwnProperty.call(GENERATED_API_PRIMARY_KEYS, apiId);\n}\n\nexport function getGeneratedApiPrimaryKey(apiId: string): string | null {\n if (!isGeneratedApiId(apiId)) {\n return null;\n }\n\n return GENERATED_API_PRIMARY_KEYS[apiId];\n}\n\nexport function createGeneratedApiMemoryStore(\n seed: readonly GeneratedApiSeedManifestEntry[] = GENERATED_API_SEED,\n): GeneratedApiStore {\n const records = new Map<string, GeneratedApiRecord[]>();\n\n for (const apiId of Object.keys(GENERATED_API_PRIMARY_KEYS)) {\n records.set(apiId, []);\n }\n\n for (const entry of seed) {\n records.set(entry.apiId, entry.records.map(cloneRecord));\n }\n\n return {\n async list(apiId: string): Promise<readonly GeneratedApiRecord[]> {\n return getRows(records, apiId).map(cloneRecord);\n },\n\n async read(apiId: string, id: string): Promise<GeneratedApiRecord | null> {\n const primaryKey = getGeneratedApiPrimaryKey(apiId);\n if (primaryKey === null) return null;\n\n const record = getRows(records, apiId).find((row) => String(row[primaryKey]) === id);\n return record === undefined ? null : cloneRecord(record);\n },\n\n async create(apiId: string, values: GeneratedApiRecord): Promise<GeneratedApiRecord> {\n const primaryKey = getGeneratedApiPrimaryKey(apiId);\n const nextRecord =\n primaryKey === null || values[primaryKey] !== undefined\n ? cloneRecord(values)\n : { ...values, [primaryKey]: createRuntimeRecordId() };\n const nextRows = [...getRows(records, apiId), nextRecord];\n records.set(apiId, nextRows);\n return cloneRecord(nextRecord);\n },\n\n async update(\n apiId: string,\n id: string,\n values: GeneratedApiRecord,\n ): Promise<GeneratedApiRecord | null> {\n const primaryKey = getGeneratedApiPrimaryKey(apiId);\n if (primaryKey === null) return null;\n\n const rows = getRows(records, apiId);\n const index = rows.findIndex((row) => String(row[primaryKey]) === id);\n if (index < 0) return null;\n\n const currentRecord = rows[index] ?? {};\n const nextRecord = { ...currentRecord, ...values, [primaryKey]: currentRecord[primaryKey] ?? id };\n const nextRows = rows.map((row, rowIndex) => (rowIndex === index ? nextRecord : row));\n records.set(apiId, nextRows);\n return cloneRecord(nextRecord);\n },\n\n async delete(apiId: string, id: string): Promise<GeneratedApiRecord | null> {\n const primaryKey = getGeneratedApiPrimaryKey(apiId);\n if (primaryKey === null) return null;\n\n const rows = getRows(records, apiId);\n const deleted = rows.find((row) => String(row[primaryKey]) === id);\n if (deleted === undefined) return null;\n\n records.set(\n apiId,\n rows.filter((row) => String(row[primaryKey]) !== id),\n );\n return cloneRecord(deleted);\n },\n };\n}\n\nexport async function handleGeneratedApiList(\n store: GeneratedApiStore,\n request: GeneratedApiRequest,\n): Promise<GeneratedApiResponse> {\n if (!isGeneratedApiId(request.apiId)) {\n return notFoundResponse(request.apiId);\n }\n\n return { status: 200, data: await store.list(request.apiId) };\n}\n\nexport async function handleGeneratedApiRead(\n store: GeneratedApiStore,\n request: GeneratedApiRequest,\n): Promise<GeneratedApiResponse> {\n if (!isGeneratedApiId(request.apiId) || request.id === undefined) {\n return notFoundResponse(request.apiId);\n }\n\n const record = await store.read(request.apiId, request.id);\n return record === null ? notFoundResponse(request.apiId) : { status: 200, data: record };\n}\n\nexport async function handleGeneratedApiCreate(\n store: GeneratedApiStore,\n request: GeneratedApiRequest,\n): Promise<GeneratedApiResponse> {\n if (!isGeneratedApiId(request.apiId)) {\n return notFoundResponse(request.apiId);\n }\n\n return { status: 201, data: await store.create(request.apiId, request.body ?? {}) };\n}\n\nexport async function handleGeneratedApiUpdate(\n store: GeneratedApiStore,\n request: GeneratedApiRequest,\n): Promise<GeneratedApiResponse> {\n if (!isGeneratedApiId(request.apiId) || request.id === undefined) {\n return notFoundResponse(request.apiId);\n }\n\n const record = await store.update(request.apiId, request.id, request.body ?? {});\n return record === null ? notFoundResponse(request.apiId) : { status: 200, data: record };\n}\n\nexport async function handleGeneratedApiDelete(\n store: GeneratedApiStore,\n request: GeneratedApiRequest,\n): Promise<GeneratedApiResponse> {\n if (!isGeneratedApiId(request.apiId) || request.id === undefined) {\n return notFoundResponse(request.apiId);\n }\n\n const record = await store.delete(request.apiId, request.id);\n return record === null ? notFoundResponse(request.apiId) : { status: 200, data: record };\n}\n\nfunction getRows(\n records: ReadonlyMap<string, readonly GeneratedApiRecord[]>,\n apiId: string,\n): readonly GeneratedApiRecord[] {\n return records.get(apiId) ?? [];\n}\n\nfunction cloneRecord(record: GeneratedApiRecord): GeneratedApiRecord {\n return { ...record };\n}\n\nfunction createRuntimeRecordId(): string {\n const randomPart = Math.random().toString(16).slice(2);\n return \`generated-\${Date.now()}-\${randomPart}\`;\n}\n\nfunction notFoundResponse(apiId: string): GeneratedApiResponse {\n return {\n status: 404,\n data: {\n error: \`Unknown API or record: \${apiId}\`,\n },\n };\n}\n`;
|
|
126
|
+
}
|
|
127
|
+
function createApiOperations(resource) {
|
|
128
|
+
return resource.api.endpoints.map((endpoint) => ({
|
|
129
|
+
apiId: resource.api.id,
|
|
130
|
+
operationId: endpoint.id,
|
|
131
|
+
intent: endpoint.intent,
|
|
132
|
+
method: endpoint.method,
|
|
133
|
+
path: joinApiPath(resource.api.basePath, endpoint.path),
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
function createOpenApi(resources) {
|
|
137
|
+
const paths = {};
|
|
138
|
+
for (const resource of resources) {
|
|
139
|
+
for (const operation of createApiOperations(resource)) {
|
|
140
|
+
const endpoint = resource.api.endpoints.find((candidate) => candidate.id === operation.operationId);
|
|
141
|
+
addOpenApiOperation(paths, operation, {
|
|
142
|
+
operationId: operation.operationId,
|
|
143
|
+
summary: endpoint?.label ??
|
|
144
|
+
`${operation.intent ?? 'custom'} ${resource.api.label ?? resource.api.id}`,
|
|
145
|
+
responses: {
|
|
146
|
+
[operation.intent === 'create' ? '201' : '200']: { description: 'Successful response.' },
|
|
147
|
+
},
|
|
148
|
+
'x-ankh-api': {
|
|
149
|
+
apiId: resource.api.id,
|
|
150
|
+
endpointId: operation.operationId,
|
|
151
|
+
...(operation.intent === undefined ? {} : { intent: operation.intent }),
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
openapi: '3.1.0',
|
|
158
|
+
info: {
|
|
159
|
+
title: 'Generated app API',
|
|
160
|
+
version: '1.0.0',
|
|
161
|
+
},
|
|
162
|
+
paths,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function addOpenApiOperation(paths, operation, openApiOperation) {
|
|
166
|
+
const path = paths[operation.path] ?? {};
|
|
167
|
+
const method = operation.method.toLowerCase();
|
|
168
|
+
paths[operation.path] = { ...path, [method]: openApiOperation };
|
|
169
|
+
}
|
|
170
|
+
function generateSupabaseMigrationSql(resources) {
|
|
171
|
+
const statements = resources.flatMap((resource) => [
|
|
172
|
+
`create schema if not exists ${quoteSqlIdentifier(resource.collection.schema ?? DEFAULT_DATABASE_SCHEMA)};`,
|
|
173
|
+
generateCreateTableSql(resource),
|
|
174
|
+
]);
|
|
175
|
+
return [
|
|
176
|
+
'-- Generated from API definitions.',
|
|
177
|
+
'-- Safe to re-run: schemas and tables are created with if not exists.',
|
|
178
|
+
'create extension if not exists pgcrypto;',
|
|
179
|
+
'',
|
|
180
|
+
...statements,
|
|
181
|
+
'',
|
|
182
|
+
].join('\n');
|
|
183
|
+
}
|
|
184
|
+
function generateCreateTableSql(resource) {
|
|
185
|
+
const schema = quoteSqlIdentifier(resource.collection.schema ?? DEFAULT_DATABASE_SCHEMA);
|
|
186
|
+
const table = quoteSqlIdentifier(resource.collection.name);
|
|
187
|
+
const columns = collectSqlFields(resource).map((field) => formatSqlColumn(field, field.name === (resource.collection.primaryKey ?? 'id')));
|
|
188
|
+
return `create table if not exists ${schema}.${table} (\n ${columns.join(',\n ')}\n);`;
|
|
189
|
+
}
|
|
190
|
+
function collectSqlFields(resource) {
|
|
191
|
+
const primaryKey = resource.collection.primaryKey ?? 'id';
|
|
192
|
+
const hasPrimaryKeyField = resource.collection.fields.some((field) => field.name === primaryKey);
|
|
193
|
+
if (hasPrimaryKeyField) {
|
|
194
|
+
return resource.collection.fields;
|
|
195
|
+
}
|
|
196
|
+
return [
|
|
197
|
+
{ name: primaryKey, type: 'uuid', required: true, unique: true },
|
|
198
|
+
...resource.collection.fields,
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
function generateSupabaseSeedSql(seedManifest) {
|
|
202
|
+
const inserts = seedManifest.flatMap((entry) => generateSeedInsertStatements(entry));
|
|
203
|
+
return [
|
|
204
|
+
'-- Generated from API seed records.',
|
|
205
|
+
'-- Safe to re-run when seed records have stable primary keys or unique constraints.',
|
|
206
|
+
...(inserts.length === 0 ? ['-- No API seed records configured.'] : inserts),
|
|
207
|
+
'',
|
|
208
|
+
].join('\n');
|
|
209
|
+
}
|
|
210
|
+
function generateSeedInsertStatements(entry) {
|
|
211
|
+
if (entry.records.length === 0) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
const { primaryKey } = entry.collection;
|
|
215
|
+
const columns = collectSeedColumns(entry);
|
|
216
|
+
if (columns.length === 0) {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
const schema = quoteSqlIdentifier(entry.collection.schema ?? DEFAULT_DATABASE_SCHEMA);
|
|
220
|
+
const table = quoteSqlIdentifier(entry.collection.name);
|
|
221
|
+
const quotedColumns = columns.map(quoteSqlIdentifier).join(', ');
|
|
222
|
+
const values = entry.records
|
|
223
|
+
.map((record) => `(${columns.map((column) => formatSqlValue(record[column])).join(', ')})`)
|
|
224
|
+
.join(',\n ');
|
|
225
|
+
const conflict = primaryKey === undefined
|
|
226
|
+
? 'on conflict do nothing'
|
|
227
|
+
: `on conflict (${quoteSqlIdentifier(primaryKey)}) do nothing`;
|
|
228
|
+
return [`insert into ${schema}.${table} (${quotedColumns}) values\n ${values}\n${conflict};`];
|
|
229
|
+
}
|
|
230
|
+
function collectSeedColumns(entry) {
|
|
231
|
+
const columns = new Set();
|
|
232
|
+
if (entry.collection.primaryKey !== undefined) {
|
|
233
|
+
columns.add(entry.collection.primaryKey);
|
|
234
|
+
}
|
|
235
|
+
for (const field of entry.collection.fields) {
|
|
236
|
+
if (entry.records.some((record) => record[field.name] !== undefined)) {
|
|
237
|
+
columns.add(field.name);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const fieldNames = new Set(entry.collection.fields.map((field) => field.name));
|
|
241
|
+
const extraColumns = entry.records
|
|
242
|
+
.flatMap((record) => Object.keys(record))
|
|
243
|
+
.filter((column) => !fieldNames.has(column) && column !== entry.collection.primaryKey)
|
|
244
|
+
.sort();
|
|
245
|
+
for (const column of extraColumns) {
|
|
246
|
+
columns.add(column);
|
|
247
|
+
}
|
|
248
|
+
return [...columns];
|
|
249
|
+
}
|
|
250
|
+
function formatSqlColumn(field, primaryKey) {
|
|
251
|
+
const defaultValue = formatSqlDefaultValue(field, primaryKey);
|
|
252
|
+
const primaryKeyClause = primaryKey ? ' primary key' : '';
|
|
253
|
+
const required = field.required === true && !primaryKey ? ' not null' : '';
|
|
254
|
+
const unique = field.unique === true && !primaryKey ? ' unique' : '';
|
|
255
|
+
return `${quoteSqlIdentifier(field.name)} ${mapSqlFieldType(field.type)}${defaultValue}${primaryKeyClause}${required}${unique}`;
|
|
256
|
+
}
|
|
257
|
+
function mapSqlFieldType(type) {
|
|
258
|
+
switch (type) {
|
|
259
|
+
case 'text':
|
|
260
|
+
return 'text';
|
|
261
|
+
case 'number':
|
|
262
|
+
return 'double precision';
|
|
263
|
+
case 'boolean':
|
|
264
|
+
return 'boolean';
|
|
265
|
+
case 'datetime':
|
|
266
|
+
return 'timestamptz';
|
|
267
|
+
case 'json':
|
|
268
|
+
return 'jsonb';
|
|
269
|
+
case 'uuid':
|
|
270
|
+
return 'uuid';
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function formatSqlDefaultValue(field, primaryKey) {
|
|
274
|
+
if (primaryKey && field.type === 'uuid' && field.defaultValue === undefined) {
|
|
275
|
+
return ' default gen_random_uuid()';
|
|
276
|
+
}
|
|
277
|
+
if (field.defaultValue === undefined) {
|
|
278
|
+
return '';
|
|
279
|
+
}
|
|
280
|
+
if (field.defaultValue === null) {
|
|
281
|
+
return ' default null';
|
|
282
|
+
}
|
|
283
|
+
if (typeof field.defaultValue === 'string') {
|
|
284
|
+
return ` default '${escapeSqlString(field.defaultValue)}'`;
|
|
285
|
+
}
|
|
286
|
+
return ` default ${String(field.defaultValue)}`;
|
|
287
|
+
}
|
|
288
|
+
function formatSqlValue(value) {
|
|
289
|
+
if (value === null || value === undefined) {
|
|
290
|
+
return 'null';
|
|
291
|
+
}
|
|
292
|
+
if (typeof value === 'string') {
|
|
293
|
+
return `'${escapeSqlString(value)}'`;
|
|
294
|
+
}
|
|
295
|
+
if (typeof value === 'number') {
|
|
296
|
+
return Number.isFinite(value) ? String(value) : 'null';
|
|
297
|
+
}
|
|
298
|
+
if (typeof value === 'boolean') {
|
|
299
|
+
return value ? 'true' : 'false';
|
|
300
|
+
}
|
|
301
|
+
if (typeof value === 'bigint') {
|
|
302
|
+
return String(value);
|
|
303
|
+
}
|
|
304
|
+
return `'${escapeSqlString(stringifyJsonValue(value))}'::jsonb`;
|
|
305
|
+
}
|
|
306
|
+
function stringifyJsonValue(value) {
|
|
307
|
+
try {
|
|
308
|
+
return JSON.stringify(value);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
return 'null';
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function quoteSqlIdentifier(identifier) {
|
|
315
|
+
return `"${identifier.replaceAll('"', '""')}"`;
|
|
316
|
+
}
|
|
317
|
+
function escapeSqlString(value) {
|
|
318
|
+
return value.replaceAll("'", "''");
|
|
319
|
+
}
|
|
320
|
+
function createPrimaryKeyRegistry(resources) {
|
|
321
|
+
return Object.fromEntries(resources.map((resource) => [resource.api.id, resource.collection.primaryKey ?? 'id']));
|
|
322
|
+
}
|
|
323
|
+
function findPrimaryKeyField(api) {
|
|
324
|
+
const { primaryKey } = api.resource.collection;
|
|
325
|
+
if (primaryKey === undefined) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
return api.resource.collection.fields.find((field) => field.name === primaryKey);
|
|
329
|
+
}
|
|
330
|
+
function createDeterministicSeedUuid(apiId, index) {
|
|
331
|
+
const input = `${apiId}:${index}`;
|
|
332
|
+
const hex = `${hashHex(input, 0)}${hashHex(input, 1)}${hashHex(input, 2)}${hashHex(input, 3)}`;
|
|
333
|
+
const variant = ((Number.parseInt(hex.charAt(16), 16) & 0x3) | 0x8).toString(16);
|
|
334
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-${variant}${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
335
|
+
}
|
|
336
|
+
function hashHex(input, salt) {
|
|
337
|
+
let hash = 2166136261 ^ salt;
|
|
338
|
+
for (const char of input) {
|
|
339
|
+
hash ^= char.charCodeAt(0);
|
|
340
|
+
hash = Math.imul(hash, 16777619);
|
|
341
|
+
}
|
|
342
|
+
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
343
|
+
}
|
|
344
|
+
function formatCollectionName(resource) {
|
|
345
|
+
return resource.collection.schema
|
|
346
|
+
? `${resource.collection.schema}.${resource.collection.name}`
|
|
347
|
+
: resource.collection.name;
|
|
348
|
+
}
|
|
349
|
+
function joinApiPath(basePath, endpointPath) {
|
|
350
|
+
const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;
|
|
351
|
+
const endpoint = endpointPath.startsWith('/') ? endpointPath : `/${endpointPath}`;
|
|
352
|
+
return endpoint === '/' ? base || '/' : `${base}${endpoint}`;
|
|
353
|
+
}
|
|
354
|
+
//# sourceMappingURL=apis.js.map
|
package/dist/apis.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apis.js","sourceRoot":"","sources":["../src/apis.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AppApiDefinition,\n AppApiEndpointDefinition,\n AppDataManifest,\n AppGeneratedApiDefinition,\n DbFieldDefinition,\n} from '@ankhorage/contracts';\n\nimport type { GeneratedInfrastructureFile } from './types';\n\nexport interface ApiInfrastructureArtifacts {\n readonly files: readonly GeneratedInfrastructureFile[];\n readonly warnings: readonly string[];\n}\n\ntype GeneratedApiWithCollectionResource = AppGeneratedApiDefinition & {\n readonly resource: NonNullable<AppGeneratedApiDefinition['resource']>;\n};\n\ninterface GeneratedApiResource {\n readonly api: GeneratedApiWithCollectionResource;\n readonly collection: GeneratedApiWithCollectionResource['resource']['collection'];\n readonly seed: readonly Record<string, unknown>[];\n}\n\ninterface GeneratedApiOperation {\n readonly apiId: string;\n readonly operationId: string;\n readonly intent: AppApiEndpointDefinition['intent'];\n readonly method: AppApiEndpointDefinition['method'];\n readonly path: string;\n}\n\ninterface GeneratedApiOpenApiOperation {\n readonly operationId: string;\n readonly summary: string;\n readonly responses: Readonly<Record<string, { readonly description: string }>>;\n readonly 'x-ankh-api': {\n readonly apiId: string;\n readonly endpointId: string;\n readonly intent?: AppApiEndpointDefinition['intent'];\n };\n}\n\ninterface GeneratedApiOpenApiPath {\n readonly delete?: GeneratedApiOpenApiOperation;\n readonly get?: GeneratedApiOpenApiOperation;\n readonly head?: GeneratedApiOpenApiOperation;\n readonly options?: GeneratedApiOpenApiOperation;\n readonly patch?: GeneratedApiOpenApiOperation;\n readonly post?: GeneratedApiOpenApiOperation;\n readonly put?: GeneratedApiOpenApiOperation;\n}\n\ninterface GeneratedApiOpenApiDocument {\n readonly openapi: '3.1.0';\n readonly info: {\n readonly title: string;\n readonly version: string;\n };\n readonly paths: Readonly<Record<string, GeneratedApiOpenApiPath>>;\n}\n\ninterface GeneratedApiSeedManifestEntry {\n readonly apiId: string;\n readonly collection: GeneratedApiResource['collection'];\n readonly records: readonly Record<string, unknown>[];\n}\n\nconst DEFAULT_DATABASE_SCHEMA = 'public';\n\nexport function generateApiInfrastructureArtifacts(args: {\n readonly data: AppDataManifest | undefined;\n readonly databaseProvider: string | undefined;\n}): ApiInfrastructureArtifacts {\n const generatedApis = listGeneratedApis(args.data);\n if (generatedApis.length === 0) return { files: [], warnings: [] };\n\n if (args.databaseProvider !== undefined && args.databaseProvider !== 'supabase') {\n return {\n files: [],\n warnings: [\n `API generation is only implemented for the supabase database provider; received ${args.databaseProvider}.`,\n ],\n };\n }\n\n const seedManifest = createSeedManifest(generatedApis);\n\n return {\n files: [\n {\n path: 'infra/minikube/db/apis.json',\n content: `${JSON.stringify(\n generatedApis.map((resource) => resource.collection),\n null,\n 2,\n )}\\n`,\n },\n {\n path: 'infra/minikube/db/api-seed.json',\n content: `${JSON.stringify(seedManifest, null, 2)}\\n`,\n },\n {\n path: 'infra/minikube/db/migrations/001_apis.sql',\n content: generateSupabaseMigrationSql(generatedApis),\n },\n {\n path: 'infra/minikube/db/seeds/001_api_seed.sql',\n content: generateSupabaseSeedSql(seedManifest),\n },\n {\n path: 'infra/minikube/db/README.md',\n content: generateReadme(generatedApis),\n },\n {\n path: 'src/generated/apis/resources.ts',\n content: generateResourcesTs(generatedApis),\n },\n {\n path: 'src/generated/apis/appApi.ts',\n content: generateApiTs(generatedApis),\n },\n {\n path: 'src/generated/apis/apiHandlers.ts',\n content: generateApiHandlersTs(generatedApis, seedManifest),\n },\n {\n path: 'src/generated/apis/openapi.json',\n content: `${JSON.stringify(createOpenApi(generatedApis), null, 2)}\\n`,\n },\n ],\n warnings: createSeedWarnings(generatedApis),\n };\n}\n\nfunction listGeneratedApis(data: AppDataManifest | undefined): readonly GeneratedApiResource[] {\n return Object.values(data?.apis ?? {})\n .filter(isGeneratedApiWithCollectionResource)\n .map((api) => ({\n api,\n collection: api.resource.collection,\n seed: materializeSeedRecords(api),\n }))\n .sort((left, right) => left.api.id.localeCompare(right.api.id));\n}\n\nfunction isGeneratedApiWithCollectionResource(\n api: AppApiDefinition,\n): api is GeneratedApiWithCollectionResource {\n return api.kind === 'generated' && api.resource?.kind === 'collection';\n}\n\nfunction createSeedManifest(\n resources: readonly GeneratedApiResource[],\n): readonly GeneratedApiSeedManifestEntry[] {\n return resources.map((resource) => ({\n apiId: resource.api.id,\n collection: resource.collection,\n records: resource.seed,\n }));\n}\n\nfunction materializeSeedRecords(\n api: GeneratedApiWithCollectionResource,\n): readonly Record<string, unknown>[] {\n const { primaryKey } = api.resource.collection;\n const primaryKeyField = findPrimaryKeyField(api);\n\n return (api.resource.seed ?? []).map((record, index) => {\n if (primaryKey === undefined || record[primaryKey] !== undefined) {\n return record;\n }\n\n if (primaryKeyField?.type !== 'uuid') {\n return record;\n }\n\n return {\n ...record,\n [primaryKey]: createDeterministicSeedUuid(api.id, index),\n };\n });\n}\n\nfunction createSeedWarnings(resources: readonly GeneratedApiResource[]): readonly string[] {\n return resources.flatMap((resource) => {\n const { primaryKey } = resource.collection;\n const primaryKeyField = findPrimaryKeyField(resource.api);\n\n if (primaryKey === undefined || primaryKeyField?.type === 'uuid') {\n return [];\n }\n\n const hasMissingPrimaryKey = resource.seed.some((record) => record[primaryKey] === undefined);\n if (!hasMissingPrimaryKey) {\n return [];\n }\n\n return [\n `API ${resource.api.id} has seed records without primary key ${primaryKey}; seed SQL cannot fully guarantee idempotence for those records.`,\n ];\n });\n}\n\nfunction generateReadme(resources: readonly GeneratedApiResource[]): string {\n const rows = resources\n .map((resource) => {\n const seedCount = resource.seed.length;\n return `- \\`${resource.api.id}\\` maps to collection \\`${formatCollectionName(resource)}\\` with ${resource.collection.fields.length} fields and ${seedCount} starter records.`;\n })\n .join('\\n');\n\n return `# Generated API Artifacts\\n\\nThese files are generated from API definitions.\\n\\n## APIs\\n\\n${rows}\\n\\n## Files\\n\\n- \\`apis.json\\` stores provider-neutral collection definitions for database adapters.\\n- \\`api-seed.json\\` stores starter records configured for generated APIs, with deterministic UUID primary keys added when needed.\\n- \\`migrations/001_apis.sql\\` creates Supabase/Postgres schemas and tables from generated API resource definitions.\\n- \\`seeds/001_api_seed.sql\\` inserts starter records using idempotent conflict handling where possible.\\n- \\`src/generated/apis/appApi.ts\\` exposes bindable API operation metadata.\\n- \\`src/generated/apis/apiHandlers.ts\\` exposes app-owned handler helpers for generated API routes.\\n- \\`src/generated/apis/openapi.json\\` exposes the generated API surface.\\n`;\n}\n\nfunction generateResourcesTs(resources: readonly GeneratedApiResource[]): string {\n return `import type { AppGeneratedApiDefinition } from '@ankhorage/contracts';\\n\\nexport const GENERATED_APIS = ${JSON.stringify(\n resources.map((resource) => resource.api),\n null,\n 2,\n )} as const satisfies readonly AppGeneratedApiDefinition[];\\n`;\n}\n\nfunction generateApiTs(resources: readonly GeneratedApiResource[]): string {\n return `export interface GeneratedApiOperation {\\n readonly apiId: string;\\n readonly operationId: string;\\n readonly intent?: 'create' | 'custom' | 'delete' | 'list' | 'read' | 'update';\\n readonly method: 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT';\\n readonly path: string;\\n}\\n\\nexport const GENERATED_API_OPERATIONS = ${JSON.stringify(\n resources.flatMap(createApiOperations),\n null,\n 2,\n )} as const satisfies readonly GeneratedApiOperation[];\\n`;\n}\n\nfunction generateApiHandlersTs(\n resources: readonly GeneratedApiResource[],\n seedManifest: readonly GeneratedApiSeedManifestEntry[],\n): string {\n return `export type GeneratedApiRecord = Readonly<Record<string, unknown>>;\\n\\nexport interface GeneratedApiSeedManifestEntry {\\n readonly apiId: string;\\n readonly records: readonly GeneratedApiRecord[];\\n}\\n\\nexport interface GeneratedApiStore {\\n list(apiId: string): Promise<readonly GeneratedApiRecord[]>;\\n read(apiId: string, id: string): Promise<GeneratedApiRecord | null>;\\n create(apiId: string, values: GeneratedApiRecord): Promise<GeneratedApiRecord>;\\n update(apiId: string, id: string, values: GeneratedApiRecord): Promise<GeneratedApiRecord | null>;\\n delete(apiId: string, id: string): Promise<GeneratedApiRecord | null>;\\n}\\n\\nexport interface GeneratedApiRequest {\\n readonly apiId: string;\\n readonly id?: string;\\n readonly body?: GeneratedApiRecord;\\n}\\n\\nexport interface GeneratedApiResponse<TData = unknown> {\\n readonly status: number;\\n readonly data: TData;\\n}\\n\\nconst GENERATED_API_PRIMARY_KEYS = ${JSON.stringify(createPrimaryKeyRegistry(resources), null, 2)} as const satisfies Readonly<Record<string, string>>;\\n\\nexport const GENERATED_API_SEED = ${JSON.stringify(\n seedManifest.map((entry) => ({ apiId: entry.apiId, records: entry.records })),\n null,\n 2,\n )} as const satisfies readonly GeneratedApiSeedManifestEntry[];\\n\\nexport function isGeneratedApiId(apiId: string): apiId is Extract<keyof typeof GENERATED_API_PRIMARY_KEYS, string> {\\n return Object.prototype.hasOwnProperty.call(GENERATED_API_PRIMARY_KEYS, apiId);\\n}\\n\\nexport function getGeneratedApiPrimaryKey(apiId: string): string | null {\\n if (!isGeneratedApiId(apiId)) {\\n return null;\\n }\\n\\n return GENERATED_API_PRIMARY_KEYS[apiId];\\n}\\n\\nexport function createGeneratedApiMemoryStore(\\n seed: readonly GeneratedApiSeedManifestEntry[] = GENERATED_API_SEED,\\n): GeneratedApiStore {\\n const records = new Map<string, GeneratedApiRecord[]>();\\n\\n for (const apiId of Object.keys(GENERATED_API_PRIMARY_KEYS)) {\\n records.set(apiId, []);\\n }\\n\\n for (const entry of seed) {\\n records.set(entry.apiId, entry.records.map(cloneRecord));\\n }\\n\\n return {\\n async list(apiId: string): Promise<readonly GeneratedApiRecord[]> {\\n return getRows(records, apiId).map(cloneRecord);\\n },\\n\\n async read(apiId: string, id: string): Promise<GeneratedApiRecord | null> {\\n const primaryKey = getGeneratedApiPrimaryKey(apiId);\\n if (primaryKey === null) return null;\\n\\n const record = getRows(records, apiId).find((row) => String(row[primaryKey]) === id);\\n return record === undefined ? null : cloneRecord(record);\\n },\\n\\n async create(apiId: string, values: GeneratedApiRecord): Promise<GeneratedApiRecord> {\\n const primaryKey = getGeneratedApiPrimaryKey(apiId);\\n const nextRecord =\\n primaryKey === null || values[primaryKey] !== undefined\\n ? cloneRecord(values)\\n : { ...values, [primaryKey]: createRuntimeRecordId() };\\n const nextRows = [...getRows(records, apiId), nextRecord];\\n records.set(apiId, nextRows);\\n return cloneRecord(nextRecord);\\n },\\n\\n async update(\\n apiId: string,\\n id: string,\\n values: GeneratedApiRecord,\\n ): Promise<GeneratedApiRecord | null> {\\n const primaryKey = getGeneratedApiPrimaryKey(apiId);\\n if (primaryKey === null) return null;\\n\\n const rows = getRows(records, apiId);\\n const index = rows.findIndex((row) => String(row[primaryKey]) === id);\\n if (index < 0) return null;\\n\\n const currentRecord = rows[index] ?? {};\\n const nextRecord = { ...currentRecord, ...values, [primaryKey]: currentRecord[primaryKey] ?? id };\\n const nextRows = rows.map((row, rowIndex) => (rowIndex === index ? nextRecord : row));\\n records.set(apiId, nextRows);\\n return cloneRecord(nextRecord);\\n },\\n\\n async delete(apiId: string, id: string): Promise<GeneratedApiRecord | null> {\\n const primaryKey = getGeneratedApiPrimaryKey(apiId);\\n if (primaryKey === null) return null;\\n\\n const rows = getRows(records, apiId);\\n const deleted = rows.find((row) => String(row[primaryKey]) === id);\\n if (deleted === undefined) return null;\\n\\n records.set(\\n apiId,\\n rows.filter((row) => String(row[primaryKey]) !== id),\\n );\\n return cloneRecord(deleted);\\n },\\n };\\n}\\n\\nexport async function handleGeneratedApiList(\\n store: GeneratedApiStore,\\n request: GeneratedApiRequest,\\n): Promise<GeneratedApiResponse> {\\n if (!isGeneratedApiId(request.apiId)) {\\n return notFoundResponse(request.apiId);\\n }\\n\\n return { status: 200, data: await store.list(request.apiId) };\\n}\\n\\nexport async function handleGeneratedApiRead(\\n store: GeneratedApiStore,\\n request: GeneratedApiRequest,\\n): Promise<GeneratedApiResponse> {\\n if (!isGeneratedApiId(request.apiId) || request.id === undefined) {\\n return notFoundResponse(request.apiId);\\n }\\n\\n const record = await store.read(request.apiId, request.id);\\n return record === null ? notFoundResponse(request.apiId) : { status: 200, data: record };\\n}\\n\\nexport async function handleGeneratedApiCreate(\\n store: GeneratedApiStore,\\n request: GeneratedApiRequest,\\n): Promise<GeneratedApiResponse> {\\n if (!isGeneratedApiId(request.apiId)) {\\n return notFoundResponse(request.apiId);\\n }\\n\\n return { status: 201, data: await store.create(request.apiId, request.body ?? {}) };\\n}\\n\\nexport async function handleGeneratedApiUpdate(\\n store: GeneratedApiStore,\\n request: GeneratedApiRequest,\\n): Promise<GeneratedApiResponse> {\\n if (!isGeneratedApiId(request.apiId) || request.id === undefined) {\\n return notFoundResponse(request.apiId);\\n }\\n\\n const record = await store.update(request.apiId, request.id, request.body ?? {});\\n return record === null ? notFoundResponse(request.apiId) : { status: 200, data: record };\\n}\\n\\nexport async function handleGeneratedApiDelete(\\n store: GeneratedApiStore,\\n request: GeneratedApiRequest,\\n): Promise<GeneratedApiResponse> {\\n if (!isGeneratedApiId(request.apiId) || request.id === undefined) {\\n return notFoundResponse(request.apiId);\\n }\\n\\n const record = await store.delete(request.apiId, request.id);\\n return record === null ? notFoundResponse(request.apiId) : { status: 200, data: record };\\n}\\n\\nfunction getRows(\\n records: ReadonlyMap<string, readonly GeneratedApiRecord[]>,\\n apiId: string,\\n): readonly GeneratedApiRecord[] {\\n return records.get(apiId) ?? [];\\n}\\n\\nfunction cloneRecord(record: GeneratedApiRecord): GeneratedApiRecord {\\n return { ...record };\\n}\\n\\nfunction createRuntimeRecordId(): string {\\n const randomPart = Math.random().toString(16).slice(2);\\n return \\`generated-\\${Date.now()}-\\${randomPart}\\`;\\n}\\n\\nfunction notFoundResponse(apiId: string): GeneratedApiResponse {\\n return {\\n status: 404,\\n data: {\\n error: \\`Unknown API or record: \\${apiId}\\`,\\n },\\n };\\n}\\n`;\n}\n\nfunction createApiOperations(resource: GeneratedApiResource): readonly GeneratedApiOperation[] {\n return resource.api.endpoints.map((endpoint) => ({\n apiId: resource.api.id,\n operationId: endpoint.id,\n intent: endpoint.intent,\n method: endpoint.method,\n path: joinApiPath(resource.api.basePath, endpoint.path),\n }));\n}\n\nfunction createOpenApi(resources: readonly GeneratedApiResource[]): GeneratedApiOpenApiDocument {\n const paths: Record<string, GeneratedApiOpenApiPath> = {};\n\n for (const resource of resources) {\n for (const operation of createApiOperations(resource)) {\n const endpoint = resource.api.endpoints.find(\n (candidate) => candidate.id === operation.operationId,\n );\n addOpenApiOperation(paths, operation, {\n operationId: operation.operationId,\n summary:\n endpoint?.label ??\n `${operation.intent ?? 'custom'} ${resource.api.label ?? resource.api.id}`,\n responses: {\n [operation.intent === 'create' ? '201' : '200']: { description: 'Successful response.' },\n },\n 'x-ankh-api': {\n apiId: resource.api.id,\n endpointId: operation.operationId,\n ...(operation.intent === undefined ? {} : { intent: operation.intent }),\n },\n });\n }\n }\n\n return {\n openapi: '3.1.0',\n info: {\n title: 'Generated app API',\n version: '1.0.0',\n },\n paths,\n };\n}\n\nfunction addOpenApiOperation(\n paths: Record<string, GeneratedApiOpenApiPath>,\n operation: GeneratedApiOperation,\n openApiOperation: GeneratedApiOpenApiOperation,\n) {\n const path = paths[operation.path] ?? {};\n const method = operation.method.toLowerCase() as Lowercase<AppApiEndpointDefinition['method']>;\n paths[operation.path] = { ...path, [method]: openApiOperation };\n}\n\nfunction generateSupabaseMigrationSql(resources: readonly GeneratedApiResource[]): string {\n const statements = resources.flatMap((resource) => [\n `create schema if not exists ${quoteSqlIdentifier(resource.collection.schema ?? DEFAULT_DATABASE_SCHEMA)};`,\n generateCreateTableSql(resource),\n ]);\n\n return [\n '-- Generated from API definitions.',\n '-- Safe to re-run: schemas and tables are created with if not exists.',\n 'create extension if not exists pgcrypto;',\n '',\n ...statements,\n '',\n ].join('\\n');\n}\n\nfunction generateCreateTableSql(resource: GeneratedApiResource): string {\n const schema = quoteSqlIdentifier(resource.collection.schema ?? DEFAULT_DATABASE_SCHEMA);\n const table = quoteSqlIdentifier(resource.collection.name);\n const columns = collectSqlFields(resource).map((field) =>\n formatSqlColumn(field, field.name === (resource.collection.primaryKey ?? 'id')),\n );\n\n return `create table if not exists ${schema}.${table} (\\n ${columns.join(',\\n ')}\\n);`;\n}\n\nfunction collectSqlFields(resource: GeneratedApiResource): readonly DbFieldDefinition[] {\n const primaryKey = resource.collection.primaryKey ?? 'id';\n const hasPrimaryKeyField = resource.collection.fields.some((field) => field.name === primaryKey);\n\n if (hasPrimaryKeyField) {\n return resource.collection.fields;\n }\n\n return [\n { name: primaryKey, type: 'uuid', required: true, unique: true },\n ...resource.collection.fields,\n ];\n}\n\nfunction generateSupabaseSeedSql(seedManifest: readonly GeneratedApiSeedManifestEntry[]): string {\n const inserts = seedManifest.flatMap((entry) => generateSeedInsertStatements(entry));\n\n return [\n '-- Generated from API seed records.',\n '-- Safe to re-run when seed records have stable primary keys or unique constraints.',\n ...(inserts.length === 0 ? ['-- No API seed records configured.'] : inserts),\n '',\n ].join('\\n');\n}\n\nfunction generateSeedInsertStatements(entry: GeneratedApiSeedManifestEntry): readonly string[] {\n if (entry.records.length === 0) {\n return [];\n }\n\n const { primaryKey } = entry.collection;\n const columns = collectSeedColumns(entry);\n if (columns.length === 0) {\n return [];\n }\n\n const schema = quoteSqlIdentifier(entry.collection.schema ?? DEFAULT_DATABASE_SCHEMA);\n const table = quoteSqlIdentifier(entry.collection.name);\n const quotedColumns = columns.map(quoteSqlIdentifier).join(', ');\n const values = entry.records\n .map((record) => `(${columns.map((column) => formatSqlValue(record[column])).join(', ')})`)\n .join(',\\n ');\n const conflict =\n primaryKey === undefined\n ? 'on conflict do nothing'\n : `on conflict (${quoteSqlIdentifier(primaryKey)}) do nothing`;\n\n return [`insert into ${schema}.${table} (${quotedColumns}) values\\n ${values}\\n${conflict};`];\n}\n\nfunction collectSeedColumns(entry: GeneratedApiSeedManifestEntry): readonly string[] {\n const columns = new Set<string>();\n if (entry.collection.primaryKey !== undefined) {\n columns.add(entry.collection.primaryKey);\n }\n\n for (const field of entry.collection.fields) {\n if (entry.records.some((record) => record[field.name] !== undefined)) {\n columns.add(field.name);\n }\n }\n\n const fieldNames = new Set(entry.collection.fields.map((field) => field.name));\n const extraColumns = entry.records\n .flatMap((record) => Object.keys(record))\n .filter((column) => !fieldNames.has(column) && column !== entry.collection.primaryKey)\n .sort();\n\n for (const column of extraColumns) {\n columns.add(column);\n }\n\n return [...columns];\n}\n\nfunction formatSqlColumn(field: DbFieldDefinition, primaryKey: boolean): string {\n const defaultValue = formatSqlDefaultValue(field, primaryKey);\n const primaryKeyClause = primaryKey ? ' primary key' : '';\n const required = field.required === true && !primaryKey ? ' not null' : '';\n const unique = field.unique === true && !primaryKey ? ' unique' : '';\n\n return `${quoteSqlIdentifier(field.name)} ${mapSqlFieldType(field.type)}${defaultValue}${primaryKeyClause}${required}${unique}`;\n}\n\nfunction mapSqlFieldType(type: DbFieldDefinition['type']): string {\n switch (type) {\n case 'text':\n return 'text';\n case 'number':\n return 'double precision';\n case 'boolean':\n return 'boolean';\n case 'datetime':\n return 'timestamptz';\n case 'json':\n return 'jsonb';\n case 'uuid':\n return 'uuid';\n }\n}\n\nfunction formatSqlDefaultValue(field: DbFieldDefinition, primaryKey: boolean): string {\n if (primaryKey && field.type === 'uuid' && field.defaultValue === undefined) {\n return ' default gen_random_uuid()';\n }\n\n if (field.defaultValue === undefined) {\n return '';\n }\n\n if (field.defaultValue === null) {\n return ' default null';\n }\n\n if (typeof field.defaultValue === 'string') {\n return ` default '${escapeSqlString(field.defaultValue)}'`;\n }\n\n return ` default ${String(field.defaultValue)}`;\n}\n\nfunction formatSqlValue(value: unknown): string {\n if (value === null || value === undefined) {\n return 'null';\n }\n\n if (typeof value === 'string') {\n return `'${escapeSqlString(value)}'`;\n }\n\n if (typeof value === 'number') {\n return Number.isFinite(value) ? String(value) : 'null';\n }\n\n if (typeof value === 'boolean') {\n return value ? 'true' : 'false';\n }\n\n if (typeof value === 'bigint') {\n return String(value);\n }\n\n return `'${escapeSqlString(stringifyJsonValue(value))}'::jsonb`;\n}\n\nfunction stringifyJsonValue(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch {\n return 'null';\n }\n}\n\nfunction quoteSqlIdentifier(identifier: string): string {\n return `\"${identifier.replaceAll('\"', '\"\"')}\"`;\n}\n\nfunction escapeSqlString(value: string): string {\n return value.replaceAll(\"'\", \"''\");\n}\n\nfunction createPrimaryKeyRegistry(\n resources: readonly GeneratedApiResource[],\n): Readonly<Record<string, string>> {\n return Object.fromEntries(\n resources.map((resource) => [resource.api.id, resource.collection.primaryKey ?? 'id']),\n );\n}\n\nfunction findPrimaryKeyField(\n api: GeneratedApiWithCollectionResource,\n): DbFieldDefinition | undefined {\n const { primaryKey } = api.resource.collection;\n if (primaryKey === undefined) {\n return undefined;\n }\n\n return api.resource.collection.fields.find((field) => field.name === primaryKey);\n}\n\nfunction createDeterministicSeedUuid(apiId: string, index: number): string {\n const input = `${apiId}:${index}`;\n const hex = `${hashHex(input, 0)}${hashHex(input, 1)}${hashHex(input, 2)}${hashHex(input, 3)}`;\n const variant = ((Number.parseInt(hex.charAt(16), 16) & 0x3) | 0x8).toString(16);\n\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-${variant}${hex.slice(17, 20)}-${hex.slice(20, 32)}`;\n}\n\nfunction hashHex(input: string, salt: number): string {\n let hash = 2166136261 ^ salt;\n\n for (const char of input) {\n hash ^= char.charCodeAt(0);\n hash = Math.imul(hash, 16777619);\n }\n\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\nfunction formatCollectionName(resource: GeneratedApiResource): string {\n return resource.collection.schema\n ? `${resource.collection.schema}.${resource.collection.name}`\n : resource.collection.name;\n}\n\nfunction joinApiPath(basePath: string, endpointPath: string): string {\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n const endpoint = endpointPath.startsWith('/') ? endpointPath : `/${endpointPath}`;\n return endpoint === '/' ? base || '/' : `${base}${endpoint}`;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { InfraGenerationInput, InfraManifestInput, InfrastructureGenerationOptions, InfrastructureGenerationResult } from './types';
|
|
2
|
-
export type {
|
|
3
|
-
export {
|
|
2
|
+
export type { ApiInfrastructureArtifacts } from './apis';
|
|
3
|
+
export { generateApiInfrastructureArtifacts } from './apis';
|
|
4
4
|
export type { GeneratedFile, GeneratedInfrastructureFile, GeneratedPackageDependency, InfraDiagnostic, InfraGenerationInput, InfraGenerationOptions, InfraGenerationResult, InfraManifestInput, InfrastructureGenerationMeta, InfrastructureGenerationOptions, InfrastructureGenerationResult, PackageDependency, } from './types';
|
|
5
5
|
export declare function generateInfra(input: InfraGenerationInput): InfrastructureGenerationResult;
|
|
6
6
|
export declare function generateInfrastructure(manifest: InfraManifestInput, options?: InfrastructureGenerationOptions): InfrastructureGenerationResult;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,oBAAoB,EACpB,kBAAkB,EAClB,+BAA+B,EAC/B,8BAA8B,EAC/B,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,oBAAoB,EACpB,kBAAkB,EAClB,+BAA+B,EAC/B,8BAA8B,EAC/B,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,0BAA0B,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,kCAAkC,EAAE,MAAM,QAAQ,CAAC;AAC5D,YAAY,EACV,aAAa,EACb,2BAA2B,EAC3B,0BAA0B,EAC1B,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,4BAA4B,EAC5B,+BAA+B,EAC/B,8BAA8B,EAC9B,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAEjB,wBAAgB,aAAa,CAAC,KAAK,EAAE,oBAAoB,GAAG,8BAA8B,CAEzF;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,kBAAkB,EAC5B,OAAO,GAAE,+BAAoC,GAC5C,8BAA8B,CAUhC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { generateMinikubeInfra } from './adapters/minikube';
|
|
2
|
-
export {
|
|
2
|
+
export { generateApiInfrastructureArtifacts } from './apis';
|
|
3
3
|
export function generateInfra(input) {
|
|
4
4
|
return generateInfrastructure(input.manifest, input.options ?? {});
|
|
5
5
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAS5D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAS5D,OAAO,EAAE,kCAAkC,EAAE,MAAM,QAAQ,CAAC;AAgB5D,MAAM,UAAU,aAAa,CAAC,KAA2B;IACvD,OAAO,sBAAsB,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,QAA4B,EAC5B,UAA2C,EAAE;IAE7C,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,kCAAkC,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,OAAO,qBAAqB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AAClD,CAAC","sourcesContent":["import { generateMinikubeInfra } from './adapters/minikube';\nimport type {\n InfraGenerationInput,\n InfraManifestInput,\n InfrastructureGenerationOptions,\n InfrastructureGenerationResult,\n} from './types';\n\nexport type { ApiInfrastructureArtifacts } from './apis';\nexport { generateApiInfrastructureArtifacts } from './apis';\nexport type {\n GeneratedFile,\n GeneratedInfrastructureFile,\n GeneratedPackageDependency,\n InfraDiagnostic,\n InfraGenerationInput,\n InfraGenerationOptions,\n InfraGenerationResult,\n InfraManifestInput,\n InfrastructureGenerationMeta,\n InfrastructureGenerationOptions,\n InfrastructureGenerationResult,\n PackageDependency,\n} from './types';\n\nexport function generateInfra(input: InfraGenerationInput): InfrastructureGenerationResult {\n return generateInfrastructure(input.manifest, input.options ?? {});\n}\n\nexport function generateInfrastructure(\n manifest: InfraManifestInput,\n options: InfrastructureGenerationOptions = {},\n): InfrastructureGenerationResult {\n if (!manifest.deployment) {\n throw new Error('Deployment configuration is missing.');\n }\n\n if (manifest.deployment.target !== 'minikube') {\n throw new Error(`Unsupported deployment target: ${manifest.deployment.target}`);\n }\n\n return generateMinikubeInfra(manifest, options);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/infra",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"description": "Provider-neutral infrastructure generation: turns app manifests, datasets, auth, state, storage, and deployment config into generated infra artifacts, migrations, seeds, APIs, and runtime dependencies.",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/infra#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"openapi"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@ankhorage/contracts": "^1.
|
|
36
|
+
"@ankhorage/contracts": "^1.16.0"
|
|
37
37
|
},
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|