From f861f7b5d9ec048383643f115ba6985980910c35 Mon Sep 17 00:00:00 2001 From: Brian Bosh Date: Wed, 23 Apr 2025 16:40:47 -0600 Subject: [PATCH] first pass --- src/lib/model.ts | 18 +++++++++++------- src/lib/schema.ts | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/lib/model.ts b/src/lib/model.ts index b0f7bcf..a752c7f 100644 --- a/src/lib/model.ts +++ b/src/lib/model.ts @@ -193,17 +193,19 @@ BaseModel.fromJsonApi = function (body) { // Attributes for (let [attribute, value] of Object.entries(body.data.attributes ?? {})) { + const attributeName = schema.transformPropertyName(attribute, schema.options?.serverTransform, schema.options?.clientTransform); const property = schema.attributes[attribute]; if (property?.type === Date && value) { value = new Date(value); } - doc.set(attribute, value, { skipMarkModified: true }); + doc.set(attributeName, value, { skipMarkModified: true }); } // Relationships for (const [relationship, value] of Object.entries(body.data.relationships ?? {})) { + const relationshipName = schema.transformPropertyName(relationship, schema.options?.serverTransform, schema.options?.clientTransform); if (Array.isArray(value.data)) { const related = value.data.map((identifier) => { const model = models[identifier.type]; @@ -214,7 +216,7 @@ BaseModel.fromJsonApi = function (body) { }); }); - doc.set(relationship, related, { skipMarkModified: true }); + doc.set(relationshipName, related, { skipMarkModified: true }); } else if (value.data) { const identifier = value.data; const model = models[identifier.type]; @@ -224,7 +226,7 @@ BaseModel.fromJsonApi = function (body) { data: body.included!.find((resource) => resource.type === identifier.type && resource.id === identifier.id), }); - doc.set(relationship, related, { skipMarkModified: true }); + doc.set(relationshipName, related, { skipMarkModified: true }); } } @@ -473,24 +475,26 @@ BaseModel.prototype.toJsonApi = function () { } } - data.attributes![attribute] = value; + const attributeName = this.schema.transformPropertyName(attribute, this.schema.options?.clientTransform, this.schema.options?.serverTransform); + data.attributes![attributeName] = value; } for (const [relationship, property] of Object.entries(this.schema.relationships)) { + const relationshipName = this.schema.transformPropertyName(relationship, this.schema.options?.clientTransform, this.schema.options?.serverTransform); if (!this.isNew && !this.isModified(relationship)) continue; const value = this.get(relationship) as ModelInstance> | ModelInstance>[] | null; if (Array.isArray(value)) { - data.relationships![relationship] = { + data.relationships![relationshipName] = { data: value.map((val) => val.identifier()), }; } else if (value) { - data.relationships![relationship] = { + data.relationships![relationshipName] = { data: value.identifier(), }; } else { - data.relationships![relationship] = { + data.relationships![relationshipName] = { data: null, }; } diff --git a/src/lib/schema.ts b/src/lib/schema.ts index e848552..e47c284 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -27,6 +27,7 @@ type AttributeDefinition = { } type RelationshipsDefinition = { + // I believe I get into trouble here [path in keyof DocType]?: RelationshipDefinition; } @@ -47,7 +48,11 @@ type RelationshipDefinition = { transform?: (val: T) => any; } +type NameTransformer = "camelCase" | "snake_case" | "literal" + type SchemaOptions = { + serverTransform?: NameTransformer; + clientTransform?: NameTransformer; } class Schema { @@ -63,6 +68,8 @@ class Schema { relationships!: RelationshipsDefinition; + options?: SchemaOptions; + init!: ( definition: SchemaDefinition, options?: SchemaOptions, @@ -71,11 +78,14 @@ class Schema { add!: ( obj: SchemaDefinition, ) => this; + + transformPropertyName!: (propertyName: string, sourceFormat: NameTransformer | undefined, targetFormat: NameTransformer | undefined) => string; } Schema.prototype.init = function (definition, options) { this.attributes = {}; this.relationships = {}; + this.options = options || {}; this.add(definition); }; @@ -90,5 +100,18 @@ Schema.prototype.add = function (obj) { return this; }; +Schema.prototype.transformPropertyName = function (propertyName: string, sourceFormat: NameTransformer = "literal", targetFormat: NameTransformer = "literal") { + if (sourceFormat==="snake_case" && targetFormat === "camelCase") { + return propertyName.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); + } else if (sourceFormat === "camelCase" && targetFormat === "snake_case") { + return propertyName.replace(/([A-Z])/g, "_$1").toLowerCase(); + } else if (targetFormat === "literal" || sourceFormat === targetFormat) { + return propertyName; + } else { + console.warn(`Unsupported transformation from ${sourceFormat} to ${targetFormat}`); + } + return propertyName; +} + export default Schema