@quatrain/api-server-express 1.1.7 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1 @@
1
1
  export { ExpressAdapter } from './adapters/ExpressAdapter';
2
- export { CrudEndpoint } from './endpoints/CrudEndpoint';
3
- export { ListEndpoint } from './endpoints/ListEndpoint';
4
- export { ValuesEndpoint } from './endpoints/ValuesEndpoint';
package/dist/index.js CHANGED
@@ -1,11 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ValuesEndpoint = exports.ListEndpoint = exports.CrudEndpoint = exports.ExpressAdapter = void 0;
3
+ exports.ExpressAdapter = void 0;
4
4
  var ExpressAdapter_1 = require("./adapters/ExpressAdapter");
5
5
  Object.defineProperty(exports, "ExpressAdapter", { enumerable: true, get: function () { return ExpressAdapter_1.ExpressAdapter; } });
6
- var CrudEndpoint_1 = require("./endpoints/CrudEndpoint");
7
- Object.defineProperty(exports, "CrudEndpoint", { enumerable: true, get: function () { return CrudEndpoint_1.CrudEndpoint; } });
8
- var ListEndpoint_1 = require("./endpoints/ListEndpoint");
9
- Object.defineProperty(exports, "ListEndpoint", { enumerable: true, get: function () { return ListEndpoint_1.ListEndpoint; } });
10
- var ValuesEndpoint_1 = require("./endpoints/ValuesEndpoint");
11
- Object.defineProperty(exports, "ValuesEndpoint", { enumerable: true, get: function () { return ValuesEndpoint_1.ValuesEndpoint; } });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@quatrain/api-server-express",
3
- "version": "1.1.7",
4
- "description": "API Server Utilities for Quatrain",
3
+ "version": "1.1.8",
4
+ "description": "Express Adapter for Quatrain API Server",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bun": "src/index.ts",
@@ -15,7 +15,7 @@
15
15
  "repository": {
16
16
  "type": "git",
17
17
  "url": "git+https://github.com/Quatrain/Core.git",
18
- "directory": "packages/api-server"
18
+ "directory": "packages/api-server-express"
19
19
  },
20
20
  "author": "Quatrain Développement SAS <developers@quatrain.com>",
21
21
  "license": "AGPL-3.0-only",
@@ -30,8 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@quatrain/api": "^1.1.4",
33
- "@quatrain/backend": "^1.2.6",
34
- "@quatrain/core": "^1.2.5",
33
+ "@quatrain/api-server": "^1.1.8",
35
34
  "express": "^4.19.2"
36
35
  },
37
36
  "scripts": {
@@ -29,7 +29,7 @@ export class ExpressAdapter implements ServerAdapter {
29
29
  body: req.body,
30
30
  params: req.params,
31
31
  query: req.query,
32
- headers: req.headers
32
+ headers: req.headers as Record<string, string | string[] | undefined>
33
33
  }
34
34
 
35
35
  const apiRes: ApiResponse = {
package/src/index.ts CHANGED
@@ -1,5 +1 @@
1
1
  export { ExpressAdapter } from './adapters/ExpressAdapter'
2
-
3
- export { CrudEndpoint } from './endpoints/CrudEndpoint'
4
- export { ListEndpoint } from './endpoints/ListEndpoint'
5
- export { ValuesEndpoint } from './endpoints/ValuesEndpoint'
package/README.md DELETED
@@ -1,13 +0,0 @@
1
- # @quatrain/api-server-express
2
-
3
- The `@quatrain/api-server-express` package provides the backend REST layer for Quatrain, exposing the underlying DataObjects and Backend engines through HTTP endpoints.
4
-
5
- ## Features
6
- - **Express Adapter**: A lightweight wrapper around Express to bootstrap APIs instantly.
7
- - **Auto-generated Endpoints**: Pre-built `CrudEndpoint` and `ListEndpoint` handlers to instantly expose a Quatrain model (e.g. `StudioProperty`, `StudioModel`) as a complete REST API.
8
- - **Middleware Support**: Pluggable Express middlewares.
9
-
10
- ---
11
- ## Getting Started
12
-
13
- Refer to the `HOWTO.md` file for usage examples.
@@ -1,3 +0,0 @@
1
- import { EndpointHandler } from '@quatrain/api';
2
- import { BaseObject } from '@quatrain/core';
3
- export declare const CrudEndpoint: (ModelClass: typeof BaseObject) => EndpointHandler;
@@ -1,101 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CrudEndpoint = void 0;
4
- const backend_1 = require("@quatrain/backend");
5
- const CrudEndpoint = (ModelClass) => {
6
- return (router, path, options) => {
7
- const methods = options.methods || ['CREATE', 'READ', 'UPDATE', 'DELETE'];
8
- // C: Create
9
- if (methods.includes('CREATE')) {
10
- router.post(path, async (req, res) => {
11
- try {
12
- const data = req.body;
13
- const newObj = await ModelClass.factory();
14
- Object.keys(data).forEach((key) => {
15
- if (newObj.has(key)) {
16
- newObj.set(key, data[key]);
17
- }
18
- });
19
- newObj.validate();
20
- await newObj.save();
21
- res.status(201).json(newObj.dataObject.toJSON());
22
- }
23
- catch (e) {
24
- if (e.name === 'ValidationError') {
25
- res.status(400).json({ error: e.message, validationErrors: e.errors });
26
- }
27
- else {
28
- backend_1.Backend.error(`[API Error] POST: ${e.message}`);
29
- res.status(400).json({ error: e.message });
30
- }
31
- }
32
- });
33
- }
34
- // R: Read (Single by ID)
35
- if (methods.includes('READ')) {
36
- const getPath = path.endsWith('/') ? `${path}:id` : `${path}/:id`;
37
- router.get(getPath, async (req, res) => {
38
- try {
39
- const obj = await ModelClass.fromBackend(req.params.id);
40
- if (!obj) {
41
- return res.status(404).json({ error: 'Object not found' });
42
- }
43
- res.json(obj.dataObject.toJSON());
44
- }
45
- catch (e) {
46
- backend_1.Backend.error(`[API Error] GET /${req.params.id}: ${e.message}`);
47
- res.status(500).json({ error: e.message });
48
- }
49
- });
50
- }
51
- // U: Update
52
- if (methods.includes('UPDATE')) {
53
- const putPath = path.endsWith('/') ? `${path}:id` : `${path}/:id`;
54
- router.put(putPath, async (req, res) => {
55
- try {
56
- const data = req.body;
57
- const obj = await ModelClass.fromBackend(req.params.id);
58
- if (!obj) {
59
- return res.status(404).json({ error: 'Object not found' });
60
- }
61
- Object.keys(data).forEach((key) => {
62
- if (obj.has(key)) {
63
- obj.set(key, data[key]);
64
- }
65
- });
66
- obj.validate();
67
- await obj.save();
68
- res.json(obj.dataObject.toJSON());
69
- }
70
- catch (e) {
71
- if (e.name === 'ValidationError') {
72
- res.status(400).json({ error: e.message, validationErrors: e.errors });
73
- }
74
- else {
75
- backend_1.Backend.error(`[API Error] PUT /${req.params.id}: ${e.message}`);
76
- res.status(400).json({ error: e.message });
77
- }
78
- }
79
- });
80
- }
81
- // D: Delete
82
- if (methods.includes('DELETE')) {
83
- const deletePath = path.endsWith('/') ? `${path}:id` : `${path}/:id`;
84
- router.delete(deletePath, async (req, res) => {
85
- try {
86
- const obj = await ModelClass.fromBackend(req.params.id);
87
- if (!obj) {
88
- return res.status(404).json({ error: 'Object not found' });
89
- }
90
- await obj.delete();
91
- res.json({ success: true });
92
- }
93
- catch (e) {
94
- backend_1.Backend.error(`[API Error] DELETE /${req.params.id}: ${e.message}`);
95
- res.status(500).json({ error: e.message });
96
- }
97
- });
98
- }
99
- };
100
- };
101
- exports.CrudEndpoint = CrudEndpoint;
@@ -1,3 +0,0 @@
1
- import { EndpointHandler } from '@quatrain/api';
2
- import { BaseObject } from '@quatrain/core';
3
- export declare const ListEndpoint: (ModelClass: typeof BaseObject) => EndpointHandler;
@@ -1,27 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ListEndpoint = void 0;
4
- const backend_1 = require("@quatrain/backend");
5
- const ListEndpoint = (ModelClass) => {
6
- return (router, path, options) => {
7
- router.get(path, async (req, res) => {
8
- try {
9
- let q = ModelClass.query();
10
- // Apply all query parameters as exact match filters
11
- for (const [key, value] of Object.entries(req.query)) {
12
- q = q.where(key, value, 'equals');
13
- }
14
- const results = await q.execute('dataObjects');
15
- res.json({
16
- items: results.items.map((r) => r.toJSON()),
17
- meta: results.meta
18
- });
19
- }
20
- catch (e) {
21
- backend_1.Backend.error(`[API Error] GET (List): ${e.message}`);
22
- res.status(500).json({ error: e.message });
23
- }
24
- });
25
- };
26
- };
27
- exports.ListEndpoint = ListEndpoint;
@@ -1,3 +0,0 @@
1
- import { EndpointHandler } from '@quatrain/api';
2
- import { BaseObject } from '@quatrain/core';
3
- export declare const ValuesEndpoint: (ModelClass: typeof BaseObject) => EndpointHandler;
@@ -1,59 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ValuesEndpoint = void 0;
4
- const backend_1 = require("@quatrain/backend");
5
- const ValuesEndpoint = (ModelClass) => {
6
- return (router, path, options) => {
7
- // Create the endpoint route, assuming 'path' is something like '/api/users'
8
- // We'll map it to '/api/users/values'
9
- const valuesPath = path.endsWith('/') ? `${path}values` : `${path}/values`;
10
- router.get(valuesPath, async (req, res) => {
11
- try {
12
- const results = await ModelClass.query()
13
- .where('status', 'deleted', 'notEquals')
14
- .execute('dataObjects');
15
- // Identify exposed fields from PROPS_DEFINITION
16
- const propsDef = ModelClass.PROPS_DEFINITION || [];
17
- const exposedFields = propsDef
18
- .filter((p) => p.htmlType && p.htmlType !== 'off')
19
- .map((p) => p.name);
20
- const searchFields = ['name', ...propsDef
21
- .filter((p) => p.type === 'StringProperty' && p.fullSearch === true)
22
- .map((p) => p.name)];
23
- let values = results.items.map((dataObject) => {
24
- const payload = {
25
- value: dataObject.uid,
26
- name: dataObject.has('name') ? dataObject.get('name') : dataObject.uid
27
- };
28
- for (const field of exposedFields) {
29
- if (dataObject.has(field)) {
30
- payload[field] = dataObject.get(field);
31
- }
32
- }
33
- const searchVals = searchFields.map(f => {
34
- const val = dataObject.get(f);
35
- return typeof val === 'string' ? val : '';
36
- }).filter(Boolean);
37
- payload._search = searchVals.join(' ').toLowerCase();
38
- return payload;
39
- });
40
- const qParam = req.query.q;
41
- if (qParam) {
42
- const searchStr = qParam.toLowerCase();
43
- values = values.filter((payload) => {
44
- return searchFields.some(field => {
45
- const val = payload[field];
46
- return typeof val === 'string' && val.toLowerCase().includes(searchStr);
47
- });
48
- });
49
- }
50
- res.json(values);
51
- }
52
- catch (e) {
53
- backend_1.Backend.error(`[API Error] GET ${valuesPath}: ${e.message}`);
54
- res.status(500).json({ error: e.message });
55
- }
56
- });
57
- };
58
- };
59
- exports.ValuesEndpoint = ValuesEndpoint;
@@ -1,106 +0,0 @@
1
- import { EndpointHandler, ServerAdapter, EndpointOptions } from '@quatrain/api'
2
- import { BaseObject, DataObjectClass } from '@quatrain/core'
3
- import { Backend } from '@quatrain/backend'
4
- import { ValidationError } from '@quatrain/core'
5
-
6
- export const CrudEndpoint = (ModelClass: typeof BaseObject): EndpointHandler => {
7
- return (router: ServerAdapter, path: string, options: EndpointOptions) => {
8
- const methods = options.methods || ['CREATE', 'READ', 'UPDATE', 'DELETE']
9
-
10
- // C: Create
11
- if (methods.includes('CREATE')) {
12
- router.post(path, async (req, res) => {
13
- try {
14
- const data = req.body
15
- const newObj = await ModelClass.factory()
16
-
17
- Object.keys(data).forEach((key) => {
18
- if (newObj.has(key)) {
19
- newObj.set(key, data[key])
20
- }
21
- });
22
-
23
- (newObj as any).validate()
24
- await (newObj as any).save()
25
- res.status(201).json(newObj.dataObject.toJSON())
26
- } catch (e: any) {
27
- if (e.name === 'ValidationError') {
28
- res.status(400).json({ error: e.message, validationErrors: e.errors })
29
- } else {
30
- Backend.error(`[API Error] POST: ${e.message}`)
31
- res.status(400).json({ error: e.message })
32
- }
33
- }
34
- })
35
- }
36
-
37
- // R: Read (Single by ID)
38
- if (methods.includes('READ')) {
39
- const getPath = path.endsWith('/') ? `${path}:id` : `${path}/:id`
40
- router.get(getPath, async (req, res) => {
41
- try {
42
- const obj = await (ModelClass as any).fromBackend(req.params.id)
43
- if (!obj) {
44
- return res.status(404).json({ error: 'Object not found' })
45
- }
46
- res.json(obj.dataObject.toJSON())
47
- } catch (e) {
48
- Backend.error(`[API Error] GET /${req.params.id}: ${(e as Error).message}`)
49
- res.status(500).json({ error: (e as Error).message })
50
- }
51
- })
52
- }
53
-
54
- // U: Update
55
- if (methods.includes('UPDATE')) {
56
- const putPath = path.endsWith('/') ? `${path}:id` : `${path}/:id`
57
- router.put(putPath, async (req, res) => {
58
- try {
59
- const data = req.body
60
- const obj = await (ModelClass as any).fromBackend(req.params.id)
61
-
62
- if (!obj) {
63
- return res.status(404).json({ error: 'Object not found' })
64
- }
65
-
66
- Object.keys(data).forEach((key) => {
67
- if (obj.has(key)) {
68
- obj.set(key, data[key])
69
- }
70
- });
71
-
72
- obj.validate()
73
- await obj.save()
74
- res.json(obj.dataObject.toJSON())
75
- } catch (e: any) {
76
- if (e.name === 'ValidationError') {
77
- res.status(400).json({ error: e.message, validationErrors: e.errors })
78
- } else {
79
- Backend.error(`[API Error] PUT /${req.params.id}: ${e.message}`)
80
- res.status(400).json({ error: e.message })
81
- }
82
- }
83
- })
84
- }
85
-
86
- // D: Delete
87
- if (methods.includes('DELETE')) {
88
- const deletePath = path.endsWith('/') ? `${path}:id` : `${path}/:id`
89
- router.delete(deletePath, async (req, res) => {
90
- try {
91
- const obj = await (ModelClass as any).fromBackend(req.params.id)
92
-
93
- if (!obj) {
94
- return res.status(404).json({ error: 'Object not found' })
95
- }
96
-
97
- await obj.delete()
98
- res.json({ success: true })
99
- } catch (e) {
100
- Backend.error(`[API Error] DELETE /${req.params.id}: ${(e as Error).message}`)
101
- res.status(500).json({ error: (e as Error).message })
102
- }
103
- })
104
- }
105
- }
106
- }
@@ -1,30 +0,0 @@
1
- import { EndpointHandler, ServerAdapter, EndpointOptions } from '@quatrain/api'
2
- import { BaseObject, DataObjectClass } from '@quatrain/core'
3
- import { Backend } from '@quatrain/backend'
4
-
5
- export const ListEndpoint = (ModelClass: typeof BaseObject): EndpointHandler => {
6
- return (router: ServerAdapter, path: string, options: EndpointOptions) => {
7
-
8
- router.get(path, async (req, res) => {
9
- try {
10
- let q = (ModelClass as any).query()
11
-
12
- // Apply all query parameters as exact match filters
13
- for (const [key, value] of Object.entries(req.query)) {
14
- q = q.where(key, value, 'equals')
15
- }
16
-
17
- const results = await q.execute('dataObjects')
18
-
19
- res.json({
20
- items: results.items.map((r: DataObjectClass<any>) => r.toJSON()),
21
- meta: results.meta
22
- })
23
- } catch (e) {
24
- Backend.error(`[API Error] GET (List): ${(e as Error).message}`)
25
- res.status(500).json({ error: (e as Error).message })
26
- }
27
- })
28
-
29
- }
30
- }
@@ -1,66 +0,0 @@
1
- import { EndpointHandler, ServerAdapter, EndpointOptions } from '@quatrain/api'
2
- import { BaseObject, DataObjectClass } from '@quatrain/core'
3
- import { Backend } from '@quatrain/backend'
4
-
5
- export const ValuesEndpoint = (ModelClass: typeof BaseObject): EndpointHandler => {
6
- return (router: ServerAdapter, path: string, options: EndpointOptions) => {
7
- // Create the endpoint route, assuming 'path' is something like '/api/users'
8
- // We'll map it to '/api/users/values'
9
- const valuesPath = path.endsWith('/') ? `${path}values` : `${path}/values`
10
-
11
- router.get(valuesPath, async (req, res) => {
12
- try {
13
- const results = await (ModelClass as any).query()
14
- .where('status', 'deleted', 'notEquals')
15
- .execute('dataObjects')
16
-
17
- // Identify exposed fields from PROPS_DEFINITION
18
- const propsDef = (ModelClass as any).PROPS_DEFINITION || []
19
- const exposedFields = propsDef
20
- .filter((p: any) => p.htmlType && p.htmlType !== 'off')
21
- .map((p: any) => p.name)
22
-
23
- const searchFields = ['name', ...propsDef
24
- .filter((p: any) => p.type === 'StringProperty' && p.fullSearch === true)
25
- .map((p: any) => p.name)]
26
-
27
- let values = results.items.map((dataObject: DataObjectClass<any>) => {
28
- const payload: any = {
29
- value: dataObject.uid,
30
- name: dataObject.has('name') ? dataObject.get('name') : dataObject.uid
31
- }
32
-
33
- for (const field of exposedFields) {
34
- if (dataObject.has(field)) {
35
- payload[field] = dataObject.get(field)
36
- }
37
- }
38
-
39
- const searchVals = searchFields.map(f => {
40
- const val = dataObject.get(f)
41
- return typeof val === 'string' ? val : ''
42
- }).filter(Boolean)
43
- payload._search = searchVals.join(' ').toLowerCase()
44
-
45
- return payload
46
- })
47
-
48
- const qParam = req.query.q as string
49
- if (qParam) {
50
- const searchStr = qParam.toLowerCase()
51
- values = values.filter((payload: any) => {
52
- return searchFields.some(field => {
53
- const val = payload[field]
54
- return typeof val === 'string' && val.toLowerCase().includes(searchStr)
55
- })
56
- })
57
- }
58
-
59
- res.json(values)
60
- } catch (e) {
61
- Backend.error(`[API Error] GET ${valuesPath}: ${(e as Error).message}`)
62
- res.status(500).json({ error: (e as Error).message })
63
- }
64
- })
65
- }
66
- }