92 lines
2.5 KiB
TypeScript
92 lines
2.5 KiB
TypeScript
export type ProjectCommandJsonValue =
|
|
| null
|
|
| boolean
|
|
| number
|
|
| string
|
|
| ProjectCommandJsonValue[]
|
|
| { [key: string]: ProjectCommandJsonValue };
|
|
|
|
export interface SerializedProjectCommand<TPayload = ProjectCommandJsonValue> {
|
|
schemaVersion: number;
|
|
type: string;
|
|
payload: TPayload;
|
|
}
|
|
|
|
export function serializeProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): string {
|
|
assertSerializedProjectCommand(command);
|
|
return JSON.stringify(command);
|
|
}
|
|
|
|
export function deserializeProjectCommand(
|
|
serialized: string
|
|
): SerializedProjectCommand<ProjectCommandJsonValue> {
|
|
const parsed: unknown = JSON.parse(serialized);
|
|
assertSerializedProjectCommand(parsed);
|
|
return parsed;
|
|
}
|
|
|
|
export function assertSerializedProjectCommand(
|
|
value: unknown
|
|
): asserts value is SerializedProjectCommand {
|
|
if (!isPlainObject(value)) {
|
|
throw new Error("Project command must be an object.");
|
|
}
|
|
if (
|
|
!Number.isSafeInteger(value.schemaVersion) ||
|
|
(value.schemaVersion as number) < 1
|
|
) {
|
|
throw new Error("Project command schema version must be a positive integer.");
|
|
}
|
|
if (typeof value.type !== "string" || !value.type.trim()) {
|
|
throw new Error("Project command type is required.");
|
|
}
|
|
assertJsonValue(value.payload, new WeakSet<object>());
|
|
}
|
|
|
|
function assertJsonValue(value: unknown, parents: WeakSet<object>): void {
|
|
if (
|
|
value === null ||
|
|
typeof value === "string" ||
|
|
typeof value === "boolean"
|
|
) {
|
|
return;
|
|
}
|
|
if (typeof value === "number") {
|
|
if (!Number.isFinite(value)) {
|
|
throw new Error("Project command numbers must be finite.");
|
|
}
|
|
return;
|
|
}
|
|
if (typeof value !== "object") {
|
|
throw new Error("Project command payload must contain JSON values only.");
|
|
}
|
|
if (parents.has(value)) {
|
|
throw new Error("Project command payload must not contain cycles.");
|
|
}
|
|
|
|
parents.add(value);
|
|
if (Array.isArray(value)) {
|
|
for (const entry of value) {
|
|
assertJsonValue(entry, parents);
|
|
}
|
|
} else {
|
|
if (!isPlainObject(value)) {
|
|
throw new Error("Project command payload must contain plain objects only.");
|
|
}
|
|
for (const entry of Object.values(value)) {
|
|
assertJsonValue(entry, parents);
|
|
}
|
|
}
|
|
parents.delete(value);
|
|
}
|
|
|
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
if (value === null || typeof value !== "object") {
|
|
return false;
|
|
}
|
|
const prototype = Object.getPrototypeOf(value);
|
|
return prototype === Object.prototype || prototype === null;
|
|
}
|