latest changes

main
Mari 3 years ago
parent c6d3032be8
commit f8f5fc4e7c
  1. 3
      .gitignore
  2. 13
      src/datatypes/Condition.ts
  3. 55
      src/datatypes/Entry.ts
  4. 29
      src/datatypes/GuidedEmpathy.ts
  5. 13
      src/datatypes/Suicidality.ts
  6. 236
      src/schemata/Serialization.ts

3
.gitignore vendored

@ -38,4 +38,5 @@ Thumbs.db
dist/**/*
# ignore yarn.lock
yarn.lock
yarn.lock
/release

@ -1,4 +1,4 @@
import {schema} from "../schemata/SchemaData.js";
import {EnumSerializer, OptionalSerializer} from "../schemata/Serialization.js";
export enum Condition {
CRITICAL = "Critical",
@ -11,12 +11,5 @@ export enum Condition {
}
export const CONDITIONS = [Condition.CRITICAL, Condition.POOR, Condition.SOSO, Condition.GOOD, Condition.EXCELLENT, Condition.UNSURE, Condition.NUMB]
export type ConditionJTD = typeof ConditionJTD
export const ConditionJTD = schema({
schema: {
enum: CONDITIONS
},
key: "condition",
references: [],
})
export const ConditionSerializer = new EnumSerializer<Condition>(CONDITIONS)
export const OptionalConditionSerializer = new OptionalSerializer(ConditionSerializer)

@ -1,12 +1,11 @@
import {Condition, ConditionJTD} from "./Condition.js";
import {GuidedEmpathy, GuidedEmpathyJTD} from "./GuidedEmpathy.js";
import {Suicidality, SuicidalityJTD} from "./Suicidality.js";
import {schema} from "../schemata/SchemaData.js";
import {SleepRecord, SleepRecordListJTD} from "./SleepRecord.js";
import {Condition, OptionalConditionSerializer} from "./Condition.js";
import {GuidedEmpathy} from "./GuidedEmpathy.js";
import {OptionalSuicidalitySerializer, Suicidality} from "./Suicidality.js";
import {SleepRecord} from "./SleepRecord.js";
import {DateTime} from "luxon";
import {DateTimeSerializer, ObjectSerializer, OptionalStringSerializer} from "../schemata/Serialization.js";
export interface Entry {
readonly startedAt: Date
readonly finishedAt: Date
export interface BaseEntry {
readonly condition?: Condition
readonly summary?: string
readonly journalEntry?: string
@ -20,24 +19,24 @@ export interface Entry {
readonly suicidality?: Suicidality
// readonly recoveries?: readonly Recovery[]
}
export type EntryJTD = typeof EntryJTD
export const EntryJTD = schema({
schema: {
properties: {
startedAt: { type: "timestamp" },
finishedAt: { type: "timestamp" },
},
optionalProperties: {
condition: ConditionJTD.reference,
summary: { type: "string" },
journalEntry: { type: "string" },
guidedEmpathy: { elements: GuidedEmpathyJTD.reference },
suicidality: SuicidalityJTD.reference,
sleepRecords: SleepRecordListJTD.reference,
}
},
typeHint: null as Entry|null,
key: "entry",
references: [ConditionJTD, GuidedEmpathyJTD, SuicidalityJTD, SleepRecordListJTD, ...SleepRecordListJTD.requiredReferences, ...GuidedEmpathyJTD.requiredReferences]
})
export interface Entry extends BaseEntry {
readonly startedAt: DateTime
readonly finishedAt: DateTime
}
export interface SerializedEntry extends BaseEntry {
readonly startedAt: string
readonly finishedAt: string
}
export const EntrySerializer = new ObjectSerializer<Entry, SerializedEntry, keyof Entry>({
startedAt: DateTimeSerializer,
finishedAt: DateTimeSerializer,
condition: OptionalConditionSerializer,
summary: OptionalStringSerializer,
journalEntry: OptionalStringSerializer,
guidedEmpathy: OptionalArrayGuidedEmpathySerializer,
sleepRecords: OptionalArraySleepRecordSerializer,
suicidality: OptionalSuicidalitySerializer,
}, ["startedAt", "finishedAt", "condition", "summary", "journalEntry", "guidedEmpathy", "sleepRecords", "suicidality"])

@ -8,8 +8,13 @@ import {
personaVerb
} from "./Persona.js";
import chalk from "chalk";
import {schema} from "../schemata/SchemaData.js";
import capitalize from "capitalize";
import {
ArraySerializer,
ObjectSerializer,
OptionalArrayStringSerializer,
OptionalSerializer
} from "../schemata/Serialization.js";
export interface GuidedEmpathy {
readonly feelings?: readonly string[]
@ -17,20 +22,14 @@ export interface GuidedEmpathy {
readonly events?: readonly string[]
readonly requests?: readonly string[]
}
export type GuidedEmpathyJTD = typeof GuidedEmpathyJTD
export const GuidedEmpathyJTD = schema({
schema: {
optionalProperties: {
feelings: { elements: { type: "string" } },
needs: { elements: { type: "string" } },
events: { elements: { type: "string" } },
requests: { elements: { type: "string" } },
}
},
typeHint: null as GuidedEmpathy|null,
key: "guidedEmpathy",
references: [],
})
export const GuidedEmpathySerializer = new ObjectSerializer<GuidedEmpathy, GuidedEmpathy, keyof GuidedEmpathy>({
feelings: OptionalArrayStringSerializer,
needs: OptionalArrayStringSerializer,
events: OptionalArrayStringSerializer,
requests: OptionalArrayStringSerializer,
}, ["feelings", "needs", "events", "requests"])
export const ArrayGuidedEmpathySerializer = new ArraySerializer(GuidedEmpathySerializer)
export const OptionalArrayGuidedEmpathySerializer = new OptionalSerializer(ArrayGuidedEmpathySerializer)
export function isPopulatedGuidedEmpathy(empathy: GuidedEmpathy | undefined): boolean {
return !!empathy && (isPopulatedArray(empathy.feelings)

@ -1,4 +1,6 @@
import {schema} from "../schemata/SchemaData.js";
import {EnumSerializer, OptionalSerializer} from "../schemata/Serialization.js";
import {Condition, CONDITIONS} from "./Condition.js";
export enum Suicidality {
NONE = "None",
@ -12,12 +14,5 @@ export enum Suicidality {
}
export const SUICIDALITIES: Suicidality[] = [Suicidality.NONE, Suicidality.PASSIVE, Suicidality.INTRUSIVE, Suicidality.ACTIVE, Suicidality.RESIGNED, Suicidality.PLANNING, Suicidality.PLANNED, Suicidality.DANGER]
export type SuicidalityJTD = typeof SuicidalityJTD
export const SuicidalityJTD = schema({
schema: {
enum: SUICIDALITIES
},
key: "suicidality",
references: [],
})
export const SuicidalitySerializer = new EnumSerializer<Suicidality>(SUICIDALITIES)
export const OptionalSuicidalitySerializer = new OptionalSerializer(SuicidalitySerializer)

@ -0,0 +1,236 @@
import {DateTime} from "luxon";
export interface Serializer<DeserializedT, SerializedT> {
checkType(data: unknown): data is SerializedT
hydrate(data: SerializedT): DeserializedT
dehydrate(data: DeserializedT): SerializedT
}
export interface LocatedError {
readonly location: string
readonly error: Error|LocatedError
}
export function isLocatedError(object: unknown): object is LocatedError {
return typeof object === "object" && object !== null && object.hasOwnProperty("location") && object.hasOwnProperty("error")
}
export function wrapErrorWithLocation(location: string, object: unknown): LocatedError {
if (typeof object === "string") {
return {
location,
error: Error(object)
}
} else if (object instanceof Error || isLocatedError(object)) {
return {
location,
error: object
}
} else {
return {
location,
error: Error(`Unknown error: ${object}`)
}
}
}
export class SelfSerializer<T> implements Serializer<T, T> {
readonly checkType: (data: unknown) => data is T;
constructor(checkType: (data: unknown) => data is T) {
this.checkType = checkType
}
dehydrate(data: T): T {
return data;
}
hydrate(data: T): T {
return data;
}
}
export const StringSerializer = new SelfSerializer((data): data is string => typeof data === "string")
export const DateTimeSerializer: Serializer<DateTime, string> = {
checkType(data: unknown): data is string {
return typeof data === "string"
},
dehydrate(data: DateTime): string {
return data.toISO();
},
hydrate(data: string): DateTime {
return DateTime.fromISO(data);
}
}
export class EnumSerializer<T extends string|number> implements Serializer<T, T> {
readonly values: readonly T[]
constructor(values: readonly T[]) {
this.values = values
}
checkType(data: unknown): data is T {
if (!(this.values as unknown[]).includes(data)) {
throw Error("not a valid value for the enum")
}
return true
}
dehydrate(data: T): T {
return data;
}
hydrate(data: T): T {
return data;
}
}
export class NullableSerializer<DeserializedT, SerializedT> implements Serializer<DeserializedT | null, SerializedT | null> {
readonly inner: Serializer<DeserializedT, SerializedT>
constructor(inner: Serializer<DeserializedT, SerializedT>) {
this.inner = inner
}
checkType(data: unknown): data is SerializedT | null {
return data === null || this.inner.checkType(data)
}
dehydrate(data: DeserializedT | null): SerializedT | null {
if (data === null) {
return null
}
return this.inner.dehydrate(data);
}
hydrate(data: SerializedT | null): DeserializedT | null {
if (data === null) {
return null
}
return this.inner.hydrate(data);
}
}
export class OptionalSerializer<DeserializedT, SerializedT> implements Serializer<DeserializedT | undefined, SerializedT | undefined> {
readonly inner: Serializer<DeserializedT, SerializedT>
constructor(inner: Serializer<DeserializedT, SerializedT>) {
this.inner = inner
}
checkType(data: unknown): data is SerializedT | undefined {
return typeof data === "undefined" || this.inner.checkType(data)
}
dehydrate(data: DeserializedT | undefined): SerializedT | undefined {
if (typeof data === "undefined") {
return data
}
return this.inner.dehydrate(data);
}
hydrate(data: SerializedT | undefined): DeserializedT | undefined {
if (typeof data === "undefined") {
return data
}
return this.inner.hydrate(data);
}
}
export const OptionalStringSerializer = new OptionalSerializer(StringSerializer)
export const OptionalDateTimeSerializer = new OptionalSerializer(DateTimeSerializer)
export type KeySerializers<DeserializedT extends {[key in KeysT]?: unknown}, SerializedT extends {[key in KeysT]?: unknown}, KeysT extends string|number> = {
[key in KeysT]: Serializer<DeserializedT[key], SerializedT[key]>
}
export class ObjectSerializer<DeserializedT extends {[key in KeysT]?: unknown}, SerializedT extends {[key in KeysT]?: unknown}, KeysT extends string|number> implements Serializer<DeserializedT, SerializedT> {
readonly inner: KeySerializers<DeserializedT, SerializedT, KeysT>
readonly keys: KeysT[]
constructor(inner: KeySerializers<DeserializedT, SerializedT, KeysT>, keys: KeysT[]) {
this.inner = inner
this.keys = keys
}
checkType(data: unknown): data is SerializedT {
if (typeof data !== "object" || data === null) {
throw Error("expected an object")
}
const map: {[key in KeysT]?: unknown} = data
for (const key of this.keys) {
try {
if (!this.inner[key].checkType(map[key])) {
// noinspection ExceptionCaughtLocallyJS
throw Error("checkType returned false")
}
} catch (e) {
throw wrapErrorWithLocation(`at key ${key}`, e)
}
}
return true
}
dehydrate(data: DeserializedT): SerializedT {
const result: Partial<SerializedT> = {}
for (const key of this.keys) {
try {
result[key] = this.inner[key].dehydrate(data[key])
} catch (e) {
throw wrapErrorWithLocation(`at key ${key}`, e)
}
}
return result as SerializedT
}
hydrate(data: SerializedT): DeserializedT {
const result: Partial<DeserializedT> = {}
for (const key of this.keys) {
try {
result[key] = this.inner[key].hydrate(data[key])
} catch (e) {
throw wrapErrorWithLocation(`at key ${key}`, e)
}
}
return result as DeserializedT
}
}
export class ArraySerializer<DeserializedT, SerializedT> implements Serializer<DeserializedT[], SerializedT[]> {
readonly inner: Serializer<DeserializedT, SerializedT>
constructor(inner: Serializer<DeserializedT, SerializedT>) {
this.inner = inner
}
checkType(data: unknown): data is SerializedT[] {
return Array.isArray(data) && data.every((value, index) => {
try {
if (!this.inner.checkType(value)) {
// noinspection ExceptionCaughtLocallyJS
throw Error("checkType returned false")
}
} catch (e) {
throw wrapErrorWithLocation(`at index ${index}`, e)
}
})
}
dehydrate(data: DeserializedT[]): SerializedT[] {
return data.map((value, index) => {
try {
return this.inner.dehydrate(value)
} catch (e) {
throw wrapErrorWithLocation(`at index ${index}`, e)
}
});
}
hydrate(data: SerializedT[]): DeserializedT[] {
return data.map((value, index) => {
try {
return this.inner.hydrate(value)
} catch (e) {
throw wrapErrorWithLocation(`at index ${index}`, e)
}
});
}
}
export const ArrayStringSerializer = new ArraySerializer(StringSerializer)
export const OptionalArrayStringSerializer = new OptionalSerializer(ArrayStringSerializer)
Loading…
Cancel
Save