Skip to content

Commit

Permalink
refactor: transforms
Browse files Browse the repository at this point in the history
  • Loading branch information
juni0r committed Nov 7, 2023
1 parent fb36ef3 commit df9e671
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 173 deletions.
58 changes: 24 additions & 34 deletions src/baseModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SupabaseClient } from '@supabase/supabase-js'

import { Issues } from './issues'
import { defaults } from './schema'
import { pluralize, TrackedDirty, failWith, asData, type Dict } from './util'
import { pluralize, TrackedDirty, failWith, asData, Dict } from './util'
import {
SupamodelError,
RecordNotFound,
Expand All @@ -12,33 +12,29 @@ import {
} from './errors'

import result from 'lodash.result'
import forEach from 'lodash.foreach'
import mapValues from 'lodash.mapvalues'

import type {
Attributes,
AnyObject,
KeyMapper,
Transform,
ZodSchemaOf,
FilterBuilder,
Scoped,
ToJSON,
ID,
} from './types'
import { Transform } from './transform'
import forEach from 'lodash.foreach'
import mapValues from 'lodash.mapvalues'

export class BaseModel {
static client: SupabaseClient<any, any, any>

static attributes: Attributes
static schema: ZodSchemaOf<Attributes>
static transforms: Dict<Transform>
static schema: ZodSchemaOf<Attributes>
static naming: KeyMapper
static primaryKey: string

static columnNameOf: Dict<string>
static attributeNameOf: Dict<string>

static get tableName() {
return (this.tableName = pluralize(this.naming(this.name)))
}
Expand All @@ -65,40 +61,33 @@ export class BaseModel {
return false
}

$get(attr: string) {
return this.$attributes[this.$model.columnNameOf[attr]]
$get(key: string) {
return this.$attributes[key]
}

$set(attr: string, value: unknown) {
this.$attributes[this.$model.columnNameOf[attr]] = value
$set(key: string, value: unknown) {
this.$attributes[key] = value
}

get $isDirty() {
return this.$attributes.$isDirty
}

$didChange(attr: string) {
return this.$attributes.$didChange(this.$model.columnNameOf[attr])
$didChange(key: string) {
return this.$attributes.$didChange(key)
}

$initial(attr: string) {
const column = this.$model.columnNameOf[attr]

return column in this.$attributes.$initial
? this.$attributes.$initial[column]
: this.$attributes[column]
$initial(key: string) {
return key in this.$attributes.$initial
? this.$attributes.$initial[key]
: this.$attributes[key]
}

$take<T extends BaseModel>(this: T, values: AnyObject) {
const { $attributes } = this
const { transforms } = this.$model

forEach(values, (value, column) => {
$attributes[column] = transforms[column].take(value)
forEach(this.$model.transforms, ({ column, take }, key) => {
if (column in values) this.$attributes[key] = take(values[column])
})

$attributes.$commit()

this.$attributes.$commit()
return this
}

Expand Down Expand Up @@ -156,20 +145,21 @@ export class BaseModel {
this.$attributes.$revert()

const { error } = await this.$model.delete(this.$id)

if (error) {
return failWith(RecordNotDeleted, error)
}

return asData(Object.defineProperty(this, '$isDeleted', { value: true }))
Object.defineProperty(this, '$isDeleted', { value: true })

return asData(this)
}

toJSON(): ToJSON {
const { attributeNameOf } = this.$model

return Object.entries(this.$attributes).reduce(
(json, [column, value]) => ({
(json, [key, value]) => ({
...json,
[attributeNameOf[column]]: result(value, 'toJSON', value),
[key]: result(value, 'toJSON', value),
}),
{},
)
Expand Down
55 changes: 19 additions & 36 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,29 @@
import type { ModelConfig, ModelConfigOptions } from './types'

import { createClient, type SupabaseClient } from '@supabase/supabase-js'
import { New, snakeCase } from './util'
import { type SupabaseClient, createClient } from '@supabase/supabase-js'
import { snakeCase } from './util'
import { BaseModel } from './baseModel'
import type { ModelConfigOptions } from './types'

export let isConfigured = false

export const _config = New<ModelConfig>({
base: BaseModel,
primaryKey: 'id' as const,
naming: snakeCase,
})

export function config<DB>() {
return _config as ModelConfig<DB>
}
export default config

export function configureSupamodel<DB = any>(
options: ModelConfigOptions<DB>,
): ModelConfig<DB> {
let { client = supabaseEnv(), ...config } = options
export let baseModel: typeof BaseModel = BaseModel
export default baseModel

if (!isSupabaseClient(client)) {
const { url, key } = client
export function configureSupamodel<DB = any>(options: ModelConfigOptions<DB>) {
let { client, base, primaryKey, naming } = options

client = createClient<DB>(url, key)
if (base) baseModel = base
if (client) {
if (!isSupabaseClient(client)) {
client = createClient<DB>(client.url, client.key)
}
BaseModel.client = client
}
const { base } = Object.assign(_config, config)

base.client = client
isConfigured = true
return _config
}
if (naming) BaseModel.naming = naming
if (primaryKey) BaseModel.primaryKey = primaryKey

export function baseModel<DB = any>() {
return class extends BaseModel {
declare static client: SupabaseClient<DB>
}
BaseModel.naming ??= snakeCase
BaseModel.primaryKey ??= 'id' as const
}

function supabaseEnv() {
export function supabaseEnv() {
const { SUPABASE_URL: url, SUPABASE_KEY: key } = process.env

if (!(url && key))
Expand All @@ -51,7 +34,7 @@ function supabaseEnv() {
return { url, key }
}

function isSupabaseClient(
export function isSupabaseClient(
object: any,
): object is SupabaseClient<any, any, any> {
return 'supabaseUrl' in object && 'supabaseKey' in object
Expand Down
3 changes: 1 addition & 2 deletions src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
configureSupamodel,
defineModel,
BaseModel,
config,
datetime,
transform,
$,
Expand All @@ -29,7 +28,7 @@ export class Model extends BaseModel {

configureSupamodel<Database>({ base: Model })

console.dir(config(), { depth: 1 })
console.dir(BaseModel, { depth: 5 })

class Record extends defineModel({
id: $(z.number()),
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export { DateTime } from 'luxon'

export { BaseModel } from './baseModel'
export { Issues } from './issues'
export { configureSupamodel, config, baseModel } from './config'
export { configureSupamodel, baseModel } from './config'
export { defineModel, withClient } from './model'
export { transform, datetime, attr, attr as $ } from './schema'
export { camelCase, snakeCase, kebabCase, pluralize } from './util'
Expand Down
85 changes: 30 additions & 55 deletions src/model.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,46 @@
import { SupabaseClient } from '@supabase/supabase-js'

import BaseModel from './baseModel'
import { zodSchemaOf } from './schema'
import { Dict, identity } from './util'
import config from './config'

import forEach from 'lodash.foreach'
import mapValues from 'lodash.mapvalues'

import type {
Attributes,
Transform,
SchemaOf,
Extend,
ModelOptions,
} from './types'
import { baseModel } from './config'
import { BaseModel } from './baseModel'
import { Transform } from './transform'
import { zodSchemaOf } from './schema'
import { Dict } from './util'

import type { Attributes, SchemaOf, Extend, ModelOptions } from './types'

export function defineModel<Attrs extends Attributes>(
attributes: Attrs,
_options: Partial<ModelOptions> = {},
{ naming, primaryKey, tableName, client }: Partial<ModelOptions> = {},
) {
const { base, naming, primaryKey, tableName, client } = {
...config(),
..._options,
}

class model extends base {
class model extends baseModel {
static attributes = attributes
static schema = zodSchemaOf(attributes)
static transforms = Dict<Transform>()
static naming = naming
static primaryKey = primaryKey

static columnNameOf = Dict<string>()
static attributeNameOf = Dict<string>()
static schema = zodSchemaOf(attributes)
}

if (client) model.client = client
if (naming) model.naming = naming
if (tableName) model.tableName = tableName

defineAttributes(model, attributes)
if (primaryKey) model.primaryKey = primaryKey

forEach(attributes, ({ column, take, emit }, key) => {
model.transforms[key] = new Transform(
column || model.naming(key),
take,
emit,
)

Object.defineProperty(model.prototype, key, {
get() {
return this.$get(key)
},
set(value: unknown) {
this.$set(key, value)
},
})
})

type Schema = SchemaOf<Attrs>
type Model = Schema &
Expand All @@ -63,32 +64,6 @@ export function defineModel<Attrs extends Attributes>(
return model as ModelClass
}

function defineAttributes(model: typeof BaseModel, attributes: Attributes) {
const { prototype, transforms, columnNameOf, attributeNameOf } = model

forEach(
mapValues(attributes, (option, attr) =>
option.column ? option : { ...option, column: model.naming(attr) },
),

({ column, take = identity, emit = identity }, attr) => {
attributeNameOf[column] = attr
columnNameOf[attr] = column

transforms[column] = { take, emit }

Object.defineProperty(prototype, attr, {
get() {
return this.$get(attr)
},
set(value: unknown) {
this.$set(attr, value)
},
})
},
)
}

export function withClient<DB>(client: SupabaseClient<DB>, execute: () => any) {
return config().base.withClient(client, execute)
return BaseModel.withClient(client, execute)
}
11 changes: 11 additions & 0 deletions src/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { TransformFn } from './types'
import { identity } from './util'

export class Transform<Int = any, Ext = any> {
constructor(
public column: string,
public take: TransformFn<Ext, Int> = identity<any>,
public emit: TransformFn<Int, Ext> = identity<any>,
) {}
}
export default Transform
5 changes: 2 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,8 @@ export interface KeyMapper {
(key: string): string
}

export interface Transform {
take: (v: any) => any
emit: (v: any) => any
export interface TransformFn<In = any, Out = any> {
(val: In): Out
}

export interface Scoped<T = any> {
Expand Down
2 changes: 1 addition & 1 deletion src/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { underscore, camelize, dasherize, pluralize } from 'inflection'
import { trackDirty, type DirtyDecorator } from './trackDirty'
import type { AnyObject, KeyMapper } from './types'
import { SupamodelError } from './errors'
import type { AnyObject, KeyMapper } from './types'

export { default as isEqual } from 'fast-deep-equal'

Expand Down
Loading

0 comments on commit df9e671

Please sign in to comment.