Support history for socket v2 and add tests

v5
Candid Dauth 2024-04-19 23:52:32 +02:00
rodzic a547401e08
commit db31dc8a2a
10 zmienionych plików z 575 dodań i 46 usunięć

Wyświetl plik

@ -0,0 +1,162 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, openClient, retry } from "../utils";
import { CRU, SocketVersion, type ID, type LegacyV2Type } from "facilmap-types";
test("Marker update history (socket v2)", async () => {
const client1 = await openClient(undefined, SocketVersion.V2);
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.adminId);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
const createdMarker = await client1.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null,
symbol: "icon1"
});
await Promise.all([
client1.listenToHistory(),
client2.listenToHistory()
]);
const onHistory1 = vi.fn();
client1.on("history", onHistory1);
const onHistory2 = vi.fn();
client2.on("history", onHistory2);
const newData = {
id: createdMarker.id,
symbol: "icon2"
};
await client1.editMarker(newData);
await retry(() => {
expect(onHistory1).toHaveBeenCalledTimes(1);
expect(onHistory2).toHaveBeenCalledTimes(1);
});
expect(onHistory1).toHaveBeenCalledWith(expect.objectContaining({
objectBefore: expect.objectContaining({
symbol: "icon1"
}),
objectAfter: expect.objectContaining({
symbol: "icon2"
})
}));
expect(onHistory2).toHaveBeenCalledWith(expect.objectContaining({
objectBefore: expect.objectContaining({
icon: "icon1"
}),
objectAfter: expect.objectContaining({
icon: "icon2"
})
}));
});
});
test("Type update history (socket v2)", async () => {
const client1 = await openClient(undefined, SocketVersion.V2);
await createTemporaryPad(client1, { createDefaultTypes: false }, async (createPadData, padData, result) => {
const client2 = await openClient(padData.adminId);
const createdType = await client1.addType({
name: "Test type",
type: "marker",
defaultSymbol: "icon1",
symbolFixed: false,
fields: [{
type: "dropdown",
name: "Test",
controlSymbol: false,
options: [{
value: "Test",
symbol: "test1"
}]
}]
});
await Promise.all([
client1.listenToHistory(),
client2.listenToHistory()
]);
const onHistory1 = vi.fn();
client1.on("history", onHistory1);
const onHistory2 = vi.fn();
client2.on("history", onHistory2);
const update = {
id: createdType.id,
defaultSymbol: "icon2",
symbolFixed: true,
fields: [{
type: "dropdown",
name: "Test",
controlSymbol: true,
options: [{
value: "Test",
symbol: "test2"
}]
}]
} satisfies LegacyV2Type<CRU.UPDATE> & { id: ID };
await client1.editType(update);
await retry(() => {
expect(onHistory1).toHaveBeenCalledTimes(1);
expect(onHistory2).toHaveBeenCalledTimes(1);
});
expect(onHistory1).toHaveBeenCalledWith(expect.objectContaining({
objectBefore: expect.objectContaining({
defaultSymbol: "icon1",
symbolFixed: false,
fields: [expect.objectContaining({
controlSymbol: false,
options: [expect.objectContaining({
symbol: "test1"
})]
})]
}),
objectAfter: expect.objectContaining({
defaultSymbol: "icon2",
symbolFixed: true,
fields: [expect.objectContaining({
controlSymbol: true,
options: [expect.objectContaining({
symbol: "test2"
})]
})]
})
}));
expect(onHistory2).toHaveBeenCalledWith(expect.objectContaining({
objectBefore: expect.objectContaining({
defaultIcon: "icon1",
iconFixed: false,
fields: [expect.objectContaining({
controlIcon: false,
options: [expect.objectContaining({
icon: "test1"
})]
})]
}),
objectAfter: expect.objectContaining({
defaultIcon: "icon2",
iconFixed: true,
fields: [expect.objectContaining({
controlIcon: true,
options: [expect.objectContaining({
icon: "test2"
})]
})]
})
}));
});
});

Wyświetl plik

@ -0,0 +1,169 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, openClient, retry } from "../utils";
import { SocketVersion } from "facilmap-types";
import { cloneDeep } from "lodash-es";
test("Create marker (Socket v2)", async () => {
// client1: Creates the marker and has it in its bbox
// client2: Has the marker in its bbox
// client3: Opens the map later
// client4: Opens the map later (Socket v3)
const client1 = await openClient(undefined, SocketVersion.V2);
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id, SocketVersion.V2);
const onMarker1 = vi.fn();
client1.on("marker", onMarker1);
const onMarker2 = vi.fn();
client2.on("marker", onMarker2);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
await client1.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client2.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
const marker = await client1.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null,
symbol: "test"
});
const expectedMarker = {
symbol: "test"
};
expect(marker).toMatchObject(expectedMarker);
await retry(() => {
expect(onMarker1).toHaveBeenCalledTimes(1);
expect(onMarker2).toHaveBeenCalledTimes(1);
});
expect(onMarker1).toHaveBeenCalledWith(expect.objectContaining(expectedMarker));
expect(onMarker2).toHaveBeenCalledWith(expect.objectContaining(expectedMarker));
const expectedMarkerRecord = { [marker.id]: expectedMarker };
expect(cloneDeep(client1.markers)).toMatchObject(expectedMarkerRecord);
expect(cloneDeep(client2.markers)).toMatchObject(expectedMarkerRecord);
const client3 = await openClient(padData.id, SocketVersion.V2);
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client3.markers)).toMatchObject(expectedMarkerRecord);
const client4 = await openClient(padData.id, SocketVersion.V3);
await client4.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client4.markers)).toMatchObject({ [marker.id]: { icon: "test" } });
});
});
test("Edit marker (socket v2)", async () => {
// client1: Creates the marker and has it in its bbox
// client2: Has the marker in its bbox
// client3: Has the marker in its bbox (Socket v3)
const client1 = await openClient(undefined, SocketVersion.V2);
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id, SocketVersion.V2);
const client3 = await openClient(padData.id, SocketVersion.V3);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
const createdMarker = await client1.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null
});
await client1.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client2.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
const onMarker1 = vi.fn();
client1.on("marker", onMarker1);
const onMarker2 = vi.fn();
client2.on("marker", onMarker2);
const onMarker3 = vi.fn();
client3.on("marker", onMarker3);
const newData = {
id: createdMarker.id,
symbol: "icon"
};
const marker = await client1.editMarker(newData);
expect(marker).toMatchObject({ symbol: "icon" });
await retry(() => {
expect(onMarker1).toHaveBeenCalledTimes(1);
expect(onMarker2).toHaveBeenCalledTimes(1);
expect(onMarker3).toHaveBeenCalledTimes(1);
});
expect(onMarker1).toHaveBeenCalledWith(expect.objectContaining({ symbol: "icon" }));
expect(onMarker2).toHaveBeenCalledWith(expect.objectContaining({ symbol: "icon" }));
expect(onMarker3).toHaveBeenCalledWith(expect.objectContaining({ icon: "icon" }));
});
});
test("Delete marker (socket v2)", async () => {
const client = await openClient(undefined, SocketVersion.V2);
await createTemporaryPad(client, {}, async (createPadData, padData) => {
const markerType = Object.values(client.types).find((t) => t.type === "marker")!;
const createdMarker = await client.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null,
symbol: "icon"
});
const deletedMarker = await client.deleteMarker({ id: createdMarker.id });
expect(deletedMarker).toEqual(createdMarker);
});
});
test("Get marker (socket v2)", async () => {
const client = await openClient(undefined, SocketVersion.V2);
await createTemporaryPad(client, {}, async (createPadData, padData) => {
const markerType = Object.values(client.types).find((t) => t.type === "marker")!;
const marker = await client.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null,
symbol: "icon"
});
expect(await client.getMarker({ id: marker.id })).toMatchObject({ symbol: "icon" });
});
});
test("Find marker", async () => {
const client = await openClient(undefined, SocketVersion.V2);
await createTemporaryPad(client, {}, async (createPadData, padData) => {
const markerType = Object.values(client.types).find((t) => t.type === "marker")!;
await client.addMarker({
name: "Marker test",
lat: 10,
lon: 10,
typeId: markerType.id,
symbol: "icon",
ele: null
});
expect(await client.findOnMap({ query: "Test" })).toMatchObject([{ symbol: "icon" }]);
});
});

Wyświetl plik

@ -0,0 +1,169 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, openClient, retry } from "../utils";
import { CRU, SocketVersion, type ID, type LegacyV2Type } from "facilmap-types";
import { cloneDeep } from "lodash-es";
test("Create type (socket v2)", async () => {
const client1 = await openClient(undefined, SocketVersion.V2);
await createTemporaryPad(client1, { createDefaultTypes: false }, async (createPadData, padData, result) => {
const client2 = await openClient(padData.id, SocketVersion.V2);
const onType1 = vi.fn();
client1.on("type", onType1);
const onType2 = vi.fn();
client2.on("type", onType2);
const type = {
name: "Test type",
type: "marker",
defaultSymbol: "icon",
symbolFixed: true,
fields: [{
type: "dropdown",
name: "Test",
controlSymbol: true,
options: [{
value: "Test",
symbol: "test"
}]
}]
} satisfies LegacyV2Type<CRU.CREATE>;
const typeResult = await client1.addType(type);
expect(typeResult).toMatchObject(type);
await retry(async () => {
expect(onType1).toBeCalledTimes(1);
expect(onType2).toBeCalledTimes(1);
});
expect(onType1).toHaveBeenNthCalledWith(1, expect.objectContaining(type));
expect(cloneDeep(client1.types)).toEqual({
[typeResult.id]: expect.objectContaining(type)
});
expect(onType2).toHaveBeenNthCalledWith(1, expect.objectContaining(type));
expect(cloneDeep(client2.types)).toEqual({
[typeResult.id]: expect.objectContaining(type)
});
const client3 = await openClient(padData.id, SocketVersion.V2);
expect(cloneDeep(client3.types)).toEqual({
[typeResult.id]: expect.objectContaining(type)
});
const client4 = await openClient(padData.id);
expect(cloneDeep(client4.types)).toEqual({
[typeResult.id]: expect.objectContaining({
defaultIcon: "icon",
iconFixed: true,
fields: [expect.objectContaining({
controlIcon: true,
options: [expect.objectContaining({
value: "Test",
icon: "test"
})]
})]
})
});
});
});
test("Update type (socket v2)", async () => {
const client1 = await openClient(undefined, SocketVersion.V2);
const onType = vi.fn();
client1.on("type", onType);
await createTemporaryPad(client1, { createDefaultTypes: false }, async (createPadData, padData, result) => {
const createdType = await client1.addType({
name: "Test type",
type: "marker"
});
const client2 = await openClient(padData.id, SocketVersion.V2);
const onType1 = vi.fn();
client1.on("type", onType1);
const onType2 = vi.fn();
client2.on("type", onType2);
const update = {
id: createdType.id,
defaultSymbol: "icon",
symbolFixed: true,
fields: [{
type: "dropdown",
name: "Test",
controlSymbol: true,
options: [{
value: "Test",
symbol: "test"
}]
}]
} satisfies LegacyV2Type<CRU.UPDATE> & { id: ID };
const typeResult = await client1.editType(update);
expect(typeResult).toMatchObject(update);
await retry(async () => {
expect(onType1).toBeCalledTimes(1);
expect(onType2).toBeCalledTimes(1);
});
expect(onType1).toHaveBeenNthCalledWith(1, expect.objectContaining(update));
expect(cloneDeep(client1.types)).toEqual({
[createdType.id]: expect.objectContaining(update)
});
expect(onType2).toHaveBeenNthCalledWith(1, expect.objectContaining(update));
expect(cloneDeep(client2.types)).toEqual({
[createdType.id]: expect.objectContaining(update)
});
const client3 = await openClient(padData.id);
expect(cloneDeep(client3.types)).toEqual({
[createdType.id]: expect.objectContaining({
defaultIcon: "icon",
iconFixed: true,
fields: [expect.objectContaining({
controlIcon: true,
options: [expect.objectContaining({
icon: "test"
})]
})]
})
});
});
});
test("Delete type (socket v2)", async () => {
const client = await openClient(undefined, SocketVersion.V2);
await createTemporaryPad(client, { createDefaultTypes: false }, async (createPadData, padData, result) => {
const type = await client.addType({
name: "Test type",
type: "marker",
defaultSymbol: "icon",
symbolFixed: true,
fields: [{
type: "dropdown",
name: "Test",
controlSymbol: true,
options: [{
value: "Test",
symbol: "test"
}]
}]
});
const deletedType = await client.deleteType({ id: type.id });
expect(deletedType).toEqual(type);
});
});

Wyświetl plik

@ -113,10 +113,6 @@ test("Edit marker", async () => {
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
await client1.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client2.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client3.updateBbox({ top: 5, bottom: 0, left: 0, right: 5, zoom: 1 });
const createdMarker = await client1.addMarker({
lat: 10,
lon: 10,
@ -129,6 +125,10 @@ test("Edit marker", async () => {
name: "Second type"
});
await client1.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client2.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client3.updateBbox({ top: 5, bottom: 0, left: 0, right: 5, zoom: 1 });
const onMarker1 = vi.fn();
client1.on("marker", onMarker1);
const onMarker2 = vi.fn();

Wyświetl plik

@ -1,4 +1,4 @@
import { SocketVersion, type SocketEvents, type MultipleEvents, type FindOnMapResult, type SocketServerToClientEmitArgs, legacyV2MarkerToCurrent, currentMarkerToLegacyV2, currentTypeToLegacyV2, legacyV2TypeToCurrent } from "facilmap-types";
import { SocketVersion, type SocketEvents, type MultipleEvents, type FindOnMapResult, type SocketServerToClientEmitArgs, legacyV2MarkerToCurrent, currentMarkerToLegacyV2, currentTypeToLegacyV2, legacyV2TypeToCurrent, mapHistoryEntry } from "facilmap-types";
import { mapMultipleEvents, type SocketConnection, type SocketHandlers } from "./socket-common";
import { SocketConnectionV3 } from "./socket-v3";
import type Database from "../database/database";
@ -8,6 +8,20 @@ function prepareEvent(...args: SocketServerToClientEmitArgs<SocketVersion.V3>):
return [[args[0], currentMarkerToLegacyV2(args[1])]];
} else if (args[0] === "type") {
return [[args[0], currentTypeToLegacyV2(args[1])]];
} else if (args[0] === "history") {
if (args[1].type === "Marker") {
return [[
args[0],
mapHistoryEntry(args[1], (obj) => obj && currentMarkerToLegacyV2(obj))
]];
} else if (args[1].type === "Type") {
return [[
args[0],
mapHistoryEntry(args[1], (obj) => obj && currentTypeToLegacyV2(obj))
]];
} else {
return [[args[0], args[1]]];
}
} else {
return [args];
}

Wyświetl plik

@ -5,47 +5,43 @@ import type { View } from "./view.js";
import type { PadData } from "./padData.js";
import type { Type } from "./type.js";
export type HistoryEntryType = "Marker" | "Line" | "View" | "Type" | "Pad";
export type HistoryEntryAction = "create" | "update" | "delete";
export type HistoryEntryObject<T extends HistoryEntryType> =
T extends "Marker" ? Omit<Marker, 'id'> :
T extends "Line" ? Omit<Line, 'id'> :
T extends "View" ? Omit<View, 'id'> :
T extends "Type" ? Omit<Type, 'id'> :
PadData;
export type HistoryEntryObjectTypes = {
"Marker": Omit<Marker, "id">;
"Line": Omit<Line, "id">;
"View": Omit<View, "id">;
"Type": Omit<Type, "id">;
"Pad": PadData;
};
type HistoryEntryBase<T extends HistoryEntryType, A extends HistoryEntryAction> = {
id: ID;
time: string;
type: T;
action: A;
padId: PadId;
} & (A extends "create" ? {
objectBefore?: undefined;
} : {
objectBefore: HistoryEntryObject<T>;
}) & (A extends "delete" ? {
objectAfter?: undefined;
} : {
objectAfter: HistoryEntryObject<T>;
}) & (T extends "Pad" ? {
objectId?: undefined;
} : {
objectId: ID;
});
export type HistoryEntryType = keyof HistoryEntryObjectTypes;
export type HistoryEntryObject<T extends HistoryEntryType> = HistoryEntryObjectTypes[T];
type HistoryEntryBase2<T extends HistoryEntryType> =
HistoryEntryBase<T, "create">
| HistoryEntryBase<T, "update">
| HistoryEntryBase<T, "delete">
export type GenericHistoryEntry<ObjectTypes extends Record<HistoryEntryType, any>> = {
[Type in HistoryEntryType]: {
[Action in HistoryEntryAction]: {
id: ID;
time: string;
type: Type;
action: Action;
padId: PadId;
} & (Action extends "create" ? {
objectBefore?: undefined;
} : {
objectBefore: ObjectTypes[Type];
}) & (Action extends "delete" ? {
objectAfter?: undefined;
} : {
objectAfter: ObjectTypes[Type];
}) & (Type extends "Pad" ? {
objectId?: undefined;
} : {
objectId: ID;
});
}[HistoryEntryAction]
}[HistoryEntryType];
export type HistoryEntry =
HistoryEntryBase2<"Marker">
| HistoryEntryBase2<"Line">
| HistoryEntryBase2<"View">
| HistoryEntryBase2<"Type">
| HistoryEntryBase2<"Pad">;
export type HistoryEntry = GenericHistoryEntry<HistoryEntryObjectTypes>;
export type HistoryEntryCreate = Omit<HistoryEntry, "id" | "time" | "padId">;

Wyświetl plik

@ -8,6 +8,7 @@ export * from "./marker.js";
export * from "./padData.js";
export * from "./route.js";
export * from "./searchResult.js";
export * from "./socket/socket-common.js";
export * from "./socket/socket-v1.js";
export * from "./socket/socket-v2.js";
export * from "./socket/socket-v3.js";

Wyświetl plik

@ -77,7 +77,7 @@ export const setLanguageRequestValidator = z.object({
});
export type SetLanguageRequest = z.infer<typeof setLanguageRequestValidator>;
export type ReplaceProperty<T extends Record<keyof any, any>, Key extends keyof T, Value> = Omit<T, Key> & Record<Key, Value>;
export type ReplaceProperty<T extends Record<keyof any, any>, Key extends keyof any, Value> = T extends Record<Key, any> ? (Omit<T, Key> & Record<Key, Value>) : T;
export type RenameProperty<T, From extends keyof any, To extends keyof any, KeepOld extends boolean = false> = T extends Record<From, any> ? (KeepOld extends true ? From : Omit<T, From>) & Record<To, T[From]> : T;
@ -92,4 +92,13 @@ export function renameProperty<T, From extends keyof any, To extends keyof any,
} else {
return obj as any;
}
}
type ReplacePropertyIfNotUndefined<T extends Record<keyof any, any>, Key extends keyof any, Value> = T[Key] extends undefined ? T : ReplaceProperty<T, Key, Value>;
export function mapHistoryEntry<Obj extends { objectBefore?: any; objectAfter?: any }, Out>(entry: Obj, mapper: (obj: (Obj extends { objectBefore: {} } ? Obj["objectBefore"] : never) | (Obj extends { objectAfter: {} } ? Obj["objectAfter"] : never)) => Out): ReplacePropertyIfNotUndefined<ReplacePropertyIfNotUndefined<Obj, "objectBefore", Out>, "objectAfter", Out> {
return {
...entry,
..."objectBefore" in entry && entry.objectBefore !== undefined ? { objectBefore: mapper(entry.objectBefore) } : {},
..."objectAfter" in entry && entry.objectAfter !== undefined ? { objectAfter: mapper(entry.objectAfter) } : {}
} as any;
}

Wyświetl plik

@ -1,11 +1,12 @@
import { idValidator, type ReplaceProperties } from "../base.js";
import { markerValidator } from "../marker.js";
import { refineRawTypeValidator, rawTypeValidator, fieldOptionValidator, refineRawFieldOptionsValidator, fieldValidator, refineRawFieldsValidator } from "../type.js";
import { refineRawTypeValidator, rawTypeValidator, fieldOptionValidator, refineRawFieldOptionsValidator, fieldValidator, refineRawFieldsValidator, defaultFields } from "../type.js";
import type { MultipleEvents } from "../events.js";
import { renameProperty, type FindOnMapMarker, type FindOnMapResult, type RenameProperty, type ReplaceProperty } from "./socket-common.js";
import { requestDataValidatorsV3, type MapEventsV3, type ResponseDataMapV3 } from "./socket-v3.js";
import type { CRU, CRUType } from "../cru.js";
import * as z from "zod";
import type { GenericHistoryEntry, HistoryEntryObjectTypes } from "../historyEntry.js";
// Socket v2:
// - “icon” is called “symbol” in `Marker.symbol`, `Type.defaultSymbol`, `Type.symbolFixed`, `Type.fields[].controlSymbol` and
@ -48,7 +49,7 @@ export const legacyV2TypeValidator = refineRawTypeValidator({
create: rawTypeValidator.create.omit({ defaultIcon: true, iconFixed: true }).extend({
defaultSymbol: rawTypeValidator.create.shape.defaultIcon,
symbolFixed: rawTypeValidator.create.shape.iconFixed,
fields: legacyV2FieldsValidator.create
fields: legacyV2FieldsValidator.create.default(defaultFields)
}),
update: rawTypeValidator.update.omit({ defaultIcon: true, iconFixed: true }).extend({
defaultSymbol: rawTypeValidator.update.shape.defaultIcon,
@ -61,6 +62,11 @@ export type LegacyV2Type<Mode extends CRU = CRU.READ> = CRUType<Mode, typeof leg
export type LegacyV2FindOnMapMarker = RenameProperty<FindOnMapMarker, "icon", "symbol", false>;
export type LegacyV2FindOnMapResult = LegacyV2FindOnMapMarker | Exclude<FindOnMapResult, FindOnMapMarker>;
export type LegacyV2HistoryEntry = GenericHistoryEntry<ReplaceProperties<HistoryEntryObjectTypes, {
Marker: Omit<LegacyV2Marker, "id">;
Type: Omit<LegacyV2Type, "id">;
}>>;
export const requestDataValidatorsV2 = {
...requestDataValidatorsV3,
addMarker: legacyV2MarkerValidator.create,
@ -88,6 +94,7 @@ export type ResponseDataMapV2 = ReplaceProperties<ResponseDataMapV3, {
export type MapEventsV2 = ReplaceProperties<MapEventsV3, {
marker: [LegacyV2Marker];
type: [LegacyV2Type];
history: [LegacyV2HistoryEntry];
}>;
export function legacyV2MarkerToCurrent<M extends Record<keyof any, any>, KeepOld extends boolean = false>(marker: M, keepOld?: KeepOld): RenameProperty<M, "symbol", "icon", KeepOld> {

Wyświetl plik

@ -121,6 +121,8 @@ export const fieldsValidator = refineRawFieldsValidator({
update: z.array(fieldValidator.update)
});
export const defaultFields = (): Field[] => [ { name: "Description", type: "textarea" as const } ];
/** The type validator without the defaultColour default value applied. */
export const rawTypeValidator = cruValidator({
id: onlyRead(idValidator),
@ -148,7 +150,7 @@ export const rawTypeValidator = cruValidator({
fields: {
read: fieldsValidator.read,
create: fieldsValidator.create.default(() => [ { name: "Description", type: "textarea" as const } ]),
create: fieldsValidator.create.default(defaultFields),
update: fieldsValidator.update.optional()
}
});