@ohos-ports/redux-devtools-cli 5.0.0-beta.0

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.
Files changed (43) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +162 -0
  3. package/app/electron.cjs +29 -0
  4. package/app/index.html +46 -0
  5. package/app/package.json +8 -0
  6. package/bin/redux-devtools.js +3 -0
  7. package/defaultDbOptions.json +7 -0
  8. package/dist/api/schema.d.ts +16 -0
  9. package/dist/api/schema.js +12 -0
  10. package/dist/api/schema_def.graphql +60 -0
  11. package/dist/bin/injectServer.d.ts +6 -0
  12. package/dist/bin/injectServer.js +78 -0
  13. package/dist/bin/openApp.d.ts +2 -0
  14. package/dist/bin/openApp.js +40 -0
  15. package/dist/bin/redux-devtools.d.ts +2 -0
  16. package/dist/bin/redux-devtools.js +73 -0
  17. package/dist/db/connector.d.ts +3 -0
  18. package/dist/db/connector.js +49 -0
  19. package/dist/db/migrations/index.d.ts +3 -0
  20. package/dist/db/migrations/index.js +84 -0
  21. package/dist/db/seeds/index.d.ts +2 -0
  22. package/dist/db/seeds/index.js +10 -0
  23. package/dist/index.d.ts +6 -0
  24. package/dist/index.js +125 -0
  25. package/dist/options.d.ts +29 -0
  26. package/dist/options.js +29 -0
  27. package/dist/routes.d.ts +5 -0
  28. package/dist/routes.js +100 -0
  29. package/dist/store.d.ts +57 -0
  30. package/dist/store.js +97 -0
  31. package/package.json +97 -0
  32. package/src/api/schema.ts +26 -0
  33. package/src/api/schema_def.graphql +60 -0
  34. package/src/bin/injectServer.ts +104 -0
  35. package/src/bin/openApp.ts +53 -0
  36. package/src/bin/redux-devtools.ts +94 -0
  37. package/src/db/connector.ts +67 -0
  38. package/src/db/migrations/index.ts +87 -0
  39. package/src/db/seeds/index.ts +12 -0
  40. package/src/index.ts +137 -0
  41. package/src/options.ts +68 -0
  42. package/src/routes.ts +144 -0
  43. package/src/store.ts +164 -0
@@ -0,0 +1,84 @@
1
+ export function up(knex) {
2
+ return Promise.all([
3
+ knex.schema.createTable('remotedev_reports', function (table) {
4
+ table.uuid('id').primary();
5
+ table.string('type');
6
+ table.string('title');
7
+ table.string('description');
8
+ table.string('action');
9
+ table.text('payload', 'longtext');
10
+ table.text('preloadedState', 'longtext');
11
+ table.text('screenshot', 'longtext');
12
+ table.string('userAgent');
13
+ table.string('version');
14
+ table.string('user');
15
+ table.string('userId');
16
+ table.string('instanceId');
17
+ table.string('meta');
18
+ table.string('exception');
19
+ table.timestamp('added').defaultTo(knex.fn.now());
20
+ table
21
+ .uuid('appId')
22
+ .references('id')
23
+ .inTable('remotedev_apps')
24
+ .onDelete('CASCADE')
25
+ .onUpdate('CASCADE')
26
+ .defaultTo('78626c31-e16b-4528-b8e5-f81301b627f4');
27
+ }),
28
+ knex.schema.createTable('remotedev_payloads', function (table) {
29
+ table.uuid('id').primary();
30
+ table.text('state');
31
+ table.text('action');
32
+ table.timestamp('added').defaultTo(knex.fn.now());
33
+ table
34
+ .uuid('reportId')
35
+ .references('id')
36
+ .inTable('remotedev_reports')
37
+ .onDelete('CASCADE')
38
+ .onUpdate('CASCADE');
39
+ }),
40
+ knex.schema.createTable('remotedev_apps', function (table) {
41
+ table.uuid('id').primary();
42
+ table.string('title');
43
+ table.string('description');
44
+ table.string('url');
45
+ table.timestamps(false, true);
46
+ }),
47
+ knex.schema.createTable('remotedev_users', function (table) {
48
+ table.uuid('id').primary();
49
+ table.string('name');
50
+ table.string('login');
51
+ table.string('email');
52
+ table.string('avatarUrl');
53
+ table.string('profileUrl');
54
+ table.string('oauthId');
55
+ table.string('oauthType');
56
+ table.string('token');
57
+ table.timestamps(false, true);
58
+ }),
59
+ knex.schema.createTable('remotedev_users_apps', function (table) {
60
+ table.boolean('readOnly').defaultTo(false);
61
+ table.uuid('userId');
62
+ table.uuid('appId');
63
+ table.primary(['userId', 'appId']);
64
+ table
65
+ .foreign('userId')
66
+ .references('id')
67
+ .inTable('remotedev_users')
68
+ .onDelete('CASCADE')
69
+ .onUpdate('CASCADE');
70
+ table
71
+ .foreign('appId')
72
+ .references('id')
73
+ .inTable('remotedev_apps')
74
+ .onDelete('CASCADE')
75
+ .onUpdate('CASCADE');
76
+ }),
77
+ ]);
78
+ }
79
+ export function down(knex) {
80
+ return Promise.all([
81
+ knex.schema.dropTable('remotedev_reports'),
82
+ knex.schema.dropTable('remotedev_apps'),
83
+ ]);
84
+ }
@@ -0,0 +1,2 @@
1
+ import { Knex } from 'knex';
2
+ export declare function seed(knex: Knex): Promise<[number[]]>;
@@ -0,0 +1,10 @@
1
+ export function seed(knex) {
2
+ return Promise.all([knex('remotedev_apps').del()]).then(function () {
3
+ return Promise.all([
4
+ knex('remotedev_apps').insert({
5
+ id: '78626c31-e16b-4528-b8e5-f81301b627f4',
6
+ title: 'Default',
7
+ }),
8
+ ]);
9
+ });
10
+ }
@@ -0,0 +1,6 @@
1
+ export default function (argv: {
2
+ [arg: string]: any;
3
+ }): Promise<{
4
+ portAlreadyUsed?: boolean;
5
+ ready: Promise<void>;
6
+ }>;
package/dist/index.js ADDED
@@ -0,0 +1,125 @@
1
+ import express from 'express';
2
+ import http from 'http';
3
+ import getPort from 'get-port';
4
+ import socketClusterServer from 'socketcluster-server';
5
+ import getOptions from './options.js';
6
+ import routes from './routes.js';
7
+ import createStore from './store.js';
8
+ // const LOG_LEVEL_NONE = 0;
9
+ // const LOG_LEVEL_ERROR = 1;
10
+ const LOG_LEVEL_WARN = 2;
11
+ const LOG_LEVEL_INFO = 3;
12
+ export default async function (argv) {
13
+ const options = Object.assign(getOptions(argv), {
14
+ allowClientPublish: false,
15
+ });
16
+ const port = options.port;
17
+ const logLevel = options.logLevel === undefined ? LOG_LEVEL_INFO : options.logLevel;
18
+ // Check port already used
19
+ const p = await getPort({ port });
20
+ if (port !== p) {
21
+ if (logLevel >= LOG_LEVEL_WARN) {
22
+ console.log(`[ReduxDevTools] Server port ${port} is already used.`);
23
+ }
24
+ return {
25
+ portAlreadyUsed: true,
26
+ ready: Promise.resolve(),
27
+ };
28
+ }
29
+ if (logLevel >= LOG_LEVEL_INFO) {
30
+ console.log('[ReduxDevTools] Start server...');
31
+ console.log('-'.repeat(80) + '\n');
32
+ }
33
+ const httpServer = http.createServer();
34
+ const agServer = socketClusterServer.attach(httpServer, options);
35
+ const app = express();
36
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
37
+ httpServer.on('request', app);
38
+ const store = createStore(options);
39
+ app.use(routes(options, store, agServer));
40
+ agServer.setMiddleware(agServer.MIDDLEWARE_INBOUND,
41
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
42
+ async (middlewareStream) => {
43
+ for await (const action of middlewareStream) {
44
+ if (action.type === action.TRANSMIT) {
45
+ const channel = action.receiver;
46
+ const data = action.data;
47
+ if (channel.substring(0, 3) === 'sc-' ||
48
+ channel === 'respond' ||
49
+ channel === 'log') {
50
+ void agServer.exchange.transmitPublish(channel, data);
51
+ }
52
+ else if (channel === 'log-noid') {
53
+ void agServer.exchange.transmitPublish('log', {
54
+ id: action.socket.id,
55
+ data: data,
56
+ });
57
+ }
58
+ }
59
+ else if (action.type === action.SUBSCRIBE) {
60
+ if (action.channel === 'report') {
61
+ store
62
+ .list()
63
+ .then(function (data) {
64
+ void agServer.exchange.transmitPublish('report', {
65
+ type: 'list',
66
+ data: data,
67
+ });
68
+ })
69
+ .catch(function (error) {
70
+ console.error(error);
71
+ });
72
+ }
73
+ }
74
+ action.allow();
75
+ }
76
+ });
77
+ void (async () => {
78
+ for await (const { socket } of agServer.listener('connection')) {
79
+ let channelToWatch, channelToEmit;
80
+ void (async () => {
81
+ for await (const request of socket.procedure('login')) {
82
+ const credentials = request.data;
83
+ if (credentials === 'master') {
84
+ channelToWatch = 'respond';
85
+ channelToEmit = 'log';
86
+ }
87
+ else {
88
+ channelToWatch = 'log';
89
+ channelToEmit = 'respond';
90
+ }
91
+ request.end(channelToWatch);
92
+ }
93
+ })();
94
+ void (async () => {
95
+ for await (const request of socket.procedure('getReport')) {
96
+ const id = request.data;
97
+ store
98
+ .get(id)
99
+ .then(function (data) {
100
+ request.end(data);
101
+ })
102
+ .catch(function (error) {
103
+ console.error(error);
104
+ });
105
+ }
106
+ })();
107
+ void (async () => {
108
+ for await (const data of socket.listener('disconnect')) {
109
+ const channel = agServer.exchange.channel('sc-' + socket.id);
110
+ channel.unsubscribe();
111
+ void agServer.exchange.transmitPublish(channelToEmit, {
112
+ id: socket.id,
113
+ type: 'DISCONNECTED',
114
+ });
115
+ }
116
+ })();
117
+ }
118
+ })();
119
+ httpServer.listen(options.port);
120
+ return {
121
+ ready: (async () => {
122
+ await agServer.listener('ready').once();
123
+ })(),
124
+ };
125
+ }
@@ -0,0 +1,29 @@
1
+ interface ProtocolOptions {
2
+ key: string | undefined;
3
+ cert: string | undefined;
4
+ passphrase: string | undefined;
5
+ }
6
+ interface DbOptions {
7
+ client: string;
8
+ connection: {
9
+ filename: string;
10
+ };
11
+ useNullAsDefault: boolean;
12
+ debug: boolean;
13
+ migrate: boolean;
14
+ }
15
+ export interface Options {
16
+ host: string | undefined;
17
+ port: number;
18
+ protocol: 'http' | 'https';
19
+ protocolOptions: ProtocolOptions | undefined;
20
+ dbOptions: DbOptions;
21
+ maxRequestBody: string;
22
+ logHTTPRequests?: boolean;
23
+ logLevel: 0 | 1 | 3 | 2;
24
+ wsEngine: string;
25
+ }
26
+ export default function getOptions(argv: {
27
+ [arg: string]: any;
28
+ }): Options;
29
+ export {};
@@ -0,0 +1,29 @@
1
+ import fs from 'fs';
2
+ export default function getOptions(argv) {
3
+ let dbOptions = argv.dbOptions;
4
+ if (typeof dbOptions === 'string') {
5
+ dbOptions = JSON.parse(fs.readFileSync(dbOptions, 'utf8'));
6
+ }
7
+ else if (typeof dbOptions === 'undefined') {
8
+ dbOptions = JSON.parse(fs.readFileSync(new URL('../defaultDbOptions.json', import.meta.url), 'utf8'));
9
+ }
10
+ return {
11
+ host: argv.hostname || process.env.npm_package_remotedev_hostname || undefined,
12
+ port: Number(argv.port || process.env.npm_package_remotedev_port) || 8000,
13
+ protocol: argv.protocol || process.env.npm_package_remotedev_protocol || 'http',
14
+ protocolOptions: !(argv.protocol === 'https')
15
+ ? undefined
16
+ : {
17
+ key: argv.key || process.env.npm_package_remotedev_key || undefined,
18
+ cert: argv.cert || process.env.npm_package_remotedev_cert || undefined,
19
+ passphrase: argv.passphrase ||
20
+ process.env.npm_package_remotedev_passphrase ||
21
+ undefined,
22
+ },
23
+ dbOptions: dbOptions,
24
+ maxRequestBody: argv.passphrase || '16mb',
25
+ logHTTPRequests: argv.logHTTPRequests,
26
+ logLevel: argv.logLevel || 3,
27
+ wsEngine: argv.wsEngine || process.env.npm_package_remotedev_wsengine || 'ws',
28
+ };
29
+ }
@@ -0,0 +1,5 @@
1
+ import type { Router } from 'express';
2
+ import { AGServer } from 'socketcluster-server';
3
+ import type { Store } from './store.js';
4
+ declare function routes(options: AGServer.AGServerOptions, store: Store, scServer: AGServer): Router;
5
+ export default routes;
package/dist/routes.js ADDED
@@ -0,0 +1,100 @@
1
+ import path from 'path';
2
+ import { createRequire } from 'module';
3
+ import { fileURLToPath } from 'url';
4
+ import express from 'express';
5
+ import morgan from 'morgan';
6
+ import bodyParser from 'body-parser';
7
+ import cors from 'cors';
8
+ import { ApolloServer } from '@apollo/server';
9
+ import { expressMiddleware } from '@as-integrations/express5';
10
+ import { resolvers, schema } from './api/schema.js';
11
+ const app = express.Router();
12
+ const require = createRequire(import.meta.url);
13
+ function serveUmdModule(name) {
14
+ app.use(express.static(path.dirname(require.resolve(name + '/package.json')) + '/umd'));
15
+ }
16
+ function routes(options, store, scServer) {
17
+ const limit = options.maxRequestBody;
18
+ const logHTTPRequests = options.logHTTPRequests;
19
+ if (logHTTPRequests) {
20
+ if (typeof logHTTPRequests === 'object')
21
+ app.use(morgan('combined', logHTTPRequests));
22
+ else
23
+ app.use(morgan('combined'));
24
+ }
25
+ const server = new ApolloServer({
26
+ typeDefs: schema,
27
+ resolvers,
28
+ });
29
+ server
30
+ .start()
31
+ .then(() => {
32
+ app.use('/graphql', cors(), bodyParser.json(), expressMiddleware(server, {
33
+ context: () => Promise.resolve({ store }),
34
+ }));
35
+ })
36
+ .catch((error) => {
37
+ console.error(error);
38
+ });
39
+ serveUmdModule('react');
40
+ serveUmdModule('react-dom');
41
+ serveUmdModule('@redux-devtools/app');
42
+ app.get('/port.js', function (req, res) {
43
+ res.send(`reduxDevToolsPort = ${options.port}`);
44
+ });
45
+ app.get('/{*splat}', function (req, res) {
46
+ res.sendFile(path.join(path.dirname(fileURLToPath(import.meta.url)), '../app/index.html'));
47
+ });
48
+ app.use(cors({ methods: 'POST' }));
49
+ app.use(bodyParser.json({ limit: limit }));
50
+ app.use(bodyParser.urlencoded({ limit: limit, extended: false }));
51
+ app.post('/', function (req, res) {
52
+ if (!req.body) {
53
+ res.status(404).end();
54
+ return;
55
+ }
56
+ switch (req.body.op) {
57
+ case 'get':
58
+ store
59
+ .get(req.body.id)
60
+ .then(function (r) {
61
+ res.send(r || {});
62
+ })
63
+ .catch(function (error) {
64
+ console.error(error);
65
+ res.sendStatus(500);
66
+ });
67
+ break;
68
+ case 'list':
69
+ store
70
+ .list(req.body.query, req.body.fields)
71
+ .then(function (r) {
72
+ res.send(r);
73
+ })
74
+ .catch(function (error) {
75
+ console.error(error);
76
+ res.sendStatus(500);
77
+ });
78
+ break;
79
+ default:
80
+ store
81
+ .add(req.body)
82
+ .then(function (r) {
83
+ res.send({
84
+ id: r.id,
85
+ error: r.error,
86
+ });
87
+ void scServer.exchange.transmitPublish('report', {
88
+ type: 'add',
89
+ data: r,
90
+ });
91
+ })
92
+ .catch(function (error) {
93
+ console.error(error);
94
+ res.status(500).send({});
95
+ });
96
+ }
97
+ });
98
+ return app;
99
+ }
100
+ export default routes;
@@ -0,0 +1,57 @@
1
+ import { AGServer } from 'socketcluster-server';
2
+ type ReportType = 'STATE' | 'ACTION' | 'STATES' | 'ACTIONS';
3
+ export interface Report {
4
+ id: string;
5
+ type: ReportType | null;
6
+ title: string | null;
7
+ description: string | null;
8
+ action: string | null;
9
+ payload: string;
10
+ preloadedState: string | null;
11
+ screenshot: string | null;
12
+ userAgent: string | null;
13
+ version: string | null;
14
+ userId: string | null;
15
+ user: string | null;
16
+ meta: string | null;
17
+ exception: string | null;
18
+ instanceId: string | null;
19
+ added: string | null;
20
+ appId?: string | null;
21
+ }
22
+ export interface ReportBaseFields {
23
+ id: string;
24
+ title: string | null;
25
+ added: string | null;
26
+ }
27
+ export interface AddData {
28
+ type: ReportType | null;
29
+ title: string | null;
30
+ description: string | null;
31
+ action: string | null;
32
+ payload: string;
33
+ preloadedState: string | null;
34
+ screenshot: string | null;
35
+ version: string | null;
36
+ userAgent: string | null;
37
+ userId: string | null;
38
+ user: {
39
+ id: string;
40
+ } | string | null;
41
+ instanceId: string | null;
42
+ meta: string | null;
43
+ exception?: Error;
44
+ appId?: string | null;
45
+ }
46
+ export interface Store {
47
+ list: (query?: string, fields?: string[]) => Promise<ReportBaseFields[]>;
48
+ listAll: (query?: string) => Promise<Report[]>;
49
+ get: (id: string) => Promise<Report | {
50
+ error: string;
51
+ }>;
52
+ add: (data: AddData) => Promise<ReportBaseFields | {
53
+ error: string;
54
+ }>;
55
+ }
56
+ declare function createStore(options: AGServer.AGServerOptions): Store;
57
+ export default createStore;
package/dist/store.js ADDED
@@ -0,0 +1,97 @@
1
+ import { v4 as uuidV4 } from 'uuid';
2
+ import { pick } from 'lodash-es';
3
+ import connector from './db/connector.js';
4
+ const reports = 'remotedev_reports';
5
+ // var payloads = 'remotedev_payloads';
6
+ let knex;
7
+ const baseFields = ['id', 'title', 'added'];
8
+ function error(msg) {
9
+ return new Promise(function (resolve) {
10
+ return resolve({ error: msg });
11
+ });
12
+ }
13
+ function list(query, fields) {
14
+ const r = knex.select(fields || baseFields).from(reports);
15
+ if (query)
16
+ return r.where(query);
17
+ return r;
18
+ }
19
+ function listAll(query) {
20
+ const r = knex.select().from(reports);
21
+ if (query)
22
+ return r.where(query);
23
+ return r;
24
+ }
25
+ function get(id) {
26
+ if (!id)
27
+ return error('No id specified.');
28
+ return knex(reports).where('id', id).first();
29
+ }
30
+ function add(data) {
31
+ if (!data.type || !data.payload) {
32
+ return error("Required parameters aren't specified.");
33
+ }
34
+ if (data.type !== 'ACTIONS' && data.type !== 'STATE') {
35
+ return error('Type ' + data.type + ' is not supported yet.');
36
+ }
37
+ const reportId = uuidV4();
38
+ const report = {
39
+ id: reportId,
40
+ type: data.type,
41
+ title: data.title || (data.exception && data.exception.message) || data.action,
42
+ description: data.description,
43
+ action: data.action,
44
+ payload: data.payload,
45
+ preloadedState: data.preloadedState,
46
+ screenshot: data.screenshot,
47
+ version: data.version,
48
+ userAgent: data.userAgent,
49
+ user: data.user,
50
+ userId: typeof data.user === 'object'
51
+ ? data.user.id
52
+ : data.user,
53
+ instanceId: data.instanceId,
54
+ meta: data.meta,
55
+ exception: composeException(data.exception),
56
+ added: new Date().toISOString(),
57
+ };
58
+ if (data.appId)
59
+ report.appId = data.appId; // TODO check if the id exists and we have access to link it
60
+ /*
61
+ var payload = {
62
+ id: uuid.v4(),
63
+ reportId: reportId,
64
+ state: data.payload
65
+ };
66
+ */
67
+ return knex
68
+ .insert(report)
69
+ .into(reports)
70
+ .then(function () {
71
+ return byBaseFields(report);
72
+ });
73
+ }
74
+ function byBaseFields(data) {
75
+ return pick(data, baseFields);
76
+ }
77
+ function createStore(options) {
78
+ knex = connector(options);
79
+ return {
80
+ list: list,
81
+ listAll: listAll,
82
+ get: get,
83
+ add: add,
84
+ };
85
+ }
86
+ function composeException(exception) {
87
+ let message = '';
88
+ if (exception) {
89
+ message = 'Exception thrown: ';
90
+ if (exception.message)
91
+ message += exception.message;
92
+ if (exception.stack)
93
+ message += '\n' + exception.stack;
94
+ }
95
+ return message;
96
+ }
97
+ export default createStore;
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "@ohos-ports/redux-devtools-cli",
3
+ "version": "5.0.0-beta.0",
4
+ "description": "CLI for remote debugging with Redux DevTools.",
5
+ "homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-cli",
6
+ "bugs": {
7
+ "url": "https://github.com/ohos-ports/ohos-ports/issues"
8
+ },
9
+ "license": "MIT",
10
+ "author": "Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)",
11
+ "files": [
12
+ "bin",
13
+ "dist",
14
+ "src",
15
+ "app",
16
+ "index.js",
17
+ "defaultDbOptions.json"
18
+ ],
19
+ "type": "module",
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "bin": {
23
+ "redux-devtools": "bin/redux-devtools.js"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/ohos-ports/ohos-ports.git",
28
+ "directory": "ports/redux-devtools-cli/5.0.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=20"
32
+ },
33
+ "dependencies": {
34
+ "@apollo/server": "^5.2.0",
35
+ "@as-integrations/express5": "^1.1.2",
36
+ "@emotion/react": "^11.14.0",
37
+ "@emotion/styled": "^11.14.1",
38
+ "@reduxjs/toolkit": "^2.11.2",
39
+ "@types/react": "^19.2.14",
40
+ "body-parser": "^2.2.2",
41
+ "chalk": "^5.6.2",
42
+ "cors": "^2.8.6",
43
+ "cross-spawn": "^7.0.6",
44
+ "electron": "^31.7.7",
45
+ "express": "^5.2.1",
46
+ "get-port": "^7.1.0",
47
+ "graphql": "^16.13.0",
48
+ "knex": "^3.1.0",
49
+ "lodash-es": "^4.17.23",
50
+ "minimist": "^1.2.8",
51
+ "morgan": "^1.10.1",
52
+ "open": "^11.0.0",
53
+ "react": "^19.2.4",
54
+ "react-dom": "^19.2.4",
55
+ "react-is": "^19.2.4",
56
+ "semver": "^7.7.4",
57
+ "socketcluster-server": "^20.0.0",
58
+ "@ohos-ports/sqlite3": "^5.1.7-beta.0",
59
+ "uuid": "^13.0.0",
60
+ "@redux-devtools/app": "^8.0.0"
61
+ },
62
+ "devDependencies": {
63
+ "@jest/globals": "^30.2.0",
64
+ "@types/body-parser": "^1.19.6",
65
+ "@types/cors": "^2.8.19",
66
+ "@types/cross-spawn": "^6.0.6",
67
+ "@types/express": "^5.0.6",
68
+ "@types/jest": "^30.0.0",
69
+ "@types/lodash-es": "^4.17.12",
70
+ "@types/minimist": "^1.2.5",
71
+ "@types/morgan": "^1.9.10",
72
+ "@types/node": "^24.11.0",
73
+ "@types/semver": "^7.7.1",
74
+ "@types/socketcluster-client": "^20.0.0",
75
+ "@types/socketcluster-server": "^20.0.0",
76
+ "@types/supertest": "^7.2.0",
77
+ "@types/uuid": "^11.0.0",
78
+ "globals": "^17.4.0",
79
+ "jest": "^30.2.0",
80
+ "ncp": "^2.0.0",
81
+ "rimraf": "^6.1.3",
82
+ "socketcluster-client": "^20.0.1",
83
+ "supertest": "^7.2.2",
84
+ "ts-jest": "^29.4.6",
85
+ "typescript": "~5.9.3"
86
+ },
87
+ "scripts": {
88
+ "build": "tsc && ncp ./src/api/schema_def.graphql ./dist/api/schema_def.graphql",
89
+ "start": "node ./bin/redux-devtools.js",
90
+ "start:electron": "node ./bin/redux-devtools.js --open",
91
+ "clean": "rimraf dist",
92
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
93
+ "lint": "eslint .",
94
+ "type-check": "tsc --noEmit",
95
+ "prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
96
+ }
97
+ }
@@ -0,0 +1,26 @@
1
+ import fs from 'fs';
2
+ import type { Store } from '../store.js';
3
+
4
+ export const schema = fs.readFileSync(
5
+ new URL('./schema_def.graphql', import.meta.url),
6
+ 'utf8',
7
+ );
8
+
9
+ export const resolvers = {
10
+ Query: {
11
+ reports: function report(
12
+ source: unknown,
13
+ args: unknown,
14
+ context: { store: Store },
15
+ ) {
16
+ return context.store.listAll();
17
+ },
18
+ report: function report(
19
+ source: unknown,
20
+ args: { id: string },
21
+ context: { store: Store },
22
+ ) {
23
+ return context.store.get(args.id);
24
+ },
25
+ },
26
+ };