-
-
-
-
-
-
-
-
-
-
- Congrats! You're successfully running JSON Server.
-
-
-
-
-
-
Routes
-
- Here are the resources that JSON Server has loaded:
-
-
-
-
-
-
- You can view database current state at any time:
-
-
-
-
- You can use any HTTP verbs (GET, POST, PUT, PATCH and DELETE) and access your resources from anywhere
- using CORS and JSONP.
-
-
-
Documentation
-
- View
- README
- on GitHub.
-
-
-
Issues
-
Please go
- here.
-
-
-
-
-
- To replace this page, create an index.html file in ./public, JSON Server will load it.
-
-
-
-
-
-
-
-
-
diff --git a/src/server/public/stylesheets/style.css b/src/server/public/stylesheets/style.css
deleted file mode 100644
index 098c6a4c2..000000000
--- a/src/server/public/stylesheets/style.css
+++ /dev/null
@@ -1,16 +0,0 @@
-a {
- color: #1882BC !important;
-}
-
-img {
- padding-top: 50px;
- padding-bottom: 20px;
-}
-
-li {
- list-style-type: square;
-}
-
-h4 {
- padding-top: 20px;
-}
\ No newline at end of file
diff --git a/src/server/rewriter.js b/src/server/rewriter.js
deleted file mode 100644
index da80b513e..000000000
--- a/src/server/rewriter.js
+++ /dev/null
@@ -1,29 +0,0 @@
-var express = require('express')
-
-module.exports = function (routes) {
- var router = express.Router()
-
- Object.keys(routes).forEach(function (route) {
-
- if (route.indexOf(':') !== -1) {
- router.all(route, function (req, res, next) {
- // Rewrite target url using params
- var target = routes[route]
- for (var param in req.params) {
- target = target.replace(':' + param, req.params[param])
- }
- req.url = target
- next()
- })
- } else {
- router.all(route + '*', function (req, res, next) {
- // Rewrite url by replacing prefix
- req.url = req.url.replace(route, routes[route])
- next()
- })
- }
-
- })
-
- return router
-}
diff --git a/src/server/router/index.js b/src/server/router/index.js
deleted file mode 100644
index 31031ac4a..000000000
--- a/src/server/router/index.js
+++ /dev/null
@@ -1,90 +0,0 @@
-var express = require('express')
-var methodOverride = require('method-override')
-var bodyParser = require('body-parser')
-var _ = require('lodash')
-var _db = require('underscore-db')
-var low = require('lowdb')
-var plural = require('./plural')
-var nested = require('./nested')
-var singular = require('./singular')
-var mixins = require('../mixins')
-
-module.exports = function (source) {
-
- // Create router
- var router = express.Router()
-
- // Add middlewares
- router.use(bodyParser.json({limit: '10mb'}))
- router.use(bodyParser.urlencoded({extended: false}))
- router.use(methodOverride())
-
- // Create database
- var db
- if (_.isObject(source)) {
- db = low()
- db.object = source
- } else {
- db = low(source)
- }
-
- // Add underscore-db methods to db
- db._.mixin(_db)
-
- // Add specific mixins
- db._.mixin(mixins)
-
- // Expose database
- router.db = db
-
- // Expose database
- router.db = db
-
- // Expose render
- router.render = function (req, res) {
- res.jsonp(res.locals.data)
- }
-
- // GET /db
- function showDatabase (req, res, next) {
- res.locals.data = db.object
- next()
- }
-
- router.get('/db', showDatabase)
-
- router.use(nested())
-
- // Create routes
- for (var prop in db.object) {
- var val = db.object[prop]
-
- if (_.isPlainObject(val)) {
- router.use('/' + prop, singular(db, prop))
- continue
- }
-
- if (_.isArray(val)) {
- router.use('/' + prop, plural(db, prop))
- continue
- }
-
- var msg =
- 'Type of "' + prop + '" (' + typeof val + ') ' +
- (_.isObject(source) ? '' : 'in ' + source) + ' is not supported. ' +
- 'Use objects or arrays of objects.'
-
- throw new Error(msg)
- }
-
- router.use(function (req, res) {
- if (!res.locals.data) {
- res.status(404)
- res.locals.data = {}
- }
-
- router.render(req, res)
- })
-
- return router
-}
diff --git a/src/server/router/nested.js b/src/server/router/nested.js
deleted file mode 100644
index b7760584b..000000000
--- a/src/server/router/nested.js
+++ /dev/null
@@ -1,18 +0,0 @@
-var express = require('express')
-var pluralize = require('pluralize')
-var utils = require('../utils')
-
-module.exports = function () {
-
- var router = express.Router()
-
- // Rewrite url to /:nested?:resourceId=:id
- router.get('/:resource/:id/:nested', function (req, res, next) {
- var prop = pluralize.singular(req.params.resource)
- req.query[prop + 'Id'] = utils.toNative(req.params.id)
- req.url = '/' + req.params.nested
- next()
- })
-
- return router
-}
diff --git a/src/server/router/plural.js b/src/server/router/plural.js
deleted file mode 100644
index 80a29d17e..000000000
--- a/src/server/router/plural.js
+++ /dev/null
@@ -1,254 +0,0 @@
-var express = require('express')
-var _ = require('lodash')
-var pluralize = require('pluralize')
-var utils = require('../utils')
-
-module.exports = function (db, name) {
-
- // Create router
- var router = express.Router()
-
- // Embed function used in GET /name and GET /name/id
- function embed (resource, e) {
- e && [].concat(e)
- .forEach(function (externalResource) {
- if (db.object[externalResource]) {
- var query = {}
- var singularResource = pluralize.singular(name)
- query[singularResource + 'Id'] = resource.id
- resource[externalResource] = db(externalResource).where(query)
- }
- })
- }
-
- // Expand function used in GET /name and GET /name/id
- function expand (resource, e) {
- e && [].concat(e)
- .forEach(function (innerResource) {
- var plural = pluralize(innerResource)
- if (db.object[plural]) {
- var prop = innerResource + 'Id'
- resource[innerResource] = db(plural).getById(resource[prop])
- }
- })
- }
-
- // GET /name
- // GET /name?q=
- // GET /name?attr=&attr=
- // GET /name?_end=&
- // GET /name?_start=&_end=&
- // GET /name?_embed=&_expand=
- function list (req, res, next) {
-
- // Resource chain
- var chain = db(name).chain()
-
- // Remove q, _start, _end, ... from req.query to avoid filtering using those
- // parameters
- var q = req.query.q
- var _start = req.query._start
- var _end = req.query._end
- var _sort = req.query._sort
- var _order = req.query._order
- var _limit = req.query._limit
- var _embed = req.query._embed
- var _expand = req.query._expand
- delete req.query.q
- delete req.query._start
- delete req.query._end
- delete req.query._sort
- delete req.query._order
- delete req.query._limit
- delete req.query._embed
- delete req.query._expand
-
- // Automatically delete query parameters that can't be found
- // in the database
- Object.keys(req.query).forEach(function (query) {
- var arr = db(name).value()
- for (var i in arr) {
- if (
- _.has(arr[i], query) ||
- query === 'callback' ||
- query === '_' ||
- query.indexOf('_lte') !== -1 ||
- query.indexOf('_gte') !== -1
- ) return
- }
- delete req.query[query]
- })
-
- if (q) {
-
- // Full-text search
- q = q.toLowerCase()
-
- chain = chain.filter(function (obj) {
- for (var key in obj) {
- var value = obj[key]
- if (db._.deepQuery(value, q)) {
- return true
- }
- }
- })
-
- }
-
- Object.keys(req.query).forEach(function (key) {
- // Don't take into account JSONP query parameters
- // jQuery adds a '_' query parameter too
- if (key !== 'callback' && key !== '_') {
- // Always use an array, in case req.query is an array
- var arr = [].concat(req.query[key])
-
- chain = chain.filter(function (element) {
- return arr
- .map(utils.toNative)
- .map(function (value) {
- var isRange = key.indexOf('_lte') !== -1 || key.indexOf('_gte') !== -1
- if (isRange) {
- var path = key.replace(/(_lte|_gte)$/, '')
- var isLowerThan = key.indexOf('_gte') !== -1
- var elementValue = _.get(element, path)
-
- if (isLowerThan) {
- return value <= elementValue
- } else {
- return value >= elementValue
- }
- } else {
- return _.matchesProperty(key, value)(element)
- }
- }).reduce(function (a, b) {
- return a || b
- })
- })
- }
- })
-
- // Sort
- if (_sort) {
- _order = _order || 'ASC'
-
- chain = chain.sortBy(function (element) {
- return element[_sort]
- })
-
- if (_order === 'DESC') {
- chain = chain.reverse()
- }
- }
-
- // Slice result
- if (_end || _limit) {
- res.setHeader('X-Total-Count', chain.size())
- res.setHeader('Access-Control-Expose-Headers', 'X-Total-Count')
- }
-
- _start = parseInt(_start, 10) || 0
-
- if (_end) {
- _end = parseInt(_end, 10)
- chain = chain.slice(_start, _end)
- } else if (_limit) {
- _limit = parseInt(_limit, 10)
- chain = chain.slice(_start, _start + _limit)
- }
-
- // embed and expand
- chain = chain
- .cloneDeep()
- .forEach(function (element) {
- embed(element, _embed)
- expand(element, _expand)
- })
-
- res.locals.data = chain.value()
- next()
- }
-
- // GET /name/:id
- // GET /name/:id?_embed=&_expand
- function show (req, res, next) {
- var _embed = req.query._embed
- var _expand = req.query._expand
- var id = utils.toNative(req.params.id)
- var resource = db(name).getById(id)
-
- if (resource) {
- // Clone resource to avoid making changes to the underlying object
- resource = _.cloneDeep(resource)
-
- // Embed other resources based on resource id
- // /posts/1?_embed=comments
- embed(resource, _embed)
-
- // Expand inner resources based on id
- // /posts/1?_expand=user
- expand(resource, _expand)
-
- res.locals.data = resource
- }
-
- next()
- }
-
- // POST /name
- function create (req, res, next) {
- for (var key in req.body) {
- req.body[key] = utils.toNative(req.body[key])
- }
-
- var resource = db(name)
- .insert(req.body)
-
- res.status(201)
- res.locals.data = resource
- next()
- }
-
- // PUT /name/:id
- // PATCH /name/:id
- function update (req, res, next) {
- for (var key in req.body) {
- req.body[key] = utils.toNative(req.body[key])
- }
-
- var resource = db(name)
- .updateById(utils.toNative(req.params.id), req.body)
-
- if (resource) {
- res.locals.data = resource
- }
-
- next()
- }
-
- // DELETE /name/:id
- function destroy (req, res, next) {
- db(name).removeById(utils.toNative(req.params.id))
-
- // Remove dependents documents
- var removable = db._.getRemovable(db.object)
-
- _.each(removable, function (item) {
- db(item.name).removeById(item.id)
- })
-
- res.locals.data = {}
- next()
- }
-
- router.route('/')
- .get(list)
- .post(create)
-
- router.route('/:id')
- .get(show)
- .put(update)
- .patch(update)
- .delete(destroy)
-
- return router
-}
diff --git a/src/server/router/singular.js b/src/server/router/singular.js
deleted file mode 100644
index 8c38ce9ca..000000000
--- a/src/server/router/singular.js
+++ /dev/null
@@ -1,40 +0,0 @@
-var express = require('express')
-var utils = require('../utils')
-
-module.exports = function (db, name) {
-
- var router = express.Router()
-
- function show (req, res, next) {
- res.locals.data = db.object[name]
- next()
- }
-
- function create (req, res, next) {
- for (var prop in req.body) {
- req.body[prop] = utils.toNative(req.body[prop])
- }
-
- res.locals.data = db.object[name] = req.body
- res.status(201)
- next()
- }
-
- function update (req, res, next) {
- for (var prop in req.body) {
- db.object[name][prop] = utils.toNative(req.body[prop])
- }
-
- res.locals.data = db.object[name]
- next()
- }
-
- router.route('/')
- .get(show)
- .post(create)
- .put(update)
- .patch(update)
-
- return router
-
-}
diff --git a/src/server/utils.js b/src/server/utils.js
deleted file mode 100644
index 397e2ffc4..000000000
--- a/src/server/utils.js
+++ /dev/null
@@ -1,22 +0,0 @@
-module.exports = {
- toNative: toNative
-}
-
-// Turns string to native.
-// Example:
-// 'true' -> true
-// '1' -> 1
-function toNative (value) {
- if (typeof value === 'string') {
- if (value === ''
- || value.trim() !== value
- || (value.length > 1 && value[0] === '0')) {
- return value
- } else if (value === 'true' || value === 'false') {
- return value === 'true'
- } else if (!isNaN(+value)) {
- return +value
- }
- }
- return value
-}
diff --git a/src/service.test.ts b/src/service.test.ts
new file mode 100644
index 000000000..818a2457b
--- /dev/null
+++ b/src/service.test.ts
@@ -0,0 +1,392 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+
+import { Low, Memory } from 'lowdb'
+
+import { Data, Item, PaginatedItems, Service } from './service.js'
+
+const defaultData = { posts: [], comments: [], object: {} }
+const adapter = new Memory