Began experimenting with dragging typings out of simple schema
This commit is contained in:
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -20,6 +20,7 @@
|
|||||||
"nearley",
|
"nearley",
|
||||||
"ngraph",
|
"ngraph",
|
||||||
"ostrio",
|
"ostrio",
|
||||||
|
"recomputation",
|
||||||
"Ruleset",
|
"Ruleset",
|
||||||
"snackbars",
|
"snackbars",
|
||||||
"Spellcasting",
|
"Spellcasting",
|
||||||
|
|||||||
@@ -6,31 +6,9 @@ import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema';
|
|||||||
import propertySchemasIndex from '/imports/api/properties/computedPropertySchemasIndex';
|
import propertySchemasIndex from '/imports/api/properties/computedPropertySchemasIndex';
|
||||||
import { storedIconsSchema } from '/imports/api/icons/Icons';
|
import { storedIconsSchema } from '/imports/api/icons/Icons';
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
||||||
|
import { InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
|
||||||
|
|
||||||
// TODO make this a union type of all CreatureProperty types
|
const PreComputeCreaturePropertySchema = new TypedSimpleSchema({
|
||||||
const CreatureProperties: Mongo.Collection<any> = new Mongo.Collection('creatureProperties');
|
|
||||||
|
|
||||||
export interface CreatureProperty extends TreeDoc {
|
|
||||||
_id: string
|
|
||||||
_migrationError?: string
|
|
||||||
tags: string[]
|
|
||||||
type: string
|
|
||||||
disabled?: boolean
|
|
||||||
icon?: {
|
|
||||||
name: string
|
|
||||||
shape: string
|
|
||||||
},
|
|
||||||
libraryNodeId?: string
|
|
||||||
slotQuantityFilled?: number
|
|
||||||
inactive?: boolean
|
|
||||||
deactivatedByAncestor?: boolean
|
|
||||||
deactivatedBySelf?: boolean
|
|
||||||
deactivatedByToggle?: boolean
|
|
||||||
deactivatingToggleId?: boolean
|
|
||||||
dirty?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const CreaturePropertySchema = new SimpleSchema({
|
|
||||||
_id: {
|
_id: {
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
@@ -75,13 +53,12 @@ const CreaturePropertySchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const DenormalisedOnlyCreaturePropertySchema = new SimpleSchema({
|
const DenormalisedOnlyCreaturePropertySchema = new TypedSimpleSchema({
|
||||||
// Denormalised flag if this property is inactive on the sheet for any reason
|
// Denormalised flag if this property is inactive on the sheet for any reason
|
||||||
// Including being disabled, or a descendant of a disabled property
|
// Including being disabled, or a descendant of a disabled property
|
||||||
inactive: {
|
inactive: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
|
||||||
removeBeforeCompute: true,
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
// Denormalised flag if this property was made inactive by an inactive
|
// Denormalised flag if this property was made inactive by an inactive
|
||||||
@@ -90,7 +67,6 @@ const DenormalisedOnlyCreaturePropertySchema = new SimpleSchema({
|
|||||||
deactivatedByAncestor: {
|
deactivatedByAncestor: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
|
||||||
removeBeforeCompute: true,
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
// Denormalised flag if this property was made inactive because of its own
|
// Denormalised flag if this property was made inactive because of its own
|
||||||
@@ -98,7 +74,6 @@ const DenormalisedOnlyCreaturePropertySchema = new SimpleSchema({
|
|||||||
deactivatedBySelf: {
|
deactivatedBySelf: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
|
||||||
removeBeforeCompute: true,
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
// Denormalised flag if this property was made inactive because of a toggle
|
// Denormalised flag if this property was made inactive because of a toggle
|
||||||
@@ -106,7 +81,6 @@ const DenormalisedOnlyCreaturePropertySchema = new SimpleSchema({
|
|||||||
deactivatedByToggle: {
|
deactivatedByToggle: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
|
||||||
removeBeforeCompute: true,
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
deactivatingToggleId: {
|
deactivatingToggleId: {
|
||||||
@@ -154,9 +128,23 @@ const DenormalisedOnlyCreaturePropertySchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
CreaturePropertySchema.extend(DenormalisedOnlyCreaturePropertySchema);
|
const CreaturePropertySchema = PreComputeCreaturePropertySchema.extend(DenormalisedOnlyCreaturePropertySchema);
|
||||||
|
|
||||||
for (const key in propertySchemasIndex) {
|
type CreaturePropertyByType<T extends keyof typeof propertySchemasIndex> =
|
||||||
|
InferType<typeof propertySchemasIndex[T]>
|
||||||
|
& InferType<typeof CreaturePropertySchema>
|
||||||
|
& InferType<typeof ColorSchema>
|
||||||
|
& InferType<typeof ChildSchema>
|
||||||
|
& InferType<typeof SoftRemovableSchema>
|
||||||
|
|
||||||
|
type ConvertToUnion<T> = T[keyof T];
|
||||||
|
type CreatureProperty = ConvertToUnion<{ [key in keyof typeof propertySchemasIndex]: CreaturePropertyByType<key> }>;
|
||||||
|
type ActionProperty = CreaturePropertyByType<'action'>;
|
||||||
|
|
||||||
|
const CreatureProperties = new Mongo.Collection<CreatureProperty>('creatureProperties');
|
||||||
|
|
||||||
|
let key: keyof typeof propertySchemasIndex;
|
||||||
|
for (key in propertySchemasIndex) {
|
||||||
const schema = new SimpleSchema({});
|
const schema = new SimpleSchema({});
|
||||||
schema.extend(propertySchemasIndex[key]);
|
schema.extend(propertySchemasIndex[key]);
|
||||||
schema.extend(CreaturePropertySchema);
|
schema.extend(CreaturePropertySchema);
|
||||||
@@ -167,12 +155,13 @@ for (const key in propertySchemasIndex) {
|
|||||||
if (key === 'any') {
|
if (key === 'any') {
|
||||||
// @ts-expect-error don't have types for .attachSchema
|
// @ts-expect-error don't have types for .attachSchema
|
||||||
CreatureProperties.attachSchema(schema);
|
CreatureProperties.attachSchema(schema);
|
||||||
|
} else {
|
||||||
|
// TODO remove all {selector: {type: any}} options
|
||||||
|
// @ts-expect-error don't have types for .attachSchema
|
||||||
|
CreatureProperties.attachSchema(schema, {
|
||||||
|
selector: { type: key }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// TODO make this an else branch and remove all {selector: {type: any}} options
|
|
||||||
// @ts-expect-error don't have types for .attachSchema
|
|
||||||
CreatureProperties.attachSchema(schema, {
|
|
||||||
selector: { type: key }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreatureProperties;
|
export default CreatureProperties;
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
import { assertAdmin } from '/imports/api/sharing/sharingPermissions';
|
import { assertAdmin } from '/imports/api/sharing/sharingPermissions';
|
||||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS';
|
||||||
|
import { InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
|
||||||
|
|
||||||
let Icons = new Mongo.Collection('icons');
|
const iconsSchema = new TypedSimpleSchema({
|
||||||
|
|
||||||
let iconsSchema = new SimpleSchema({
|
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
unique: true,
|
unique: true,
|
||||||
@@ -34,6 +33,12 @@ let iconsSchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type Icon = InferType<typeof iconsSchema>;
|
||||||
|
|
||||||
|
const Icons = new Mongo.Collection<Icon>('icons');
|
||||||
|
// @ts-expect-error don't have types for .attachSchema
|
||||||
|
Icons.attachSchema(iconsSchema);
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Icons._ensureIndex({
|
Icons._ensureIndex({
|
||||||
'name': 'text',
|
'name': 'text',
|
||||||
@@ -42,7 +47,7 @@ if (Meteor.isServer) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const storedIconsSchema = new SimpleSchema({
|
const storedIconsSchema = new TypedSimpleSchema({
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
@@ -51,12 +56,15 @@ const storedIconsSchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Icons.attachSchema(iconsSchema);
|
|
||||||
|
|
||||||
// This method does not validate icons against the schema, use wisely;
|
// This method does not validate icons against the schema, use wisely;
|
||||||
const writeIcons = new ValidatedMethod({
|
const writeIcons = new ValidatedMethod({
|
||||||
name: 'icons.write',
|
name: 'icons.write',
|
||||||
validate: null,
|
validate: null,
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 20,
|
||||||
|
timeInterval: 10000,
|
||||||
|
},
|
||||||
run(icons) {
|
run(icons) {
|
||||||
assertAdmin(this.userId);
|
assertAdmin(this.userId);
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
@@ -88,6 +96,7 @@ const findIcons = new ValidatedMethod({
|
|||||||
{
|
{
|
||||||
// relevant documents have a higher score.
|
// relevant documents have a higher score.
|
||||||
fields: {
|
fields: {
|
||||||
|
// @ts-expect-error don't have types for meta text scoring
|
||||||
score: { $meta: 'textScore' }
|
score: { $meta: 'textScore' }
|
||||||
},
|
},
|
||||||
// `score` property specified in the projection fields above.
|
// `score` property specified in the projection fields above.
|
||||||
@@ -7,6 +7,7 @@ import { CreatureProperty } from '/imports/api/creature/creatureProperties/Creat
|
|||||||
import { InlineCalculation } from '/imports/api/properties/subSchemas/inlineCalculationField';
|
import { InlineCalculation } from '/imports/api/properties/subSchemas/inlineCalculationField';
|
||||||
import { CalculatedField } from '/imports/api/properties/subSchemas/computedField';
|
import { CalculatedField } from '/imports/api/properties/subSchemas/computedField';
|
||||||
import Property from '/imports/api/properties/Properties.type';
|
import Property from '/imports/api/properties/Properties.type';
|
||||||
|
import { TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
|
||||||
|
|
||||||
export type CreatureAction = Action & CreatureProperty & {
|
export type CreatureAction = Action & CreatureProperty & {
|
||||||
overridden?: boolean
|
overridden?: boolean
|
||||||
@@ -314,7 +315,7 @@ const ComputedOnlyActionSchema = createPropertySchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedActionSchema = new SimpleSchema()
|
const ComputedActionSchema = new TypedSimpleSchema({})
|
||||||
.extend(ActionSchema)
|
.extend(ActionSchema)
|
||||||
.extend(ComputedOnlyActionSchema);
|
.extend(ComputedOnlyActionSchema);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import { Random } from 'meteor/random';
|
import { Random } from 'meteor/random';
|
||||||
|
import { InferType, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
|
||||||
|
|
||||||
const AdjustmentSchema = new SimpleSchema({
|
const AdjustmentSchema = new TypedSimpleSchema({
|
||||||
_id: {
|
_id: {
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
max: 17,
|
||||||
autoValue() {
|
autoValue() {
|
||||||
if (!this.isSet) return Random.id();
|
if (!this.isSet) return Random.id();
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ const AdjustmentSchema = new SimpleSchema({
|
|||||||
'self', // the character who took the action
|
'self', // the character who took the action
|
||||||
'each', // rolled once for `each` target
|
'each', // rolled once for `each` target
|
||||||
'every', // rolled once and applied to `every` target
|
'every', // rolled once and applied to `every` target
|
||||||
],
|
] as const,
|
||||||
},
|
},
|
||||||
// The stat this rolls applies to, if damage type is set, this is ignored
|
// The stat this rolls applies to, if damage type is set, this is ignored
|
||||||
stat: {
|
stat: {
|
||||||
@@ -32,4 +32,6 @@ const AdjustmentSchema = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type Adjustment = InferType<typeof AdjustmentSchema>;
|
||||||
|
|
||||||
export default AdjustmentSchema;
|
export default AdjustmentSchema;
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import { TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
|
||||||
|
|
||||||
export interface Colored {
|
const ColorSchema = new TypedSimpleSchema({
|
||||||
color?: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
const ColorSchema = new SimpleSchema({
|
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
// match hex colors of the form #A23 or #A23f56
|
// match hex colors of the form #A23 or #A23f56
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import {
|
|||||||
fieldToCompute,
|
fieldToCompute,
|
||||||
computedOnlyField,
|
computedOnlyField,
|
||||||
} from '/imports/api/properties/subSchemas/computedField';
|
} from '/imports/api/properties/subSchemas/computedField';
|
||||||
import SimpleSchema from 'simpl-schema';
|
import { Definition, TypedSimpleSchema } from '/imports/api/utility/TypedSimpleSchema';
|
||||||
|
|
||||||
// Search through the schema for keys whose type is 'fieldToCompute' etc.
|
// Search through the schema for keys whose type is 'fieldToCompute' etc.
|
||||||
// replace the type with Object and attach extend the schema with
|
// replace the type with Object and attach extend the schema with
|
||||||
// the required fields to make the computation work
|
// the required fields to make the computation work
|
||||||
export default function createPropertySchema(definition) {
|
export default function createPropertySchema(definition: Definition) {
|
||||||
const computationFields = {
|
const computationFields = {
|
||||||
inlineCalculationFieldToCompute: [],
|
inlineCalculationFieldToCompute: [],
|
||||||
computedOnlyInlineCalculationField: [],
|
computedOnlyInlineCalculationField: [],
|
||||||
@@ -20,9 +20,9 @@ export default function createPropertySchema(definition) {
|
|||||||
};
|
};
|
||||||
const computedKeys = Object.keys(computationFields);
|
const computedKeys = Object.keys(computationFields);
|
||||||
|
|
||||||
for (let key in definition) {
|
for (const key in definition) {
|
||||||
const def = definition[key];
|
const def = definition[key];
|
||||||
if (computedKeys.includes(def.type)) {
|
if (typeof def === 'object' && 'type' in def && computedKeys.includes(def.type)) {
|
||||||
computationFields[def.type].push(key);
|
computationFields[def.type].push(key);
|
||||||
applyDefaultCalculationValue(definition, key);
|
applyDefaultCalculationValue(definition, key);
|
||||||
def.type = Object;
|
def.type = Object;
|
||||||
@@ -31,6 +31,7 @@ export default function createPropertySchema(definition) {
|
|||||||
`computed field: '${key}' of '${def.type}' is expected to be optional`
|
`computed field: '${key}' of '${def.type}' is expected to be optional`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
//@ts-expect-error removeBeforeCompute is an extension of SimpleSchema
|
||||||
if (def.removeBeforeCompute) {
|
if (def.removeBeforeCompute) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`computed field: '${key}' of '${def.type}' should not be removed before computation`
|
`computed field: '${key}' of '${def.type}' should not be removed before computation`
|
||||||
@@ -40,7 +41,7 @@ export default function createPropertySchema(definition) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a schema with the edited definition
|
// Create a schema with the edited definition
|
||||||
const schema = new SimpleSchema(definition);
|
const schema = new TypedSimpleSchema(definition);
|
||||||
|
|
||||||
// Extend the schema with all the computation fields
|
// Extend the schema with all the computation fields
|
||||||
computationFields.inlineCalculationFieldToCompute.forEach(key => {
|
computationFields.inlineCalculationFieldToCompute.forEach(key => {
|
||||||
@@ -69,7 +70,7 @@ function applyDefaultCalculationValue(definition, key) {
|
|||||||
// on the fields to compute
|
// on the fields to compute
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let defaultValue = def.defaultValue;
|
const defaultValue = def.defaultValue;
|
||||||
if (!defaultValue) return;
|
if (!defaultValue) return;
|
||||||
let calcField;
|
let calcField;
|
||||||
if (def.type === 'fieldToCompute') {
|
if (def.type === 'fieldToCompute') {
|
||||||
104
app/imports/api/utility/TypedSimpleSchema.ts
Normal file
104
app/imports/api/utility/TypedSimpleSchema.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import SimpleSchema, { SimpleSchemaDefinition } from 'simpl-schema';
|
||||||
|
|
||||||
|
// It DOES NOT support a constructor with multiple schemas.
|
||||||
|
export type Definition = Exclude<SimpleSchemaDefinition, any[]>;
|
||||||
|
|
||||||
|
// This is a no-op wrapper, effectively implementing a phantom type.
|
||||||
|
export class TypedSimpleSchema<T extends Definition> extends SimpleSchema {
|
||||||
|
constructor(definition: T) {
|
||||||
|
super(definition);
|
||||||
|
}
|
||||||
|
// Extending the schema with another schema &'s their definitions
|
||||||
|
extend<U extends Definition>(otherSchema: TypedSimpleSchema<U>): TypedSimpleSchema<T & U> {
|
||||||
|
return super.extend(otherSchema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It cannot be a method due to https://github.com/microsoft/TypeScript/issues/36931.
|
||||||
|
export function validate<T extends Definition>(schema: TypedSimpleSchema<T>, value: unknown): asserts value is InferSchema<T> {
|
||||||
|
schema.validate(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this type emerges anywhere in calculations, congratulations!
|
||||||
|
// You've just hit an unimplemented corner case :D
|
||||||
|
type NotImplementedMarker = { readonly NotImplementedMarker: unique symbol };
|
||||||
|
|
||||||
|
// Internal calculation markers.
|
||||||
|
type ArrayMarker = { readonly ArrayMarker: unique symbol };
|
||||||
|
type ObjectMarker = { readonly ObjectMarker: unique symbol };
|
||||||
|
|
||||||
|
export type InferType<T> = ExpandRecursively<MakeUndefinedOptional<InferTypeInner<T>>>;
|
||||||
|
|
||||||
|
// Infer TypeScript type from SimpleSchema type.
|
||||||
|
type InferTypeInner<T> =
|
||||||
|
T extends typeof Array ? ArrayMarker :
|
||||||
|
T extends typeof Boolean ? boolean :
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
T extends typeof Function ? Function :
|
||||||
|
T extends typeof Number ? number :
|
||||||
|
T extends typeof Object ? ObjectMarker :
|
||||||
|
T extends typeof String ? string :
|
||||||
|
T extends RegExp ? string :
|
||||||
|
T extends TypedSimpleSchema<infer U> ? InferSchema<U> :
|
||||||
|
NotImplementedMarker;
|
||||||
|
|
||||||
|
// Infer TypeScript type from a single field definition.
|
||||||
|
export type InferField<Def extends Definition, Key extends keyof Def> =
|
||||||
|
Key extends string
|
||||||
|
? Def[Key] extends { type: infer Typ }
|
||||||
|
? ArrayMarker extends InferTypeInner<Typ>
|
||||||
|
? Array<InferField<Def, `${Key}.$`>>
|
||||||
|
: ObjectMarker extends InferTypeInner<Typ>
|
||||||
|
? { [L in keyof Def as L extends `${Key}.${infer SubKey}` ? SubKey extends `${string}.${string}` ? never : SubKey : never]: InferField<Def, L> }
|
||||||
|
: Def[Key] extends { allowedValues: infer Allowed extends string[] }
|
||||||
|
? InferOptional<Def, Key, InferEnum<Allowed>>
|
||||||
|
: InferOptional<Def, Key, InferTypeInner<Typ>>
|
||||||
|
: NotImplementedMarker
|
||||||
|
: NotImplementedMarker
|
||||||
|
|
||||||
|
// Infer union from string array (allowedValues should me marked as const for this to work)
|
||||||
|
type InferEnum<T extends string[]> = T[number];
|
||||||
|
|
||||||
|
// Infer optional from optional field
|
||||||
|
type InferOptional<Def, Key extends keyof Def, U> = Def[Key] extends { optional: true } ? U | undefined : U;
|
||||||
|
|
||||||
|
type MakeUndefinedOptional<Type> = { [Property in keyof Type as undefined extends Type[Property] ? never : Property]: Type[Property]; }
|
||||||
|
& { [Property in keyof Type as undefined extends Type[Property] ? Property : never]+?: Type[Property]; };
|
||||||
|
|
||||||
|
// Infer TypeScript type from a schema definition.
|
||||||
|
export type InferSchema<Def extends Definition> = InferField<
|
||||||
|
{ '': { type: typeof Object } }
|
||||||
|
& { [Key in keyof Def as Key extends string ? `.${Key}` : never]: Def[Key] }, ''
|
||||||
|
>;
|
||||||
|
|
||||||
|
const testSchema = new TypedSimpleSchema({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
age: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
children: {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
defaultValue: [],
|
||||||
|
},
|
||||||
|
'children.$': {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
allowedValues: ['cat', 'dog'] as const,
|
||||||
|
optional: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// expands object types recursively
|
||||||
|
type ExpandRecursively<T> = T extends object
|
||||||
|
? T extends infer O ? { [K in keyof O]: ExpandRecursively<O[K]> } : never
|
||||||
|
: T;
|
||||||
|
|
||||||
|
type testType = InferType<typeof testSchema>;
|
||||||
|
|
||||||
|
type subType = ExpandRecursively<testType>
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
|
"noErrorTruncation": true,
|
||||||
"outDir": "build",
|
"outDir": "build",
|
||||||
"paths": {
|
"paths": {
|
||||||
"/*": [
|
"/*": [
|
||||||
|
|||||||
Reference in New Issue
Block a user