How to explicitly skip checking part of the schema in zod - zod

I would like to understand if and how it is possible to skip validating parts of a schema in zod?
In the following example, I would like to validate the foo schema to make sure that the object contains a property id of type number and a property data of type array but (maybe cause there is a lot of data) I would like to prevent validating all the actual array entries in data.
import {z} from 'zod';
const foo = z.object({
id: z.number(),
data: z.array(z.string()),
});

This does the job:
const dataItem = z.custom<DataItem>(); // type DataItem defined by you elsewhere
const foo = z.object({
id: z.number(),
data: z.array(dataItem),
});
// { id: string; data: DataItem[] }
https://github.com/colinhacks/zod/discussions/1575

Related

zod (or toZod): how to model "type" field in discriminated union

I have this type:
export interface ConnectorForModel {
_type: "connector.for.model",
connectorDefinitionId: string
}
I want to model is as a zod schema. Actually I am using toZod, like this:
export const ConnectorForModelZod: toZod<ConnectorForModel> = z.object({
_type: z.literal("connector.for.model"),
connectorDefinitionId: z.string()
});
And I get this type error:
Type 'ZodLiteral<"connector.for.model">' is not assignable to type 'never'.
Whats the right way to express this?
I think the quickest way to get this working is with ZodType:
import { z } from "zod";
export interface ConnectorForModel {
_type: "connector.for.model";
connectorDefinitionId: string;
}
export const ConnectorForModelZod: z.ZodType<ConnectorForModel> = z.object({
_type: z.literal("connector.for.model"),
connectorDefinitionId: z.string()
});
Aside: I tend to define my types from my zod schemas rather than the other way around. If you don't have control over the type that you're working with then the given approach is the way to go, but you could potentially avoid writing the same code twice using z.TypeOf on the zod schema
type ConnectorForModel = z.TypeOf<typeof ConnectorForModelZod>;
This type would be equivalent to your interface.

How do I merge slices of state for my presentational component properly (ngrx/store)?

I'm still trying to learn ngrx - the store is set-up and everything seems to work fine. The database I'm using is SQL so basically I'm "clongin" the tables and load them into the store but have problems joining the state in the end when selecting an employee.
The employee entity looks something like this:
export class Employee = {
id: number;
firstName: string;
lastName: string;
degreeId?: number;
degree: Degree;
}
export class Degree = {
id: number;
description: string;
}
Now in my component, I'd like to get the specific employee to display like this:
{
id: 1,
firstName: George,
lastName: Costanza,
degreeId: 2,
degree: {
id: 2,
description: 'College'
}
}
What I tried is to create a selector that merges these two entities:
export const getEmployeeWithAllData = createSelector(
getSelectedEmployee,
getRelationalData,
(employee, data) => {
const employeesDegree = degree[employee.degreeId]
employee.degree = employeesDegree
return employee;
}
);
This does seem to work if I don't use ngrx-store-freeze - so since I don't know if I am creating the selector correctly or if ngrx-store-freeze has a bug I'm asking this question.
Am I really mutating state when I do this?
If yes, how can I select a specific employee from my store with all the relational data that he has?
What I'm doing doesn't really feel right. In my actual application the employee has about 8 fields of relational data which I need to join...
Edit: I forgot to include the error ngrx-store-freeze throws:
ERROR TypeError: Cannot assign to read only property 'degree' of object '[object Object]'
at eval (employee.selector.ts:85)
at eval (store.es5.js:602)
at memoized (store.es5.js:539)
at defaultStateFn (store.es5.js:573)
at eval (store.es5.js:605)
at MapSubscriber.memoized [as project] (store.es5.js:539)
at MapSubscriber._next (map.js:79)
at MapSubscriber.Subscriber.next (Subscriber.js:95)
at MapSubscriber._next (map.js:85)
at MapSubscriber.Subscriber.next (Subscriber.js:95)
yes, you are trying to mutate the state. You can prevent this simply like bellow
export const getEmployeeWithAllData = createSelector(
getSelectedEmployee,
getRelationalData,
(employee, data) => {
const emp = JSON.parse(JSON.stringify(employee)
emp.degree = degree[employee.degreeId]
return emp;
}
)

Flow doesn't infer type correctly

I define an multiple subtypes in the Action creator in redux:
Action creator:
export type Action = { type: "SELECT", index: number } | { type: "OTHER" };
Reducer:
module.exports = (state: string = "", action: Action): string => {
switch (action.type) {
case "SELECT":
return action.index;
default:
return state;
}
};
but if I define SELECT in a constant const select = "SELECT" and implement it in the code above I obtain an error message:
property `index`. Property not found in object type
Note: Taking flow-pattern as it is F8 app:
https://github.com/fbsamples/f8app/blob/master/js/actions/types.js
How should implement it by avoiding having "SELECT" keyword both in the action and in the reducer?
You would normally have another constant with the action type, which would be used for both your action and reducer.
const SELECT = 'SELECT';
or even better (to avoid any conflicts):
const SELECT = 'redux/<module>/SELECT';
In the same action file or in another (that's totally up to you). Then just export like export const SELECT, and import like import { SELECT } from './constants'.
You should also take a look at redux-ducks, might be of your interest.
EDIT:
They use bitwise OR to group all possible different action cases. That helps testing whether the code is syntactically correct with flow (by setting their dispatched actions to match type Action). See their full explanation here.
That does not take away the fact that they have to dispatch the action with their desired action type though. login

How do you clear an immutable.js map back to its defaults

I have an immutable.js map. For example:
// default, when user first gets on page
var myObject = Immutable.Map({
productID: '',
colors: ['brown', 'red'],
sku: ''
sizes: [10]
})
Now, depending on how they get to my app - I populate that above "myObject" with different data.
so, for example: lets say they come from pathA
// pathA passes in some data... hydrate the myObject
var myObject = Immutable.Map({
productID: '090ABl',
colors: ['brown', 'red', 'yellow'],
sku: '__whatever'
sizes: [10, 18, 9]
})
so, for example: lets say they come from pathB
** this is where the issue comes from. I have that previous "state" of myObject hanging around. I need to "clear and go back to the initial state". I am using redux.
// pathB passes in some data... hydrate the myObject
var myObject = Immutable.Map({
productID: '090XZLG',
colors: ['red', 'yellow'],
sku: '__food'
sizes: [9]
})
The data is combing etc.. I need it to "clear out.". Curious if there is an Immutable.js method that enables to refresh the myObject with a new one, that is the same as the initial state. I am new to immutable.js so I am a bit curious about why its so hard to do simple things :-)
When using immutable for the state object in redux, in many of my reducers I set a certain key to what I get from the initial state.
i.e.:
function reeducer(state = initialState, action) {
switch (action.type) {
...
case CLEAR_SELECTION:
return state.set('selected', initialState.get('selected'));
...
}
}
And of course to make it a bit cleaner (specially to avoid repeating a string) one could create a function for this:
function resetKey(state, initialState, key) {
return state.set(key, initialState.get(key));
}
(Alternatively it could be called copyKey or copySection)
Or it could be a resetPath/copyPath
function resetPath(state, initialState, path) {
return state.setIn(key, initialState.getIn(path));
}
The immutable.js object does not 'know' what its 'defaults' are, so there is no way of resetting it. If you have a default object in your application just keep a reference to that in a variable, like you would if it were a plain JavaScript object:
var defaultObject = Immutable.Map({
productID: '',
colors: ['brown', 'red'],
sku: ''
sizes: [10]
})
Then when the user arrives at a certain path, you simply make your modifications and keep them in a different variable:
var myObject = defaultObject.set(...);

autoform won't render select option field

I have an issue regarding collection2 with relationships and autoform.
I try to implement an 1:n relationship, where each object has exactly 1 objectType, while to each objectType multiple objects can be referred to.
My schema looks as follows:
// register collections
Objects = new Mongo.Collection('objects');
ObjectTypes = new Mongo.Collection('objectTypes');
// define schema
var Schemas = {};
Schemas.ObjectType = new SimpleSchema({ // object type schema
name: {
type: String
}
});
Schemas.Object = new SimpleSchema({ // object schema
type: {
type: ObjectTypes.Schema,
optional: true
},
title: {
type: String
}
});
// attach schemas
ObjectTypes.attachSchema(Schemas.ObjectType);
Objects.attachSchema(Schemas.Object);
My autoform looks like this:
{{> quickForm collection="Objects" id="insertTestForm" type="insert"}}
I actually would expect a select option field for my type attribute, however, a text input appears. Anyone knows why?
According to the documentation [1], it should be a select option field:
If you use a field that has a type that is a Mongo.Collection instance, autoform will automatically provide select options based on _id and name fields from the related Mongo.Collection. You may override with your own options to use a field other than name or to show a limited subset of all documents. You can also use allowedValues to limit which _ids should be shown in the options list.
[1] https://github.com/aldeed/meteor-collection2/blob/master/RELATIONSHIPS.md#user-content-autoform
EDIT
If I use
type: ObjectTypes,
instead of
type: ObjectTypes.Schema,
my app crashes, throwing the following error:
Your app is crashing. Here's the latest log.
/Users/XXX/.meteor/packages/meteor-tool/.1.1.3.ik16id++os.osx.x86_64+web.browser+web.cordova/mt-os.osx.x86_64/dev_bundle/server-lib/node_modules/fibers/future.js:245
throw(ex);
^
RangeError: Maximum call stack size exceeded
Exited with code: 8
Your application is crashing. Waiting for file change.
Your type isn't "a Mongo.Collection instance" like the documentation says; it's a Schema. Try this:
Schemas.Object = new SimpleSchema({
type: {
type: ObjectTypes,
optional: true
},
...
Since nobody could help me solve this incident, I came up with an alternate solution:
// register collections
Objects = new Mongo.Collection('objects');
ObjectTypes = new Mongo.Collection('objectTypes');
// define schema
var Schemas = {};
Schemas.Object = new SimpleSchema({ // object schema
type: {
type: String,
optional: true,
autoform: {
return ObjectTypes.find().map(function(c) {
return{label: c.name, value: c._id}
});
}
},
// ...
});
// attach schema
Objects.attachSchema(Schemas.Object);
As u can see, I manually map the attributes I need from the objectTypes collection into the autoform attribute. Since it returns an array of objects, containing the label and value attributes, autoform will automatically render a select option.

Resources