Open kettanaito opened 3 years ago
Note that factory
already supports the ModelDictionary
generic. This task may be as much as exporting the Limit
utility type publicly, so it's possible to construct your own custom types with the built-in relations/internal typings:
import { Limit } from '@mswjs/data'
type MyDictionary = Limit<{
user: { id: string }
}>
When publicly exported,
Limit
stops being a good naming choice. We should name it in a less general way.
Could we rename Limit
to ModelDictionary
? like
diff --git a/src/glossary.ts b/src/glossary.ts
index fa730e0..a9cb2ef 100644
--- a/src/glossary.ts
+++ b/src/glossary.ts
@@ -54,9 +54,9 @@ export type EntityInstance<
> = InternalEntityProperties<ModelName> &
Value<Dictionary[ModelName], Dictionary>
-export type ModelDictionary = Limit<Record<string, Record<string, any>>>
-
-export type Limit<T extends Record<string, any>> = {
+export type ModelDictionary<
+ T extends Record<string, any> = Record<string, Record<string, any>>
+> = {
[RK in keyof T]: {
[SK in keyof T[RK]]: T[RK][SK] extends
| (() => BaseTypes)
@marcosvega91, hm, I suppose we can. What benefit do you see in renaming it?
I was coming to talk exactly about this workflow. 😄
At my company we're using codegen heavily to generate :
Our main goal now for frontend testing would be to leverage all this existing codegen and being able to easily map the existing types to factories. And I think I'd go a step further and also make a codegen utility to generate factories out of the types. But passing the generic is a great first step!
As a quick win, could you already expose the Limit type?
We don't currently generate the types already, but I personally maintain a set of entity related types, so yes, it would be nice to be able to communicate these types to factory
.
Note that
factory
already supports theModelDictionary
generic. This task may be as much as exporting theLimit
utility type publicly, so it's possible to construct your own custom types with the built-in relations/internal typings:import { Limit } from '@mswjs/data' type MyDictionary = Limit<{ user: { id: string } }>
When publicly exported,
Limit
stops being a good naming choice. We should name it in a less general way.
@kettanaito is it still valid?
I am tried something like:
type UserDictionary = Limit<{
user: {
id: string;
name: string;
}
}>;
const userMockFactory = factory<UserDictionary>({
user: {
id: primaryKey(datatype.uuid),
name: () => 'name'
}
});
but I have:
TS2739: Type 'PrimaryKeyDeclaration<string>' is missing the following properties from type '{ error: "expected a value or a relation"; oneOf: "user"; }': error, oneOf
To me, ModelDictionary
isn't usable because we cannot provide any generic type...
How can I provide the interface that I would like to mock?
Hey @jogelin. I'd advise against using those internal types to annotate your structures at the moment. We need to design a much better typing experience for manual types, and specifically for the data structure generic support in the factory
. You shouldn't be using Limit
and friends, just the shape of your data:
// Once again, an intention, not the current implementation
interface User {
id: string
firstName: string
}
const db = factory<{ user: User }>({ user: {...} })
We are welcoming contributors so this may be a great time to consider becoming one!
From what I can see, the ManyOf
and OneOf
relationships make this a bit difficult to infer. I think that we will wind up needing partial type argument inference ( https://github.com/microsoft/TypeScript/issues/26242 ) to make the syntax much nicer than this:
// https://github.com/type-challenges/type-challenges/issues/2990
type IsRequiredKey<T, K extends keyof T> = (
K extends keyof T ? (T extends Required<Pick<T, K>> ? true : false) : never
) extends true
? true
: false;
type GetNullableProperty<T> = T extends ModelValueType
? NullableProperty<T>
: () => T;
type Limit<Schema, Definition> = {
[Key in keyof Definition]: Definition[Key] extends
| PrimaryKey<any>
| ManyOf<any, boolean>
| OneOf<any, boolean>
| NullableProperty<any>
? Definition[Key]
: Key extends keyof Schema
? IsRequiredKey<Schema, Key> extends true
? () => Schema[Key]
: GetNullableProperty<Schema[Key]> // schema should be source of truth?
: never;
};
type FactoryAPIFromSchema<
Schema extends Record<string, any>,
Dictionary extends ModelDictionary = ModelDictionary,
> = FactoryAPI<{
[ModelName in keyof Dictionary]: Limit<
ModelName extends keyof Schema ? Schema[ModelName] : never,
Dictionary[ModelName]
>;
}>;
// Usage requires dictionary to be defined prior to inferring relationships
const dict = {
user: {
id: primaryKey(Number),
phone_number: () => '1',
name: () => 'ferris bueller',
},
truancy: {
id: primaryKey(String),
user: oneOf('user'),
},
};
export const db: FactoryAPIFromSchema<
{ user: Schemas['User'], truancy: Schemas['Truancy'] },
typeof dict // we need to be able to infer this, without inferring the schema
> = factory(dict);
In the mean time, I guess this can be a workaround. Or maybe someone else can figure out how to use currying in order to simulate partial type inference. I've heard it can be done but not quite sure if it would work here or not
@jpribyl I can confirm it works! Thank you! 🙂
I just don't know how to solve an enum. 🙁 ..Can you help me, please?
enum OperationStatus {
Running = 'RUNNING',
Finished = 'FINISHED',
}
type LoadOperation = {
id: string
status: OperationStatus
}
const dict = {
task: {
id: primaryKey(String),
status: OperationStatus, // <=
startedAt: nullable(String),
finishedAt: nullable(String),
},
}
export const db: FactoryAPIFromSchema<
{ task: OperationTask },
typeof dict // <= error
> = factory(dict)
Error:
Type '{ loadOperation: { id: PrimaryKey<string>; name: StringConstructor; description: NullableProperty<string>; lastSucceededAt: NullableProperty<string>; avgDuration: NullableProperty<...>; runningTasks: ManyOf<...>; }; operationTask: { ...; }; }' does not satisfy the constraint 'ModelDictionary'.
Property 'operationTask' is incompatible with index signature.
Type '{ id: PrimaryKey<string>; operationId: OneOf<"loadOperation", false>; status: typeof OperationStatus; transactionId: NullableProperty<string>; startedAt: NullableProperty<...>; finishedAt: NullableProperty<...>; duration: NullableProperty<...>; }' is not assignable to type 'Limit<ModelDefinition>'.
Property 'status' is incompatible with index signature.
Type 'typeof OperationStatus' is not assignable to type 'ModelDefinitionValue'.
Type 'typeof OperationStatus' is not assignable to type 'NestedModelDefinition'.
Property 'Aborted' is incompatible with index signature.
Type 'import("/home/brany/projects/one-metadata-frontend/apps/mdm-admin-frontend/src/generated/graphql").OperationStatus' is not assignable to type 'ManyOf<any, boolean> | OneOf<any, boolean> | NullableProperty<any> | ModelValueTypeGetter | NestedModelDefinition'.
This is my current compromise.
import { factory, primaryKey } from 'mswjs/data'
import { ModelDefinitionValue } from '@mswjs/data/lib/glossary'
export function defineModel<T>(
generator: () => { [key in keyof T]: ModelDefinitionValue }
) {
return generator()
}
type User {
id: number
name: string
}
// All keys of `User` type need to be defined here
const user = defineModel<User>(() => ({
id: primaryKey(Number),
name: String
}))
export const db = factory({
user
})
This helper cannot strictly type check up to the value type of each property, but it can eliminate the omission of definition of each property.
I hope it helps.
Do we have any update about that one ? I tried the upper solutions but the user
created by user.create()
give a user
where all fields have ModelDefinitionValue
type, which cause an issue when we want to pass it to generic Graphql implementation :
export function defineModel<T>(
generator: () => { [key in keyof T]: ModelDefinitionValue }
) {
return generator()
}
type User {
id: number
name: string
}
// All keys of `User` type need to be defined here
const user = defineModel<User>(() => ({
id: primaryKey(Number),
name: String
}))
export const db = factory({
user
})
const user1 = user.create();
//User !== typeof user1
It would be nice if the generate object would have the exact same type that User
.
Having a similar approach that test-data-bot
(https://github.com/jackfranklin/test-data-bot#typescript-support)
interface User {
id: number;
name: string;
}
const userBuilder = build<User>('User', {
fields: {
id: sequence(),
name: fake(f => f.name.findName()),
},
});
const user1 = userBuilder();
//User === typeof user1
I just don't know how to solve an enum. 🙁 ..Can you help me, please?
Hello @MatejBransky did you manage to solve this somehow? I have the same problem
Unfortunately, I didn't solve it. We ended up doing without @mswjs/data and making our own db with lodash/chain.
Unfortunately, I didn't solve it. We ended up doing without @mswjs/data and making our own db with lodash/chain.
I think it would be very interesting to know more about your custom solution if thats possible :)
It should be possible to approach data modeling type-first by reusing existing TypeScript interfaces to annotate the data. This is useful when there are types generated from the server (i.e. with GraphQL Codegen).