diff --git a/.gitignore b/.gitignore index 489ac4c..f8118ed 100644 --- a/.gitignore +++ b/.gitignore @@ -105,5 +105,5 @@ dist .tern-port .npmrc -*.jsonl +examples/**/*.jsonl .idea/ diff --git a/examples/basic/data-source.ts b/examples/basic/data-source.ts new file mode 100644 index 0000000..7bb202e --- /dev/null +++ b/examples/basic/data-source.ts @@ -0,0 +1,18 @@ +import {DataSource, dataSources, Resource} from '../../src'; + +export const autoIncrement = async (dataSource: DataSource) => { + const data = await dataSource.getMultiple() as Record[]; + + const highestId = data.reduce( + (highestId, d) => (Number(d.id) > highestId ? Number(d.id) : highestId), + -Infinity + ); + + if (Number.isFinite(highestId)) { + return (highestId + 1).toString(); + } + + return "1"; +}; + +export const dataSource = (resource: Resource) => new dataSources.jsonlFile.DataSource(resource, 'examples/basic'); diff --git a/examples/basic/serializers.ts b/examples/basic/serializers.ts new file mode 100644 index 0000000..a4b0da8 --- /dev/null +++ b/examples/basic/serializers.ts @@ -0,0 +1,28 @@ +export const TEXT_SERIALIZER_PAIR = { + serialize(obj: unknown, level = 0): string { + if (Array.isArray(obj)) { + return obj.map((o) => this.serialize(o)).join('\n\n'); + } + + if (typeof obj === 'object') { + if (obj !== null) { + return Object.entries(obj) + .map(([key, value]) => `${Array(level * 2).fill(' ').join('')}${key}: ${this.serialize(value, level + 1)}`) + .join('\n'); + } + + return ''; + } + + if (typeof obj === 'number' && Number.isFinite(obj)) { + return obj.toString(); + } + + if (typeof obj === 'string') { + return obj; + } + + return ''; + }, + deserialize: (str: string) => str as T +}; diff --git a/examples/basic/server.ts b/examples/basic/server.ts new file mode 100644 index 0000000..2ed28f9 --- /dev/null +++ b/examples/basic/server.ts @@ -0,0 +1,53 @@ +import { + application, + resource, + valibot as v, + serializers +} from '../../src'; +import {TEXT_SERIALIZER_PAIR} from './serializers'; +import {autoIncrement, dataSource} from './data-source'; + +const Piano = resource(v.object( + { + brand: v.string() + }, + v.never() +)) + .name('Piano') + .id('id', { + generationStrategy: autoIncrement, + }); + +// TODO implement authentication and RBAC on each resource + +const User = resource(v.object( + { + firstName: v.string(), + middleName: v.string(), + lastName: v.string(), + bio: v.string(), + birthday: v.datelike() + }, + v.never() +)) + .name('User') + .fullText('bio') + .id('id', { + generationStrategy: autoIncrement, + }); + +const app = application({ + name: 'piano-service', + dataSource, +}) + .contentType('application/json', serializers.applicationJson) + .contentType('text/json', serializers.textJson) + .contentType('text/plain', TEXT_SERIALIZER_PAIR) + .resource(Piano) + .resource(User); + +const server = app.createServer({ + baseUrl: '/api' +}); + +server.listen(3000); diff --git a/src/data-sources/file-jsonl.ts b/src/data-sources/file-jsonl.ts index 7e9911c..f85e6d2 100644 --- a/src/data-sources/file-jsonl.ts +++ b/src/data-sources/file-jsonl.ts @@ -1,4 +1,5 @@ import {readFile, writeFile} from 'fs/promises'; +import {join} from 'path'; import {DataSource as DataSourceInterface, Resource} from '../core'; export class DataSource> implements DataSourceInterface { @@ -6,8 +7,8 @@ export class DataSource> implements DataSourceI data: T[] = []; - constructor(private readonly resource: Resource) { - this.path = `${this.resource.collectionName}.jsonl`; + constructor(private readonly resource: Resource, private readonly baseDir = '') { + this.path = join(baseDir, `${this.resource.collectionName}.jsonl`); } async initialize() { diff --git a/src/server.ts b/src/server.ts deleted file mode 100644 index a57bea2..0000000 --- a/src/server.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - application, - DataSource, - resource, - valibot as v, - dataSources, - serializers -} from '.'; - -const autoIncrement = async (dataSource: DataSource) => { - const data = await dataSource.getMultiple() as Record[]; - - const highestId = data.reduce( - (highestId, d) => (Number(d.id) > highestId ? Number(d.id) : highestId), - -Infinity - ); - - if (Number.isFinite(highestId)) { - return (highestId + 1).toString(); - } - - return "1"; -}; - - -const TEXT_SERIALIZER_PAIR = { - serialize(obj: unknown, level = 0): string { - if (Array.isArray(obj)) { - return obj.map((o) => this.serialize(o)).join('\n\n'); - } - - if (typeof obj === 'object') { - if (obj !== null) { - return Object.entries(obj) - .map(([key, value]) => `${Array(level * 2).fill(' ').join('')}${key}: ${this.serialize(value, level + 1)}`) - .join('\n'); - } - - return ''; - } - - if (typeof obj === 'number' && Number.isFinite(obj)) { - return obj.toString(); - } - - if (typeof obj === 'string') { - return obj; - } - - return ''; - }, - deserialize: (str: string) => str as T -}; - -const Piano = resource(v.object( - { - brand: v.string() - }, - v.never() -)) - .name('Piano') - .id('id', { - generationStrategy: autoIncrement, - }); - -// TODO implement authentication and RBAC on each resource - -const User = resource(v.object( - { - firstName: v.string(), - middleName: v.string(), - lastName: v.string(), - bio: v.string(), - birthday: v.datelike() - }, - v.never() -)) - .name('User') - .fullText('bio') - .id('id', { - generationStrategy: autoIncrement, - }); - -const app = application({ - name: 'piano-service', - dataSource: (resource) => new dataSources.jsonlFile.DataSource(resource), -}) - .contentType('application/json', serializers.applicationJson) - .contentType('text/json', serializers.textJson) - .contentType('text/plain', TEXT_SERIALIZER_PAIR) - .resource(Piano) - .resource(User); - -const server = app.createServer({ - baseUrl: '/api' -}); - -server.listen(3000); -