@ankhorage/data-sources 1.0.0 → 1.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ankhorage/data-sources
2
2
 
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 20ed34c: Validate generated API collection fields and explicit primary-key references in the owning data-source package so Studio and other callers can consume one canonical diagnostic surface.
8
+
3
9
  ## 1.0.0
4
10
 
5
11
  ### Major Changes
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generated-api/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EAGnB,UAAU,EACV,oBAAoB,EACpB,0BAA0B,EAE1B,yBAAyB,EACzB,sBAAsB,EAEtB,8BAA8B,EAC9B,gCAAgC,EAChC,WAAW,EACZ,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EACV,sBAAsB,EAGvB,MAAM,yBAAyB,CAAC;AAEjC,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,sBAAsB,GACjC,0BAA0B,CAAC,gCAAgC,CAAC,CAW9D;AAED,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,sBAAsB,GACjC,SAAS,oBAAoB,EAAE,CA6BjC;AAED,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,sBAAsB,GACjC,gCAAgC,CAsBlC;AAED,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,sBAAsB,EAClC,QAAQ,EAAE,8BAA8B,GACvC,kBAAkB,CAiBpB;AAED,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,sBAAsB,EAClC,QAAQ,EAAE,8BAA8B,EACxC,SAAS,EAAE,yBAAyB,GACnC,mBAAmB,CAqBrB;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,yBAAyB,GACnC,WAAW,CAEb;AAED,wBAAgB,gCAAgC,CAAC,UAAU,EAAE,sBAAsB,GAAG,UAAU,CAe/F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generated-api/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EAGnB,UAAU,EACV,oBAAoB,EACpB,0BAA0B,EAE1B,yBAAyB,EACzB,sBAAsB,EAEtB,8BAA8B,EAC9B,gCAAgC,EAChC,WAAW,EACZ,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EACV,sBAAsB,EAGvB,MAAM,yBAAyB,CAAC;AAIjC,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,sBAAsB,GACjC,0BAA0B,CAAC,gCAAgC,CAAC,CAW9D;AAED,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,sBAAsB,GACjC,SAAS,oBAAoB,EAAE,CA6BjC;AAED,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,sBAAsB,GACjC,gCAAgC,CAsBlC;AAED,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,sBAAsB,EAClC,QAAQ,EAAE,8BAA8B,GACvC,kBAAkB,CAiBpB;AAED,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,sBAAsB,EAClC,QAAQ,EAAE,8BAA8B,EACxC,SAAS,EAAE,yBAAyB,GACnC,mBAAmB,CAqBrB;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,yBAAyB,GACnC,WAAW,CAEb;AAED,wBAAgB,gCAAgC,CAAC,UAAU,EAAE,sBAAsB,GAAG,UAAU,CAe/F"}
@@ -1,3 +1,4 @@
1
+ import { validateGeneratedApiCollection } from './validateGeneratedApiCollection';
1
2
  export function createGeneratedApiDataSource(definition) {
2
3
  const diagnostics = validateGeneratedApiDefinition(definition);
3
4
  if (diagnostics.some((diagnostic) => diagnostic.severity === 'error')) {
@@ -120,6 +121,7 @@ function validateGeneratedApiResource(definition, resource, resourceIds, diagnos
120
121
  if (resource.collection.name.trim().length === 0) {
121
122
  diagnostics.push(resourceDiagnostic(definition, resource, 'Generated API resource collections must have a non-empty name.', 'collection.name'));
122
123
  }
124
+ diagnostics.push(...validateGeneratedApiCollection(definition, resource));
123
125
  if (resource.operations.length === 0) {
124
126
  diagnostics.push(resourceDiagnostic(definition, resource, 'Generated API resources require at least one operation.', 'operations'));
125
127
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/generated-api/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DataContractValue,\n DataEndpointConfig,\n DataOperationConfig,\n DataOperationIntent,\n DataOperationParameter,\n DataSchema,\n DataSourceDiagnostic,\n DataSourceDiagnosticResult,\n GeneratedApiAuthRequirement,\n GeneratedApiCrudOperation,\n GeneratedApiDefinition,\n GeneratedApiOperationPolicyRef,\n GeneratedApiResourceDefinition,\n GeneratedRestApiDataSourceConfig,\n OperationId,\n} from '@ankhorage/contracts/data';\nimport type {\n DbCollectionDefinition,\n DbFieldDefinition,\n DbFieldType,\n} from '@ankhorage/contracts/db';\n\nexport function createGeneratedApiDataSource(\n definition: GeneratedApiDefinition,\n): DataSourceDiagnosticResult<GeneratedRestApiDataSourceConfig> {\n const diagnostics = validateGeneratedApiDefinition(definition);\n if (diagnostics.some((diagnostic) => diagnostic.severity === 'error')) {\n return { ok: false, diagnostics };\n }\n\n return {\n ok: true,\n data: normalizeGeneratedApiDataSource(definition),\n diagnostics,\n };\n}\n\nexport function validateGeneratedApiDefinition(\n definition: GeneratedApiDefinition,\n): readonly DataSourceDiagnostic[] {\n const diagnostics: DataSourceDiagnostic[] = [];\n const resourceIds = new Set<string>();\n\n if (definition.basePath.trim().length === 0 || !definition.basePath.startsWith('/')) {\n diagnostics.push({\n code: 'invalid-config',\n dataSourceId: definition.id,\n message: 'Generated API basePath must be a non-empty absolute path.',\n path: 'basePath',\n severity: 'error',\n });\n }\n\n if (definition.resources.length === 0) {\n diagnostics.push({\n code: 'invalid-config',\n dataSourceId: definition.id,\n message: 'Generated API requires at least one resource.',\n path: 'resources',\n severity: 'error',\n });\n }\n\n for (const resource of definition.resources) {\n validateGeneratedApiResource(definition, resource, resourceIds, diagnostics);\n }\n\n return diagnostics;\n}\n\nexport function normalizeGeneratedApiDataSource(\n definition: GeneratedApiDefinition,\n): GeneratedRestApiDataSourceConfig {\n const endpoints: Record<string, DataEndpointConfig> = {};\n const schemas: Record<string, DataSchema> = {};\n\n for (const resource of definition.resources) {\n endpoints[resource.id] = createGeneratedApiEndpoint(definition, resource);\n schemas[resource.id] = createGeneratedApiResourceSchema(resource.collection);\n }\n\n return {\n id: definition.id,\n kind: 'api',\n origin: 'generated',\n protocol: 'rest',\n generatedApiId: definition.id,\n name: definition.name,\n description: definition.description,\n adapter: definition.database,\n endpoints,\n schemas,\n metadata: createGeneratedApiSourceMetadata(definition),\n };\n}\n\nexport function createGeneratedApiEndpoint(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n): DataEndpointConfig {\n const operations: Record<OperationId, DataOperationConfig> = {};\n\n for (const operation of resource.operations) {\n const config = createGeneratedApiOperation(definition, resource, operation);\n operations[config.id] = config;\n }\n\n return {\n id: resource.id,\n kind: 'database',\n name: resource.name,\n description: resource.description,\n path: resource.path,\n operations,\n metadata: createGeneratedApiResourceMetadata(definition, resource),\n };\n}\n\nexport function createGeneratedApiOperation(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n operation: GeneratedApiCrudOperation,\n): DataOperationConfig {\n const operationId = createGeneratedApiOperationId(resource.id, operation);\n const primaryKey = resolveGeneratedApiPrimaryKey(resource.collection);\n const primaryKeyParameter =\n primaryKey === undefined ? undefined : createPrimaryKeyParameter(primaryKey);\n\n return {\n id: operationId,\n endpointId: resource.id,\n name: createGeneratedApiOperationName(resource, operation),\n protocol: 'database',\n intent: mapGeneratedApiOperationIntent(operation),\n path: resource.path,\n request: createGeneratedApiOperationRequest(\n resource.collection,\n operation,\n primaryKeyParameter,\n ),\n response: createGeneratedApiOperationResponse(resource.collection, operation),\n metadata: createGeneratedApiOperationMetadata(definition, resource, operation),\n };\n}\n\nexport function createGeneratedApiOperationId(\n resourceId: string,\n operation: GeneratedApiCrudOperation,\n): OperationId {\n return `${resourceId}.${operation}`;\n}\n\nexport function createGeneratedApiResourceSchema(collection: DbCollectionDefinition): DataSchema {\n const properties: Record<string, DataSchema> = {};\n const required: string[] = [];\n\n for (const field of collection.fields) {\n properties[field.name] = createGeneratedApiFieldSchema(field);\n if (field.required === true) required.push(field.name);\n }\n\n return {\n type: 'object',\n title: collection.name,\n required: required.length > 0 ? required : undefined,\n properties,\n };\n}\n\nfunction validateGeneratedApiResource(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n resourceIds: Set<string>,\n diagnostics: DataSourceDiagnostic[],\n): void {\n if (resource.id.trim().length === 0) {\n diagnostics.push(\n resourceDiagnostic(definition, resource, 'Resource IDs must be non-empty.', 'id'),\n );\n } else if (resourceIds.has(resource.id)) {\n diagnostics.push(\n resourceDiagnostic(definition, resource, `Resource ID '${resource.id}' is duplicated.`, 'id'),\n );\n }\n resourceIds.add(resource.id);\n\n if (!resource.path.startsWith('/')) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resource paths must start with `/`.',\n 'path',\n ),\n );\n }\n\n if (resource.collection.name.trim().length === 0) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resource collections must have a non-empty name.',\n 'collection.name',\n ),\n );\n }\n\n if (resource.operations.length === 0) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resources require at least one operation.',\n 'operations',\n ),\n );\n }\n\n const primaryKey = resolveGeneratedApiPrimaryKey(resource.collection);\n if (\n primaryKey === undefined &&\n resource.operations.some((operation) => operation !== 'list' && operation !== 'create')\n ) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resources need a primaryKey field for read/update/delete operations.',\n 'collection.primaryKey',\n ),\n );\n }\n}\n\nfunction resourceDiagnostic(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n message: string,\n path: string,\n): DataSourceDiagnostic {\n return {\n code: 'invalid-config',\n dataSourceId: definition.id,\n endpointId: resource.id,\n message,\n path: `resources.${resource.id}.${path}`,\n severity: 'error',\n };\n}\n\nfunction createGeneratedApiSourceMetadata(definition: GeneratedApiDefinition): DataContractValue {\n return {\n ...readMetadataRecord(definition.metadata),\n basePath: definition.basePath,\n source: 'generated-api',\n auth: createGeneratedApiAuthMetadata(definition.auth),\n };\n}\n\nfunction createGeneratedApiResourceMetadata(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n): DataContractValue {\n return {\n ...readMetadataRecord(resource.metadata),\n adapterId: definition.database.id,\n collection: resource.collection.name,\n schema: resource.collection.schema ?? null,\n source: 'generated-api',\n seed: resource.seed ?? [],\n policies: createGeneratedApiPolicyMetadata(resource.policies),\n };\n}\n\nfunction createGeneratedApiOperationMetadata(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n operation: GeneratedApiCrudOperation,\n): DataContractValue {\n return {\n adapterId: definition.database.id,\n collection: resource.collection.name,\n operation,\n schema: resource.collection.schema ?? null,\n source: 'generated-api',\n policies: createGeneratedApiPolicyMetadata(\n resource.policies?.filter(\n (policy) => policy.operation === undefined || policy.operation === operation,\n ),\n ),\n };\n}\n\nfunction createGeneratedApiAuthMetadata(\n auth: GeneratedApiAuthRequirement | undefined,\n): DataContractValue {\n if (auth === undefined) return null;\n\n const metadata: Record<string, DataContractValue> = {};\n if (auth.required !== undefined) metadata.required = auth.required;\n if (auth.roles !== undefined) metadata.roles = auth.roles;\n if (auth.permissions !== undefined) metadata.permissions = auth.permissions;\n if (auth.policy !== undefined) metadata.policy = auth.policy;\n if (auth.metadata !== undefined) metadata.metadata = auth.metadata;\n return metadata;\n}\n\nfunction createGeneratedApiPolicyMetadata(\n policies: readonly GeneratedApiOperationPolicyRef[] | undefined,\n): readonly DataContractValue[] {\n return (policies ?? []).map((policy) => {\n const metadata: Record<string, DataContractValue> = { id: policy.id };\n if (policy.operation !== undefined) metadata.operation = policy.operation;\n return metadata;\n });\n}\n\nfunction createGeneratedApiOperationName(\n resource: GeneratedApiResourceDefinition,\n operation: GeneratedApiCrudOperation,\n): string {\n return `${operation} ${resource.name ?? resource.id}`;\n}\n\nfunction mapGeneratedApiOperationIntent(operation: GeneratedApiCrudOperation): DataOperationIntent {\n if (operation === 'list' || operation === 'read') return 'read';\n if (operation === 'create') return 'create';\n if (operation === 'update') return 'update';\n return 'delete';\n}\n\nfunction createGeneratedApiOperationRequest(\n collection: DbCollectionDefinition,\n operation: GeneratedApiCrudOperation,\n primaryKeyParameter: DataOperationParameter | undefined,\n): DataOperationConfig['request'] {\n if (operation === 'list') {\n return {\n parameters: [\n { name: 'limit', location: 'query', schema: { type: 'integer' } },\n { name: 'offset', location: 'query', schema: { type: 'integer' } },\n ],\n };\n }\n\n if (operation === 'read' || operation === 'delete') {\n return primaryKeyParameter === undefined ? undefined : { parameters: [primaryKeyParameter] };\n }\n\n const parameters =\n operation === 'update' && primaryKeyParameter !== undefined ? [primaryKeyParameter] : undefined;\n\n return {\n parameters,\n schema: createGeneratedApiResourceSchema(collection),\n };\n}\n\nfunction createGeneratedApiOperationResponse(\n collection: DbCollectionDefinition,\n operation: GeneratedApiCrudOperation,\n): DataOperationConfig['response'] {\n const schema = createGeneratedApiResourceSchema(collection);\n\n if (operation === 'list') return { schema: { type: 'array', items: schema } };\n if (operation === 'delete') {\n return { schema: { type: 'object', properties: { deleted: { type: 'boolean' } } } };\n }\n return { schema };\n}\n\nfunction createPrimaryKeyParameter(primaryKey: string): DataOperationParameter {\n return {\n name: primaryKey,\n location: 'path',\n required: true,\n schema: { type: 'string' },\n };\n}\n\nfunction resolveGeneratedApiPrimaryKey(collection: DbCollectionDefinition): string | undefined {\n return collection.primaryKey ?? collection.fields.find((field) => field.unique === true)?.name;\n}\n\nfunction createGeneratedApiFieldSchema(field: DbFieldDefinition): DataSchema {\n return {\n ...mapDbFieldTypeToDataSchema(field.type),\n default: field.defaultValue,\n };\n}\n\nfunction mapDbFieldTypeToDataSchema(type: DbFieldType): DataSchema {\n if (type === 'boolean') return { type: 'boolean' };\n if (type === 'datetime') return { type: 'string', format: 'date-time' };\n if (type === 'json') return { type: 'object' };\n if (type === 'number') return { type: 'number' };\n if (type === 'uuid') return { type: 'string', format: 'uuid' };\n return { type: 'string' };\n}\n\nfunction readMetadataRecord(\n value: DataContractValue | undefined,\n): Record<string, DataContractValue> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return {};\n const metadata: Record<string, DataContractValue> = {};\n for (const [key, item] of Object.entries(value)) metadata[key] = item;\n return metadata;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/generated-api/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DataContractValue,\n DataEndpointConfig,\n DataOperationConfig,\n DataOperationIntent,\n DataOperationParameter,\n DataSchema,\n DataSourceDiagnostic,\n DataSourceDiagnosticResult,\n GeneratedApiAuthRequirement,\n GeneratedApiCrudOperation,\n GeneratedApiDefinition,\n GeneratedApiOperationPolicyRef,\n GeneratedApiResourceDefinition,\n GeneratedRestApiDataSourceConfig,\n OperationId,\n} from '@ankhorage/contracts/data';\nimport type {\n DbCollectionDefinition,\n DbFieldDefinition,\n DbFieldType,\n} from '@ankhorage/contracts/db';\n\nimport { validateGeneratedApiCollection } from './validateGeneratedApiCollection';\n\nexport function createGeneratedApiDataSource(\n definition: GeneratedApiDefinition,\n): DataSourceDiagnosticResult<GeneratedRestApiDataSourceConfig> {\n const diagnostics = validateGeneratedApiDefinition(definition);\n if (diagnostics.some((diagnostic) => diagnostic.severity === 'error')) {\n return { ok: false, diagnostics };\n }\n\n return {\n ok: true,\n data: normalizeGeneratedApiDataSource(definition),\n diagnostics,\n };\n}\n\nexport function validateGeneratedApiDefinition(\n definition: GeneratedApiDefinition,\n): readonly DataSourceDiagnostic[] {\n const diagnostics: DataSourceDiagnostic[] = [];\n const resourceIds = new Set<string>();\n\n if (definition.basePath.trim().length === 0 || !definition.basePath.startsWith('/')) {\n diagnostics.push({\n code: 'invalid-config',\n dataSourceId: definition.id,\n message: 'Generated API basePath must be a non-empty absolute path.',\n path: 'basePath',\n severity: 'error',\n });\n }\n\n if (definition.resources.length === 0) {\n diagnostics.push({\n code: 'invalid-config',\n dataSourceId: definition.id,\n message: 'Generated API requires at least one resource.',\n path: 'resources',\n severity: 'error',\n });\n }\n\n for (const resource of definition.resources) {\n validateGeneratedApiResource(definition, resource, resourceIds, diagnostics);\n }\n\n return diagnostics;\n}\n\nexport function normalizeGeneratedApiDataSource(\n definition: GeneratedApiDefinition,\n): GeneratedRestApiDataSourceConfig {\n const endpoints: Record<string, DataEndpointConfig> = {};\n const schemas: Record<string, DataSchema> = {};\n\n for (const resource of definition.resources) {\n endpoints[resource.id] = createGeneratedApiEndpoint(definition, resource);\n schemas[resource.id] = createGeneratedApiResourceSchema(resource.collection);\n }\n\n return {\n id: definition.id,\n kind: 'api',\n origin: 'generated',\n protocol: 'rest',\n generatedApiId: definition.id,\n name: definition.name,\n description: definition.description,\n adapter: definition.database,\n endpoints,\n schemas,\n metadata: createGeneratedApiSourceMetadata(definition),\n };\n}\n\nexport function createGeneratedApiEndpoint(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n): DataEndpointConfig {\n const operations: Record<OperationId, DataOperationConfig> = {};\n\n for (const operation of resource.operations) {\n const config = createGeneratedApiOperation(definition, resource, operation);\n operations[config.id] = config;\n }\n\n return {\n id: resource.id,\n kind: 'database',\n name: resource.name,\n description: resource.description,\n path: resource.path,\n operations,\n metadata: createGeneratedApiResourceMetadata(definition, resource),\n };\n}\n\nexport function createGeneratedApiOperation(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n operation: GeneratedApiCrudOperation,\n): DataOperationConfig {\n const operationId = createGeneratedApiOperationId(resource.id, operation);\n const primaryKey = resolveGeneratedApiPrimaryKey(resource.collection);\n const primaryKeyParameter =\n primaryKey === undefined ? undefined : createPrimaryKeyParameter(primaryKey);\n\n return {\n id: operationId,\n endpointId: resource.id,\n name: createGeneratedApiOperationName(resource, operation),\n protocol: 'database',\n intent: mapGeneratedApiOperationIntent(operation),\n path: resource.path,\n request: createGeneratedApiOperationRequest(\n resource.collection,\n operation,\n primaryKeyParameter,\n ),\n response: createGeneratedApiOperationResponse(resource.collection, operation),\n metadata: createGeneratedApiOperationMetadata(definition, resource, operation),\n };\n}\n\nexport function createGeneratedApiOperationId(\n resourceId: string,\n operation: GeneratedApiCrudOperation,\n): OperationId {\n return `${resourceId}.${operation}`;\n}\n\nexport function createGeneratedApiResourceSchema(collection: DbCollectionDefinition): DataSchema {\n const properties: Record<string, DataSchema> = {};\n const required: string[] = [];\n\n for (const field of collection.fields) {\n properties[field.name] = createGeneratedApiFieldSchema(field);\n if (field.required === true) required.push(field.name);\n }\n\n return {\n type: 'object',\n title: collection.name,\n required: required.length > 0 ? required : undefined,\n properties,\n };\n}\n\nfunction validateGeneratedApiResource(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n resourceIds: Set<string>,\n diagnostics: DataSourceDiagnostic[],\n): void {\n if (resource.id.trim().length === 0) {\n diagnostics.push(\n resourceDiagnostic(definition, resource, 'Resource IDs must be non-empty.', 'id'),\n );\n } else if (resourceIds.has(resource.id)) {\n diagnostics.push(\n resourceDiagnostic(definition, resource, `Resource ID '${resource.id}' is duplicated.`, 'id'),\n );\n }\n resourceIds.add(resource.id);\n\n if (!resource.path.startsWith('/')) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resource paths must start with `/`.',\n 'path',\n ),\n );\n }\n\n if (resource.collection.name.trim().length === 0) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resource collections must have a non-empty name.',\n 'collection.name',\n ),\n );\n }\n\n diagnostics.push(...validateGeneratedApiCollection(definition, resource));\n\n if (resource.operations.length === 0) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resources require at least one operation.',\n 'operations',\n ),\n );\n }\n\n const primaryKey = resolveGeneratedApiPrimaryKey(resource.collection);\n if (\n primaryKey === undefined &&\n resource.operations.some((operation) => operation !== 'list' && operation !== 'create')\n ) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resources need a primaryKey field for read/update/delete operations.',\n 'collection.primaryKey',\n ),\n );\n }\n}\n\nfunction resourceDiagnostic(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n message: string,\n path: string,\n): DataSourceDiagnostic {\n return {\n code: 'invalid-config',\n dataSourceId: definition.id,\n endpointId: resource.id,\n message,\n path: `resources.${resource.id}.${path}`,\n severity: 'error',\n };\n}\n\nfunction createGeneratedApiSourceMetadata(definition: GeneratedApiDefinition): DataContractValue {\n return {\n ...readMetadataRecord(definition.metadata),\n basePath: definition.basePath,\n source: 'generated-api',\n auth: createGeneratedApiAuthMetadata(definition.auth),\n };\n}\n\nfunction createGeneratedApiResourceMetadata(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n): DataContractValue {\n return {\n ...readMetadataRecord(resource.metadata),\n adapterId: definition.database.id,\n collection: resource.collection.name,\n schema: resource.collection.schema ?? null,\n source: 'generated-api',\n seed: resource.seed ?? [],\n policies: createGeneratedApiPolicyMetadata(resource.policies),\n };\n}\n\nfunction createGeneratedApiOperationMetadata(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n operation: GeneratedApiCrudOperation,\n): DataContractValue {\n return {\n adapterId: definition.database.id,\n collection: resource.collection.name,\n operation,\n schema: resource.collection.schema ?? null,\n source: 'generated-api',\n policies: createGeneratedApiPolicyMetadata(\n resource.policies?.filter(\n (policy) => policy.operation === undefined || policy.operation === operation,\n ),\n ),\n };\n}\n\nfunction createGeneratedApiAuthMetadata(\n auth: GeneratedApiAuthRequirement | undefined,\n): DataContractValue {\n if (auth === undefined) return null;\n\n const metadata: Record<string, DataContractValue> = {};\n if (auth.required !== undefined) metadata.required = auth.required;\n if (auth.roles !== undefined) metadata.roles = auth.roles;\n if (auth.permissions !== undefined) metadata.permissions = auth.permissions;\n if (auth.policy !== undefined) metadata.policy = auth.policy;\n if (auth.metadata !== undefined) metadata.metadata = auth.metadata;\n return metadata;\n}\n\nfunction createGeneratedApiPolicyMetadata(\n policies: readonly GeneratedApiOperationPolicyRef[] | undefined,\n): readonly DataContractValue[] {\n return (policies ?? []).map((policy) => {\n const metadata: Record<string, DataContractValue> = { id: policy.id };\n if (policy.operation !== undefined) metadata.operation = policy.operation;\n return metadata;\n });\n}\n\nfunction createGeneratedApiOperationName(\n resource: GeneratedApiResourceDefinition,\n operation: GeneratedApiCrudOperation,\n): string {\n return `${operation} ${resource.name ?? resource.id}`;\n}\n\nfunction mapGeneratedApiOperationIntent(operation: GeneratedApiCrudOperation): DataOperationIntent {\n if (operation === 'list' || operation === 'read') return 'read';\n if (operation === 'create') return 'create';\n if (operation === 'update') return 'update';\n return 'delete';\n}\n\nfunction createGeneratedApiOperationRequest(\n collection: DbCollectionDefinition,\n operation: GeneratedApiCrudOperation,\n primaryKeyParameter: DataOperationParameter | undefined,\n): DataOperationConfig['request'] {\n if (operation === 'list') {\n return {\n parameters: [\n { name: 'limit', location: 'query', schema: { type: 'integer' } },\n { name: 'offset', location: 'query', schema: { type: 'integer' } },\n ],\n };\n }\n\n if (operation === 'read' || operation === 'delete') {\n return primaryKeyParameter === undefined ? undefined : { parameters: [primaryKeyParameter] };\n }\n\n const parameters =\n operation === 'update' && primaryKeyParameter !== undefined ? [primaryKeyParameter] : undefined;\n\n return {\n parameters,\n schema: createGeneratedApiResourceSchema(collection),\n };\n}\n\nfunction createGeneratedApiOperationResponse(\n collection: DbCollectionDefinition,\n operation: GeneratedApiCrudOperation,\n): DataOperationConfig['response'] {\n const schema = createGeneratedApiResourceSchema(collection);\n\n if (operation === 'list') return { schema: { type: 'array', items: schema } };\n if (operation === 'delete') {\n return { schema: { type: 'object', properties: { deleted: { type: 'boolean' } } } };\n }\n return { schema };\n}\n\nfunction createPrimaryKeyParameter(primaryKey: string): DataOperationParameter {\n return {\n name: primaryKey,\n location: 'path',\n required: true,\n schema: { type: 'string' },\n };\n}\n\nfunction resolveGeneratedApiPrimaryKey(collection: DbCollectionDefinition): string | undefined {\n return collection.primaryKey ?? collection.fields.find((field) => field.unique === true)?.name;\n}\n\nfunction createGeneratedApiFieldSchema(field: DbFieldDefinition): DataSchema {\n return {\n ...mapDbFieldTypeToDataSchema(field.type),\n default: field.defaultValue,\n };\n}\n\nfunction mapDbFieldTypeToDataSchema(type: DbFieldType): DataSchema {\n if (type === 'boolean') return { type: 'boolean' };\n if (type === 'datetime') return { type: 'string', format: 'date-time' };\n if (type === 'json') return { type: 'object' };\n if (type === 'number') return { type: 'number' };\n if (type === 'uuid') return { type: 'string', format: 'uuid' };\n return { type: 'string' };\n}\n\nfunction readMetadataRecord(\n value: DataContractValue | undefined,\n): Record<string, DataContractValue> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return {};\n const metadata: Record<string, DataContractValue> = {};\n for (const [key, item] of Object.entries(value)) metadata[key] = item;\n return metadata;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { DataSourceDiagnostic, GeneratedApiDefinition, GeneratedApiResourceDefinition } from '@ankhorage/contracts/data';
2
+ export declare function validateGeneratedApiCollection(definition: GeneratedApiDefinition, resource: GeneratedApiResourceDefinition): readonly DataSourceDiagnostic[];
3
+ //# sourceMappingURL=validateGeneratedApiCollection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateGeneratedApiCollection.d.ts","sourceRoot":"","sources":["../../src/generated-api/validateGeneratedApiCollection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EACtB,8BAA8B,EAC/B,MAAM,2BAA2B,CAAC;AAEnC,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,sBAAsB,EAClC,QAAQ,EAAE,8BAA8B,GACvC,SAAS,oBAAoB,EAAE,CAkDjC"}
@@ -0,0 +1,35 @@
1
+ export function validateGeneratedApiCollection(definition, resource) {
2
+ const diagnostics = [];
3
+ const { fields, primaryKey } = resource.collection;
4
+ const fieldNames = new Set();
5
+ if (fields.length === 0) {
6
+ diagnostics.push(diagnostic(definition, resource, 'Generated API resources require fields.', 'fields'));
7
+ }
8
+ fields.forEach((field, index) => {
9
+ const name = field.name.trim();
10
+ if (!name) {
11
+ diagnostics.push(diagnostic(definition, resource, 'Generated API field names must be non-empty.', `fields.${index}.name`));
12
+ return;
13
+ }
14
+ if (fieldNames.has(name)) {
15
+ diagnostics.push(diagnostic(definition, resource, `Generated API field '${name}' is duplicated.`, `fields.${index}.name`));
16
+ return;
17
+ }
18
+ fieldNames.add(name);
19
+ });
20
+ if (primaryKey !== undefined && !fields.some((field) => field.name === primaryKey)) {
21
+ diagnostics.push(diagnostic(definition, resource, `Primary key '${primaryKey}' does not match a collection field.`, 'primaryKey'));
22
+ }
23
+ return diagnostics;
24
+ }
25
+ function diagnostic(definition, resource, message, path) {
26
+ return {
27
+ code: 'invalid-config',
28
+ dataSourceId: definition.id,
29
+ endpointId: resource.id,
30
+ message,
31
+ path: `resources.${resource.id}.collection.${path}`,
32
+ severity: 'error',
33
+ };
34
+ }
35
+ //# sourceMappingURL=validateGeneratedApiCollection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateGeneratedApiCollection.js","sourceRoot":"","sources":["../../src/generated-api/validateGeneratedApiCollection.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,8BAA8B,CAC5C,UAAkC,EAClC,QAAwC;IAExC,MAAM,WAAW,GAA2B,EAAE,CAAC;IAC/C,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,QAAQ,CAAC,UAAU,CAAC;IACnD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IAErC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,WAAW,CAAC,IAAI,CACd,UAAU,CAAC,UAAU,EAAE,QAAQ,EAAE,yCAAyC,EAAE,QAAQ,CAAC,CACtF,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,WAAW,CAAC,IAAI,CACd,UAAU,CACR,UAAU,EACV,QAAQ,EACR,8CAA8C,EAC9C,UAAU,KAAK,OAAO,CACvB,CACF,CAAC;YACF,OAAO;QACT,CAAC;QACD,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,WAAW,CAAC,IAAI,CACd,UAAU,CACR,UAAU,EACV,QAAQ,EACR,wBAAwB,IAAI,kBAAkB,EAC9C,UAAU,KAAK,OAAO,CACvB,CACF,CAAC;YACF,OAAO;QACT,CAAC;QACD,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,EAAE,CAAC;QACnF,WAAW,CAAC,IAAI,CACd,UAAU,CACR,UAAU,EACV,QAAQ,EACR,gBAAgB,UAAU,sCAAsC,EAChE,YAAY,CACb,CACF,CAAC;IACJ,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,UAAU,CACjB,UAAkC,EAClC,QAAwC,EACxC,OAAe,EACf,IAAY;IAEZ,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,YAAY,EAAE,UAAU,CAAC,EAAE;QAC3B,UAAU,EAAE,QAAQ,CAAC,EAAE;QACvB,OAAO;QACP,IAAI,EAAE,aAAa,QAAQ,CAAC,EAAE,eAAe,IAAI,EAAE;QACnD,QAAQ,EAAE,OAAO;KAClB,CAAC;AACJ,CAAC","sourcesContent":["import type {\n DataSourceDiagnostic,\n GeneratedApiDefinition,\n GeneratedApiResourceDefinition,\n} from '@ankhorage/contracts/data';\n\nexport function validateGeneratedApiCollection(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n): readonly DataSourceDiagnostic[] {\n const diagnostics: DataSourceDiagnostic[] = [];\n const { fields, primaryKey } = resource.collection;\n const fieldNames = new Set<string>();\n\n if (fields.length === 0) {\n diagnostics.push(\n diagnostic(definition, resource, 'Generated API resources require fields.', 'fields'),\n );\n }\n\n fields.forEach((field, index) => {\n const name = field.name.trim();\n if (!name) {\n diagnostics.push(\n diagnostic(\n definition,\n resource,\n 'Generated API field names must be non-empty.',\n `fields.${index}.name`,\n ),\n );\n return;\n }\n if (fieldNames.has(name)) {\n diagnostics.push(\n diagnostic(\n definition,\n resource,\n `Generated API field '${name}' is duplicated.`,\n `fields.${index}.name`,\n ),\n );\n return;\n }\n fieldNames.add(name);\n });\n\n if (primaryKey !== undefined && !fields.some((field) => field.name === primaryKey)) {\n diagnostics.push(\n diagnostic(\n definition,\n resource,\n `Primary key '${primaryKey}' does not match a collection field.`,\n 'primaryKey',\n ),\n );\n }\n\n return diagnostics;\n}\n\nfunction diagnostic(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n message: string,\n path: string,\n): DataSourceDiagnostic {\n return {\n code: 'invalid-config',\n dataSourceId: definition.id,\n endpointId: resource.id,\n message,\n path: `resources.${resource.id}.collection.${path}`,\n severity: 'error',\n };\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/data-sources",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "1.0.1",
5
5
  "description": "Headless toolkit for importing, generating, normalizing, and testing Ankhorage data-source configs.",
6
6
  "homepage": "https://github.com/ankhorage/data-sources#readme",
7
7
  "bugs": {
@@ -21,6 +21,8 @@ import type {
21
21
  DbFieldType,
22
22
  } from '@ankhorage/contracts/db';
23
23
 
24
+ import { validateGeneratedApiCollection } from './validateGeneratedApiCollection';
25
+
24
26
  export function createGeneratedApiDataSource(
25
27
  definition: GeneratedApiDefinition,
26
28
  ): DataSourceDiagnosticResult<GeneratedRestApiDataSourceConfig> {
@@ -207,6 +209,8 @@ function validateGeneratedApiResource(
207
209
  );
208
210
  }
209
211
 
212
+ diagnostics.push(...validateGeneratedApiCollection(definition, resource));
213
+
210
214
  if (resource.operations.length === 0) {
211
215
  diagnostics.push(
212
216
  resourceDiagnostic(
@@ -0,0 +1,91 @@
1
+ import type { GeneratedApiDefinition } from '@ankhorage/contracts/data';
2
+ import { describe, expect, it } from 'bun:test';
3
+
4
+ import { validateGeneratedApiDefinition } from './index';
5
+
6
+ function createDefinition(): GeneratedApiDefinition {
7
+ return {
8
+ id: 'posts-api',
9
+ protocol: 'rest',
10
+ basePath: '/api',
11
+ database: { id: 'primary-db', kind: 'database' },
12
+ resources: [
13
+ {
14
+ id: 'posts',
15
+ path: '/posts',
16
+ operations: ['list', 'read'],
17
+ collection: {
18
+ name: 'posts',
19
+ primaryKey: 'id',
20
+ fields: [
21
+ { name: 'id', type: 'uuid', required: true, unique: true },
22
+ { name: 'title', type: 'text' },
23
+ ],
24
+ },
25
+ },
26
+ ],
27
+ };
28
+ }
29
+
30
+ function diagnosticPaths(definition: GeneratedApiDefinition): readonly string[] {
31
+ return validateGeneratedApiDefinition(definition).map((diagnostic) => diagnostic.path ?? '');
32
+ }
33
+
34
+ describe('generated API collection validation', () => {
35
+ it('rejects resources without fields', () => {
36
+ const definition = createDefinition();
37
+ const [resource] = definition.resources;
38
+ if (!resource) throw new Error('Expected resource fixture.');
39
+
40
+ const paths = diagnosticPaths({
41
+ ...definition,
42
+ resources: [{ ...resource, collection: { ...resource.collection, fields: [] } }],
43
+ });
44
+
45
+ expect(paths).toContain('resources.posts.collection.fields');
46
+ });
47
+
48
+ it('rejects empty and duplicate field names', () => {
49
+ const definition = createDefinition();
50
+ const [resource] = definition.resources;
51
+ if (!resource) throw new Error('Expected resource fixture.');
52
+
53
+ const paths = diagnosticPaths({
54
+ ...definition,
55
+ resources: [
56
+ {
57
+ ...resource,
58
+ collection: {
59
+ ...resource.collection,
60
+ fields: [
61
+ { name: '', type: 'text' },
62
+ { name: 'title', type: 'text' },
63
+ { name: 'title', type: 'text' },
64
+ ],
65
+ },
66
+ },
67
+ ],
68
+ });
69
+
70
+ expect(paths).toContain('resources.posts.collection.fields.0.name');
71
+ expect(paths).toContain('resources.posts.collection.fields.2.name');
72
+ });
73
+
74
+ it('rejects primary keys that do not reference a field', () => {
75
+ const definition = createDefinition();
76
+ const [resource] = definition.resources;
77
+ if (!resource) throw new Error('Expected resource fixture.');
78
+
79
+ const paths = diagnosticPaths({
80
+ ...definition,
81
+ resources: [
82
+ {
83
+ ...resource,
84
+ collection: { ...resource.collection, primaryKey: 'missing' },
85
+ },
86
+ ],
87
+ });
88
+
89
+ expect(paths).toContain('resources.posts.collection.primaryKey');
90
+ });
91
+ });
@@ -0,0 +1,76 @@
1
+ import type {
2
+ DataSourceDiagnostic,
3
+ GeneratedApiDefinition,
4
+ GeneratedApiResourceDefinition,
5
+ } from '@ankhorage/contracts/data';
6
+
7
+ export function validateGeneratedApiCollection(
8
+ definition: GeneratedApiDefinition,
9
+ resource: GeneratedApiResourceDefinition,
10
+ ): readonly DataSourceDiagnostic[] {
11
+ const diagnostics: DataSourceDiagnostic[] = [];
12
+ const { fields, primaryKey } = resource.collection;
13
+ const fieldNames = new Set<string>();
14
+
15
+ if (fields.length === 0) {
16
+ diagnostics.push(
17
+ diagnostic(definition, resource, 'Generated API resources require fields.', 'fields'),
18
+ );
19
+ }
20
+
21
+ fields.forEach((field, index) => {
22
+ const name = field.name.trim();
23
+ if (!name) {
24
+ diagnostics.push(
25
+ diagnostic(
26
+ definition,
27
+ resource,
28
+ 'Generated API field names must be non-empty.',
29
+ `fields.${index}.name`,
30
+ ),
31
+ );
32
+ return;
33
+ }
34
+ if (fieldNames.has(name)) {
35
+ diagnostics.push(
36
+ diagnostic(
37
+ definition,
38
+ resource,
39
+ `Generated API field '${name}' is duplicated.`,
40
+ `fields.${index}.name`,
41
+ ),
42
+ );
43
+ return;
44
+ }
45
+ fieldNames.add(name);
46
+ });
47
+
48
+ if (primaryKey !== undefined && !fields.some((field) => field.name === primaryKey)) {
49
+ diagnostics.push(
50
+ diagnostic(
51
+ definition,
52
+ resource,
53
+ `Primary key '${primaryKey}' does not match a collection field.`,
54
+ 'primaryKey',
55
+ ),
56
+ );
57
+ }
58
+
59
+ return diagnostics;
60
+ }
61
+
62
+ function diagnostic(
63
+ definition: GeneratedApiDefinition,
64
+ resource: GeneratedApiResourceDefinition,
65
+ message: string,
66
+ path: string,
67
+ ): DataSourceDiagnostic {
68
+ return {
69
+ code: 'invalid-config',
70
+ dataSourceId: definition.id,
71
+ endpointId: resource.id,
72
+ message,
73
+ path: `resources.${resource.id}.collection.${path}`,
74
+ severity: 'error',
75
+ };
76
+ }