@appsemble/node-utils 0.20.45 → 0.21.1

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 (104) hide show
  1. package/README.md +3 -3
  2. package/app.d.ts +17 -0
  3. package/app.js +49 -0
  4. package/csv.d.ts +2 -0
  5. package/csv.js +55 -0
  6. package/fs.d.ts +3 -3
  7. package/fs.js +1 -1
  8. package/fs.test.js +5 -5
  9. package/getAppsembleMessages.d.ts +9 -0
  10. package/getAppsembleMessages.js +32 -0
  11. package/handleError.test.js +4 -4
  12. package/icon.d.ts +44 -0
  13. package/icon.js +69 -0
  14. package/index.d.ts +22 -1
  15. package/index.js +22 -1
  16. package/interceptors.test.js +5 -6
  17. package/jsonschema.d.ts +2 -0
  18. package/jsonschema.js +7 -0
  19. package/koa.d.ts +6 -0
  20. package/koa.js +27 -0
  21. package/logger.js +1 -1
  22. package/mergeMessages.d.ts +9 -0
  23. package/mergeMessages.js +17 -0
  24. package/middleware/boom.d.ts +7 -0
  25. package/middleware/boom.js +32 -0
  26. package/middleware/boom.test.js +58 -0
  27. package/middleware/conditional.d.ts +2 -0
  28. package/middleware/conditional.js +9 -0
  29. package/middleware/frontend.d.ts +3 -0
  30. package/middleware/frontend.js +40 -0
  31. package/{loggerMiddleware.js → middleware/loggerMiddleware.js} +1 -1
  32. package/{loggerMiddleware.test.js → middleware/loggerMiddleware.test.js} +14 -15
  33. package/odata.d.ts +18 -0
  34. package/odata.js +45 -0
  35. package/organizationBlocklist.d.ts +4 -0
  36. package/organizationBlocklist.js +5 -0
  37. package/package.json +45 -5
  38. package/parsers.d.ts +3 -0
  39. package/parsers.js +18 -0
  40. package/readAsset.d.ts +13 -0
  41. package/readAsset.js +6 -0
  42. package/readFileOrString.d.ts +1 -1
  43. package/render.d.ts +21 -0
  44. package/render.js +46 -0
  45. package/resource.d.ts +43 -0
  46. package/resource.js +172 -0
  47. package/scim.d.ts +26 -0
  48. package/scim.js +32 -0
  49. package/server/controllers/action.d.ts +7 -0
  50. package/server/controllers/action.js +129 -0
  51. package/server/controllers/appMembers.d.ts +3 -0
  52. package/server/controllers/appMembers.js +19 -0
  53. package/server/controllers/appMessages.d.ts +3 -0
  54. package/server/controllers/appMessages.js +75 -0
  55. package/server/controllers/assets.d.ts +5 -0
  56. package/server/controllers/assets.js +74 -0
  57. package/server/controllers/index.d.ts +7 -0
  58. package/server/controllers/index.js +8 -0
  59. package/server/controllers/oauth2Provider.d.ts +3 -0
  60. package/server/controllers/oauth2Provider.js +7 -0
  61. package/server/controllers/resources.d.ts +8 -0
  62. package/server/controllers/resources.js +273 -0
  63. package/server/controllers/resources.test.d.ts +1 -0
  64. package/server/controllers/resources.test.js +552 -0
  65. package/server/controllers/teams.d.ts +3 -0
  66. package/server/controllers/teams.js +12 -0
  67. package/server/routes/appRouter/blockAssetHandler.d.ts +3 -0
  68. package/server/routes/appRouter/blockAssetHandler.js +13 -0
  69. package/server/routes/appRouter/blockCssHandler.d.ts +3 -0
  70. package/server/routes/appRouter/blockCssHandler.js +15 -0
  71. package/server/routes/appRouter/bulmaHandler.d.ts +3 -0
  72. package/server/routes/appRouter/bulmaHandler.js +118 -0
  73. package/server/routes/appRouter/cssHandler.d.ts +3 -0
  74. package/server/routes/appRouter/cssHandler.js +12 -0
  75. package/server/routes/appRouter/iconHandler.d.ts +3 -0
  76. package/server/routes/appRouter/iconHandler.js +23 -0
  77. package/server/routes/appRouter/index.d.ts +3 -0
  78. package/server/routes/appRouter/index.js +85 -0
  79. package/server/routes/appRouter/indexHandler.d.ts +5 -0
  80. package/server/routes/appRouter/indexHandler.js +69 -0
  81. package/server/routes/appRouter/manifestHandler.d.ts +3 -0
  82. package/server/routes/appRouter/manifestHandler.js +47 -0
  83. package/server/routes/appRouter/robotsHandler.d.ts +2 -0
  84. package/server/routes/appRouter/robotsHandler.js +6 -0
  85. package/server/routes/appRouter/screenshotHandler.d.ts +3 -0
  86. package/server/routes/appRouter/screenshotHandler.js +19 -0
  87. package/server/routes/appRouter/serviceWorkerHandler.d.ts +3 -0
  88. package/server/routes/appRouter/serviceWorkerHandler.js +18 -0
  89. package/server/routes/appRouter/staticHandler.d.ts +2 -0
  90. package/server/routes/appRouter/staticHandler.js +9 -0
  91. package/server/types.d.ts +356 -0
  92. package/server/types.js +2 -0
  93. package/testFixtures.d.ts +3 -3
  94. package/testFixtures.js +1 -1
  95. package/tinyRouter.d.ts +16 -0
  96. package/tinyRouter.js +34 -0
  97. package/tinyRouter.test.d.ts +1 -0
  98. package/tinyRouter.test.js +85 -0
  99. package/vitest.config.d.ts +2 -0
  100. package/vitest.setup.d.ts +1 -0
  101. package/{jest.setup.js → vitest.setup.js} +1 -1
  102. /package/{jest.setup.d.ts → middleware/boom.test.d.ts} +0 -0
  103. /package/{loggerMiddleware.d.ts → middleware/loggerMiddleware.d.ts} +0 -0
  104. /package/{loggerMiddleware.test.d.ts → middleware/loggerMiddleware.test.d.ts} +0 -0
@@ -0,0 +1,58 @@
1
+ import { notFound, unauthorized } from '@hapi/boom';
2
+ import { request, setTestApp } from 'axios-test-instance';
3
+ import Koa from 'koa';
4
+ import { boomMiddleware } from './boom.js';
5
+ describe('boomMiddleware', () => {
6
+ let app;
7
+ beforeEach(async () => {
8
+ app = new Koa();
9
+ app.use(boomMiddleware());
10
+ await setTestApp(app);
11
+ });
12
+ it('should catch boom errors', async () => {
13
+ app.use(() => {
14
+ throw notFound('Error not found', "It's nowhere to be seen!");
15
+ });
16
+ const response = await request.get('/');
17
+ expect(response).toMatchObject({
18
+ status: 404,
19
+ data: {
20
+ statusCode: 404,
21
+ error: 'Not Found',
22
+ message: 'Error not found',
23
+ data: "It's nowhere to be seen!",
24
+ },
25
+ });
26
+ });
27
+ it('should rethrow non-boom errors', async () => {
28
+ app.silent = true;
29
+ const error = new Error('This is a test error');
30
+ let result;
31
+ app.use(() => {
32
+ throw error;
33
+ });
34
+ app.on('error', (err) => {
35
+ result = err;
36
+ });
37
+ await request.get('/');
38
+ expect(result).toBe(error);
39
+ });
40
+ it('should set Koa headers correctly', async () => {
41
+ app.use(() => {
42
+ throw unauthorized('Not authorized!', ['Basic realm="Access to test data", charset="UTF-8"']);
43
+ });
44
+ const response = await request.get('/');
45
+ expect(response).toMatchObject({
46
+ status: 401,
47
+ headers: expect.objectContaining({
48
+ 'www-authenticate': 'Basic realm="Access to test data", charset="UTF-8"',
49
+ }),
50
+ data: {
51
+ statusCode: 401,
52
+ error: 'Unauthorized',
53
+ message: 'Not authorized!',
54
+ },
55
+ });
56
+ });
57
+ });
58
+ //# sourceMappingURL=boom.test.js.map
@@ -0,0 +1,2 @@
1
+ import { type Context, type Middleware } from 'koa';
2
+ export declare function conditional(check: (ctx: Context) => boolean, middleware: Middleware): Middleware;
@@ -0,0 +1,9 @@
1
+ export function conditional(check, middleware) {
2
+ return (ctx, next) => {
3
+ if (check(ctx)) {
4
+ return middleware(ctx, next);
5
+ }
6
+ return next();
7
+ };
8
+ }
9
+ //# sourceMappingURL=conditional.js.map
@@ -0,0 +1,3 @@
1
+ import { type Middleware } from 'koa';
2
+ import { type Configuration } from 'webpack';
3
+ export declare function frontend(webpackConfigs: Configuration[], argv: Record<string, any>, serveStudio?: boolean): Promise<Middleware>;
@@ -0,0 +1,40 @@
1
+ import { isIP } from 'node:net';
2
+ import expressToKoa from 'express-to-koa';
3
+ import compose from 'koa-compose';
4
+ import webpack from 'webpack';
5
+ import webpackDevMiddleware from 'webpack-dev-middleware';
6
+ /**
7
+ * Bypass the dev server for API requests to speed them up.
8
+ */
9
+ const skipRoute = /^\/(api|oauth2\/token)/;
10
+ export async function frontend(webpackConfigs, argv, serveStudio = false) {
11
+ // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
12
+ // @ts-ignore Because the webpack core config isn’t built when building the server, an error is
13
+ // expected here at build time, but while type checking.
14
+ const { createAppConfig, createStudioConfig } = await import('@appsemble/webpack-core');
15
+ const configApp = createAppConfig({ mode: 'development' });
16
+ const configStudio = createStudioConfig({ mode: 'development' });
17
+ const configs = [configApp, ...(serveStudio ? [configStudio] : []), ...webpackConfigs];
18
+ const compiler = webpack(configs);
19
+ const devMiddleware = webpackDevMiddleware(compiler, { serverSideRender: true });
20
+ // @ts-expect-error outputFileSystem exists despite what the types say.
21
+ const fs = devMiddleware.context.outputFileSystem;
22
+ const koaDevMiddleware = expressToKoa(devMiddleware);
23
+ return compose([
24
+ (ctx, next) => {
25
+ ctx.fs = fs;
26
+ return next();
27
+ },
28
+ (ctx, next) => {
29
+ const { hostname } = ctx;
30
+ if (!skipRoute.test(ctx.path)) {
31
+ return koaDevMiddleware(ctx, next);
32
+ }
33
+ if (new URL(argv.host).hostname === hostname || isIP(hostname)) {
34
+ return next();
35
+ }
36
+ return koaDevMiddleware(ctx, next);
37
+ },
38
+ ]);
39
+ }
40
+ //# sourceMappingURL=frontend.js.map
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { logger } from './logger.js';
2
+ import { logger } from '../logger.js';
3
3
  function rangeFormat(value, map) {
4
4
  return Object.entries(map).find(([v]) => v === 'default' || value < Number(v))[1];
5
5
  }
@@ -1,8 +1,7 @@
1
+ import { logger, loggerMiddleware } from '@appsemble/node-utils';
1
2
  import { request, setTestApp } from 'axios-test-instance';
2
3
  import chalk from 'chalk';
3
4
  import Koa from 'koa';
4
- import { logger } from './logger.js';
5
- import { loggerMiddleware } from './loggerMiddleware.js';
6
5
  class TestError extends Error {
7
6
  constructor(message) {
8
7
  super(message);
@@ -11,9 +10,9 @@ class TestError extends Error {
11
10
  }
12
11
  let app;
13
12
  beforeEach(async () => {
14
- import.meta.jest.spyOn(logger, 'info').mockImplementation(() => logger);
15
- import.meta.jest.spyOn(logger, 'log').mockImplementation(() => logger);
16
- import.meta.jest.useFakeTimers({ now: 0 });
13
+ vi.spyOn(logger, 'info').mockImplementation(() => logger);
14
+ vi.spyOn(logger, 'log').mockImplementation(() => logger);
15
+ vi.useFakeTimers();
17
16
  app = new Koa();
18
17
  app.use(async (ctx, next) => {
19
18
  Object.defineProperty(ctx.request, 'origin', {
@@ -40,7 +39,7 @@ it('should log requests', async () => {
40
39
  });
41
40
  it('should log success responses as info', async () => {
42
41
  app.use((ctx) => {
43
- import.meta.jest.advanceTimersByTime(1);
42
+ vi.advanceTimersByTime(1);
44
43
  ctx.status = 200;
45
44
  });
46
45
  await request.get('/fries');
@@ -48,7 +47,7 @@ it('should log success responses as info', async () => {
48
47
  });
49
48
  it('should log redirect responses as info', async () => {
50
49
  app.use((ctx) => {
51
- import.meta.jest.advanceTimersByTime(33);
50
+ vi.advanceTimersByTime(33);
52
51
  ctx.redirect('/');
53
52
  });
54
53
  await request.get('/fries');
@@ -56,7 +55,7 @@ it('should log redirect responses as info', async () => {
56
55
  });
57
56
  it('should log bad responses as warn', async () => {
58
57
  app.use((ctx) => {
59
- import.meta.jest.advanceTimersByTime(3);
58
+ vi.advanceTimersByTime(3);
60
59
  ctx.status = 400;
61
60
  });
62
61
  await request.get('/burrito');
@@ -64,7 +63,7 @@ it('should log bad responses as warn', async () => {
64
63
  });
65
64
  it('should log error responses as error', async () => {
66
65
  app.use((ctx) => {
67
- import.meta.jest.advanceTimersByTime(53);
66
+ vi.advanceTimersByTime(53);
68
67
  ctx.status = 503;
69
68
  });
70
69
  await request.get('/wrap');
@@ -72,7 +71,7 @@ it('should log error responses as error', async () => {
72
71
  });
73
72
  it('should log long request lengths yellow', async () => {
74
73
  app.use((ctx) => {
75
- import.meta.jest.advanceTimersByTime(400);
74
+ vi.advanceTimersByTime(400);
76
75
  ctx.status = 200;
77
76
  });
78
77
  await request.get('/banana');
@@ -80,19 +79,19 @@ it('should log long request lengths yellow', async () => {
80
79
  });
81
80
  it('should log extremely long request lengths red', async () => {
82
81
  app.use((ctx) => {
83
- import.meta.jest.advanceTimersByTime(1337);
82
+ vi.advanceTimersByTime(1337);
84
83
  ctx.status = 200;
85
84
  });
86
85
  await request.get('/pepperoni');
87
86
  expect(logger.log).toHaveBeenCalledWith('info', `${chalk.bold('GET')} https://example.com:1337/pepperoni ${chalk.green('200 OK')} ${chalk.red('1337ms')}`);
88
87
  });
89
88
  it('should log errors as internal server errors and rethrow', async () => {
90
- const spy = import.meta.jest.fn();
89
+ const spy = vi.fn();
91
90
  const error = new Error('fail');
92
91
  let context;
93
92
  app.on('error', spy);
94
93
  app.use((ctx) => {
95
- import.meta.jest.advanceTimersByTime(86);
94
+ vi.advanceTimersByTime(86);
96
95
  context = ctx;
97
96
  throw error;
98
97
  });
@@ -102,7 +101,7 @@ it('should log errors as internal server errors and rethrow', async () => {
102
101
  });
103
102
  it('should append the response length if it is defined', async () => {
104
103
  app.use((ctx) => {
105
- import.meta.jest.advanceTimersByTime(1);
104
+ vi.advanceTimersByTime(1);
106
105
  ctx.status = 200;
107
106
  ctx.body = '{}';
108
107
  });
@@ -111,7 +110,7 @@ it('should append the response length if it is defined', async () => {
111
110
  });
112
111
  it('should log handled errors correctly', async () => {
113
112
  app.use(() => {
114
- import.meta.jest.advanceTimersByTime(15);
113
+ vi.advanceTimersByTime(15);
115
114
  throw new TestError();
116
115
  });
117
116
  await request.get('potatoes');
package/odata.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { type Token } from '@odata/parser';
2
+ export declare enum Edm {
3
+ null = "null",
4
+ Boolean = "Edm.Boolean",
5
+ Byte = "Edm.Byte",
6
+ Date = "Edm.Date",
7
+ DateTimeOffset = "Edm.DateTimeOffset",
8
+ Decimal = "Edm.Decimal",
9
+ Double = "Edm.Double",
10
+ Guid = "Edm.Guid",
11
+ Int16 = "Edm.Int16",
12
+ Int32 = "Edm.Int32",
13
+ Int64 = "Edm.Int64",
14
+ SByte = "Edm.SByte",
15
+ Single = "Edm.Single",
16
+ String = "Edm.String"
17
+ }
18
+ export declare function processLiteral(token: Token): Date | boolean | number | string;
package/odata.js ADDED
@@ -0,0 +1,45 @@
1
+ export var Edm;
2
+ (function (Edm) {
3
+ Edm["null"] = "null";
4
+ Edm["Boolean"] = "Edm.Boolean";
5
+ Edm["Byte"] = "Edm.Byte";
6
+ Edm["Date"] = "Edm.Date";
7
+ Edm["DateTimeOffset"] = "Edm.DateTimeOffset";
8
+ Edm["Decimal"] = "Edm.Decimal";
9
+ Edm["Double"] = "Edm.Double";
10
+ Edm["Guid"] = "Edm.Guid";
11
+ Edm["Int16"] = "Edm.Int16";
12
+ Edm["Int32"] = "Edm.Int32";
13
+ Edm["Int64"] = "Edm.Int64";
14
+ Edm["SByte"] = "Edm.SByte";
15
+ Edm["Single"] = "Edm.Single";
16
+ Edm["String"] = "Edm.String";
17
+ })(Edm || (Edm = {}));
18
+ export function processLiteral(token) {
19
+ switch (token.value) {
20
+ case Edm.Boolean:
21
+ return token.raw === 'true';
22
+ case Edm.String:
23
+ return JSON.parse(`"${token.raw.slice(1, -1).replaceAll('"', '\\"')}"`);
24
+ case Edm.Byte:
25
+ case Edm.Decimal:
26
+ case Edm.Double:
27
+ case Edm.Int16:
28
+ case Edm.Int32:
29
+ case Edm.Int64:
30
+ case Edm.SByte:
31
+ case Edm.Single:
32
+ return Number(token.raw);
33
+ case Edm.Date:
34
+ case Edm.DateTimeOffset:
35
+ // The Date constructor will convert it to UTC.
36
+ return new Date(token.raw);
37
+ case Edm.Guid:
38
+ return token.raw;
39
+ case Edm.null:
40
+ return null;
41
+ default:
42
+ throw new TypeError(`${token.position}: Unhandled OData literal type: ${token.value}`);
43
+ }
44
+ }
45
+ //# sourceMappingURL=odata.js.map
@@ -0,0 +1,4 @@
1
+ /**
2
+ * A list of organization names that are not allowed to be used.
3
+ */
4
+ export declare const organizationBlocklist: string[];
@@ -0,0 +1,5 @@
1
+ /**
2
+ * A list of organization names that are not allowed to be used.
3
+ */
4
+ export const organizationBlocklist = ['www', 'api', 'email', 'mail', 'app'];
5
+ //# sourceMappingURL=organizationBlocklist.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsemble/node-utils",
3
- "version": "0.20.45",
3
+ "version": "0.21.1",
4
4
  "description": "NodeJS utilities used by Appsemble internally.",
5
5
  "keywords": [
6
6
  "app",
@@ -28,31 +28,71 @@
28
28
  ],
29
29
  "scripts": {
30
30
  "prepack": "tsc --noEmit false",
31
- "test": "NODE_OPTIONS=--experimental-vm-modules jest"
31
+ "test": "vitest"
32
32
  },
33
33
  "dependencies": {
34
- "@appsemble/utils": "0.20.45",
34
+ "@appsemble/types": "0.21.1",
35
+ "@appsemble/utils": "0.21.1",
36
+ "@formatjs/fast-memoize": "^2.0.0",
37
+ "@fortawesome/fontawesome-free": "^6.0.0",
38
+ "@hapi/boom": "^10.0.0",
39
+ "@koa/cors": "^4.0.0",
40
+ "@odata/parser": "^0.2.0",
35
41
  "@types/koa": "^2.0.0",
36
42
  "axios": "^1.0.0",
43
+ "bulma": "0.9.3",
44
+ "bulma-checkradio": "2.1.1",
45
+ "bulma-slider": "2.0.4",
46
+ "bulma-switch": "^2.0.0",
37
47
  "chalk": "^4.0.0",
38
48
  "cli-highlight": "^2.0.0",
49
+ "csv-parse": "^5.0.0",
50
+ "date-fns": "^2.0.0",
51
+ "express-to-koa": "^2.0.0",
39
52
  "fast-glob": "^3.0.0",
40
53
  "form-data": "^4.0.0",
54
+ "intl-messageformat": "^10.0.0",
55
+ "jsonschema": "^1.0.0",
41
56
  "koa": "^2.0.0",
42
57
  "koa-compose": "^4.0.0",
58
+ "koa-compress": "^5.0.0",
59
+ "koa-mount": "^4.0.0",
60
+ "koa-range": "^0.3.0",
61
+ "koa-static": "^5.0.0",
62
+ "koas-body-parser": "^0.7.0",
63
+ "koas-core": "^0.7.0",
64
+ "koas-operations": "^0.7.0",
65
+ "koas-parameters": "^0.7.0",
66
+ "koas-security": "^0.7.0",
67
+ "koas-serializer": "^0.7.0",
68
+ "koas-status-code": "^0.7.0",
69
+ "koas-swagger-ui": "^0.7.0",
70
+ "language-tags": "^1.0.0",
71
+ "lodash-es": "^4.0.0",
43
72
  "logform": "^2.0.0",
73
+ "mime-types": "^2.0.0",
74
+ "mustache": "^4.0.0",
75
+ "parse-duration": "^1.0.0",
44
76
  "parse-json": "^7.0.0",
77
+ "sass": "^1.0.0",
78
+ "sharp": "^0.32.0",
45
79
  "sort-keys": "^5.0.0",
46
- "type-fest": "^3.0.0",
80
+ "strip-bom": "^5.0.0",
81
+ "type-fest": "^4.0.0",
82
+ "webpack": "^5.0.0",
83
+ "webpack-dev-middleware": "^6.0.0",
47
84
  "winston": "^3.0.0",
48
85
  "yaml": "^2.0.0",
49
86
  "yargs": "^17.0.0"
50
87
  },
51
88
  "devDependencies": {
52
89
  "@sinonjs/fake-timers": "^10.0.0",
90
+ "@types/koa-compress": "^4.0.0",
91
+ "@types/koa-range": "^0.3.0",
53
92
  "@types/sinonjs__fake-timers": "^8.0.0",
54
93
  "axios-mock-adapter": "^1.0.0",
55
- "axios-test-instance": "^7.0.0"
94
+ "axios-test-instance": "^7.0.0",
95
+ "vitest": "^0.33.0"
56
96
  },
57
97
  "peerDependencies": {
58
98
  "prettier": "^2"
package/parsers.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { type Parser } from 'koas-body-parser';
2
+ export declare const xWwwFormUrlencodedParser: Parser<unknown>;
3
+ export declare const csvParser: Parser<unknown[]>;
package/parsers.js ADDED
@@ -0,0 +1,18 @@
1
+ import { parse } from 'node:querystring';
2
+ import { parse as parseCSV } from 'csv-parse';
3
+ import { bufferParser } from 'koas-body-parser';
4
+ export const xWwwFormUrlencodedParser = async (body, mediaTypeObject, options, ctx) => {
5
+ const buffer = await bufferParser(body, mediaTypeObject, options, ctx);
6
+ return parse(String(buffer));
7
+ };
8
+ export const csvParser = (body) => new Promise((resolve, reject) => {
9
+ body.pipe(parseCSV({ bom: true, columns: true }, (error, records) => {
10
+ if (error) {
11
+ reject(error);
12
+ }
13
+ else {
14
+ resolve(records);
15
+ }
16
+ }));
17
+ });
18
+ //# sourceMappingURL=parsers.js.map
package/readAsset.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
3
+ export declare const assetDir: import("url").URL;
4
+ /**
5
+ * Read a file from the server assets directory.
6
+ *
7
+ * @param filename The path to the file to read, relative to the assets directory.
8
+ *
9
+ * param encoding If specified, decode the file using this encoding.
10
+ * @returns The content of the specified file.
11
+ */
12
+ export declare function readAsset(filename: string): Promise<Buffer>;
13
+ export declare function readAsset(filename: string, encoding: BufferEncoding): Promise<string>;
package/readAsset.js ADDED
@@ -0,0 +1,6 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ export const assetDir = new URL('assets/', import.meta.url);
3
+ export function readAsset(filename, encoding) {
4
+ return readFile(new URL(filename, assetDir), encoding);
5
+ }
6
+ //# sourceMappingURL=readAsset.js.map
@@ -1,4 +1,4 @@
1
- /// <reference types="node" />
1
+ /// <reference types="node" resolution-mode="require"/>
2
2
  /**
3
3
  * If the string represents an existing path, read the file. Otherwise, return the string itself.
4
4
  *
package/render.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { type ContentSecurityPolicy } from '@appsemble/node-utils';
2
+ import { type Context } from 'koa';
3
+ /**
4
+ * Render settings as an HTML script tag.
5
+ *
6
+ * @param settings The settings to render. This must be a JSON serializable object.
7
+ * @param statements Custom JavaScript statements to append.
8
+ * @returns A tuple of the digest and the HTML script tag. The digest should be added to the CSP
9
+ * `script-src`.
10
+ */
11
+ export declare function createSettings(settings: unknown, statements?: string[]): [digest: string, script: string];
12
+ export declare function render(ctx: Context, filename: string, data: Record<string, unknown>): Promise<void>;
13
+ /**
14
+ * Convert a CSP key / values pair object into a real content security policy string.
15
+ *
16
+ * Any falsy values will be excluded.
17
+ *
18
+ * @param csp The CSP object to convert
19
+ * @returns The CSP object as a string
20
+ */
21
+ export declare function makeCSP(csp: ContentSecurityPolicy): string;
package/render.js ADDED
@@ -0,0 +1,46 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { compareStrings } from '@appsemble/utils';
4
+ import mustache from 'mustache';
5
+ /**
6
+ * Render settings as an HTML script tag.
7
+ *
8
+ * @param settings The settings to render. This must be a JSON serializable object.
9
+ * @param statements Custom JavaScript statements to append.
10
+ * @returns A tuple of the digest and the HTML script tag. The digest should be added to the CSP
11
+ * `script-src`.
12
+ */
13
+ export function createSettings(settings, statements = []) {
14
+ const script = [`window.settings=${JSON.stringify(settings)}`, ...statements].join(';');
15
+ const hash = createHash('sha256').update(script, 'utf8').digest('base64');
16
+ return [`'sha256-${hash}'`, `<script>${script}</script>`];
17
+ }
18
+ export async function render(ctx, filename, data) {
19
+ if (process.env.NODE_ENV === 'test') {
20
+ ctx.body = JSON.stringify({ filename, data });
21
+ }
22
+ else {
23
+ const template = await (process.env.NODE_ENV === 'production'
24
+ ? readFile(new URL(`../../dist/${filename}`, import.meta.url), 'utf8')
25
+ : ctx.fs.promises.readFile(`/${filename}`, 'utf8'));
26
+ ctx.body = mustache.render(template, data);
27
+ }
28
+ ctx.type = 'html';
29
+ }
30
+ /**
31
+ * Convert a CSP key / values pair object into a real content security policy string.
32
+ *
33
+ * Any falsy values will be excluded.
34
+ *
35
+ * @param csp The CSP object to convert
36
+ * @returns The CSP object as a string
37
+ */
38
+ export function makeCSP(csp) {
39
+ return Object.entries(csp)
40
+ .map(([key, values]) => [key, values.filter(Boolean)])
41
+ .filter(([, values]) => values === null || values === void 0 ? void 0 : values.length)
42
+ .sort(([a], [b]) => a.localeCompare(b))
43
+ .map(([key, values]) => `${key} ${[...new Set(values)].sort(compareStrings).join(' ')}`)
44
+ .join('; ');
45
+ }
46
+ //# sourceMappingURL=render.js.map
package/resource.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { type PreparedAsset } from '@appsemble/node-utils';
2
+ import { type App, type ResourceDefinition } from '@appsemble/types';
3
+ import { type PreValidatePropertyFunction } from 'jsonschema';
4
+ import { type Context } from 'koa';
5
+ import { type File } from 'koas-body-parser';
6
+ /**
7
+ * Get the resource definition of an app by name.
8
+ *
9
+ * If there is no match, a 404 HTTP error is thrown.
10
+ *
11
+ * @param app The app to get the resource definition of
12
+ * @param resourceType The name of the resource definition to get.
13
+ * @param view The view that’s being used.
14
+ * @returns The matching resource definition.
15
+ */
16
+ export declare function getResourceDefinition(app: App, resourceType: string, view?: string): ResourceDefinition;
17
+ /**
18
+ * Extracts the IDs of resource request body.
19
+ *
20
+ * @param ctx The Koa context to extract the body from.
21
+ * @returns A tuple which consists of:
22
+ *
23
+ * 1. One or more resources processed from the request body.
24
+ * 2. A list of newly uploaded assets which should be linked to the resources.
25
+ * 3. preValidateProperty function used for reconstructing resources from a CSV file.
26
+ */
27
+ export declare function extractResourceBody(ctx: Context): [Record<string, unknown> | Record<string, unknown>[], File[], PreValidatePropertyFunction];
28
+ /**
29
+ * Process an incoming resource request body.
30
+ *
31
+ * This handles JSON schema validation, resource expiration, and asset linking and validation.
32
+ *
33
+ * @param ctx The Koa context to process.
34
+ * @param definition The resource definition to use for processing the request body.
35
+ * @param knownAssetIds A list of asset IDs that are already known to be linked to the resource.
36
+ * @param knownExpires A previously known expires value.
37
+ * @returns A tuple which consists of:
38
+ *
39
+ * 1. One or more resources processed from the request body.
40
+ * 2. A list of newly uploaded assets which should be linked to the resources.
41
+ * 3. Asset IDs from the `knownAssetIds` array which are no longer used.
42
+ */
43
+ export declare function processResourceBody(ctx: Context, definition: ResourceDefinition, knownAssetIds?: string[], knownExpires?: Date): [Record<string, unknown> | Record<string, unknown>[], PreparedAsset[], string[]];