Hey, I am new to TS.
Is there an example of how to derive an interface from a SimpleSchema?
Hey, I am new to TS.
Is there an example of how to derive an interface from a SimpleSchema?
This might help?
I also found this, but did not understand much.
The guy wants the d file though.
Hi there)
I was wondering if you found a way to use those two together?
Otherwise it seems a little bit like typing same things twice.
Hey guys,
an old topic but still hot.
Any existing solution yet?
Add a types/
folder to your app.
Add it to your tsconfig.json
, too:
"typeRoots": ["./types"],
Put this in types/simpl-schema/index.d.ts
:
declare module 'simpl-schema' {
import { Mongo } from 'meteor/mongo';
// Type definitions for simpl-schema
type Integer = RegExp;
type SchemaType =
| String
| Number
| Integer
| Boolean
| Object
| ArrayLike<any>
| SchemaDefinition<any>
| Date
| SimpleSchema
| SimpleSchemaGroup;
type SimpleSchemaGroup = { definitions: ArrayLike<{ type: SchemaType }> };
interface CleanOption {
filter?: boolean;
autoConvert?: boolean;
removeEmptyStrings?: boolean;
trimStrings?: boolean;
getAutoValues?: boolean;
isModifier?: boolean;
extendAutoValueContext?: boolean;
}
export type AutoValueFunctionSelf<T> = {
key: string;
closestSubschemaFieldName: string | null;
isSet: boolean;
unset: () => void;
value: T | Mongo.Modifier<T>;
operator: string;
field(otherField: string): {
isSet: boolean;
value: any;
operator: string;
};
siblingField(otherField: string): {
isSet: boolean;
value: any;
operator: string;
};
parentField(otherField: string): {
isSet: boolean;
value: any;
operator: string;
};
}
type ValidationError = {
name: string,
type: string,
value: any,
};
type AutoValueFunction<T> = (
this: AutoValueFunctionSelf<T>,
) => T | undefined;
interface ValidationFunctionSelf<T> {
value: T;
key: string;
genericKey: string;
definition: SchemaDefinition<T>;
isSet: boolean;
operator: any;
validationContext: ValidationContext;
field: (fieldName: string) => any;
siblingField: (fieldName: string) => any;
addValidationErrors: (errors: ValidationError[]) => {};
}
type ValidationFunction = (
this: ValidationFunctionSelf<any>
) => string | undefined;
export interface SchemaDefinition<T> {
type: SchemaType;
label?: string | Function;
optional?: boolean | Function;
min?: number | boolean | Date | Function;
max?: number | boolean | Date | Function;
minCount?: number | Function;
maxCount?: number | Function;
allowedValues?: any[] | Function;
decimal?: boolean;
exclusiveMax?: boolean;
exclusiveMin?: boolean;
regEx?: RegExp | RegExp[];
custom?: ValidationFunction;
blackbox?: boolean;
autoValue?: AutoValueFunction<T>;
defaultValue?: any;
trim?: boolean;
// allow custom extensions
[key: string]: any;
}
interface EvaluatedSchemaDefinition {
type: ArrayLike<{ type: SchemaType }>;
label?: string;
optional?: boolean;
min?: number | boolean | Date;
max?: number | boolean | Date;
minCount?: number;
maxCount?: number;
allowedValues?: any[];
decimal?: boolean;
exclusiveMax?: boolean;
exclusiveMin?: boolean;
regEx?: RegExp | RegExp[];
blackbox?: boolean;
defaultValue?: any;
trim?: boolean;
// allow custom extensions
[key: string]: any;
}
interface ValidationOption {
modifier?: boolean;
upsert?: boolean;
clean?: boolean;
filter?: boolean;
upsertextendedCustomContext?: boolean;
}
interface SimpleSchemaValidationContext {
validate(obj: any, options?: ValidationOption): boolean;
validateOne(doc: any, keyName: string, options?: ValidationOption): boolean;
resetValidation(): void;
isValid(): boolean;
invalidKeys(): { name: string; type: string; value?: any }[];
addInvalidKeys(errors: ValidationError[]): void;
keyIsInvalid(name: any): boolean;
keyErrorMessage(name: any): string;
getErrorObject(): any;
}
class ValidationContext {
constructor(ss: any);
addValidationErrors(errors: ValidationError[]): void;
clean(...args: any[]): any;
getErrorForKey(key: any, ...args: any[]): any;
isValid(): any;
keyErrorMessage(key: any, ...args: any[]): any;
keyIsInvalid(key: any, ...args: any[]): any;
reset(): void;
setValidationErrors(errors: ValidationError): void;
validate(obj: any, ...args: any[]): any;
validationErrors(): any;
}
interface MongoObjectStatic {
forEachNode(func: Function, options?: { endPointsOnly: boolean }): void;
getValueForPosition(position: string): any;
setValueForPosition(position: string, value: any): void;
removeValueForPosition(position: string): void;
getKeyForPosition(position: string): any;
getGenericKeyForPosition(position: string): any;
getInfoForKey(key: string): any;
getPositionForKey(key: string): string;
getPositionsForGenericKey(key: string): string[];
getValueForKey(key: string): any;
addKey(key: string, val: any, op: string): any;
removeGenericKeys(keys: string[]): void;
removeGenericKey(key: string): void;
removeKey(key: string): void;
removeKeys(keys: string[]): void;
filterGenericKeys(test: Function): void;
setValueForKey(key: string, val: any): void;
setValueForGenericKey(key: string, val: any): void;
getObject(): any;
getFlatObject(options?: { keepArrays?: boolean }): any;
affectsKey(key: string): any;
affectsGenericKey(key: string): any;
affectsGenericKeyImplicit(key: string): any;
}
interface MongoObject {
expandKey(val: any, key: string, obj: any): void;
}
class SimpleSchema {
static Integer: Integer;
static RegEx: {
Email: RegExp;
EmailWithTLD: RegExp;
Domain: RegExp;
WeakDomain: RegExp;
IP: RegExp;
IPv4: RegExp;
IPv6: RegExp;
Url: RegExp;
Id: RegExp;
ZipCode: RegExp;
Phone: RegExp;
};
debug: boolean;
constructor(
schema: { [key: string]: SchemaDefinition<any> | SchemaType } | any[],
options?: any | { humanizeAutoLabels?: boolean; tracker?: any; check?: any },
);
/**
* Returns whether the obj is a SimpleSchema object.
* @param {Object} [obj] An object to test
* @returns {Boolean} True if the given object appears to be a SimpleSchema instance
*/
static isSimpleSchema(obj: any): boolean;
static oneOf(...schemas: SchemaType[]): SchemaType;
// If you need to allow properties other than those listed above, call this from your app or package
static extendOptions(allowedOptionFields: string[]): void;
static setDefaultMessages(messages: {
messages: { [key: string]: { [key: string]: string } };
}): void;
namedContext(name?: string): SimpleSchemaValidationContext;
addDocValidator(validator: (doc: any) => ValidationError[]): any;
/**
* @method SimpleSchema#pick
* @param {[fields]} The list of fields to pick to instantiate the subschema
* @returns {SimpleSchema} The subschema
*/
pick(...fields: string[]): SimpleSchema;
/**
* @method SimpleSchema#omit
* @param {[fields]} The list of fields to omit to instantiate the subschema
* @returns {SimpleSchema} The subschema
*/
omit(...fields: string[]): SimpleSchema;
/**
* Extends this schema with another schema, key by key.
*
* @param {SimpleSchema|Object} schema
* @returns The SimpleSchema instance (chainable)
*/
extend(
schema:
| Partial<SchemaDefinition<any>>
| SimpleSchema
| { [key: string]: Partial<SchemaDefinition<any>> },
): SimpleSchema;
clean(doc: any, options?: CleanOption): any;
/**
* @param {String} [key] One specific or generic key for which to get the schema.
* @returns {Object} The entire schema object or just the definition for one key.
*
* Note that this returns the raw, unevaluated definition object. Use `getDefinition`
* if you want the evaluated definition, where any properties that are functions
* have been run to produce a result.
*/
schema<T>(key?: string): SchemaDefinition<T> | { [key: string]: SchemaDefinition<T> };
/**
* @returns {Object} The entire schema object with subschemas merged. This is the
* equivalent of what schema() returned in SimpleSchema < 2.0
*
* Note that this returns the raw, unevaluated definition object. Use `getDefinition`
* if you want the evaluated definition, where any properties that are functions
* have been run to produce a result.
*/
mergedSchema(): { [key: string]: SchemaDefinition<any> };
/**
* Returns the evaluated definition for one key in the schema
*
* @param {String} key Generic or specific schema key
* @param {Array(String)} [propList] Array of schema properties you need; performance optimization
* @param {Object} [functionContext] The context to use when evaluating schema options that are functions
* @returns {Object} The schema definition for the requested key
*/
getDefinition(
key: string,
propList?: ArrayLike<string>,
functionContext?: any,
): EvaluatedSchemaDefinition;
/**
* Returns a string identifying the best guess data type for a key. For keys
* that allow multiple types, the first type is used. This can be useful for
* building forms.
*
* @param {String} key Generic or specific schema key
* @returns {String} A type string. One of:
* string, number, boolean, date, object, stringArray, numberArray, booleanArray,
* dateArray, objectArray
*/
getQuickTypeForKey(
key: string,
):
| 'string'
| 'number'
| 'boolean'
| 'date'
| 'object'
| 'stringArray'
| 'numberArray'
| 'booleanArray'
| 'dateArray'
| 'objectArray'
| undefined;
/**
* Given a key that is an Object, returns a new SimpleSchema instance scoped to that object.
*
* @param {String} key Generic or specific schema key
*/
getObjectSchema(key: string): SimpleSchema;
// Returns an array of all the autovalue functions, including those in subschemas all the
// way down the schema tree
autoValueFunctions(): AutoValueFunction<any>[];
// Returns an array of all the blackbox keys, including those in subschemas
blackboxKeys(): ArrayLike<string>;
// Check if the key is a nested dot-syntax key inside of a blackbox object
keyIsInBlackBox(key: string): boolean;
/**
* Change schema labels on the fly, causing mySchema.label computation
* to rerun. Useful when the user changes the language.
*
* @param {Object} labels A dictionary of all the new label values, by schema key.
*/
labels(labels: { [key: string]: string }): void;
/**
* Gets a field's label or all field labels reactively.
*
* @param {String} [key] The schema key, specific or generic.
* Omit this argument to get a dictionary of all labels.
* @returns {String} The label
*/
label(key: any): any;
/**
* Gets a field's property
*
* @param {String} [key] The schema key, specific or generic.
* Omit this argument to get a dictionary of all labels.
* @param {String} [prop] Name of the property to get.
*
* @returns {any} The property value
*/
get(key?: string, prop?: string): any;
// shorthand for getting defaultValue
defaultValue(key): any;
messages(messages: any): void;
// Returns a string message for the given error type and key. Passes through
// to message-box pkg.
messageForError(type: any, key: any, def: any, value: any): string;
// Returns true if key is explicitly allowed by the schema or implied
// by other explicitly allowed keys.
// The key string should have $ in place of any numeric array positions.
allowsKey(key: any): boolean;
newContext(): ValidationContext;
/**
* Returns all the child keys for the object identified by the generic prefix,
* or all the top level keys if no prefix is supplied.
*
* @param {String} [keyPrefix] The Object-type generic key for which to get child keys. Omit for
* top-level Object-type keys
* @returns {[[Type]]} [[Description]]
*/
objectKeys(keyPrefix?: any): any[];
/**
* @param obj {Object|Object[]} Object or array of objects to validate.
* @param [options] {Object} Same options object that ValidationContext#validate takes
*
* Throws an Error with name `ClientError` and `details` property containing the errors.
*/
validate(obj: any, options?: ValidationOption): void;
/**
* @param obj {Object} Object to validate.
* @param [options] {Object} Same options object that ValidationContext#validate takes
*
* Returns a Promise that resolves with the errors
*/
validateAndReturnErrorsPromise(
obj: any,
options?: ValidationOption,
): Promise<ArrayLike<Error>>;
validator(options?: ValidationOption): (args: { [key: string]: any }) => void;
}
export default SimpleSchema;
}
Add this to types/meteor-mongo.d.ts
:
declare module 'meteor/mongo' {
import SimpleSchema from 'simpl-schema';
module Mongo {
export interface Collection<T> {
schema: SimpleSchema;
attachSchema(schema: SimpleSchema): void;
attachJSONSchema(schema: any): void;
helpers(methods: object): void;
_name: string;
}
}
}
The definitions arenāt perfect, but work for us.
That is awesome to have type definitions for the SimpleSchema library. I hope we have a way to include these type definitions directly in the Meteor packages soon.
And I think that the original poster was asking about something slightly different which is being able to generate a Typescript interface from the SimpleSchema.
Hereās an example of a package that does something similar with another schema library: https://www.npmjs.com/package/joi-extract-type
export const jobOperatorRoleSchema = Joi.object({
id: Joi.string().required(),
user_id: Joi.string().required(),
job_id: Joi.string().required(),
role: Joi.string().valid(['recruiter', 'requester']),
pipeline_rules: Joi.array().items(rule),
});
type extractComplexType = Joi.extractType<typeof jobOperatorRoleSchema>;
export const extractedComplexType: extractComplexType = {
id: '2015',
user_id: '102',
job_id: '52',
role: 'admin',
pipeline_rules: [extractedRule],
};
Being able to do something like that with SimpleSchema would be amazing since all Mongo operations could return an interface automatically derived from the SimpleSchema for example.
@hexsprite yes, that would be really AWESOME!!! This way weād have an elegant mapping-solution from a mongoDb-collection2-schema up to a typescript interface to further use where neededā¦
@aldeed what are your thoughts on this?
Made my day!!! Thanks a lot
Hi!
I happened to have the same issue (donāt want to both code the schema and the interfaces) and ended up in this forum a couple of months ago.
Since I didnāt find a solution, I decided to write my first npm package!
It generates Typescript interfaces based on simpl-schema definitions.
Itās highly experimental but I have been using it for some days (!) on my hobby project and it serves the purpose just fine.
It still lacks a lot of polishing but if people start using it and / or contribute, that will motivate me to improve it.
Please tell me what you think!
Hi, this is cool, I managed to make it work with ValidatedMethod!
Now, Iād prefer not to define all my method parameter schemas in a single file but directly where the ValidatedMethod is written, because itās tedious to import both the schema and the type:
import { schemas } from '/server/core/method-params-schemas'
import { MyMethodParams } from '/server/core/method-params-schemas-generated-types'
new ValidatedMethod({
name: 'myMethod',
validate: schemas['MyMethodParams'].validator(),
run({param1, param2, param3}: MyMethodParams) {
Also, the generator takes some time and for now Iām calling it manually instead. But ultimately it could work in the background when we save files having schemas.
Letās keep in touch!
Yay!
So glad you found it useful.
Regarding your feedback:
On both of these issues, I must admit I probably wonāt investigate at least in the near future: I was happy to publish this package but donāt plan to spend too much time refining it. Iād love people to contribute, though
Still, Iāll maintain it as long as Iāll be using it, and I just pushed version 1.0.3 for a fix and support of SimpleSchema.oneOf() definitions!
Again, if anybody wants to contribute Iāll be happy to support it.
Thanks again for your feedback!
Thank you; on a blank project with no existing schemas I tried Joi Schemas with joi-extract-type (suggested by hexsprite above), and it works great with ValidatedMethod.
My methods look like this:
import DeclareJoiValidatedMethod from '../core/joi-method'
import * as Joi from '@hapi/joi';
import 'joi-extract-type';
const MyFirstMethodSchema = Joi.object({
userId: Joi.string().required(),
roles: Joi.array().items(Joi.string()).required(),
});
DeclareJoiValidatedMethod({
name: 'myMethod',
schema: MyFirstMethodSchema,
run({userId, roles}: Joi.extractType<typeof MyFirstMethodSchema>): boolean {
...
return true
}
});
with the helper function DeclareJoiValidatedMethod inspired from this post:
export interface JoiValidatedMethodOptions<T> {
name: string,
schema: Joi.ObjectSchema,
validate?(obj: Object): void,
run(obj: Object): T;
}
export function joiValidate<T>(vmOptions: JoiValidatedMethodOptions<T>) {
vmOptions.validate = (obj: Object) => {
const result = Joi.validate(obj, vmOptions.schema, { stripUnknown: true });
if (result.error) {
const err = new ValidationError(result.error.details.map(err => {
// map joi error to ValidationError
return {
name: err.path,
type: err.type,
message: err.message
};
}));
throw err;
}
// call validatedMethod.run with the validated/cleaned values!
const runFunc = vmOptions.run;
vmOptions.run = function () {
return runFunc.bind(this)(result.value);
}
}
return vmOptions as ValidatedMethodOptionsWithMixins<string, typeof vmOptions.run>;
}
function DeclareJoiValidatedMethod<T>(optionObj: JoiValidatedMethodOptions<T>) {
return new ValidatedMethod(joiValidate(optionObj));
}
Annoyingly, joi-extract-type
doesnāt appear to support later versions of Joi
. However, I seem to have found a solution using the io-ts
package that allows for both compile time and run time checking using only a single prop schema:
import * as t from "io-ts";
import { PathReporter } from "io-ts/PathReporter";
const MethodProps = t.type({
someProp1: t.string,
someProp: t.number,
});
type MethodTypes = t.TypeOf<typeof MethodProps>;
const methodName = "myMethod";
const MyValidatedMethod = new ValidatedMethod<string, (...params: MethodTypes[])=> void>({
name: methodName,
validate(obj) {
const isValid = MethodProps.is(obj);
if (!isValid) {
const decoded = MethodProps.decode(obj);
const report = PathReporter.report(decoded);
const error = new Meteor.Error("Fail");
error.error = "validation-error";
error.details = report.join(", ");
throw error;
}
},
run(params): void {
// Some logic
console.log(params.someProp1);
},
});
Indeed. It seemed complicated to make Joi support it, but I was already using the Ajv library with Uniforms, and I found out that the latest version of Ajv (8.6) had native Typescript type inference based on JSON Type Definition (JTD) schemas. So now hereās what my new methods look like:
const itemRecordSchema = {
properties: {
distance: {type: 'float64'}
},
optionalProperties: {
tag: {type: 'string'}
}
} as const
const reportDistancesSchema = {
properties: {
items: {elements: itemRecordSchema},
userMsg: {type: 'string'}
}
} as const
DeclareAjvValidatedMethod({
name: 'reportDistances',
schema: reportDistancesSchema,
run({items, userMsg}: JTDDataType<typeof reportDistancesSchema>) {
My DeclareAjvValidatedMethod is quite similar to DeclareJoiValidatedMethod, except that it uses ajv.compile() outside the validate function, then uses the returned validator function inside it.
NB: Ajvās type inference is said to require Typescript 4.2.4, which is available in Meteor 2.3.
Notice the āas constā after schemas, it you omit them all property types will be āunknownā.
More info: Using with TypeScript | Ajv JSON schema validator