feat: more fixes

This commit is contained in:
2023-11-01 16:10:12 +01:00
parent 4bdd4b20d0
commit 62d5ea4275
23 changed files with 80 additions and 289 deletions

View File

@@ -24,6 +24,10 @@ type NameOf<I extends SchemaMap[keyof SchemaMap]> = keyof IncludeProperty<Schema
export class Validator {
private readonly ajv: Ajv.default;
get errors() {
return this.ajv.errors;
}
constructor(additionalSchemas: AnySchema[] = []) {
this.ajv = new Ajv.default({
schemas: [schema, ...additionalSchemas],
@@ -36,12 +40,21 @@ export class Validator {
});
}
/**
* Add additional schemas to the validator
*/
public addSchema(...schema: AnySchema[]) {
this.ajv.addSchema(schema);
}
/**
* Validates anything against a given schema name or infers schema name from object
* @param instance Instance to validate
* @param schema Name of schema to validate instance against or the schema itself
*/
public validate<T>(instance: unknown, schema: NameOf<T>): instance is T {
return this.ajv.validate(schema as string, instance);
public validate<T extends SchemaMap[keyof SchemaMap]>(instance: unknown, schema: NameOf<T>): instance is T;
public validate(instance: unknown, schema: Ajv.Schema): boolean;
public validate(instance: unknown, schema: Ajv.Schema | string): boolean {
return this.ajv.validate(schema, instance);
}
}

View File

@@ -32,7 +32,11 @@
},
"./schema.json": {
"import": "./lib/schema.json",
"types": "./lib/schema.d.ts"
"types": "./lib/schema.json.d.ts"
},
"./elasticsearch-mappings.json": {
"import": "./lib/elasticsearch.json",
"types": "./lib/elasticsearch.json.d.ts"
}
},
"files": [

View File

@@ -24,6 +24,8 @@
"test": "c8 mocha"
},
"dependencies": {
"ajv": "8.12.0",
"ajv-formats": "2.1.1",
"@elastic/elasticsearch": "8.10.0",
"@openstapps/json-schema-generator": "workspace:*",
"@openstapps/tsup-plugin": "workspace:*",

View File

@@ -1,85 +0,0 @@
/*
* Copyright (C) 2019-2021 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare module 'aggregations.json' {
const value: AggregationSchema;
export default value;
}
/**
* An elasticsearch bucket aggregation
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-aggregations-bucket.html
*/
export interface AggregationSchema {
[aggregationName: string]: ESTermsFilter | ESNestedAggregation;
}
/**
* An elasticsearch terms filter
*/
export interface ESTermsFilter {
/**
* Terms filter definition
*/
terms: {
/**
* Field to apply filter to
*/
field: string;
/**
* Number of results
*/
size?: number;
};
}
/**
* Filter that filters by name of the the field type
*/
export interface ESAggTypeFilter {
/**
* The type of the object to find
*/
term: {
/**
* The name of the type
*/
type: string;
};
}
/**
* Filter that matches everything
*/
export interface ESAggMatchAllFilter {
/**
* Filter that matches everything
*/
match_all: object;
}
/**
* For nested aggregations
*/
export interface ESNestedAggregation {
/**
* Possible nested Aggregations
*/
aggs: AggregationSchema;
/**
* Possible filter for types
*/
filter: ESAggTypeFilter | ESAggMatchAllFilter;
}

View File

@@ -1,124 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {IndicesPutTemplateRequest, MappingProperty} from '@elastic/elasticsearch/lib/api/types';
/*
* Copyright (C) 2019-2021 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {AggregationSchema} from './aggregations';
import {IndicesPutTemplateRequest, SearchRequest} from '@elastic/elasticsearch/lib/api/types.js';
declare module 'mappings.json' {
const value: ElasticsearchTemplateCollection;
export default value;
export interface ElasticsearchConfig {
mappings: Record<string, IndicesPutTemplateRequest>;
search: Partial<SearchRequest>;
}
/**
* Template output of the mapping generation
*/
export interface MappingGenTemplate {
/**
* All generated aggregations
*/
aggregations: AggregationSchema;
/**
* All errors that occurred
*/
errors: string[];
/**
* All mappings that were generated
*/
mappings: ElasticsearchTemplateCollection;
}
export type SimpleType = MappingProperty['type'] &
('keyword' | 'float' | 'boolean' | 'date' | 'integer' | 'text');
/**
* The Typemap is used to get the corresponding ElasticsearchDataType for a name provided by the ProjectReflection
*/
export interface ElasticsearchTypemap {
/**
* The `stringLiteral` type must always be provided
*/
stringLiteral: {
/**
* The default can be chosen freely, but must be provided
*/
default: SimpleType;
};
/**
* The name of the JS type, so for `number` it would be number
*/
[name: string]: {
/**
* The default ElasticsearchDataType that should be used, if no tag or only not implemented tags are found
*/
default: SimpleType;
/**
* The name of the tag, so for `@integer` it would be `integer`
*/
[name: string]: SimpleType;
};
}
/**
* The Fieldmap contains all tag names for fields and the corresponding fields
*
* The Fieldmap works in a similar fashion to the Typemap
*/
export interface ElasticsearchFieldmap {
/**
* The name of the tag, so for `@sortable` it would be `sortable`
*/
[name: string]: {
/**
* The default value if no parameter is provided
*/
default: {
/**
* To allow the usage of `prev.fields = {...prev.fields, ...fieldmap[tag.tagName].default}`
*
* We could also have used `default: any`, but this adds slightly more improved type-safety.
*/
[name: string]: any;
};
/**
* The tag parameters that will be ignored
*
* Some tag parameters might not be important for your implementation, so you can add their names here to not get
* any errors. The `default` will be used in that case.
*/
ignore: string[];
/**
* The parameters of the tag, so for `@sortable ducet` it would be `ducet`
*/
[name: string]: {
/**
* To allow the usage of `prev.fields = {...prev.fields, ...fieldmap[tag.tagName][tag.text.trim()]}`
*
* We could also have used `default: any`, but this adds slightly more improved type-safety.
*/
[name: string]: any;
};
};
}
/**
* A collection of Elasticsearch Templates
*/
export type ElasticsearchTemplateCollection = Record<string, Omit<IndicesPutTemplateRequest, 'name'>>;
declare const elasticsearchConfig: ElasticsearchConfig;
export default elasticsearchConfig;

View File

@@ -58,7 +58,7 @@ export class Context {
this.propertyPath,
new Map(),
);
const result = transformDefinition(derivedContext, definition);
const result = transformDefinition(derivedContext, definition!);
referenceName ??= crypto.createHash('md5').update(JSON.stringify(result)).digest('hex');
this.generator.cache.set(referenceName, {mapping: result, dependencies: derivedContext.dependencies});

View File

@@ -3,6 +3,7 @@ import {ElasticsearchOptionsDSL} from '../dsl/schema.js';
import {IndicesPutTemplateRequest, MappingProperty} from '@elastic/elasticsearch/lib/api/types.js';
import {MappingGenerator} from './mapping-generator.js';
import {getTags, INDEXABLE_TAG_NAME} from './tags.js';
import {ElasticsearchConfig} from '../../schema/mappings.js';
export interface GeneratorOptions {
/**
@@ -24,15 +25,16 @@ export interface GeneratorOptions {
/**
* Fully transform a project
*/
export function transformProject(project: JSONSchema7) {
export function transformProject(project: JSONSchema7): ElasticsearchConfig {
const context = new MappingGenerator(project, OPTIONS);
const results = [];
const results: Record<string, IndicesPutTemplateRequest> = {};
for (const name in project.definitions) {
const definition = project.definitions[name];
if (typeof definition !== 'object' || !getTags(definition).has(INDEXABLE_TAG_NAME)) continue;
results.push(context.buildTemplate(name));
const [type, template] = context.buildTemplate(name);
results[type] = template;
}
return {
mappings: results,

View File

@@ -41,7 +41,7 @@ export class MappingGenerator {
);
}
buildTemplate(name: string): IndicesPutTemplateRequest {
buildTemplate(name: string): [string, IndicesPutTemplateRequest] {
const thingType = ((this.project.definitions![name] as JSONSchema7).properties!.type as JSONSchema7)
.const;
if (typeof thingType !== 'string') {
@@ -71,6 +71,6 @@ export class MappingGenerator {
}
}
return request;
return [thingType, request];
}
}

View File

@@ -1,5 +1,13 @@
import {transformProject} from './generator/index.js';
import {SchemaConsumer} from '@openstapps/json-schema-generator';
import {readFile} from 'fs/promises';
import path from 'path';
import {fileURLToPath} from 'url';
const mappingTypes = await readFile(
path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'schema', 'mappings.d.ts'),
'utf8',
);
/**
* JSON Schema Generator plugin for Elasticsearch Mappings
@@ -10,6 +18,7 @@ export function elasticsearchMappingGenerator(fileName: string): [string, Schema
function (schema) {
return {
[fileName]: JSON.stringify(transformProject(schema)),
[`${fileName}.d.ts`]: mappingTypes,
};
},
];

View File

@@ -24,10 +24,10 @@ export function compileSchema(path: string, tsconfig: string): [schma: JSONSchem
Object.assign(fullSchema.definitions, generator.createSchema(schema).definitions);
}
const schemaTypes = `import {JSONSchema7} from 'json-schema';\n\nexport interface SchemaMap {\n${[
const schemaTypes = `import {JSONSchema7} from 'json-schema';\nimport * as index from './index.js';\n\nexport interface SchemaMap {\n${[
...schemaNames,
]
.map(schemaName => ` '${schemaName}': core.${schemaName};`)
.map(schemaName => ` '${schemaName}': index.${schemaName};`)
.join('\n')}\n}\n\nconst schema: JSONSchema7;\nexport default schema;`;
return [fullSchema, schemaTypes];

View File

@@ -76,7 +76,7 @@ export function jsonSchemaPlugin(
return {
[schemaName]: JSON.stringify(jsonSchema),
[`${schemaName.replace(/\.json$/, '')}.d.ts`]: types,
[`${schemaName}.d.ts`]: types,
};
}).call(this);