diff --git a/src/convenient.ts b/src/convenient.ts new file mode 100644 index 0000000..11c7fef --- /dev/null +++ b/src/convenient.ts @@ -0,0 +1,61 @@ +import { + type IndexedKeysMessageSchema, type Invalid, isSchemaLeaf, type SchemaLeaf, + type ValidIndexedKeysMessageSchema, + type ValidSchemaLeaf +} from "./indexedKeysSchema"; +import { get, set } from "./raw"; + +type PlainObjectOfSchema = TSchema extends ValidIndexedKeysMessageSchema ? { + [K in keyof TSchema]: TSchema[K] extends ValidSchemaLeaf + ? TField + : TSchema[K] extends SchemaLeaf + ? Invalid<"Schema needs to be validated before you use it!"> + : PlainObjectOfSchema; + } + : never; + +export function toPlainObject, TSchemaInner>( + message: readonly unknown[], + schema: TSchema): PlainObjectOfSchema { + + const object: Partial> = {}; + + for (const [fieldName, nestedSchemaNode] of Object.entries(schema)) { + const nestedNode = nestedSchemaNode as ValidIndexedKeysMessageSchema | ValidSchemaLeaf; + let valueToSet = undefined; + if (isSchemaLeaf(nestedNode)) { + valueToSet = get(message, nestedNode); + } else { + valueToSet = toPlainObject(message, nestedNode); + } + + object[fieldName as keyof PlainObjectOfSchema] = valueToSet as any; + } + + return object as PlainObjectOfSchema; +} + +export function toIndexedKeysMessage, TSchemaInner>( + plainObject: PlainObjectOfSchema, + schema: TSchema): unknown[] { + + const message: unknown[] = []; + populateIndexedKeysMessage(message, plainObject, schema); + return message; +} + +function populateIndexedKeysMessage, TSchemaInner>( + messageToPopulate: unknown[], + plainObject: PlainObjectOfSchema, + schema: TSchema) { + + for (const [fieldName, nestedSchemaNode] of Object.entries(schema)) { + const nestedNode = nestedSchemaNode as IndexedKeysMessageSchema | ValidSchemaLeaf; + const leafValueOrSubObject = plainObject[fieldName as keyof PlainObjectOfSchema]; + if (isSchemaLeaf(nestedNode)) { + set(messageToPopulate, nestedNode, leafValueOrSubObject); + } else { + populateIndexedKeysMessage(messageToPopulate, leafValueOrSubObject as PlainObjectOfSchema, nestedNode) + } + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index efb9d4d..fbc6244 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,5 @@ -export { validateSchema, withIndex, get, set, toPlainObject, toIndexedKeysMessage, InvalidSchemaError } from './indexedKeysSchema' +export { validateSchema, withIndex, InvalidSchemaError } from './indexedKeysSchema' +export { get, set } from "./raw"; +export { toPlainObject, toIndexedKeysMessage } from "./convenient"; + export type { ValidIndexedKeysMessageSchema } from './indexedKeysSchema' \ No newline at end of file diff --git a/src/indexedKeysSchema.ts b/src/indexedKeysSchema.ts index c98bd2f..64cf55f 100644 --- a/src/indexedKeysSchema.ts +++ b/src/indexedKeysSchema.ts @@ -4,13 +4,13 @@ const isSchemaLeafTag = Symbol("isSchemaLeaf"); const isValidSchemaLeaf = Symbol("isValidSchemaLeaf"); type IndexesPath = number[]; -type SchemaLeaf = { +export type SchemaLeaf = { indexesPathReversed: IndexesPath, fieldType: TField, [isSchemaLeafTag]: true }; -type ValidSchemaLeaf = SchemaLeaf & { [isValidSchemaLeaf]: true }; +export type ValidSchemaLeaf = SchemaLeaf & { [isValidSchemaLeaf]: true }; export type IndexedKeysMessageSchema = { [K in keyof TSchema]: TSchema[K] extends SchemaLeaf @@ -25,7 +25,7 @@ type ToValidIndexedKeysMessageSchema = { }; const invalid = Symbol('invalid') -type Invalid = { [invalid]: T } +export type Invalid = { [invalid]: T } export type ValidIndexedKeysMessageSchema = { [K in keyof TSchema]: TSchema[K] extends ValidSchemaLeaf @@ -103,8 +103,8 @@ export function withIndex(index: NonNegativeInteger } // intentionally not validating that it has the "isValidSchemaLeaf" symbol property, because it actually doesn't - it's just a type trick -function isSchemaLeaf(value: IndexedKeysMessageSchema | ValidSchemaLeaf): value is ValidSchemaLeaf; -function isSchemaLeaf(value: IndexedKeysMessageSchema | SchemaLeaf): value is SchemaLeaf { +export function isSchemaLeaf(value: IndexedKeysMessageSchema | ValidSchemaLeaf): value is ValidSchemaLeaf; +export function isSchemaLeaf(value: IndexedKeysMessageSchema | SchemaLeaf): value is SchemaLeaf { return Object.hasOwn(value, isSchemaLeafTag); } @@ -122,85 +122,3 @@ function addIndexToPathsRecursively( } } -export function get(message: readonly unknown[], schemaField: ValidSchemaLeaf) { - const indexesPathReversed = schemaField.indexesPathReversed; - let currentSlice: readonly unknown[] = message; - - for (let pathIndex = schemaField.indexesPathReversed.length - 1; pathIndex >= 1; pathIndex--) { - currentSlice = currentSlice[indexesPathReversed[pathIndex]] as readonly unknown[]; - } - - const lastIndexInPath = indexesPathReversed[0]; - return currentSlice[lastIndexInPath] as TField; -} - -export function set(targetMessage: unknown[], schemaField: ValidSchemaLeaf, value: TField) { - const indexesPathReversed = schemaField.indexesPathReversed; - let currentSlice: unknown[] = targetMessage; - - for (let pathIndex = schemaField.indexesPathReversed.length - 1; pathIndex >= 1; pathIndex--) { - if (currentSlice[indexesPathReversed[pathIndex]] === undefined) { - currentSlice[indexesPathReversed[pathIndex]] = []; - } - - currentSlice = currentSlice[indexesPathReversed[pathIndex]] as unknown[]; - } - - const lastIndexInPath = indexesPathReversed[0]; - currentSlice[lastIndexInPath] = value; -} - -type PlainObjectOfSchema = TSchema extends ValidIndexedKeysMessageSchema ? { - [K in keyof TSchema]: TSchema[K] extends ValidSchemaLeaf - ? TField - : TSchema[K] extends SchemaLeaf - ? Invalid<"Schema needs to be validated before you use it!"> - : PlainObjectOfSchema; - } - : never; - -export function toPlainObject, TSchemaInner>( - message: readonly unknown[], - schema: TSchema): PlainObjectOfSchema { - - const object: Partial> = {}; - - for (const [fieldName, nestedSchemaNode] of Object.entries(schema)) { - const nestedNode = nestedSchemaNode as ValidIndexedKeysMessageSchema | ValidSchemaLeaf; - let valueToSet = undefined; - if (isSchemaLeaf(nestedNode)) { - valueToSet = get(message, nestedNode); - } else { - valueToSet = toPlainObject(message, nestedNode); - } - - object[fieldName as keyof PlainObjectOfSchema] = valueToSet as any; - } - - return object as PlainObjectOfSchema; -} - -export function toIndexedKeysMessage, TSchemaInner>( - plainObject: PlainObjectOfSchema, - schema: TSchema): unknown[] { - - const message: unknown[] = []; - populateIndexedKeysMessage(message, plainObject, schema); - return message; -} - -function populateIndexedKeysMessage, TSchemaInner>( - messageToPopulate: unknown[], - plainObject: PlainObjectOfSchema, - schema: TSchema) { - - for (const [fieldName, nestedSchemaNode] of Object.entries(schema)) { - const nestedNode = nestedSchemaNode as IndexedKeysMessageSchema | ValidSchemaLeaf; - const leafValueOrSubObject = plainObject[fieldName as keyof PlainObjectOfSchema]; - if (isSchemaLeaf(nestedNode)) { - set(messageToPopulate, nestedNode, leafValueOrSubObject); - } else { - populateIndexedKeysMessage(messageToPopulate, leafValueOrSubObject as PlainObjectOfSchema, nestedNode) - } - } -} diff --git a/src/raw.ts b/src/raw.ts new file mode 100644 index 0000000..f44dea2 --- /dev/null +++ b/src/raw.ts @@ -0,0 +1,29 @@ +import type { ValidSchemaLeaf } from "./indexedKeysSchema"; + +export function get(message: readonly unknown[], schemaField: ValidSchemaLeaf) { + const indexesPathReversed = schemaField.indexesPathReversed; + let currentSlice: readonly unknown[] = message; + + for (let pathIndex = schemaField.indexesPathReversed.length - 1; pathIndex >= 1; pathIndex--) { + currentSlice = currentSlice[indexesPathReversed[pathIndex]] as readonly unknown[]; + } + + const lastIndexInPath = indexesPathReversed[0]; + return currentSlice[lastIndexInPath] as TField; +} + +export function set(targetMessage: unknown[], schemaField: ValidSchemaLeaf, value: TField) { + const indexesPathReversed = schemaField.indexesPathReversed; + let currentSlice: unknown[] = targetMessage; + + for (let pathIndex = schemaField.indexesPathReversed.length - 1; pathIndex >= 1; pathIndex--) { + if (currentSlice[indexesPathReversed[pathIndex]] === undefined) { + currentSlice[indexesPathReversed[pathIndex]] = []; + } + + currentSlice = currentSlice[indexesPathReversed[pathIndex]] as unknown[]; + } + + const lastIndexInPath = indexesPathReversed[0]; + currentSlice[lastIndexInPath] = value; +} \ No newline at end of file