Repository navigation
Conversation
- Update node version to 16 in build and lint workflow - Ensure deletion result BigInt values are type-checked
192404b to
ad8c051
Compare
0688727 to
2d92e62
Compare
- Upgrade typescript to support satisfies operator
2d92e62 to
2342396
Compare
| // we don't want to spam the user with all the errors. Take the first one. | ||
| const message = error.issues[0].message; |
There was a problem hiding this comment.
| // we don't want to spam the user with all the errors. Take the first one. | |
| const message = error.issues[0].message; | |
| // we don't want to spam the user with all the errors. Take the first one. | |
| const message = error.issues[0].message; |
Why not? I prefer to see the bag of errors than filtering and rerunning the code to find the next problem
There was a problem hiding this comment.
It's something I was planning to change. If the user enters 10k events with a bad ID format, it will print 10k repeated errors.
There was a problem hiding this comment.
I would expect a single error ZodError and if that prints all the values under issues when logging, so be it!
In other word, don't hide ZodError and let them use error.issues however they want. At this layer of problem, it is almost a system error.
| validateField(schemas.streamName, streamName); | ||
| validateField( | ||
| schemas.listPersistentSubscriptionsToStreamOptions.optional(), | ||
| options | ||
| ); |
There was a problem hiding this comment.
This pattern feels really C# style, specially if you are gonna be using Zod, instead I would expect to validate a DTO
There was a problem hiding this comment.
You raise a good point. I will take that into consideration.
|
I am curious to learn more about who is asking for this and what problems they are running into and why. I would appreciate context since Non-ESDB peeps do not get to have access to Linear to understand more about the linked ticket. I prefer lean clients that are extremely simple, and definitely would appreciate if EventStoreDB doesn't rely on Maybe the solution is better documentation or typespec or even better, introduce Value Objects to enforce correctness. |
Thanks for the input. Some people were experiencing runtime errors, and we were thinking |
Is there any context about their environments and what exactly was the problem? Are they doing type checking of any kind? How many times a given team made the mistake vs. newbies that weren't familiar with the stack? Are they copy+pasting code or do they have a slim wrapper around ESDB client? Could they have their own validation layer in their end if they really want "Safety"? I would love to understand and have more public information about what drove the decision here before we all pay the cost because of "some" users. I do not want to dismiss their experience, while also make sure a layer such as ESDB client is lean, simple and performance. |
We had 2 issues in the past where someone tried to append to a stream with an invalid UUID, and another case when someone tried to append to a stream with an invalid 'expected revision'.
I am not sure about their skill level or their familiarity with the stack. This small PR is more of a developer experience improvement, and I left it in draft for experimenting. Also, if we were to proceed with this solution, users could continue to use the functions as before without many changes. |
That is a really low instances. What would the server do in those cases?
With low instances and without much understand of the situation, I would prefer if ESDB isn't reactionary in such situation and seek to honestly comprehend what was the root caused of it.
Well, sort of, I wish it was that easy. It will introduce a new external dependency, which in the worst case, tools like package manager will try to dedupe the dependency and may cause potential issues in production. Or taking into consideration I would expect ESDB to be conservative with the external package dependencies, especially in the JavaScript ecosystem and |
| id: z.string().uuid(), | ||
| contentType: z.literal("application/json"), | ||
| type: z.string(), | ||
| data: jsonType, |
There was a problem hiding this comment.
| data: jsonType, | |
| data: jsonType, |
I am confused about this type 🤔 What is the motivation on enforcing a given data payload structure from the perspective of the ESDB Client?
I would expect the data to be any JSON payload
| ]) satisfies z.ZodType<JSONType>; | ||
|
|
||
| export const metadataType = z.union([ | ||
| jsonType, |
There was a problem hiding this comment.
Same here 🤔 I would expect this to be inlined with other SDKs where the metadata is a byte[] of anything
| import * as common from "./common"; | ||
| import * as stream from "./stream"; | ||
| import * as persistentSubscription from "./persistentSubscription"; | ||
| import * as projection from "./projection"; | ||
| import { z } from "zod"; | ||
|
|
||
| z.setErrorMap((issue, ctx) => { | ||
| const fieldName = issue.path.join("."); | ||
| let message = ctx.defaultError; | ||
|
|
||
| // When the path is empty, it means that the error is on the root. | ||
| if (issue.path.length !== 0) { | ||
| switch (issue.code) { | ||
| case "too_small": | ||
| message = `the field '${fieldName}' must be greater than or equal to ${issue.minimum}. Received ${ctx.data}`; | ||
| break; | ||
| case "too_big": | ||
| message = `the field '${fieldName}' must be less than or equal to ${issue.maximum}. Received ${ctx.data}`; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return { message }; | ||
| }); | ||
|
|
||
| const schemas = { | ||
| ...common, | ||
| ...stream, | ||
| ...persistentSubscription, | ||
| ...projection, | ||
| }; | ||
|
|
||
| export default schemas; |
There was a problem hiding this comment.
| import * as common from "./common"; | |
| import * as stream from "./stream"; | |
| import * as persistentSubscription from "./persistentSubscription"; | |
| import * as projection from "./projection"; | |
| import { z } from "zod"; | |
| z.setErrorMap((issue, ctx) => { | |
| const fieldName = issue.path.join("."); | |
| let message = ctx.defaultError; | |
| // When the path is empty, it means that the error is on the root. | |
| if (issue.path.length !== 0) { | |
| switch (issue.code) { | |
| case "too_small": | |
| message = `the field '${fieldName}' must be greater than or equal to ${issue.minimum}. Received ${ctx.data}`; | |
| break; | |
| case "too_big": | |
| message = `the field '${fieldName}' must be less than or equal to ${issue.maximum}. Received ${ctx.data}`; | |
| break; | |
| } | |
| } | |
| return { message }; | |
| }); | |
| const schemas = { | |
| ...common, | |
| ...stream, | |
| ...persistentSubscription, | |
| ...projection, | |
| }; | |
| export default schemas; | |
| export * from "./common"; | |
| export * from "./stream"; | |
| export * from "./persistentSubscription"; | |
| export * from "./projection"; | |
| import { z } from "zod"; | |
| z.setErrorMap((issue, ctx) => { | |
| const fieldName = issue.path.join("."); | |
| let message = ctx.defaultError; | |
| // When the path is empty, it means that the error is on the root. | |
| if (issue.path.length !== 0) { | |
| switch (issue.code) { | |
| case "too_small": | |
| message = `the field '${fieldName}' must be greater than or equal to ${issue.minimum}. Received ${ctx.data}`; | |
| break; | |
| case "too_big": | |
| message = `the field '${fieldName}' must be less than or equal to ${issue.maximum}. Received ${ctx.data}`; | |
| break; | |
| } | |
| } | |
| return { message }; | |
| }); |
That style of export is an anti-pattern, you can do export * from and if people want everything under a given object as a module scope they can do import * as schemas from
| z.setErrorMap((issue, ctx) => { | ||
| const fieldName = issue.path.join("."); | ||
| let message = ctx.defaultError; | ||
|
|
||
| // When the path is empty, it means that the error is on the root. | ||
| if (issue.path.length !== 0) { | ||
| switch (issue.code) { | ||
| case "too_small": | ||
| message = `the field '${fieldName}' must be greater than or equal to ${issue.minimum}. Received ${ctx.data}`; | ||
| break; | ||
| case "too_big": | ||
| message = `the field '${fieldName}' must be less than or equal to ${issue.maximum}. Received ${ctx.data}`; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return { message }; | ||
| }); |
There was a problem hiding this comment.
| z.setErrorMap((issue, ctx) => { | |
| const fieldName = issue.path.join("."); | |
| let message = ctx.defaultError; | |
| // When the path is empty, it means that the error is on the root. | |
| if (issue.path.length !== 0) { | |
| switch (issue.code) { | |
| case "too_small": | |
| message = `the field '${fieldName}' must be greater than or equal to ${issue.minimum}. Received ${ctx.data}`; | |
| break; | |
| case "too_big": | |
| message = `the field '${fieldName}' must be less than or equal to ${issue.maximum}. Received ${ctx.data}`; | |
| break; | |
| } | |
| } | |
| return { message }; | |
| }); | |
| z.setErrorMap((issue, ctx) => { | |
| const fieldName = issue.path.join("."); | |
| let message = ctx.defaultError; | |
| // When the path is empty, it means that the error is on the root. | |
| if (issue.path.length !== 0) { | |
| switch (issue.code) { | |
| case "too_small": | |
| message = `the field '${fieldName}' must be greater than or equal to ${issue.minimum}. Received ${ctx.data}`; | |
| break; | |
| case "too_big": | |
| message = `the field '${fieldName}' must be less than or equal to ${issue.maximum}. Received ${ctx.data}`; | |
| break; | |
| } | |
| } | |
| return { message }; | |
| }); |
Would this code change Zod as a singleton? If so, how will this work with project using Zod for other things and potentially introducing unexpected behavior from ESDB client due to the singleton issue?
There was a problem hiding this comment.
I could encapsulate the error mapping in a separate function.
|
Maybe this SDK could follow the lead of other strong type languages and introduce classes/JS objects/Value Objects where you get to validate the integrity of the data in the constructor instead of relying on types from TypeScript. |
|
@w1am sorry I comment again, just that yesterday while I was trying to fall sleep, I remember another way. Instead of using This way, the type system could stay the same and does not have to rely on "classes" but just types. |
Yes, that's a possibility. I'll keep that in mind when making the decision. |
Changed: Integrate Zod for Stronger Type Checking
This PR aims to enhance type safety and data integrity, ensuring our application handles data more robustly and reduces the likelihood of runtime errors due to type mismatches. Additional validations include checking UUIDs and ensuring certain numbers fall within specified limits.