A
arktype2mo ago
francis

Is there a way to set a default value that is not valid?

This is due to svelte 5 - where you have to provide a meaningful non-undefined prop for bound values. e.g. for radio buttons, you need to default to "", not undefined. I am not sure how to define an Arktype type that accepts only an enum of values from the radio button, but defaults to empty-string. When I do the obvious inputType.default("") I get errors: ParseError: Default value is not assignable: must be "enum_option" (was "")
104 Replies
ssalbdivad
ssalbdivad2mo ago
I would just cast your value
francis
francisOP2mo ago
oh, I mean this is a runtime error it breaks at type instantiation time
ssalbdivad
ssalbdivad2mo ago
Ahh Hmm Well instead of using a default value, you could morph the base object to add it if it's not there
francis
francisOP2mo ago
this is potentially more broadly useful to default a form control to an invalid (but meaningful) value for the user to modify
ssalbdivad
ssalbdivad2mo ago
Yeah that's much more relevant than if it were a type issue I would consider some syntax to bypass default value checking Would you want to create a GitHub issue?
francis
francisOP2mo ago
sure, I will do that after this meeting, thank you!
ssalbdivad
ssalbdivad2mo ago
Sometimes all the runtime safety can be too smart for it's own good 😛 Something like this is what I meant for the morph workaround, not sure if that is exactly what you're looking for beyond the syntax obviously being less elegant:
const parseFormData = type({
name: "'foo' | 'bar'"
}).pipe(data => {
data.name ??= "" as never
return data
})
const parseFormData = type({
name: "'foo' | 'bar'"
}).pipe(data => {
data.name ??= "" as never
return data
})
Dimava
Dimava2mo ago
What do you think about making two different validators? Or an context-aware validator
francis
francisOP2mo ago
I ended up fixing this elsewhere, but I needed to use the same validator for defaults and for real validation I didn't end up making an issue since I'm not entirely sure the use case for this anymore
Dimava
Dimava2mo ago
How did you solve this?
francis
francisOP2mo ago
I didn't. superforms + arktype is incompatible currently so I gave up on using it for now but I also fixed it on the client side since the issue was transforming a form default undefined value to a non-undefined value for binding to a child prop in svelte 5 (which can no longer be undefined from the parent)
ssalbdivad
ssalbdivad2mo ago
@ciscoheat has an ArkType resolver in sveltekit-superforms so there must be some potential for compatibility, no? If there are fundamental incompatibilities would be good to understand them so I think about how an adapter might work.
francis
francisOP2mo ago
ah no it was just a breaking change in internals I t hink
francis
francisOP2mo ago
GitHub
Appears to no longer be compatible with arktype as of `[email protected]....
const testObj = type({ foo: "'foo'" }); superValidate({ foo: null }, arktype(testObj, { defaults: { foo: "foo" } })); This now triggers: file://project/node_modules/@ark...
ciscoheat
ciscoheat2mo ago
Yes, I can confirm that this breaks the tests. I'll do some investigation Seems like error.path is now a ReadOnlyPath Any way to extract the path as an array, as it was before?
Dimava
Dimava2mo ago
. slice()? Or is it a string?
ciscoheat
ciscoheat2mo ago
It's an object @ark/util/out/path.js:33
Dimava
Dimava2mo ago
It says it extends ReadonlyArray<PropertyKey> So it's an array So .slice() it to clone to PropertyKey[]
ciscoheat
ciscoheat2mo ago
Yes, but also an object since it got some extra methods, but I'll try Got the same error when trying slice
TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function
new ReadonlyPath node_modules/.pnpm/@ark+util@0.23.0/node_modules/@ark/util/out/path.js:38:14
_validate src/lib/adapters/arktype.ts:35:58
33| const issues = [];
34| for (const error of result) {
35| issues.push({ message: error.message, path: error.path.slice() });
| ^
36| }
37| return {
TypeError: Spread syntax requires ...iterable[Symbol.iterator] to be a function
new ReadonlyPath node_modules/.pnpm/@ark+util@0.23.0/node_modules/@ark/util/out/path.js:38:14
_validate src/lib/adapters/arktype.ts:35:58
33| const issues = [];
34| for (const error of result) {
35| issues.push({ message: error.message, path: error.path.slice() });
| ^
36| }
37| return {
Solved it with Array.from(error.path)
ssalbdivad
ssalbdivad2mo ago
Oh hmm this sucks. Having the array subclass is more of a convenience for path serialization- I wasn't expecting it to be a breaking change. So this was a runtime issue where using TraversalPath ends up not being spreadable?
ciscoheat
ciscoheat2mo ago
Yes
ssalbdivad
ssalbdivad2mo ago
ReadonlyArray is just Array at runtime So it's an array subclass but it shouldn't actually be different in terms of what you can do with it so maybe I forgot to bind something somewhere or it's one of those array subclass pitfalls I didn't know about
ciscoheat
ciscoheat2mo ago
Yeah, arrays are messy in that way But I managed to fix it so it works with Array.from
ssalbdivad
ssalbdivad2mo ago
Definitely appreciate you found that but I do want to make sure that doesn't happen to anyone else. It seems like it was my fault
Dimava
Dimava2mo ago
Would it work with .slice could you check
ciscoheat
ciscoheat2mo ago
It didn't work with slice
ssalbdivad
ssalbdivad2mo ago
He said no I think ahha But even so TraversalPath is really only worthwhile if you can use it just like an array I think that's been true of ArkErrors
Dimava
Dimava2mo ago
Weird
ssalbdivad
ssalbdivad2mo ago
Do you happen to have a minimal-ish repro of the non-iterable issue
Dimava
Dimava2mo ago
^this please
ssalbdivad
ssalbdivad2mo ago
Oh no I totally get it now Yeah I'm dumb
ssalbdivad
ssalbdivad2mo ago
No description
ciscoheat
ciscoheat2mo ago
const result = schema(data);
if (!(result instanceof type.errors)) {
return "OK";
}
const issues = [];
for (const error of result) {
issues.push({ message: error.message, path: error.path.slice() });
}
const result = schema(data);
if (!(result instanceof type.errors)) {
return "OK";
}
const issues = [];
for (const error of result) {
issues.push({ message: error.message, path: error.path.slice() });
}
ssalbdivad
ssalbdivad2mo ago
I mean I do blame JS here also because array constructor is a terrible API haha But my solution is not a solution
ssalbdivad
ssalbdivad2mo ago
I actually wrote this when working on TraversalPath
ciscoheat
ciscoheat2mo ago
The Array constructor defies all logic
ssalbdivad
ssalbdivad2mo ago
Like yeah there are other janky things about JS but this feels so obviously wrong from a language/ API design perspective
ciscoheat
ciscoheat2mo ago
No description
ssalbdivad
ssalbdivad2mo ago
Hmm so what do I actually do here I don't even know how to work around this I guess I need to accept ... args and just treat a single number as an array of one instead of doing the nonsense JS does
ciscoheat
ciscoheat2mo ago
What was the problem with the (real) array in the first place?
ssalbdivad
ssalbdivad2mo ago
There wasn't a problem really there were a lot of places in the code base where I wanted to do special stuff related to serialization and comparison of paths so I thought it would make my life easier to create an array subclass that let you do it directly, but seems like I was wrong
ciscoheat
ciscoheat2mo ago
Ok, yeah, I guess it's a bit of a problem if the error path doesn't behave just like an array
ssalbdivad
ssalbdivad2mo ago
It definitely needs to behave just like an array or it's not a good abstraction
ciscoheat
ciscoheat2mo ago
Yes
ssalbdivad
ssalbdivad2mo ago
But AFAIK I've had succes with ArkErrors being an array subclass with extra functionality and not causing problems So maybe it's just that I screwed up the constructor
ciscoheat
ciscoheat2mo ago
Maybe you can use that internally, and call Array.from when you're about to return the errors? If you need the extra features of the subclass
ssalbdivad
ssalbdivad2mo ago
I think I will try to get it to behave consistently if I can't do that I'm just going to remove it
ciscoheat
ciscoheat2mo ago
I'm sure you'll figure something out. 🙂
ssalbdivad
ssalbdivad2mo ago
Yeah I mean worst case scenario I just convert it back to some helper functions I need to remember to call It's really weird that using .freeze on an array breaks stuff like .map and .slice
ciscoheat
ciscoheat2mo ago
Yes, I didn't know that
ssalbdivad
ssalbdivad2mo ago
I can't really even figure out how that is possible I guess if the new array is already frozen when it is mapped? I don't see why it would be
ciscoheat
ciscoheat2mo ago
Maybe because the prototype of freeze is on Object And arrays are "objects"
ssalbdivad
ssalbdivad2mo ago
Yeah but I still don't see what would be mutated there Like when I tried to do this:
const path = new ReadonlyPath(5)

attest(path).snap()
attest(path).instanceOf(ReadonlyPath)

attest(path.slice()).snap()
const path = new ReadonlyPath(5)

attest(path).snap()
attest(path).instanceOf(ReadonlyPath)

attest(path.slice()).snap()
I get: Cannot redefine property: 0
Dimava
Dimava2mo ago
Tf Looks like a bug What if you freeze a regular array?
ssalbdivad
ssalbdivad2mo ago
And the snapshot logic that breaks:
if (Array.isArray(o))
return o.map(item => _serialize(item, opts, nextSeen))
if (Array.isArray(o))
return o.map(item => _serialize(item, opts, nextSeen))
It just breaks on .map. Yeah arrays are kinda like objects under the hood but the question is why is 0 being written to
Dimava
Dimava2mo ago
Object.freeze([1]).slice() ?
ssalbdivad
ssalbdivad2mo ago
That works
Dimava
Dimava2mo ago
Huh
ciscoheat
ciscoheat2mo ago
Subclassing in typescript compiles to something messy?
Dimava
Dimava2mo ago
class Foo extends Array {}
Object.freeze(new Foo(1,2)).slice()
class Foo extends Array {}
Object.freeze(new Foo(1,2)).slice()
?
ssalbdivad
ssalbdivad2mo ago
It seems like it breaks if you freeze in the constructor
class Foo extends Array {
constructor(...args: any[]) {
super(...args)
Object.freeze(this)
}
}

// Cannot assign to read only property 'length' of object '[object Array]'
new Foo().slice()
class Foo extends Array {
constructor(...args: any[]) {
super(...args)
Object.freeze(this)
}
}

// Cannot assign to read only property 'length' of object '[object Array]'
new Foo().slice()
I don't see why that is semantically different from what you wrote though
Dimava
Dimava2mo ago
Huh What if you void this.length? Before freesing
ssalbdivad
ssalbdivad2mo ago
Idk exactly what you mean but if I add void this.length it doesn't do anything
ciscoheat
ciscoheat2mo ago
I'm stumped
ssalbdivad
ssalbdivad2mo ago
I'm going to post it and see if anyone knows haha
Dimava
Dimava2mo ago
Hmmm
ciscoheat
ciscoheat2mo ago
Gotta take some rest now, nice chatting
Dimava
Dimava2mo ago
I do have an idea though @ssalbdivad does class extends array .slice() produces an instance of the same class?
ssalbdivad
ssalbdivad2mo ago
Yes
Dimava
Dimava2mo ago
I guess thats where it breaks idk why
ssalbdivad
ssalbdivad2mo ago
What is where it breaks
Dimava
Dimava2mo ago
Probably because it writes to it after its constructed Try setting its species to Array Yk whats species?
ssalbdivad
ssalbdivad2mo ago
No idea How does that make sense though
Dimava
Dimava2mo ago
MDN Web Docs
Array[Symbol.species] - JavaScript | MDN
The Array[Symbol.species] static accessor property returns the constructor used to construct return values from array methods.
ssalbdivad
ssalbdivad2mo ago
Shouldn't it be written to in the constructor If the constructor accepts ...items
Dimava
Dimava2mo ago
No .map acts exactly like a polyfill
ssalbdivad
ssalbdivad2mo ago
Map is like: 1. init [] 2. push items?
Dimava
Dimava2mo ago
sorta Probably assings rather then pushes tho Hmmmm @ssalbdivad so could you try Path[Symbol.species] =Array?
class MyArray extends Array {
// Overwrite MyArray species to the parent Array constructor
static get [Symbol.species]() {
return Array;
}
}
class MyArray extends Array {
// Overwrite MyArray species to the parent Array constructor
static get [Symbol.species]() {
return Array;
}
}
ssalbdivad
ssalbdivad2mo ago
But I would want it to still be the same class
Dimava
Dimava2mo ago
Hmmm Why?
ssalbdivad
ssalbdivad2mo ago
Because to me that is intuitively how map should work It does fix the issue though I mean that makes sense Based on what you are saying, because it doesn't run the subclass constructor
Dimava
Dimava2mo ago
\o/ we found a fix Not the fix tho Because it writes after constructor
ssalbdivad
ssalbdivad2mo ago
The way that makes sense to me to create a plain Array from a subclass would be Array.from(subinstance) or [...subinstance] That is so ridiculously unintuitive
Dimava
Dimava2mo ago
Try making a polyfill that doesn't do that
ssalbdivad
ssalbdivad2mo ago
I mean because of Array's terrible constructor API I guesss? So this all ties back to that?
Dimava
Dimava2mo ago
Idk ¯\_(ツ)_/¯
ssalbdivad
ssalbdivad2mo ago
Okay I guess I kind of see it You need to construct an array to have something like ...args to pass
Dimava
Dimava2mo ago
You can't implement a .map that doesn't write into array without using a second array Yeh that's second array Es6 feature Array.prototype.map works even on objects lol Try Array.prototype.push into an object lol Well it may be not optimized but it does work
ssalbdivad
ssalbdivad2mo ago
Yeah that's what I mean So they wanted it to be possible to polyfill so they end up with this very weird logic I mean still very unintuitive but more reasonable once you think about it than the constructor API itself
Dimava
Dimava2mo ago
It may have less sense now but it probably did 25 year ago when it was made
ssalbdivad
ssalbdivad2mo ago
There's no way that was ever a good decision
Dimava
Dimava2mo ago
If it would be made now it would always make an Array instance like toSorted does
ssalbdivad
ssalbdivad2mo ago
Doesn't matter which overload came first or if both at the same time- adding overlapping signatures was a big mistake
Dimava
Dimava2mo ago
Wym
ssalbdivad
ssalbdivad2mo ago
Dimava
Dimava2mo ago
Ah Well maybe
ssalbdivad
ssalbdivad2mo ago
To be fair it's one of the few times where I've felt like something in JS is egregiously bad and relatively easy to run into if you avoid == and know typeof null === "object" Most complaints are pretty contrived
Dimava
Dimava2mo ago
It was probably made in that first 10 days js was made in
ssalbdivad
ssalbdivad2mo ago
Do you think they added both right away or added the ..args later?
Dimava
Dimava2mo ago
... Is es6
ssalbdivad
ssalbdivad2mo ago
Okay but you could still do the same thing with arguments
Dimava
Dimava2mo ago
What thing
ssalbdivad
ssalbdivad2mo ago
Accept n items in the constructor IDK it kind of feels like it would be cyclic though I don't know what the internal logic looks like there. I guess that has to be native code @ciscoheat Well I definitely am not at all invested in the array actually being frozen at runtime so I'm just going to remove that. In the next release, array methods should work fine with TraversalPath, so at that point feel free to switch back to the previous implementation. Or not, what's there now should be fine too 👍

Did you find this page helpful?