@leonid-shutov/uncommonjs 1.0.0-alpha.5 → 1.0.0-alpha.6

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/README.md CHANGED
@@ -1 +1,41 @@
1
- Loader
1
+ # uncommon‑js ⚙️
2
+
3
+ **uncommon‑js** — a utility library for Node.js that provides a custom module system based on code sandboxing, using isolated V8 contexts.
4
+
5
+ # Example
6
+
7
+ ```js
8
+ ({
9
+ create: {
10
+ description: 'Creating a book',
11
+ method: repository.book.create,
12
+ expectedErrors: {
13
+ BOOK_ALREADY_EXISTS: PASS,
14
+ },
15
+ },
16
+ getByCode: {
17
+ description: 'Getting a book by code',
18
+ method: async (code) => {
19
+ const book = await repository.book.getByCode(code);
20
+ if (book === null) throw NotFoundError.from('book', { meta: { code } });
21
+ return book;
22
+ },
23
+ expectedErrors: {
24
+ BOOK_NOT_FOUND: PASS,
25
+ },
26
+ },
27
+ });
28
+ ```
29
+
30
+ ```js
31
+ ({
32
+ create: {
33
+ method: ({ code, name }) => db.pg.query(`INSERT INTO "public"."Book" (code, name) VALUES ($1, $2)`, [code, name]),
34
+ expectedErrors: {
35
+ 23505: AlreadyExistsError.from('book'),
36
+ },
37
+ },
38
+ getByCode: async (code) =>
39
+ (await db.pg.query(`SELECT * FROM "public"."Book" WHERE code = $1`, [code])).rows[0] ?? null,
40
+ });
41
+ ```
package/eslint.config.js CHANGED
@@ -2,4 +2,22 @@
2
2
 
3
3
  const init = require('eslint-config-metarhia');
4
4
 
5
- module.exports = [...init, { rules: { 'no-extra-parens': 'off' } }];
5
+ module.exports = [
6
+ ...init,
7
+ {
8
+ files: ['lib/**/*.js', 'test/**/*.js'],
9
+ rules: {
10
+ strict: 'off',
11
+ 'max-len': ['error', { code: 120, ignoreUrls: true }],
12
+ 'no-nested-ternary': 'off',
13
+ camelcase: 'off',
14
+ curly: 'off',
15
+ },
16
+ languageOptions: {
17
+ globals: {
18
+ self: true,
19
+ $: true,
20
+ },
21
+ },
22
+ },
23
+ ];
package/lib/deps.js CHANGED
@@ -9,10 +9,7 @@ const loadNpm = () => {
9
9
  return Object.fromEntries(modules.map((module) => [module, require(module)]));
10
10
  };
11
11
 
12
- const loadInternals = () =>
13
- Object.fromEntries(
14
- builtinModules.map((name) => [name, require(`node:${name}`)]),
15
- );
12
+ const loadInternals = () => Object.fromEntries(builtinModules.map((name) => [name, require(`node:${name}`)]));
16
13
 
17
14
  const loadModules = () => ({
18
15
  npm: loadNpm(),
package/lib/errors.js CHANGED
@@ -25,8 +25,7 @@ const UnexpectedError = createDomainError('UnexpectedError', {
25
25
  message: 'Unexpected Error',
26
26
  code: 'UNEXPECTED_ERROR',
27
27
  });
28
- UnexpectedError.from = (description) =>
29
- new UnexpectedError(`Unexpected error while ${description.toLowerCase()}`);
28
+ UnexpectedError.from = (description) => new UnexpectedError(`Unexpected error while ${description.toLowerCase()}`);
30
29
 
31
30
  const NotFoundError = createDomainError('NotFoundError', {
32
31
  message: 'Not Found',
package/lib/loader.js CHANGED
@@ -7,22 +7,45 @@ const { isService, loadService } = require('./service.js');
7
7
 
8
8
  const removeIndex = (key) => key.replace(/^\d+-/, '');
9
9
 
10
- const loadFile = async (context, container, filePath, key) => {
11
- key ??= path.basename(filePath, '.js');
10
+ const runInContext = async (filePath, context) => {
12
11
  const src = await fsp.readFile(filePath, 'utf8');
13
12
  const code = `'use strict';\n${src}`;
14
13
  const script = new vm.Script(code);
15
- const sandbox = { ...context, module: container };
16
- const result = await script.runInContext(vm.createContext(sandbox));
14
+ return await script.runInContext(vm.createContext(context));
15
+ };
16
+
17
+ const loadEntry = (definition, context) => {
18
+ if (isService(definition)) return loadService(definition, context);
19
+ else return definition;
20
+ };
21
+
22
+ const loadGetter = async (context, container, filePath, name) => {
23
+ const sandbox = { ...context, $: container };
24
+ const fun = await runInContext(filePath, sandbox);
25
+ if (fun === undefined) return;
26
+ Object.assign(container, {
27
+ get [name]() {
28
+ return fun();
29
+ },
30
+ });
31
+ };
32
+
33
+ const loadFile = async (context, container, filePath, key) => {
34
+ key ??= path.basename(filePath, '.js');
35
+
36
+ const sandbox = { ...context, $: container };
37
+ const result = await runInContext(filePath, sandbox);
38
+
17
39
  if (result === undefined) return;
40
+
18
41
  for (const [method, definition] of Object.entries(result)) {
19
- if (isService(definition, context)) {
20
- result[method] = loadService(definition, context);
21
- }
42
+ result[method] = loadEntry(definition, context);
22
43
  }
44
+
23
45
  if (container[key] === undefined) container[key] = result;
24
46
  else Object.assign(container[key], result);
25
47
  sandbox.self = container[key];
48
+ sandbox._ = container[key];
26
49
  };
27
50
 
28
51
  const loadDir = async (context, container, dirPath, key) => {
@@ -42,7 +65,8 @@ const loadDir = async (context, container, dirPath, key) => {
42
65
  const location = path.join(dirPath, name);
43
66
  let basename = path.basename(name, '.js');
44
67
  basename = removeIndex(basename);
45
- if (basename === key) await loadFile(context, container, location, key);
68
+ if (key === 'getters') await loadGetter(context, container, location, basename);
69
+ else if (basename === key) await loadFile(context, container, location, key);
46
70
  else await loadFile(context, nextContainer, location, basename);
47
71
  }
48
72
 
package/lib/rest.js CHANGED
@@ -2,12 +2,7 @@
2
2
 
3
3
  const { Schema } = require('metaschema');
4
4
 
5
- const {
6
- NotFoundError,
7
- AlreadyExistsError,
8
- ConstraintViolationError,
9
- AuthorizationError,
10
- } = require('./errors.js');
5
+ const { NotFoundError, AlreadyExistsError, ConstraintViolationError, AuthorizationError } = require('./errors.js');
11
6
  const { loadApplication } = require('./application.js');
12
7
 
13
8
  class ValidationError extends AggregateError {}
package/lib/service.js CHANGED
@@ -1,14 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const isService = (definition, context) =>
4
- definition?.method !== undefined && context.PASS !== undefined;
3
+ const isService = (definition) => definition?.method !== undefined;
5
4
 
6
5
  const loadService = (definition, context) => {
7
6
  const { description, method } = definition;
8
7
  const logger = context.logger ?? context.console ?? { info: () => {} };
9
8
  return (...args) => {
10
- const interpolatedDesc =
11
- typeof description === 'function' ? description(...args) : description;
9
+ const interpolatedDesc = typeof description === 'function' ? description(...args) : description;
12
10
  logger.info(interpolatedDesc);
13
11
 
14
12
  const handleError = (error) => {
@@ -17,9 +15,7 @@ const loadService = (definition, context) => {
17
15
  if (domainError === context.PASS) throw error;
18
16
  if (domainError === undefined) {
19
17
  domainError =
20
- description !== undefined
21
- ? context.UnexpectedError.from(interpolatedDesc)
22
- : new context.UnexpectedError();
18
+ description !== undefined ? context.UnexpectedError.from(interpolatedDesc) : new context.UnexpectedError();
23
19
  }
24
20
  domainError.cause = error;
25
21
  throw domainError;
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@leonid-shutov/uncommonjs",
3
- "version": "1.0.0-alpha.5",
3
+ "version": "1.0.0-alpha.6",
4
4
  "author": "Leonid Shutov <leonid.shutov.main@gmail.com>",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/leonid-shutov/uncommonjs#readme",
7
7
  "main": "uncommon.js",
8
8
  "scripts": {
9
- "lint": "eslint lib/"
9
+ "lint": "eslint .",
10
+ "fmt": "prettier --write ."
10
11
  },
11
12
  "repository": {
12
13
  "type": "git",
@@ -17,8 +18,9 @@
17
18
  },
18
19
  "devDependencies": {
19
20
  "@types/node": "^20.17.30",
20
- "eslint": "^9.31.0",
21
- "eslint-config-metarhia": "^9.1.1",
21
+ "typescript": "^5.9.3",
22
+ "eslint": "^9.36.0",
23
+ "eslint-config-metarhia": "^9.1.3",
22
24
  "prettier": "^3.6.2"
23
25
  },
24
26
  "dependencies": {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  module.exports = {
4
- printWidth: 80,
4
+ printWidth: 120,
5
5
  singleQuote: true,
6
6
  trailingComma: 'all',
7
7
  tabWidth: 2,
@@ -2,5 +2,9 @@
2
2
 
3
3
  ({
4
4
  age: 0,
5
+ isRotten: false,
5
6
  isRipe: () => self.age === 1,
7
+ rot: {
8
+ method: () => (self.isRotten = true),
9
+ },
6
10
  });
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ () => $.age < 3 && !$.isRotten;
@@ -1,3 +1,3 @@
1
1
  'use strict';
2
2
 
3
- () => module.age++;
3
+ () => $.age++;
package/test/test.js CHANGED
@@ -16,3 +16,19 @@ test('Methods', async () => {
16
16
  chestnut.grow();
17
17
  assert.strictEqual(chestnut.isRipe(), true);
18
18
  });
19
+
20
+ test('Getters', async () => {
21
+ const context = vm.createContext({});
22
+ await loadDir(context, context, PATH_TO_APPLICATION);
23
+ const chestnut = context.application.chestnut;
24
+ assert.strictEqual(chestnut.isEdible, true);
25
+ });
26
+
27
+ test('Service', async () => {
28
+ const context = vm.createContext({});
29
+ await loadDir(context, context, PATH_TO_APPLICATION);
30
+ const chestnut = context.application.chestnut;
31
+ assert.strictEqual(chestnut.isRotten, false);
32
+ chestnut.rot();
33
+ assert.strictEqual(chestnut.isRotten, true);
34
+ });