feathersjs / feathers

The API and real-time application framework
https://feathersjs.com
MIT License
15.04k stars 751 forks source link

[FR] MongoDB strict JSON v1 mode (remove convert all EJSON types to JSON compatible ones) #3202

Open FossPrime opened 1 year ago

FossPrime commented 1 year ago

Problem

Using setNow, softDelete and many other hooks will generate non JSON types in the database. Which causes problems when making comparisons on the client side or sharing server code with the client. Date !== string date, nor does ObjectID or Geocode.

Continuation of this 2019 issue

Instead of this:
Screenshot 2023-05-30 09 13 25

Most of us would much rather have this: (source)
Screenshot 2023-05-30 09 14 29

Proposed solution

Strict JSON mode on the adapter.

Example to rectify deviants ``` /* Replacement for feathers-mongodb database adapter ensures no exotic objects are created in MongoDB. Works like middleware using inheritance. */ import { MongoDBService as Service } from '@feathersjs/mongodb' import { ObjectId } from 'mongodb' import { select } from '@feathersjs/adapter-commons' import { Logger } from '../logger.js' const logger = new Logger('MenDB') const USE_STRICT_JSON = true // Casts all values to JSON (Dates, Geoloc, ObjectId, etc) function toStrictJson(data) { if(USE_STRICT_JSON !== true) { return data } if (data) { // Only on create, update, patch if (!Array.isArray(data)) { return JSON.parse(JSON.stringify(data)) } else { return data.map(e => JSON.parse(JSON.stringify(e))) } } else { return data } } export default class MenDB extends Service { // Override to create String Id's // Uses strict json for deep cloning _setId (ctx) { return (item) => { const entry = toStrictJson(item) // Object.assign({}, item) // Generate a String ID if ID not provided if (typeof entry[ctx.id] === 'undefined') { entry[ctx.id] = new ObjectId().toHexString() } return entry } } // Debugging helper async _get(id, params = {}) { logger.info('SUPER GET', params.tenant ? params.tenant + '/' + id : id) return super._get(id, params) } // Manually sets string ID before creation // Otherwise, MongoDB would set it to a ObjectId async _create (data, params = {}) { let payload = null let promise = null const model = await this.getModel(params) if(Array.isArray(data)) { payload = data.map(this._setId(this)) promise = model.insertMany(payload) } else { payload = this._setId(this)(data) promise = model.insertOne(payload) } // const mongoResult = promise await promise const selectFn = select(params, this.id) // console.log(selectFn, mongoResult, this.id, params) const result = selectFn(payload) // console.log(result) return result } async _patch(id, _data, params) { return super._patch(id, toStrictJson(_data), params) } async _update(id, data, params) { return super._update(id, toStrictJson(data), params) } } ```

Alternative solution

We could export a strict JSON adapter, completely separately. This would negate any performance penalty for those who do not need isomorphic-friendly types.

Risks and other considerations