Comments (6)
After thinking about this for a while... I haven't reached any good conclusion. But I have some thoughts, so prepare for a half-formed braindump 🙂
What is .readonly()?
I'm a bit worried about the fact that "readonly" tends to refer to two slightly different things in TypeScript. Having a .readonly()
method could mean these two things:
- Making all fields of
v.object
/v.record
/v.tuple
/v.array
readonly (e.g.v.object({ a: ..., b... }).readonly()
). - Making a specific field of
v.object
readonly (e.g.v.object({ a: v.number().readonly() })
).
Therefore .readonly
's meaning would be somewhat overloaded, and the meaning of the following would be ambiguous:
v.object({
a: v.array(v.number()).readonly()
});
Is the output type now { readonly a: readonly [number] }
, { readonly a: [number] }
or { a: readonly [number] }
? 🙂
One solution for this would, of course, to have different named methods for those different use cases. Like v.array(...).immutable()
for example. That'd be pretty descriptive. Maybe.
Helper functions
I'm not fully convinced this even needs to be in the Valita core. .assert
, .map
and .chain
methods are pretty cool and open up interesting possibilities for implementing some functionality as external helper functions. For example, the "make some object fields readonly" can be implemented in the following way:
// Flatten an intersection { a: A } & { b: B } to { a: A, b: B }
type Flatten<V> = { [K in keyof V]: V[K] };
// Create a type predicate that asserts that the objects given fields are readonly.
function ro<O extends Record<string, unknown>, F extends (keyof O)[]>(
..._fields: F
) {
return (
o: O
): o is Flatten<Omit<O, F[number]> & Readonly<Pick<O, F[number]>>> => true;
}
const SCHEMA = v
.object({
regular_field: v.string(),
optional_field: v.string().optional(),
readonly_field: v.string(),
})
.assert(ro("readonly_field"));
type InferredType = v.Infer<typeof SCHEMA>;
// type InferredType = {
// regular_field: string;
// optional_field?: string | undefined;
// readonly readonly_field: string;
// }
A similar solution can be cooked up for making the whole object / array readonly.
Let's get radical
One interesting & bit radical approach would be to just make all Valita output readonly by default. It would have the additional benefit of discouraging people to mutate the output (and therefore accidentally mutating the input too, as Valita passes through the original object if it can get away with it). YMMV, of course 🙂
from valita.
That's a good idea 👍 I wonder what the API should be, as there are two cases that could be supported:
- Making a specific property readonly (like in the example you provided).
- Making the whole object/array readonly (
readonly [...]
andReadonly<{ ... }>
).
from valita.
Yeah, having a way to make the entire object readonly would be useful too, and would save some repetitive typing.
I'm not sufficiently familiar with the implementation to be able to suggest a good API. For specific properties, using .readonly()
(similar to .optional()
) would feel natural.
from valita.
for read only arrays/tuples and objects/records having a .readonly()
makes sense as well these should transform the type into a ReadOnlyTuple
for example.
from valita.
I see your point about the ambiguity. Using both readonly
(for a single field) and immutable
(for an entire object) might be a good approach.
By the way, doesn't .optional()
suffer from ambiguity as well? If I use this definition:
v.object({
a: v.array(v.number()).optional()
});
...will this result in { a?: number[] }
, { a: (number | undefined)[] }
or { a?: (number | undefined)[] }
?
Or even simpler: Does this
v.object({
a: v.number().optional()
});
...result in { a?: number }
, { a: number | undefined }
or { a?: number | undefined }
?
The helper function isn't bad, but the ergonomics from a user point of view aren't great: Why do I mark optional fields as optional in the definition itself, while I have to use separate assertions to make a field readonly, even though optional and readonly are both core features of TypeScript?
Regarding readonly fields by default: I wouldn't be opposed, but then you'll need a way to mark some fields as mutable, which results in the same discussion 🙂 It's probably something that could be discussed separately.
from valita.
No, it's not the same. In TypeScript the optional modifier acts very different compared to readonly
. It only applies to properties and you cannot mark, let's say an array, as optional. An array can't be optional, but an object property can. So there is no ambiguity regarding optional.
However, the readonly
keyword can be used for two separate purposes:
interface Foo {
readonly bar: string[]
}
// vs
interface Foo {
bar: readonly string[]
}
That's where the ambiguity comes from.
from valita.
Related Issues (20)
- v.lazy() causes error HOT 1
- Validating typed arrays HOT 5
- Deno support HOT 1
- Exporting AbstractType HOT 2
- A safe way to transform Zod parser to Valita HOT 2
- Input & output types HOT 10
- TS 4094, issueTrue is private HOT 2
- Can t.Infer be made to _not_ unroll deep schemas? HOT 4
- Annotation support? HOT 5
- Runtime schema inspection / type narrowing HOT 5
- RFE: Add a way to infer readonly types HOT 2
- Improved error reporting for .map() HOT 2
- Help developer catch error early HOT 6
- Should we add `intersect` method to UnionType? HOT 2
- export type classes HOT 6
- Why I can't use `.try(value)` when using `.optional()`? HOT 3
- What is the purpose of `.default(...)` ? HOT 2
- The `protoless` optimization breaks Firebase/Firestore HOT 1
- Passing ParseOptions to chain? HOT 6
- Question: How to combine records and objects HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from valita.