A
arktypeโ€ข2w ago
Bobakanoosh

Using scope with morphs

I store some data in a json file, so I'm using string.json.parse to read that data in and validate it.
const UserConfigSchema = scope({
config: type("string.json.parse").to({
// ERROR:
// "windowMeta" is unresolvable
windowMeta: "Record<string, windowMeta>",
}),
windowMeta: {
"position?": {
x: "number",
y: "number",
},
},
}).export();
const UserConfigSchema = scope({
config: type("string.json.parse").to({
// ERROR:
// "windowMeta" is unresolvable
windowMeta: "Record<string, windowMeta>",
}),
windowMeta: {
"position?": {
x: "number",
y: "number",
},
},
}).export();
Maybe I shouldn't be using scope here and should instead be directly using the windowMeta type within the Record?
31 Replies
ssalbdivad
ssalbdivadโ€ข2w ago
Yeah this is a common gotcha that I will add to docs. type is the type function from the default scope so it doesn't know about your keywords Normally, there are two options for stuff like this so you can reference your aliases. One would be a tuple expression, e.g. instead of doing something like type({bar: "number"}).and({foo: "string"}) you can use [{bar: "number"}, "&", {foo: "string"}]. The tuple expression for a morph is "=>", but I'd actually never considered that to is kind of unique since it is kind of sugar for a morph allowing you to just specify the output. Maybe worth creating a tuple expression syntax for that. All that to say what the actual solution iss.. Which is a thunk expression, which allows you to invoke type from the current scope. One sec I'll link some examples
Bobakanoosh
BobakanooshOPโ€ข2w ago
Explanations are helpful so you're all good lol
ssalbdivad
ssalbdivadโ€ข2w ago
It's actually crazy this even works in TS haha So basically
const $ = scope({
config: () =>
$.type("string.json.parse").to({
windowMeta: "Record<string, windowMeta>"
}),
windowMeta: {
"position?": {
x: "number",
y: "number"
}
}
})

const types = $.export()
const $ = scope({
config: () =>
$.type("string.json.parse").to({
windowMeta: "Record<string, windowMeta>"
}),
windowMeta: {
"position?": {
x: "number",
y: "number"
}
}
})

const types = $.export()
Bobakanoosh
BobakanooshOPโ€ข2w ago
ahhh self referencing kinda very nice ok This does break the type.Any<TData> from the other example though ๐Ÿ™ which I kinda get since it's not taking an object any more
ssalbdivad
ssalbdivadโ€ข2w ago
Hmm, I mean it's probably not this specifically that broke it but just the types resolving correctly
ssalbdivad
ssalbdivadโ€ข2w ago
GitHub
Tuple expression for .to(def) ยท Issue #1205 ยท arktypeio/arktype
There is an existing tuple expression for morphs (using "=>"), but it accepts a function as the second param. .to is a helpful sugar that allows providing a validated definition, but t...
ssalbdivad
ssalbdivadโ€ข2w ago
Oh I see Because youre previous version was just an object
Bobakanoosh
BobakanooshOPโ€ข2w ago
Right yeah but now it takes a string and outputs an object
ssalbdivad
ssalbdivadโ€ข2w ago
Btw important that modules are not schemas they are groups of schemas but you can't use them to directly validate anything
Bobakanoosh
BobakanooshOPโ€ข2w ago
thats what .export() is for right?
ssalbdivad
ssalbdivadโ€ข2w ago
KEKW actually what I said wasn't even right
Bobakanoosh
BobakanooshOPโ€ข2w ago
so you can use the schema within the group
ssalbdivad
ssalbdivadโ€ข2w ago
scope allows you to define things you can export it to a module which is a collection of schemas but neither one is itself a schema types.config would be a schema where types is a module
Bobakanoosh
BobakanooshOPโ€ข2w ago
Yeah I end up passing $.export().config to the class's super
ssalbdivad
ssalbdivadโ€ข2w ago
Hmm this is interesting TS is complaining here though. I mean assignability can be a little weird with parse encoding stuff but ideally everything shoudl be assignable to type.Any so maybe this is a bug?
Bobakanoosh
BobakanooshOPโ€ข2w ago
Here's the full snippet:
class SomeClass<TData extends any> {
public constructor(public schema: type.Any<TData>) {
}
}

const $ = scope({
config: () =>
$.type("string.json.parse").to({
windowMeta: "Record<string, windowMeta>"
}),
windowMeta: {
"position?": {
x: "number",
y: "number"
}
}
})

const types = $.export()

type UserConfig = typeof types.config.infer;

class SomeParentClass extends SomeClass<UserConfig> {
constructor() {
super(types.config);
}
}
class SomeClass<TData extends any> {
public constructor(public schema: type.Any<TData>) {
}
}

const $ = scope({
config: () =>
$.type("string.json.parse").to({
windowMeta: "Record<string, windowMeta>"
}),
windowMeta: {
"position?": {
x: "number",
y: "number"
}
}
})

const types = $.export()

type UserConfig = typeof types.config.infer;

class SomeParentClass extends SomeClass<UserConfig> {
constructor() {
super(types.config);
}
}
error is:
Type '(In: string & { " attributes": { base: string; attributes: Nominal<"json">; }; }) => To<{ windowMeta: Record<string, { position?: { x: number; y: number; } | undefined; }>; }>' is not assignable to type '{ windowMeta: { [x: string]: { position?: { x: number; y: number; } | undefined; }; }; }'
Type '(In: string & { " attributes": { base: string; attributes: Nominal<"json">; }; }) => To<{ windowMeta: Record<string, { position?: { x: number; y: number; } | undefined; }>; }>' is not assignable to type '{ windowMeta: { [x: string]: { position?: { x: number; y: number; } | undefined; }; }; }'
ssalbdivad
ssalbdivadโ€ข2w ago
Yeah I see This seems like maybe just TS being weird? This is allowed:
const $ = scope({
config: () =>
$.type("string.json.parse").to({
windowMeta: "Record<string, windowMeta>"
}),
windowMeta: {
"position?": {
x: "number",
y: "number"
}
}
})

const types = $.export()

// should be same assignment TS doesn't complain here
const t: type.Any = types.config
const $ = scope({
config: () =>
$.type("string.json.parse").to({
windowMeta: "Record<string, windowMeta>"
}),
windowMeta: {
"position?": {
x: "number",
y: "number"
}
}
})

const types = $.export()

// should be same assignment TS doesn't complain here
const t: type.Any = types.config
Seems like it can figure it out if you infer the input from the whole type instead of just what it .infers to: ```ts import { scope, type type } from "arktype" class SomeClass<t extends type.Any> { constructor(public schema: t) {} } const $ = scope({ config: () => $.type("string.json.parse").to({ windowMeta: "Record<string, windowMeta>" }), windowMeta: { "position?": { x: "number", y: "number" } } }) const types = $.export() const t: type.Any = types.config type UserConfig = typeof types.config.infer class SomeParentClass extends SomeClass<typeof types.config> { constructor() { super(types.config) } } ```` Ohhhh I 100% get it now It actually makes sense what happened originally I forgot you were explicitly passing UserConfig so it wasn't being compared to any So the reason you got an error when you turned it into a morph is that .infer extracts the *output* type, whereas types.config is going to be a type that includes the whole morph. So depends a bit on how you want to pass those types around and which you need where, but the easiest thing would be to just change: ```ts type UserConfig = typeof types.config.infer ``` to ```ts type UserConfig = typeof types.config.t ``` t is the whole type representation with inputs and outputs. You shouldn't use it directly as a value- for that you'd want infer or inferIn`. but to pass whole types around that's the way to do it
Bobakanoosh
BobakanooshOPโ€ข2w ago
hmmmm okay trying that 1 sec there's a few other things I haven't included in my snippets thus far lol
ssalbdivad
ssalbdivadโ€ข2w ago
I mean either solution would work really, but I'm glad to see it actually makes sense there's an error there. When generics and TS are involved sometimes I just assume it's nonsense haha I guess I should try and focus on actually getting docs done so you and other people can read about this stuff lol I may be trying to avoid that by answering other questions ๐Ÿ™ˆ
Bobakanoosh
BobakanooshOPโ€ข2w ago
Admittedly would be nice, I know you're super busy though ๐Ÿ˜‚ even if they're not docs but moreso "recipes". Common use-cases that cover mechanics of the library that aren't super evident/easy to think of at first. e.g. type for password and confirmPassword. I figured that out eventually but a recipe of having one field rely on another of it's own type would've helped a ton.
ssalbdivad
ssalbdivadโ€ข2w ago
Yeah that is a common one You should have searched the Discord though ๐Ÿ˜›
Bobakanoosh
BobakanooshOPโ€ข2w ago
I keep wanting to use this lib because I see the potential but it can be demotivating losing hours to it :/ (as it usually goes with complex types though) I might've searched the discord tbh i forget was weeks ago
ssalbdivad
ssalbdivadโ€ข2w ago
No description
ssalbdivad
ssalbdivadโ€ข2w ago
There really has been a lot of progress recently I'm getting ready to publish. To be fair some of these are not written yet but we're getting there ๐Ÿง—
Bobakanoosh
BobakanooshOPโ€ข2w ago
It's definetly come leaps and bounds from even a few weeks ago, I see the progress ๐Ÿ™‚
ssalbdivad
ssalbdivadโ€ข2w ago
But the other part of the problem I think is that a lot of people are trying to integrate ArkType with their own complex stuff and then you run into problems that are both related to quirks of TS as well as having to deal with internal parts of arktype that most users are not really meant to deal with And TBH that stuff will also happen if you're e.g. writing a Zod schema and wanting to pass it to your own generics based on constraints
Bobakanoosh
BobakanooshOPโ€ข2w ago
And TBH that stuff will also happen if you're e.g. writing a Zod schema and wanting to pass it to your own generics based on constraints
Yeah I ran into this when making some frontend form/form input components
ssalbdivad
ssalbdivadโ€ข2w ago
And also TBH the docs won't help that much there either because they're really meant for the more core use cases, and I can't really cover all the nuances of generic inference when integrating with external TS But stuff like a type call not being resolvable within a scope is something lots of people have and will run into so I hope I can at least cover some of those bases And maybe add some FAQs about integrating external generics since I get so many questions about that especially here haha
Bobakanoosh
BobakanooshOPโ€ข2w ago
Yeah those are some good "basics" that I think would go a long way. Appreciate all your work!! For posterity, finally got everything working, last part was having a default value in the event the parsing failed. For that I just needed to use the type type.Any<TData>['inferOut'] Thanks again!
ssalbdivad
ssalbdivadโ€ข2w ago
Yeah if you need to extract the output that's what you want or ['infer'] is actually an alias of that
Want results from more Discord servers?
Add your server