├── .assets ├── aspectjs-256.png └── aspectjs-example.png ├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .gitattributes ├── .gitignore ├── .gitlab-ci.yml ├── .prettierignore ├── .prettierrc.cjs ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build └── package-json.type.cts ├── ci-templates └── node.yml ├── index.typedoc.ts ├── jest.config.ts ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── common │ ├── .assets │ │ └── aspectjs-example.png │ ├── .gitignore │ ├── .gitlab-ci.yml │ ├── README.md │ ├── index.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ ├── rollup.config.cjs │ ├── src │ │ ├── annotation │ │ │ ├── annotation-context.ts │ │ │ ├── annotation-kind.type.ts │ │ │ ├── annotation-ref.ts │ │ │ ├── annotation.class.spec.ts │ │ │ ├── annotation.property.spec.ts │ │ │ ├── annotation.types.ts │ │ │ ├── context │ │ │ │ ├── annotations.global.spec.ts │ │ │ │ ├── annotations.global.ts │ │ │ │ └── registry │ │ │ │ │ ├── annotation-context-registry.provider.ts │ │ │ │ │ ├── annotation-context-set.ts │ │ │ │ │ ├── annotation-context.registry.spec.ts │ │ │ │ │ ├── annotation-context.registry.ts │ │ │ │ │ ├── bound-annotation-context.ts │ │ │ │ │ ├── by-ref-selector.ts │ │ │ │ │ ├── by-target-selector.ts │ │ │ │ │ ├── hooks │ │ │ │ │ └── register-annotation-context.hook.ts │ │ │ │ │ ├── selection-filter.ts │ │ │ │ │ └── selector.ts │ │ │ ├── factory │ │ │ │ ├── annotation-factory.provider.ts │ │ │ │ ├── annotation.factory.spec.ts │ │ │ │ ├── annotation.factory.ts │ │ │ │ ├── decorator-hook.registry.spec.ts │ │ │ │ ├── decorator-hook.registry.ts │ │ │ │ ├── decorator-hook.type.ts │ │ │ │ └── hooks │ │ │ │ │ ├── call-annotation-stub.hook.ts │ │ │ │ │ └── jit-canvas.hook.ts │ │ │ ├── registry │ │ │ │ ├── annotation-registry.provider.ts │ │ │ │ └── annotation.registry.ts │ │ │ └── target │ │ │ │ ├── annotation-target-factory.provider.ts │ │ │ │ ├── annotation-target.factory.spec.ts │ │ │ │ ├── annotation-target.factory.ts │ │ │ │ ├── annotation-target.impl.ts │ │ │ │ ├── annotation-target.ts │ │ │ │ ├── annotation-target.utils.ts │ │ │ │ ├── bound-annotation-target.ts │ │ │ │ └── impl │ │ │ │ ├── class-annotation-target.impl.ts │ │ │ │ ├── method-annotation-target.impl.ts │ │ │ │ ├── parameter-annotation-target.impl.ts │ │ │ │ └── property-annotation-target.impl.ts │ │ ├── public_api.ts │ │ └── reflect │ │ │ ├── module │ │ │ ├── reflect-module-config.type.ts │ │ │ └── reflect-module.type.ts │ │ │ ├── reflect-provider.type.ts │ │ │ ├── reflect.context.global.ts │ │ │ ├── reflect.context.spec.ts │ │ │ ├── reflect.context.ts │ │ │ ├── reflect.error.ts │ │ │ └── runtime-state.provider.ts │ ├── testing │ │ ├── index.ts │ │ ├── src │ │ │ ├── public_api.ts │ │ │ ├── testing-context.global.ts │ │ │ └── testing-context.spec.ts │ │ └── tsconfig.json │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── utils │ │ ├── index.ts │ │ ├── src │ │ ├── _meta.util.ts │ │ ├── abstract │ │ │ ├── abstract-token-impl.type.ts │ │ │ ├── abstract-token.type.ts │ │ │ ├── abstract.type.spec.ts │ │ │ └── abstract.type.ts │ │ ├── assert.util.ts │ │ ├── clone.util.ts │ │ ├── meta.util.ts │ │ ├── public_api.ts │ │ ├── types.util.ts │ │ └── utils.ts │ │ └── tsconfig.json ├── core │ ├── .assets │ │ └── aspectjs-example.png │ ├── .gitignore │ ├── .gitlab-ci.yml │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── README.md │ ├── index.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ ├── rollup.config.cjs │ ├── src │ │ ├── advice │ │ │ ├── advice-sort.ts │ │ │ ├── advice-target.type.ts │ │ │ ├── advice-type.type.ts │ │ │ ├── advice.context.spec.ts │ │ │ ├── advice.context.ts │ │ │ ├── advice.type.ts │ │ │ ├── joinpoint.ts │ │ │ ├── mutable-advice.context.ts │ │ │ └── registry │ │ │ │ ├── advice-entry.model.ts │ │ │ │ ├── advice.registry.spec.ts │ │ │ │ ├── advice.registry.ts │ │ │ │ └── advices-selection.model.ts │ │ ├── advices │ │ │ ├── abstract.spec.ts │ │ │ ├── advices.spec.ts │ │ │ ├── after-return │ │ │ │ ├── after-return-class.spec.ts │ │ │ │ ├── after-return-method.spec.ts │ │ │ │ ├── after-return.annotation.ts │ │ │ │ ├── after-return.context.ts │ │ │ │ └── after-return.type.ts │ │ │ ├── after-throw │ │ │ │ ├── after-throw-class.spec.ts │ │ │ │ ├── after-throw-method.spec.ts │ │ │ │ ├── after-throw-parameter.spec.ts │ │ │ │ ├── after-throw-property-set.spec.ts │ │ │ │ ├── after-throw-property.spec.ts │ │ │ │ ├── after-throw.annotation.ts │ │ │ │ ├── after-throw.context.ts │ │ │ │ └── after-throw.type.ts │ │ │ ├── after │ │ │ │ ├── after.annotation.ts │ │ │ │ ├── after.context.ts │ │ │ │ └── after.type.ts │ │ │ ├── around │ │ │ │ ├── around-class.spec.ts │ │ │ │ ├── around-method.spec.ts │ │ │ │ ├── around-parameter.spec.ts │ │ │ │ ├── around-property-set.spec.ts │ │ │ │ ├── around-property.spec.ts │ │ │ │ ├── around.annotation.ts │ │ │ │ ├── around.context.ts │ │ │ │ ├── around.spec.ts │ │ │ │ └── around.type.ts │ │ │ ├── before │ │ │ │ ├── before-class.spec.ts │ │ │ │ ├── before-method.spec.ts │ │ │ │ ├── before-parameter.spec.ts │ │ │ │ ├── before-property-set.spec.ts │ │ │ │ ├── before-property.spec.ts │ │ │ │ ├── before-static-method.spec.ts │ │ │ │ ├── before-static-property-set.spec.ts │ │ │ │ ├── before-static-property.spec.ts │ │ │ │ ├── before.annotation.ts │ │ │ │ ├── before.context.ts │ │ │ │ └── before.type.ts │ │ │ ├── compile │ │ │ │ ├── compile-class.spec.ts │ │ │ │ ├── compile-method.spec.ts │ │ │ │ ├── compile-parameter.spec.ts │ │ │ │ ├── compile-property.spec.ts │ │ │ │ ├── compile.annotation.ts │ │ │ │ ├── compile.context.ts │ │ │ │ └── compile.type.ts │ │ │ └── early-declaration.spec.ts │ │ ├── annotations │ │ │ ├── order.annotation.ts │ │ │ └── order.spec.ts │ │ ├── aspect │ │ │ ├── aspect-metadata.type.ts │ │ │ ├── aspect.annotation.ts │ │ │ ├── aspect.registry.ts │ │ │ ├── aspect.spec.ts │ │ │ └── aspect.type.ts │ │ ├── errors │ │ │ ├── advice.error.ts │ │ │ ├── aspect.error.ts │ │ │ └── weaving.error.ts │ │ ├── jit │ │ │ ├── canvas │ │ │ │ ├── canvas.utils.ts │ │ │ │ ├── jit-abstract-method-canvas.strategy.ts │ │ │ │ ├── jit-canvas.strategy.ts │ │ │ │ ├── jit-canvas.type.ts │ │ │ │ ├── jit-class-canvas.strategy.ts │ │ │ │ ├── jit-method-canvas.strategy.ts │ │ │ │ ├── jit-parameter-canvas.strategy.ts │ │ │ │ └── jit-property-canvas.strategy.ts │ │ │ ├── jit-weaver.hook.ts │ │ │ ├── jit-weaver.spec.ts │ │ │ ├── jit-weaver.ts │ │ │ └── joinpoint.factory.ts │ │ ├── pointcut │ │ │ ├── pointcut-expression.factory.ts │ │ │ ├── pointcut-expression.spec.ts │ │ │ ├── pointcut-expression.type.ts │ │ │ ├── pointcut-kind.type.ts │ │ │ └── pointcut.ts │ │ ├── public_api.ts │ │ ├── utils.ts │ │ ├── utils │ │ │ ├── annotation-mixin-target.ts │ │ │ ├── annotation-mixin.spec.ts │ │ │ └── annotation-mixin.ts │ │ └── weaver │ │ │ ├── canvas │ │ │ └── canvas-strategy.type.ts │ │ │ ├── compilation-state.provider.ts │ │ │ ├── context │ │ │ ├── weaver.context.global.ts │ │ │ ├── weaver.context.spec.ts │ │ │ └── weaver.context.ts │ │ │ ├── weaver.module.ts │ │ │ └── weaver.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── docs │ ├── .gitignore │ ├── .gitlab-ci.yml │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── TODO.md │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── .vuepress │ │ │ ├── config.ts │ │ │ ├── custom-blocks │ │ │ │ ├── custom-md.ts │ │ │ │ └── quote-blocks.ts │ │ │ ├── locales.ts │ │ │ ├── md │ │ │ │ └── utils.ts │ │ │ ├── navbar │ │ │ │ ├── navbar.plugin.ts │ │ │ │ └── navbar.ts │ │ │ ├── public │ │ │ │ ├── favicon.ico │ │ │ │ └── logo.png │ │ │ ├── run-typedoc.ts │ │ │ ├── sidebar │ │ │ │ ├── sidebar-sorter.ts │ │ │ │ └── sidebar.ts │ │ │ ├── styles │ │ │ │ ├── blocks.scss │ │ │ │ ├── config.scss │ │ │ │ ├── index.scss │ │ │ │ └── palette.scss │ │ │ ├── theme.ts │ │ │ ├── typedoc │ │ │ │ ├── README.typedoc.md │ │ │ │ ├── index.ts │ │ │ │ ├── tsconfig.json │ │ │ │ ├── typedoc.plugin.ts │ │ │ │ └── typedoc.ts │ │ │ └── utils.ts │ │ ├── 00.guide │ │ │ ├── 000.motivations.md │ │ │ ├── 001.getting-started.md │ │ │ ├── 005.core-concepts │ │ │ │ ├── 010.annotations.md │ │ │ │ ├── 020.aspects.md │ │ │ │ ├── 030.advices.md │ │ │ │ └── README.md │ │ │ ├── 006.advanced-usage │ │ │ │ ├── 060.reflect-context.md │ │ │ │ └── README.md │ │ │ ├── 010.examples │ │ │ │ ├── 001.recipes.md │ │ │ │ ├── 010.deprecated.md │ │ │ │ └── README.md │ │ │ ├── 20.context.md │ │ │ └── README.md │ │ ├── 20.projects │ │ │ ├── 10.memo.md │ │ │ ├── 20.transactional.md │ │ │ └── README.md │ │ └── README.md │ └── tsconfig.json ├── httyped-client │ ├── .gitignore │ ├── .gitlab-ci.yml │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── README.md │ ├── TODO.md │ ├── demo │ │ ├── address.mapper.ts │ │ ├── address.model.ts │ │ ├── client-factory.global.ts │ │ ├── company.model..ts │ │ ├── main.ts │ │ ├── package.json │ │ ├── post.mapper.ts │ │ ├── post.model.ts │ │ ├── posts.api.ts │ │ ├── user.mapper.ts │ │ ├── user.model.ts │ │ ├── users.api.ts │ │ └── users.client.ts │ ├── draft.md │ ├── index.ts │ ├── jest.config.ts │ ├── package.json │ ├── rollup.config.cjs │ ├── src │ │ ├── annotations │ │ │ ├── annotation-factory.ts │ │ │ ├── body.annotation.spec.ts │ │ │ ├── body.annotation.ts │ │ │ ├── fetch │ │ │ │ ├── delete.annotation.ts │ │ │ │ ├── fetch-annotations.ts │ │ │ │ ├── fetch.annotation.spec.ts │ │ │ │ ├── get.annotation.ts │ │ │ │ ├── head.annotation.ts │ │ │ │ ├── options.annotation.ts │ │ │ │ ├── patch.annotation.ts │ │ │ │ ├── post.annotation.ts │ │ │ │ ├── put.annotation.ts │ │ │ │ └── response-body.spec.ts │ │ │ ├── header.annotation.spec.ts │ │ │ ├── header.annotation.ts │ │ │ ├── headers.annotation.spec.ts │ │ │ ├── headers.annotation.ts │ │ │ ├── http-client.annotation.ts │ │ │ ├── path-variable.annotation.spec.ts │ │ │ ├── path-variable.annotation.ts │ │ │ ├── request-param.annotation.spec.ts │ │ │ ├── request-param.annotation.ts │ │ │ ├── request-params.annotation.ts │ │ │ └── type-hint.annotation.ts │ │ ├── aspects │ │ │ ├── abstract-aop-http-client.aspect.ts │ │ │ └── httyped-client.aspect.ts │ │ ├── client-factory │ │ │ ├── client-config.type.ts │ │ │ ├── client.factory.ts │ │ │ ├── content-type-json-request-handler.ts │ │ │ ├── default-json-response-handler.ts │ │ │ ├── path-variables-handler.type.ts │ │ │ └── request-param-handler.type.ts │ │ ├── public_api.ts │ │ ├── test-helpers │ │ │ └── all-fetch-annotations.helper.ts │ │ ├── types │ │ │ ├── body-metadata.type.ts │ │ │ ├── fetch-adapter.type.ts │ │ │ ├── http-class-metadata.type.ts │ │ │ ├── http-endpoint-metadata.type.ts │ │ │ ├── mapper.error.ts │ │ │ ├── mapper.type.ts │ │ │ ├── request-handler.type.ts │ │ │ ├── response-handler.type.ts │ │ │ └── type-hint.type.ts │ │ └── url-canparse.polyfill.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tsconfig.tsx.json ├── memo │ ├── .gitlab-ci.yml │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── index.ts │ ├── jest.config.ts │ ├── package.json │ ├── rollup.config.cjs │ ├── src │ │ ├── memo-annotation-factory.ts │ │ ├── memo.annotation.ts │ │ ├── memo.aspect.spec.ts │ │ ├── memo.aspect.ts │ │ └── public_api.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── nestjs-httyped-client │ ├── .gitignore │ ├── .gitlab-ci.yml │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── README.md │ ├── TODO.md │ ├── index.ts │ ├── jest.config.ts │ ├── package.json │ ├── rollup.config.cjs │ ├── src │ │ ├── nestjs-client-factory.type.ts │ │ ├── nestjs-client.annotation.ts │ │ ├── nestjs-client.aspect.ts │ │ └── public_api.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tsconfig.tsx.json ├── nestjs │ ├── .gitignore │ ├── .gitlab-ci.yml │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── README.md │ ├── common │ │ ├── index.ts │ │ ├── register │ │ │ ├── index.ts │ │ │ ├── src │ │ │ │ ├── nestjs-common.aspect.ts │ │ │ │ └── public_api.ts │ │ │ └── tsconfig.json │ │ ├── src │ │ │ ├── annotation-factory.global.ts │ │ │ ├── annotations │ │ │ │ ├── body.annotation.ts │ │ │ │ ├── controller.annotation.ts │ │ │ │ ├── delete.annotation.ts │ │ │ │ ├── get.annotation.ts │ │ │ │ ├── head.annotation.ts │ │ │ │ ├── header.annotation.ts │ │ │ │ ├── headers.annotation.ts │ │ │ │ ├── injectable.annotation.ts │ │ │ │ ├── options.annotation.ts │ │ │ │ ├── param.annotation.ts │ │ │ │ ├── patch.annotation.ts │ │ │ │ ├── post.annotation.ts │ │ │ │ ├── put.annotation.ts │ │ │ │ └── query.annotation.ts │ │ │ ├── public_api.ts │ │ │ └── type.utils.ts │ │ ├── tsconfig.json │ │ └── tsconfig.lib.json │ ├── index.ts │ ├── jest.config.ts │ ├── package.json │ ├── rollup.config.cjs │ ├── src │ │ └── public_api.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json └── persistence │ ├── .gitignore │ ├── .gitlab-ci.yml │ ├── .vscode │ ├── launch.json │ └── settings.json │ ├── README.md │ ├── index.ts │ ├── jest.config.ts │ ├── package.json │ ├── rollup.config.cjs │ ├── src │ ├── annotation-factory.ts │ ├── datasource.registry.ts │ ├── persistence-error.ts │ ├── public_api.ts │ ├── test-helpers │ │ ├── init-db.ts │ │ ├── initdb │ │ │ └── 00.user-post.sql │ │ └── typeorm │ │ │ ├── init-ds.ts │ │ │ ├── post.entity.ts │ │ │ └── user.entity.ts │ ├── transactional │ │ ├── annotations │ │ │ └── transactional.annotation.ts │ │ ├── transaction-manager.ts │ │ ├── transaction.ts │ │ ├── transactional-error.ts │ │ ├── transactional.aspect.ts │ │ └── transactional.spec.ts │ └── utils.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── typeorm │ ├── _index.ts │ ├── src │ ├── public_api.ts │ ├── typeorm-datasource-definition.ts │ ├── typeorm-transaction-manager.spec.ts │ ├── typeorm-transaction-manager.ts │ ├── typeorm-transaction-registry.ts │ └── typeorm-transactional.aspect.ts │ ├── tsconfig.json │ └── tsconfig.lib.json ├── rollup.config.cjs ├── rollup.config.ts ├── tsconfig.json └── tsconfig.spec.json /.assets/aspectjs-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicolasThierion/aspectjs/dda0d35f73d111072855a9ac284f2a6119d361c9/.assets/aspectjs-256.png -------------------------------------------------------------------------------- /.assets/aspectjs-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicolasThierion/aspectjs/dda0d35f73d111072855a9ac284f2a6119d361c9/.assets/aspectjs-example.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Settings for editors and IDEs. 2 | # References at https://editorconfig.org/. 3 | 4 | root = true 5 | 6 | # Settings for any file. 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Unlinted files and folders. 2 | # References at https://eslint.org/docs/user-guide/configuring#eslintignore 3 | 4 | # Generated docs, bundles and type definitions. 5 | docs/ 6 | dist/ 7 | types/ 8 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /* eslint-env node */ 3 | 4 | 'use strict'; 5 | 6 | /** 7 | * An object with ESLint options. 8 | * @type {import('eslint').Linter.Config} 9 | */ 10 | const options = { 11 | root: true, 12 | parser: '@typescript-eslint/parser', 13 | parserOptions: { 14 | ecmaFeatures: { 15 | jsx: false, 16 | }, 17 | ecmaVersion: 2021, 18 | }, 19 | plugins: ['@typescript-eslint'], 20 | extends: [ 21 | 'eslint:recommended', 22 | 'plugin:prettier/recommended', 23 | 'plugin:@typescript-eslint/eslint-recommended', 24 | 'plugin:@typescript-eslint/recommended', 25 | ], 26 | rules: { 27 | '@typescript-eslint/no-explicit-any': 'off', 28 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 29 | '@typescript-eslint/no-non-null-assertion': 'off', 30 | '@typescript-eslint/no-empty-function': 'off', 31 | }, 32 | }; 33 | 34 | module.exports = options; 35 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # File attributes for Git repository. 2 | # References at https://git-scm.com/docs/gitattributes. 3 | 4 | # Handle files as text and ensure Unix line endings. 5 | * text=auto eol=lf 6 | 7 | # Ignore differences on NPM's lockfile. 8 | # Since version 5.7.0, NPM automatically handle merge conflicts. 9 | package-lock.json -diff 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Unversioned files and folders. 2 | # References at https://git-scm.com/docs/gitignore. 3 | 4 | # Finder's configuration files (Mac). 5 | .DS_Store 6 | 7 | # Node.js modules. 8 | node_modules/ 9 | 10 | # NPM's shrinkwrap. 11 | # We're using `package-lock.json` instead. 12 | npm-shrinkwrap.json 13 | 14 | # Yarn's lockfile, Plug'n'Play, folder and settings. 15 | # We're using NPM and it provide its own lockfile and settings. 16 | yarn.lock 17 | .pnp.* 18 | .yarn/ 19 | .yarnrc 20 | .yarnrc.yaml 21 | 22 | # PNPM's lockfile, workspaces and hooks. 23 | # We're using NPM and it provide its own lockfile and settings. 24 | pnpm-lock.yaml 25 | pnpm-workspaces.yaml 26 | pnpmfile.js 27 | 28 | # Log's files. 29 | *.log 30 | *.log.* 31 | dist/ 32 | *.tgz 33 | .history -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Unlinted files and folders. 2 | # References at https://prettier.io/docs/en/ignore.html#ignoring-files. 3 | 4 | # Generated docs, bundles and type definitions. 5 | docs/ 6 | dist/ 7 | README.md -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'all', 4 | }; 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["aspectjs", "Pointcut", "typeorm"], 3 | "workbench.colorCustomizations": { 4 | "activityBar.activeBackground": "#65c89b", 5 | "activityBar.activeBorder": "#9259c6", 6 | "activityBar.background": "#65c89b", 7 | "activityBar.foreground": "#15202b", 8 | "activityBar.inactiveForeground": "#15202b99", 9 | "activityBarBadge.background": "#945bc4", 10 | "activityBarBadge.foreground": "#e7e7e7", 11 | "sash.hoverBorder": "#65c89b", 12 | "statusBar.background": "#42b883", 13 | "statusBar.foreground": "#15202b", 14 | "statusBarItem.hoverBackground": "#359268", 15 | "statusBarItem.remoteBackground": "#42b883", 16 | "statusBarItem.remoteForeground": "#15202b", 17 | "titleBar.activeBackground": "#42b883", 18 | "titleBar.activeForeground": "#15202b", 19 | "titleBar.inactiveBackground": "#42b88399", 20 | "titleBar.inactiveForeground": "#15202b99", 21 | "commandCenter.border": "#15202b99" 22 | }, 23 | "peacock.color": "#42b883", 24 | "typescript.preferences.importModuleSpecifier": "project-relative", 25 | "typescript.updateImportsOnFileMove.enabled": "always", 26 | "typescript.tsdk": "node_modules/typescript/lib", 27 | "markiscodecoverage.searchCriteria": "dist/coverage/lcov*.info", 28 | "terminal.integrated.customGlyphs": true 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nicolas THIERION 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.typedoc.ts: -------------------------------------------------------------------------------- 1 | export * from './packages/common/index'; 2 | export * from './packages/core/index'; 3 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "version": "0.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /packages/common/.assets/aspectjs-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicolasThierion/aspectjs/dda0d35f73d111072855a9ac284f2a6119d361c9/packages/common/.assets/aspectjs-example.png -------------------------------------------------------------------------------- /packages/common/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/common/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - ci-templates/node.yml 3 | 4 | ### COMMON 5 | 6 | common:lint: 7 | variables: 8 | CWD: packages/common 9 | extends: .node:lint 10 | stage: test 11 | needs: 12 | - node:prepare 13 | 14 | common:unit: 15 | extends: .node:test:unit 16 | variables: 17 | CWD: packages/common 18 | stage: test 19 | needs: 20 | - node:prepare 21 | 22 | common:build: 23 | extends: .node:build 24 | variables: 25 | CWD: packages/common 26 | stage: compile 27 | needs: 28 | - node:prepare 29 | 30 | common:publish:gitlab: 31 | extends: .node:gitlab:publish 32 | stage: publish 33 | variables: 34 | CWD: packages/common 35 | PACKAGE_DIR: ./dist 36 | 37 | common:publish:npm: 38 | extends: .node:npm:publish 39 | stage: release 40 | variables: 41 | CWD: packages/common 42 | PACKAGE_DIR: ./dist 43 | rules: 44 | - if: $CI_COMMIT_REF_NAME =~ /^v\d+\.\d+\.\d+.*$/ 45 | when: manual 46 | -------------------------------------------------------------------------------- /packages/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/public_api'; 2 | -------------------------------------------------------------------------------- /packages/common/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { JestConfigWithTsJest } from 'ts-jest'; 2 | import config from '../../jest.config'; 3 | import { join } from 'path'; 4 | 5 | export default { 6 | ...config, 7 | coverageDirectory: join(__dirname, 'dist', 'coverage'), 8 | collectCoverageFrom: [`./**/*.{js,ts}`], 9 | } satisfies JestConfigWithTsJest; 10 | -------------------------------------------------------------------------------- /packages/common/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aspectjs/common", 3 | "version": "0.3.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@aspectjs/common", 9 | "version": "0.3.0", 10 | "license": "MIT" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/common/rollup.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const { createConfig } = require('../../rollup.config.cjs'); 3 | module.exports = createConfig(__dirname); 4 | -------------------------------------------------------------------------------- /packages/common/src/annotation/annotation-context.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationRef } from './annotation-ref'; 2 | import { Annotation, AnnotationKind, AnnotationStub } from './annotation.types'; 3 | import type { AnnotationTarget } from './target/annotation-target'; 4 | 5 | /** 6 | * Holds data about the execution context where the annotation is being invoked. 7 | */ 8 | export class AnnotationContext< 9 | T extends AnnotationKind = AnnotationKind, 10 | S extends AnnotationStub = AnnotationStub, 11 | X = unknown, 12 | > { 13 | /** 14 | * The reference to the annotation being invked. 15 | */ 16 | public readonly ref: AnnotationRef; 17 | 18 | /** 19 | * @param ref @internal 20 | */ 21 | constructor( 22 | /** 23 | * The annotation, or its reference, that is being invoked. 24 | */ 25 | ref: Annotation | AnnotationRef, 26 | /** 27 | * The arguments passed to the annotation. 28 | */ 29 | public readonly args: Parameters, 30 | /** 31 | * The target of the annotation. 32 | */ 33 | public readonly target: AnnotationTarget, 34 | ) { 35 | this.ref = AnnotationRef.of(ref); 36 | } 37 | 38 | /** 39 | * 40 | * @returns The signature of the annotation, in the form `@: on `. 41 | */ 42 | toString(): string { 43 | return `@${this.ref.groupId}:${this.ref.name} on ${this.target.label}`; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/common/src/annotation/annotation.property.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-function */ 2 | import { configureTesting } from '@aspectjs/common/testing'; 3 | import type { Annotation, AnnotationKind } from './annotation.types'; 4 | import { AnnotationFactory } from './factory/annotation.factory'; 5 | 6 | let factory: AnnotationFactory; 7 | const FACTORY_GROUP_TEST_ID = 'testFactory'; 8 | 9 | describe(`Property Annotations`, () => { 10 | const APropertyStub = jest.fn(function AProperty( 11 | _x?: string, 12 | _y?: number, 13 | ) {}); 14 | let AProperty: Annotation; 15 | 16 | beforeEach(() => { 17 | configureTesting(); 18 | factory = new AnnotationFactory(FACTORY_GROUP_TEST_ID); 19 | AProperty = factory.create('APropertyStub', APropertyStub); 20 | }); 21 | afterEach(() => { 22 | jest.clearAllMocks(); 23 | }); 24 | 25 | it('returns a property decorator', () => { 26 | const decorator = AProperty('0', 0); 27 | expect(typeof decorator).toBe('function'); 28 | class A { 29 | @AProperty('0', 0) 30 | property = 'property'; 31 | } 32 | const res = decorator(A, 'property'); 33 | expect(typeof res).toBe('undefined'); 34 | }); 35 | 36 | describe('applied on a property', () => { 37 | let A = class A { 38 | someProp = 'someProp'; 39 | static someStaticProp = 'someStaticProp'; 40 | }; 41 | beforeEach(() => { 42 | class AImpl { 43 | @AProperty('0', 0) 44 | someProp = 'someProp'; 45 | static someStaticProp = 'someStaticProp'; 46 | } 47 | A = AImpl; 48 | }); 49 | it('keeps the class instance type', () => { 50 | const a = new A(); 51 | expect(a).toBeInstanceOf(A); 52 | }); 53 | it('calls through the annotation stub', () => { 54 | expect(APropertyStub).toBeCalledTimes(1); 55 | }); 56 | it(`keeps the class attributes`, () => { 57 | expect(new A().someProp).toEqual('someProp'); 58 | }); 59 | it(`keeps the static class attributes`, () => { 60 | expect(A.someStaticProp).toEqual('someStaticProp'); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /packages/common/src/annotation/context/annotations.global.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationContextRegistry } from './registry/annotation-context.registry'; 2 | 3 | import { reflectContext } from '../../reflect/reflect.context.global'; 4 | import { AnnotationRef } from '../annotation-ref'; 5 | import type { Annotation, AnnotationStub } from '../annotation.types'; 6 | import { AnnotationByTargetSelector } from './registry/by-target-selector'; 7 | 8 | export function getAnnotations( 9 | annotation: S, 10 | ): AnnotationByTargetSelector; 11 | export function getAnnotations( 12 | ...annotations: (Annotation | AnnotationRef | string)[] 13 | ): AnnotationByTargetSelector; 14 | export function getAnnotations( 15 | ...annotations: (Annotation | AnnotationRef | string)[] 16 | ): AnnotationByTargetSelector { 17 | return reflectContext() 18 | .get(AnnotationContextRegistry) 19 | .select(...annotations); 20 | } 21 | -------------------------------------------------------------------------------- /packages/common/src/annotation/context/registry/annotation-context-registry.provider.ts: -------------------------------------------------------------------------------- 1 | import { DecoratorHookRegistry } from '../../factory/decorator-hook.registry'; 2 | import { AnnotationTargetFactory } from '../../target/annotation-target.factory'; 3 | import { AnnotationContextRegistry } from './annotation-context.registry'; 4 | import { REGISTER_ANNOTATION_HOOK } from './hooks/register-annotation-context.hook'; 5 | 6 | import type { ReflectProvider } from '../../../reflect/reflect-provider.type'; 7 | 8 | /** 9 | * @internal 10 | */ 11 | export const ANNOTATION_CONTEXT_REGISTRY_PROVIDERS: ReflectProvider[] = [ 12 | { 13 | provide: AnnotationContextRegistry, 14 | deps: [AnnotationTargetFactory], 15 | factory: (targetFactory: AnnotationTargetFactory) => { 16 | return new AnnotationContextRegistry(targetFactory); 17 | }, 18 | }, 19 | { 20 | provide: DecoratorHookRegistry, 21 | deps: [DecoratorHookRegistry], 22 | factory: (decoratorHookRegistry: DecoratorHookRegistry) => { 23 | return decoratorHookRegistry.add(REGISTER_ANNOTATION_HOOK); 24 | }, 25 | }, 26 | ]; 27 | -------------------------------------------------------------------------------- /packages/common/src/annotation/context/registry/annotation-context.registry.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationContext } from '../../annotation-context'; 2 | import { AnnotationRef } from '../../annotation-ref'; 3 | import { Annotation } from '../../annotation.types'; 4 | import type { AnnotationTargetFactory } from '../../target/annotation-target.factory'; 5 | import { _AnnotationContextSet } from './annotation-context-set'; 6 | import { AnnotationByTargetSelector } from './by-target-selector'; 7 | 8 | /** 9 | * Store all registered annotations 10 | */ 11 | export class AnnotationContextRegistry { 12 | private readonly annotationSet = new _AnnotationContextSet(); 13 | constructor(private targetFactory: AnnotationTargetFactory) {} 14 | 15 | register(annotationContext: AnnotationContext) { 16 | this.annotationSet.addAnnotation(annotationContext); 17 | } 18 | 19 | select( 20 | ...annotations: ( 21 | | Pick 22 | | Annotation 23 | | string 24 | )[] 25 | ): AnnotationByTargetSelector { 26 | const annotationsFilter = annotations.length 27 | ? new Set( 28 | annotations.filter((a) => a !== undefined).map(AnnotationRef.of), 29 | ) 30 | : undefined; 31 | 32 | return new AnnotationByTargetSelector( 33 | this.targetFactory, 34 | this.annotationSet, 35 | annotationsFilter, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/common/src/annotation/context/registry/bound-annotation-context.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationContext } from '../../annotation-context'; 2 | import { AnnotationKind, AnnotationStub } from '../../annotation.types'; 3 | import { BoundAnnotationTarget } from '../../target/bound-annotation-target'; 4 | 5 | export interface BoundAnnotationContext< 6 | T extends AnnotationKind = AnnotationKind, 7 | S extends AnnotationStub = AnnotationStub, 8 | X = unknown, 9 | > extends AnnotationContext { 10 | readonly target: BoundAnnotationTarget; 11 | } 12 | -------------------------------------------------------------------------------- /packages/common/src/annotation/context/registry/hooks/register-annotation-context.hook.ts: -------------------------------------------------------------------------------- 1 | import type { DecoratorHook } from '../../../factory/decorator-hook.type'; 2 | import { AnnotationContextRegistry } from '../annotation-context.registry'; 3 | 4 | /** 5 | * Returns an {@link DecoratorHook} that adds annotations to the {@link AnnotationContextRegistry} 6 | * @param annotationContextRegistry 7 | * @returns 8 | */ 9 | export const REGISTER_ANNOTATION_HOOK: DecoratorHook = { 10 | name: '@aspectjs::hook:registerAnnotation', 11 | createDecorator: (reflect, context) => { 12 | const annotationContextRegistry = reflect.get(AnnotationContextRegistry); 13 | annotationContextRegistry.register(context); 14 | }, 15 | order: 10, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/common/src/annotation/context/registry/selection-filter.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '../../annotation.types'; 2 | import { AnnotationTarget } from '../../target/annotation-target'; 3 | 4 | export type AnnotationSelectionFilter = { 5 | target: AnnotationTarget; 6 | 7 | /** 8 | * Which kind of annotation to select ? 9 | */ 10 | types?: AnnotationKind[]; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/common/src/annotation/factory/annotation-factory.provider.ts: -------------------------------------------------------------------------------- 1 | import { DecoratorHookRegistry } from './decorator-hook.registry'; 2 | import { CALL_ANNOTATION_STUB } from './hooks/call-annotation-stub.hook'; 3 | 4 | import type { ReflectProvider } from '../../reflect/reflect-provider.type'; 5 | 6 | /** 7 | * @internal 8 | */ 9 | export const ANNOTATION_HOOK_REGISTRY_PROVIDERS: ReflectProvider[] = [ 10 | { 11 | provide: DecoratorHookRegistry, 12 | factory: () => { 13 | return ( 14 | new DecoratorHookRegistry() 15 | // .add(SETUP_JIT_CANVAS_DECORATOR_PROVIDER) 16 | .add(CALL_ANNOTATION_STUB) 17 | ); 18 | }, 19 | }, 20 | ]; 21 | -------------------------------------------------------------------------------- /packages/common/src/annotation/factory/decorator-hook.registry.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@aspectjs/common/utils'; 2 | 3 | import type { DecoratorHook } from './decorator-hook.type'; 4 | 5 | /** 6 | * @internal 7 | */ 8 | export class DecoratorHookRegistry { 9 | protected readonly hooks: Map = new Map(); 10 | 11 | static readonly __providerName = 'DecoratorHookRegistry'; 12 | values() { 13 | return this.hooks.values(); 14 | } 15 | 16 | add(annotationsHook: DecoratorHook) { 17 | assert(!!annotationsHook.name); 18 | assert(!this.hooks.has(annotationsHook.name)); 19 | this.hooks.set(annotationsHook.name, annotationsHook); 20 | return this; 21 | } 22 | 23 | remove(annotationsHook: DecoratorHook | string) { 24 | const name = 25 | typeof annotationsHook === 'string' 26 | ? annotationsHook 27 | : annotationsHook.name; 28 | assert(!!name); 29 | this.hooks.delete(name); 30 | return this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/common/src/annotation/factory/decorator-hook.type.ts: -------------------------------------------------------------------------------- 1 | import { ReflectContext } from '../../reflect/reflect.context'; 2 | import { AnnotationContext } from '../annotation-context'; 3 | import type { 4 | AnnotationKind, 5 | AnnotationStub, 6 | Decorator, 7 | } from '../annotation.types'; 8 | 9 | /** 10 | * @internal 11 | * A DecoratorHook is a configuration for the {@link AnnotationFactory} 12 | * to create typescript decorators that corresponds to a given annotation. 13 | */ 14 | export type DecoratorHook< 15 | T extends AnnotationKind = AnnotationKind, 16 | S extends AnnotationStub = AnnotationStub, 17 | > = { 18 | // TODO: refactor into a single parameter 19 | createDecorator: ( 20 | reflect: ReflectContext, 21 | context: AnnotationContext, 22 | annotationStub: S, 23 | ) => Decorator | void; 24 | order?: number; 25 | name: string; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/common/src/annotation/factory/hooks/call-annotation-stub.hook.ts: -------------------------------------------------------------------------------- 1 | import type { DecoratorHook } from '../decorator-hook.type'; 2 | 3 | export const CALL_ANNOTATION_STUB: DecoratorHook = { 4 | name: '@aspectjs::hook.annotationStub', 5 | order: 0, 6 | createDecorator: (_reflect, context, annotationStub) => { 7 | return annotationStub(...context.args); 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/common/src/annotation/factory/hooks/jit-canvas.hook.ts: -------------------------------------------------------------------------------- 1 | import { assert, ConcreteConstructorType } from '@aspectjs/common/utils'; 2 | import { AnnotationKind } from '../../annotation.types'; 3 | import { DecoratorHook } from '../decorator-hook.type'; 4 | 5 | /** 6 | * TODO: remove 7 | * @internal 8 | */ 9 | export const SETUP_JIT_CANVAS_JOOK: DecoratorHook = { 10 | name: 'ajs::hook.jit-canvas', 11 | order: -120, 12 | createDecorator: (_reflect, context) => { 13 | return (...targetArgs: any[]) => { 14 | if (context.target.kind === AnnotationKind.CLASS) { 15 | assert(targetArgs.length === 1); 16 | assert(typeof targetArgs[0] === 'function'); 17 | const decoree = function XXX(this: any, ...args: any[]) { 18 | const compiledSymbol = context.target.getMetadata( 19 | 'ajs.jit-aop-decoree', 20 | () => { 21 | return context.target.proto 22 | .constructor as ConcreteConstructorType; 23 | }, 24 | ); 25 | return new compiledSymbol(...args); 26 | }; 27 | 28 | return decoree; 29 | } 30 | 31 | return; 32 | }; 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /packages/common/src/annotation/registry/annotation-registry.provider.ts: -------------------------------------------------------------------------------- 1 | import type { ReflectProvider } from '../../reflect/reflect-provider.type'; 2 | import { AnnotationRegistry } from './annotation.registry'; 3 | 4 | /** 5 | * Registry for all available annotations. 6 | * Annotations created throuth the AnnotationFactory will be registered here. 7 | * @internal 8 | */ 9 | export const ANNOTATION_REGISTRY_PROVIDERS: ReflectProvider[] = [ 10 | { 11 | provide: AnnotationRegistry, 12 | factory: () => { 13 | return new AnnotationRegistry(); 14 | }, 15 | }, 16 | ]; 17 | -------------------------------------------------------------------------------- /packages/common/src/annotation/registry/annotation.registry.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationRef } from '../annotation-ref'; 2 | 3 | export class AnnotationRegistry { 4 | private readonly annotations: Set = new Set(); 5 | has(annotationRef: AnnotationRef): boolean { 6 | return this.annotations.has(annotationRef.toString()); 7 | } 8 | 9 | register(annotationRef: AnnotationRef) { 10 | if (this.has(annotationRef)) { 11 | throw new Error(`Annotation "${annotationRef}" already exists`); 12 | } else { 13 | this.annotations.add(annotationRef.toString()); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/common/src/annotation/target/annotation-target-factory.provider.ts: -------------------------------------------------------------------------------- 1 | import type { ReflectProvider } from '../../reflect/reflect-provider.type'; 2 | import { AnnotationTargetFactory } from './annotation-target.factory'; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export const ANNOTATION_TARGET_FACTORY_PROVIDERS: ReflectProvider[] = [ 8 | { 9 | provide: AnnotationTargetFactory, 10 | factory: () => { 11 | return new AnnotationTargetFactory(); 12 | }, 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /packages/common/src/annotation/target/annotation-target.utils.ts: -------------------------------------------------------------------------------- 1 | import { ConstructorType, Prototype } from '@aspectjs/common/utils'; 2 | import { AnnotationTarget } from './annotation-target'; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export function _findPropertyDescriptor( 8 | obj: Prototype | ConstructorType, 9 | propertyKey: string | symbol, 10 | ): PropertyDescriptor | undefined { 11 | if (!obj) { 12 | return; 13 | } 14 | const descriptor = Object.getOwnPropertyDescriptor(obj, propertyKey); 15 | if (descriptor) { 16 | return descriptor; 17 | } 18 | 19 | return _findPropertyDescriptor(Object.getPrototypeOf(obj), propertyKey); 20 | } 21 | 22 | export function defuseAdvices( 23 | target: AnnotationTarget, 24 | fn: () => R, 25 | ) { 26 | try { 27 | target.defineMetadata('@ajs:defuseAdvices', true); 28 | return fn(); 29 | } finally { 30 | target.defineMetadata('@ajs:defuseAdvices', false); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/common/src/annotation/target/bound-annotation-target.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '../annotation.types'; 2 | import { 3 | ClassAnnotationTarget, 4 | MethodAnnotationTarget, 5 | ParameterAnnotationTarget, 6 | PropertyAnnotationTarget, 7 | } from './annotation-target'; 8 | 9 | export interface BoundClassAnnotationTarget 10 | extends ClassAnnotationTarget { 11 | eval(): X; 12 | } 13 | 14 | interface BoundParameterAnnotationTarget 15 | extends ParameterAnnotationTarget { 16 | eval(): unknown; 17 | } 18 | interface BoundMethodAnnotationTarget 19 | extends MethodAnnotationTarget { 20 | eval(): (...args: unknown[]) => unknown; 21 | } 22 | interface BoundPropertyAnnotationTarget 23 | extends PropertyAnnotationTarget { 24 | eval(): unknown; 25 | } 26 | 27 | /** 28 | * Represents the symbol (Class, Method, Parameter, Property) annotated by an annotation. 29 | * This symbol is bound to an actual instance, and has a "value" attribute. 30 | * @param T The kind of annotation 31 | * @param X The type of the class that target belongs to. 32 | */ 33 | export type BoundAnnotationTarget< 34 | T extends AnnotationKind = AnnotationKind, 35 | X = unknown, 36 | > = T extends AnnotationKind.CLASS 37 | ? BoundClassAnnotationTarget 38 | : T extends AnnotationKind.PARAMETER 39 | ? BoundParameterAnnotationTarget 40 | : T extends AnnotationKind.METHOD 41 | ? BoundMethodAnnotationTarget 42 | : T extends AnnotationKind.PROPERTY 43 | ? BoundPropertyAnnotationTarget 44 | : never; 45 | -------------------------------------------------------------------------------- /packages/common/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './annotation/annotation-context'; 2 | export * from './annotation/annotation-ref'; 3 | export * from './annotation/annotation.types'; 4 | export * from './annotation/context/annotations.global'; 5 | export { AnnotationContextRegistry } from './annotation/context/registry/annotation-context.registry'; 6 | export * from './annotation/context/registry/bound-annotation-context'; 7 | export * from './annotation/context/registry/by-ref-selector'; 8 | export * from './annotation/context/registry/by-target-selector'; 9 | export * from './annotation/context/registry/selector'; 10 | export * from './annotation/factory/annotation.factory'; 11 | export * from './annotation/factory/decorator-hook.registry'; 12 | export * from './annotation/factory/decorator-hook.type'; 13 | export * from './annotation/target/annotation-target'; 14 | export * from './annotation/target/annotation-target.factory'; 15 | export * from './annotation/target/annotation-target.impl'; 16 | export * from './annotation/target/bound-annotation-target'; 17 | // export * from './annotation/trigger/annotation-trigger.registry'; 18 | // export * from './annotation/trigger/annotation-trigger.type'; 19 | export * from '../utils/src/abstract/abstract-token.type'; 20 | export * from '../utils/src/abstract/abstract.type'; 21 | export * from './reflect/module/reflect-module-config.type'; 22 | export * from './reflect/module/reflect-module.type'; 23 | export * from './reflect/reflect-provider.type'; 24 | export * from './reflect/reflect.context'; 25 | export * from './reflect/reflect.context.global'; 26 | export * from './reflect/reflect.error'; 27 | -------------------------------------------------------------------------------- /packages/common/src/reflect/module/reflect-module-config.type.ts: -------------------------------------------------------------------------------- 1 | import type { ReflectProvider } from '../reflect-provider.type'; 2 | 3 | export interface ReflectModuleConfiguration { 4 | providers: ReflectProvider[]; 5 | } 6 | -------------------------------------------------------------------------------- /packages/common/src/reflect/module/reflect-module.type.ts: -------------------------------------------------------------------------------- 1 | import { ReflectModuleConfiguration } from './reflect-module-config.type'; 2 | 3 | export const ReflectModule = function ReflectModule( 4 | config: ReflectModuleConfiguration, 5 | ) { 6 | return function (target: any) { 7 | target[Symbol.for('@ajs:rmd')] = config; 8 | 9 | return target; 10 | } satisfies ClassDecorator; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/common/src/reflect/reflect-provider.type.ts: -------------------------------------------------------------------------------- 1 | import { ConstructorType } from '@aspectjs/common/utils'; 2 | 3 | /** 4 | * @internal 5 | * Type provided by a ReflectProvider 6 | */ 7 | export type ReflectProviderType = ConstructorType & { 8 | __providerName?: string; 9 | }; 10 | 11 | /** 12 | * @internal 13 | * Provide a service to the Reflect Context 14 | */ 15 | export type ReflectProvider = { 16 | deps?: (ConstructorType | string)[]; 17 | provide: ReflectProviderType | string; 18 | factory: (...args: any[]) => T; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/common/src/reflect/reflect.context.global.ts: -------------------------------------------------------------------------------- 1 | import { ReflectContext } from './reflect.context'; 2 | 3 | /** 4 | * Get the reflect context. 5 | * @internal 6 | * @returns The reflect context. 7 | */ 8 | export const reflectContext = () => { 9 | return (globalThis as any).__reflectContext as ReflectContext; 10 | }; 11 | 12 | /** 13 | * @internal 14 | * Replace the current reflect context. Internally called by {@link ./../../testing/src/setup#configureTesting} 15 | * @param context 16 | * @returns 17 | */ 18 | export const _setReflectContext = (context: ReflectContext) => 19 | ((globalThis as any).__reflectContext = context); 20 | 21 | _setReflectContext(reflectContext() ?? new ReflectContext()); 22 | -------------------------------------------------------------------------------- /packages/common/src/reflect/reflect.error.ts: -------------------------------------------------------------------------------- 1 | export class ReflectError extends Error {} 2 | -------------------------------------------------------------------------------- /packages/common/src/reflect/runtime-state.provider.ts: -------------------------------------------------------------------------------- 1 | import { ReflectProvider } from './reflect-provider.type'; 2 | 3 | (globalThis as any).__aspectjs_global_runtime_instance = 0; 4 | /** 5 | * @internal 6 | */ export class __RuntimeState { 7 | instanceId = (globalThis as any).__aspectjs_global_runtime_instance++; // will be incremented again next time configureTesting() is called 8 | } 9 | 10 | export const RUNTIME_STATE_PROVIDER: ReflectProvider = { 11 | provide: __RuntimeState, 12 | factory: () => { 13 | return new __RuntimeState(); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/common/testing/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/public_api'; 2 | -------------------------------------------------------------------------------- /packages/common/testing/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './testing-context.global'; 2 | -------------------------------------------------------------------------------- /packages/common/testing/src/testing-context.spec.ts: -------------------------------------------------------------------------------- 1 | import { ReflectModule } from '@aspectjs/common'; 2 | import { configureTesting } from './testing-context.global'; 3 | 4 | describe('configureTesting()', () => { 5 | it('adds the given providers to the reflect context', () => { 6 | const testingValue = {}; 7 | const testingProvider = { 8 | provide: 'testing', 9 | factory: () => testingValue, 10 | }; 11 | 12 | @ReflectModule({ 13 | providers: [testingProvider], 14 | }) 15 | class TestingModule {} 16 | 17 | @ReflectModule({ 18 | providers: [ 19 | { 20 | provide: 'testing2', 21 | factory: () => 'testing2Value', 22 | }, 23 | ], 24 | }) 25 | class TestingModule2 {} 26 | 27 | let testingContext = configureTesting(TestingModule, TestingModule2); 28 | expect(testingContext.get('testing2')).toEqual('testing2Value'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/common/testing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../../tsconfig.json", 4 | "exclude": ["jest.config.ts", "./**/*.spec.ts"], 5 | "include": ["./src/**/*.ts", "index.ts"], 6 | "compilerOptions": { 7 | "baseUrl": "." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "include": ["./src/**/*.ts", "./index.ts"], 5 | "compilerOptions": { 6 | "baseUrl": "." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/common/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "exclude": ["jest.config.ts", "./**/*.spec.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/common/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.spec.json", 4 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/common/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/public_api'; 2 | -------------------------------------------------------------------------------- /packages/common/utils/src/abstract/abstract-token-impl.type.ts: -------------------------------------------------------------------------------- 1 | import { ConstructorType } from '../types.util'; 2 | import type { AbstractToken } from './abstract-token.type'; 3 | 4 | export class _AbstractTokenImpl implements AbstractToken { 5 | constructor( 6 | public readonly counter: number, 7 | public readonly template?: T | ConstructorType, 8 | ) {} 9 | toSting() { 10 | return '[ABSTRACT_TOKEN placeholder]'; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/common/utils/src/abstract/abstract-token.type.ts: -------------------------------------------------------------------------------- 1 | import { ConstructorType } from '../types.util'; 2 | 3 | export interface AbstractToken { 4 | readonly template?: T | ConstructorType; 5 | } 6 | -------------------------------------------------------------------------------- /packages/common/utils/src/abstract/abstract.type.spec.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationFactory, AnnotationKind } from '@aspectjs/common'; 2 | import { configureTesting } from '@aspectjs/common/testing'; 3 | import { _defuseAbstract, abstract } from './abstract.type'; 4 | 5 | describe('abstract() helper', () => { 6 | let MethodAnnotation: any; 7 | beforeEach(() => { 8 | configureTesting(); 9 | const af = new AnnotationFactory('test'); 10 | MethodAnnotation = af.create(AnnotationKind.METHOD, 'ClassAnnotation'); 11 | }); 12 | 13 | it('should throw an error when called', () => { 14 | expect(() => abstract()).toThrow(); 15 | }); 16 | 17 | describe(`within ${_defuseAbstract.name}`, () => { 18 | describe('when used as a return value', () => { 19 | it('does not throw an error', () => { 20 | class X { 21 | @MethodAnnotation() 22 | method() { 23 | return abstract(); 24 | } 25 | } 26 | expect(() => _defuseAbstract(() => new X().method())).not.toThrow(); 27 | }); 28 | }); 29 | 30 | describe('when used more than once', () => { 31 | it('throws an error', () => { 32 | class X { 33 | @MethodAnnotation() 34 | method() { 35 | abstract(); 36 | return abstract(); 37 | } 38 | } 39 | expect(() => _defuseAbstract(() => new X().method())).toThrow( 40 | new Error( 41 | '"abstract()" placeholder should only be used as a return value.', 42 | ), 43 | ); 44 | }); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/common/utils/src/assert.util.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from './utils'; 2 | 3 | let __debug = false; 4 | 5 | export function setDebug(debug: boolean) { 6 | __debug = debug; 7 | } 8 | 9 | export function isDebug() { 10 | return __debug; 11 | } 12 | 13 | export function assert(condition: boolean, errorProvider?: () => Error): void; 14 | export function assert( 15 | condition: () => boolean, 16 | errorProvider?: () => Error, 17 | ): void; 18 | export function assert(condition: boolean, msg?: string): void; 19 | export function assert(condition: true, msg?: string): void; 20 | export function assert(condition: false, msg?: string): never; 21 | export function assert( 22 | condition: boolean | (() => boolean), 23 | msg?: string | (() => Error), 24 | ) { 25 | if (__debug) { 26 | const conditionValue = 27 | typeof condition === 'function' ? condition() : condition; 28 | 29 | if (!conditionValue) { 30 | /* eslint-disable no-debugger */ 31 | debugger; 32 | const e = isFunction(msg) ? msg() : new Error(msg ?? 'assertion error'); 33 | const stack = e.stack?.split('\n') ?? []; 34 | stack.splice(1, 1); 35 | e.stack = stack.join('\n'); 36 | 37 | throw e; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/common/utils/src/clone.util.ts: -------------------------------------------------------------------------------- 1 | import { assert } from './assert.util'; 2 | import { defineMetadata, getMetadata, getMetadataKeys } from './meta.util'; 3 | 4 | /** 5 | * @internal 6 | */ 7 | export const _copyPropsAndMeta = ( 8 | target: T, 9 | source: T, 10 | propertyKeys: (string | symbol)[] = [], 11 | ) => { 12 | const valueType = typeof source; 13 | 14 | assert(valueType === 'object' || valueType === 'function'); 15 | if (valueType === 'object' || valueType === 'function') { 16 | // copy static props 17 | Object.defineProperties( 18 | target, 19 | Object.entries(Object.getOwnPropertyDescriptors(source)) 20 | .filter(([name]) => { 21 | const ownDescriptor = Object.getOwnPropertyDescriptor(target, name); 22 | 23 | return !ownDescriptor || ownDescriptor.configurable; 24 | }) 25 | .reduce( 26 | (descriptors, [name, descriptor]) => ({ 27 | ...descriptors, 28 | [name]: descriptor, 29 | }), 30 | {}, 31 | ), 32 | ); 33 | 34 | getMetadataKeys(source) 35 | .map((key) => [key, getMetadata(key, source)] as [string, any]) 36 | .forEach(([key, value]) => { 37 | defineMetadata(key, value, target); 38 | }); 39 | 40 | propertyKeys.forEach((pk) => { 41 | getMetadataKeys(source, pk) 42 | .map((key) => [key, getMetadata(key, source, pk)] as [string, any]) 43 | .forEach(([key, value]) => { 44 | defineMetadata(key, value, target, pk); 45 | }); 46 | }); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /packages/common/utils/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './abstract/abstract-token.type'; 2 | export * from './abstract/abstract.type'; 3 | export * from './assert.util'; 4 | export * from './clone.util'; 5 | export * from './meta.util'; 6 | export * from './types.util'; 7 | export * from './utils'; 8 | -------------------------------------------------------------------------------- /packages/common/utils/src/types.util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @internal 3 | */ 4 | export type AbstractConstructorType = abstract new ( 5 | ...args: any[] 6 | ) => X; 7 | 8 | export type ConcreteConstructorType = new (...value: any[]) => X; 9 | export type ConstructorType = 10 | | ConcreteConstructorType 11 | | AbstractConstructorType; 12 | 13 | /** 14 | * @internal 15 | */ 16 | export type MethodPropertyDescriptor = PropertyDescriptor & { 17 | value: (...args: any[]) => any; 18 | get: never; 19 | }; 20 | 21 | /** 22 | * @internal 23 | */ 24 | // eslint-disable-next-line @typescript-eslint/ban-types 25 | export type Prototype = Record & { 26 | // eslint-disable-next-line @typescript-eslint/ban-types 27 | constructor: ConstructorType; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/common/utils/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Prototype } from './types.util'; 2 | 3 | export function getPrototype( 4 | // eslint-disable-next-line @typescript-eslint/ban-types 5 | target: Record | Function | undefined, 6 | ): Prototype { 7 | if (isFunction(target)) { 8 | return target.prototype; 9 | } else if (target === null || target === undefined) { 10 | return target as any; 11 | } 12 | // eslint-disable-next-line no-prototype-builtins 13 | return Object.getOwnPropertyDescriptor(target, 'constructor')?.enumerable === 14 | false 15 | ? target 16 | : Object.getPrototypeOf(target); 17 | } 18 | 19 | export function isAnnotation(obj: unknown): boolean { 20 | return ( 21 | typeof obj === 'function' && 22 | typeof (obj as any).ref?.groupId === 'string' && 23 | typeof (obj as any).ref?.name === 'string' 24 | ); 25 | } 26 | 27 | export function isFunction( 28 | value: unknown, 29 | ): value is (...args: unknown[]) => unknown { 30 | return typeof value === 'function'; 31 | } 32 | 33 | export function isObject(value: unknown): value is object { 34 | return typeof value === 'object' && !Array.isArray(value); 35 | } 36 | 37 | export function isNumber(value: unknown): value is number { 38 | return typeof value === 'number'; 39 | } 40 | 41 | export function isUndefined(value: unknown): value is undefined { 42 | return typeof value === 'undefined'; 43 | } 44 | 45 | export function isEmpty(value: unknown[]): boolean { 46 | return value.length === 0; 47 | } 48 | 49 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 50 | export function isPromise(obj: any): obj is Promise { 51 | return isFunction(obj?.then); 52 | } 53 | 54 | export function isClassInstance(obj: any): boolean { 55 | return ( 56 | typeof obj === 'object' && getPrototype(obj) === Object.getPrototypeOf(obj) 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /packages/common/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../../tsconfig.json", 4 | "exclude": ["jest.config.ts", "./**/*.spec.ts"], 5 | "include": ["./src/**/*.ts", "index.ts"], 6 | "compilerOptions": { 7 | "baseUrl": "." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/.assets/aspectjs-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicolasThierion/aspectjs/dda0d35f73d111072855a9ac284f2a6119d361c9/packages/core/.assets/aspectjs-example.png -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/core/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - ci-templates/node.yml 3 | 4 | core:lint: 5 | variables: 6 | CWD: packages/core 7 | extends: .node:lint 8 | stage: test 9 | needs: 10 | - node:prepare 11 | 12 | core:unit: 13 | extends: .node:test:unit 14 | variables: 15 | CWD: packages/core 16 | stage: test 17 | needs: 18 | - node:prepare 19 | 20 | core:build: 21 | extends: .node:build 22 | variables: 23 | CWD: packages/core 24 | stage: compile 25 | needs: 26 | - node:prepare 27 | 28 | core:publish:gitlab: 29 | extends: .node:gitlab:publish 30 | stage: publish 31 | variables: 32 | CWD: packages/core 33 | PACKAGE_DIR: ./dist 34 | 35 | core:publish:npm: 36 | extends: .node:npm:publish 37 | stage: release 38 | variables: 39 | CWD: packages/core 40 | PACKAGE_DIR: ./dist 41 | rules: 42 | - if: $CI_COMMIT_REF_NAME =~ /^v\d+\.\d+\.\d+.*$/ 43 | when: manual 44 | -------------------------------------------------------------------------------- /packages/core/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "node", 5 | "name": "vscode-jest-tests.v2.core", 6 | "request": "launch", 7 | "args": [ 8 | "--runInBand", 9 | "--watchAll=false", 10 | "--testNamePattern", 11 | "${jest.testNamePattern}", 12 | "--runTestsByPath", 13 | "${jest.testFile}" 14 | ], 15 | "skipFiles": [ 16 | "/**/*.js", 17 | "${workspaceFolder}/node_modules/**/*.js", 18 | "${workspaceFolder}/node_modules/**/*.mjs" 19 | ], 20 | "cwd": "${workspaceFolder}", 21 | "console": "integratedTerminal", 22 | "internalConsoleOptions": "neverOpen", 23 | "disableOptimisticBPs": true, 24 | "program": "${workspaceFolder}/../../node_modules/.bin/jest" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "jest.jestCommandLine": "../../node_modules/.bin/jest" 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Core features of AspectJS. Declares the annotations to create aspects, advices, pointcuts, 3 | * and all the classes and providers to make the weaving happen. 4 | * @module 5 | */ 6 | export * from './src/public_api'; 7 | -------------------------------------------------------------------------------- /packages/core/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { JestConfigWithTsJest } from 'ts-jest'; 3 | import config from '../../jest.config'; 4 | 5 | export default { 6 | ...config, 7 | coverageDirectory: join(__dirname, 'dist', 'coverage'), 8 | collectCoverageFrom: ['./**/*.{js,ts}'], 9 | } satisfies JestConfigWithTsJest; 10 | -------------------------------------------------------------------------------- /packages/core/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aspectjs/core", 3 | "version": "0.2.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@aspectjs/core", 9 | "version": "0.2.0", 10 | "license": "MIT" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aspectjs/core", 3 | "version": "0.5.4", 4 | "description": "Aspect Oriented Programming based on decorators, for browser & node", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/NicolasThierion/aspectjs.git", 8 | "directory": "packages/core" 9 | }, 10 | "scripts": { 11 | "test": "npm run test:lint && npm run test:unit", 12 | "test:lint": "eslint \"src/**/*.{ts,js,json}\"", 13 | "test:unit": "npm run clean && jest --runInBand --collectCoverage", 14 | "pack": "npm pack ./dist", 15 | "lint": "eslint \"*/**/*.{ts,js,json}\" --fix", 16 | "build": "rollup --config ./rollup.config.cjs", 17 | "clean": "rimraf dist && rimraf ./*.tgz" 18 | }, 19 | "author": "Nicolas Thierion ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/NicolasThierion/aspectjs/issues" 23 | }, 24 | "homepage": "https://aspectjs.gitlab.io/", 25 | "type": "module", 26 | "sideEffects": false, 27 | "exports": { 28 | ".": { 29 | "types": "./index.d.ts", 30 | "fesm2020": "./fesm2020/core.mjs", 31 | "esm2020": "./esm2020/index.mjs", 32 | "esm": "./fesm2020/index.mjs", 33 | "require": "./cjs/index.cjs", 34 | "import": "./fesm2020/core.mjs", 35 | "unpkg": "./umd/core.umd.min.js" 36 | } 37 | }, 38 | "module": "./fesm2020/core.mjs", 39 | "types": "./index.d.ts", 40 | "main": "./umd/core.umd.js", 41 | "peerDependencies": { 42 | "@aspectjs/common": "*" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/core/rollup.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const { createConfig } = require('../../rollup.config.cjs'); 3 | module.exports = createConfig(__dirname); 4 | -------------------------------------------------------------------------------- /packages/core/src/advice/advice-sort.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AnnotationContextRegistry, 3 | AnnotationKind, 4 | AnnotationTargetFactory, 5 | } from '@aspectjs/common'; 6 | import { getPrototype } from '@aspectjs/common/utils'; 7 | import { Order } from '../annotations/order.annotation'; 8 | import { AdviceEntry } from './registry/advice-entry.model'; 9 | 10 | export class AdviceSorter { 11 | constructor( 12 | private readonly annotationContextRegistry: AnnotationContextRegistry, 13 | private readonly annotationTargetFactory: AnnotationTargetFactory, 14 | ) {} 15 | 16 | sort(adviceEntry1: AdviceEntry, adviceEntry2: AdviceEntry) { 17 | const orderAnnotations1 = this.getOrderAnnotation(adviceEntry1); 18 | const orderAnnotations2 = this.getOrderAnnotation(adviceEntry2); 19 | 20 | const [order1, order2] = [ 21 | orderAnnotations1?.args[0], 22 | orderAnnotations2?.args[0], 23 | ]; 24 | if ((order1 === undefined && order2) === undefined) { 25 | return 0; 26 | } 27 | 28 | if ( 29 | order1 === Order.HIGHEST_PRECEDENCE || 30 | order2 === undefined || 31 | order2 === Order.LOWEST_PRECEDENCE 32 | ) { 33 | return -1; 34 | } else if ( 35 | order2 === Order.HIGHEST_PRECEDENCE || 36 | order1 === undefined || 37 | order1 === Order.LOWEST_PRECEDENCE 38 | ) { 39 | return 1; 40 | } 41 | 42 | return order1 - order2; 43 | } 44 | 45 | private getOrderAnnotation(entry: AdviceEntry) { 46 | const adviceTarget = this.annotationTargetFactory.of( 47 | getPrototype(entry.aspect).constructor, 48 | entry.advice.name, 49 | ); 50 | 51 | return this.annotationContextRegistry 52 | .select(Order) 53 | .on({ 54 | target: adviceTarget, 55 | types: [AnnotationKind.CLASS, AnnotationKind.METHOD], 56 | }) 57 | .find({ 58 | searchParents: true, 59 | }) 60 | .reverse()[0]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/core/src/advice/advice-target.type.ts: -------------------------------------------------------------------------------- 1 | import type { AnnotationTarget } from '@aspectjs/common'; 2 | import type { 3 | PointcutKind, 4 | ToAnnotationKind, 5 | } from '../pointcut/pointcut-kind.type'; 6 | 7 | export type AdviceTarget< 8 | T extends PointcutKind = PointcutKind, 9 | X = unknown, 10 | > = AnnotationTarget, X> & { 11 | eval(): T extends PointcutKind.PARAMETER ? never : unknown; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/core/src/advice/advice-type.type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Speficies the type of an advice 3 | */ 4 | export enum AdviceKind { 5 | /** 6 | * Advice is triggered at compile time (eg: when the annotation is processed) 7 | */ 8 | COMPILE = 'Compile', 9 | /** 10 | * Advice is triggered before the execution of the joinpoint 11 | */ 12 | BEFORE = 'Before', 13 | /** 14 | * Advice is triggered around the execution of the joinpoint 15 | */ 16 | AROUND = 'Around', 17 | /** 18 | * Advice is triggered after the joinpoint returned sucessfully 19 | */ 20 | AFTER_RETURN = 'AfterReturn', 21 | /** 22 | * Advice is triggered after the joinpoint thrown an error 23 | */ 24 | AFTER_THROW = 'AfterThrow', 25 | /** 26 | * Advice is triggered after the execution of the joinpoint 27 | */ 28 | AFTER = 'After', 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/src/advice/advice.context.ts: -------------------------------------------------------------------------------- 1 | import type { AfterContext } from '../advices/after/after.context'; 2 | import type { PointcutKind } from '../pointcut/pointcut-kind.type'; 3 | 4 | import type { AfterReturnContext } from '../advices/after-return/after-return.context'; 5 | import type { AfterThrowContext } from '../advices/after-throw/after-throw.context'; 6 | import type { AroundContext } from '../advices/around/around.context'; 7 | import type { CompileContext } from '../advices/compile/compile.context'; 8 | import type { BeforeContext } from './../advices/before/before.context'; 9 | 10 | /** 11 | * Holds details about execution context that are passed to an advice when it is called. 12 | */ 13 | export type AdviceContext = 14 | | AfterContext 15 | | BeforeContext 16 | | AfterReturnContext 17 | | AfterThrowContext 18 | | AroundContext 19 | | CompileContext; 20 | -------------------------------------------------------------------------------- /packages/core/src/advice/advice.type.ts: -------------------------------------------------------------------------------- 1 | import type { AfterReturnAdvice } from '../advices/after-return/after-return.type'; 2 | import type { AfterThrowAdvice } from '../advices/after-throw/after-throw.type'; 3 | import type { AfterAdvice } from '../advices/after/after.type'; 4 | import type { AroundAdvice } from '../advices/around/around.type'; 5 | import type { BeforeAdvice } from '../advices/before/before.type'; 6 | import type { CompileAdvice } from '../advices/compile/compile.type'; 7 | import type { PointcutKind } from '../pointcut/pointcut-kind.type'; 8 | import { AdviceKind } from './advice-type.type'; 9 | 10 | export type Advice< 11 | T extends PointcutKind = PointcutKind, 12 | X = unknown, 13 | V extends AdviceKind = any, 14 | > = V extends AdviceKind.COMPILE 15 | ? CompileAdvice 16 | : V extends AdviceKind.BEFORE 17 | ? BeforeAdvice 18 | : V extends AdviceKind.AROUND 19 | ? AroundAdvice 20 | : V extends AdviceKind.AFTER_RETURN 21 | ? AfterReturnAdvice 22 | : V extends AdviceKind.AFTER_THROW 23 | ? AfterThrowAdvice 24 | : V extends AdviceKind.AFTER 25 | ? AfterAdvice 26 | : never; 27 | -------------------------------------------------------------------------------- /packages/core/src/advice/joinpoint.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Hold the original function, 3 | * bound to its execution context and it original parameters. 4 | * - Call this method without parameters to call the original function without parameters. 5 | * - Call this method with an new parameters to call the original function with the given parameters. 6 | * 7 | * In any way, calling a joinpoint twice will throw a WeavingError 8 | */ 9 | export type JoinPoint = (...args: unknown[]) => T; 10 | -------------------------------------------------------------------------------- /packages/core/src/advice/registry/advice-entry.model.ts: -------------------------------------------------------------------------------- 1 | import { getMetadata } from '@aspectjs/common/utils'; 2 | 3 | import type { AspectType } from '../../aspect/aspect.type'; 4 | import type { PointcutKind } from '../../pointcut/pointcut-kind.type'; 5 | import { AdviceKind } from '../advice-type.type'; 6 | import type { Advice } from '../advice.type'; 7 | 8 | export type AdviceRegBuckets = { 9 | [t in PointcutKind]?: { 10 | [p in AdviceKind]?: Map; 11 | }; 12 | }; 13 | 14 | export type AdviceEntry< 15 | T extends PointcutKind = PointcutKind, 16 | X = unknown, 17 | P extends AdviceKind = AdviceKind, 18 | > = { 19 | advice: Advice; 20 | aspect: AspectType; 21 | }; 22 | 23 | // eslint-disable-next-line @typescript-eslint/no-namespace 24 | export namespace AdviceEntry { 25 | export function of(aspect: AspectType, advice: Advice): AdviceEntry { 26 | return getMetadata( 27 | 'advice-entry', 28 | aspect, 29 | advice.name, 30 | () => ({ 31 | aspect, 32 | advice, 33 | }), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/core/src/advices/after-return/after-return.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { PointcutExpression } from '../../pointcut/pointcut-expression.type'; 3 | import { _CORE_ANNOTATION_FACTORY } from '../../utils'; 4 | /* eslint-disable @typescript-eslint/no-unused-vars */ 5 | 6 | export const AfterReturn = _CORE_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'AfterReturn', 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | function (...pointcutExps: PointcutExpression[]) {}, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/core/src/advices/after-return/after-return.context.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { AnnotationsByRefSelector } from '@aspectjs/common'; 3 | import type { 4 | PointcutKind, 5 | ToAnnotationKind, 6 | } from '../../pointcut/pointcut-kind.type'; 7 | import type { AdviceTarget } from './../../advice/advice-target.type'; 8 | 9 | import type { AfterReturnAdvice } from './after-return.type'; 10 | 11 | /** 12 | * Execution context passed to advices of type {@link AfterReturnAdvice} 13 | */ 14 | export interface AfterReturnContext< 15 | T extends PointcutKind = PointcutKind, 16 | X = unknown, 17 | > { 18 | /** The annotation contexts **/ 19 | readonly annotations: AnnotationsByRefSelector< 20 | ToAnnotationKind 21 | >['annotations']; 22 | 23 | /** The 'this' instance bound to the current execution context **/ 24 | readonly instance: X; 25 | /** the arguments originally passed to the joinpoint **/ 26 | readonly args: unknown[]; 27 | /** The value originally returned by the joinpoint **/ 28 | readonly value: unknown; 29 | /** The symbol targeted by this advice (class, method, property or parameter **/ 30 | readonly target: AdviceTarget; 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/advices/after-return/after-return.type.ts: -------------------------------------------------------------------------------- 1 | import { AdviceKind } from '../../advice/advice-type.type'; 2 | import { Pointcut } from '../../pointcut/pointcut'; 3 | import type { PointcutKind } from '../../pointcut/pointcut-kind.type'; 4 | import { AfterReturnContext } from './after-return.context'; 5 | 6 | export type AfterReturnPointcut = 7 | Pointcut; 8 | 9 | export type AfterReturnAdvice< 10 | T extends PointcutKind = PointcutKind, 11 | X = unknown, 12 | > = { 13 | name: string; 14 | pointcuts: AfterReturnPointcut[]; 15 | } & (( 16 | ctxt: AfterReturnContext, 17 | returnValue: any, 18 | ) => T | null | undefined); 19 | -------------------------------------------------------------------------------- /packages/core/src/advices/after-throw/after-throw.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { AnnotationKind } from '@aspectjs/common'; 3 | import type { PointcutExpression } from '../../pointcut/pointcut-expression.type'; 4 | import { _CORE_ANNOTATION_FACTORY } from '../../utils'; 5 | 6 | /** 7 | * Annotation to declare an advice to be applied after a method throws an exception. 8 | * 9 | * The AfterThrow annotation is used to apply after-throwing advice to a method. 10 | * After-throwing advice is executed after the target method throws an exception. 11 | * It is commonly used to handle exceptions, perform cleanup tasks, or additional actions 12 | * that need to be executed after an exception is thrown. 13 | * 14 | * @param pointcutExp - The pointcut expression specifying the join points where the after-throwing advice should be applied. 15 | * @returns An ES decorator representing the AfterThrow annotation. 16 | * 17 | * @example 18 | * ```typescript 19 | * import { AfterThrow, on } from '@aspectjs/core'; 20 | * 21 | * class MyClass { 22 | * @AfterThrow(on.classes.withAnnotations(SomeAnnotation)) 23 | * myMethod() { 24 | * // Method implementation 25 | * } 26 | * } 27 | * ``` 28 | */ 29 | export const AfterThrow = _CORE_ANNOTATION_FACTORY.create( 30 | AnnotationKind.METHOD, 31 | 'AfterThrow', 32 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 33 | // @ts-ignore 34 | function (...pointcutExps: PointcutExpression[]) {}, 35 | ); 36 | -------------------------------------------------------------------------------- /packages/core/src/advices/after-throw/after-throw.context.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { AnnotationsByRefSelector } from '@aspectjs/common'; 3 | import type { 4 | PointcutKind, 5 | ToAnnotationKind, 6 | } from '../../pointcut/pointcut-kind.type'; 7 | import type { AdviceTarget } from './../../advice/advice-target.type'; 8 | 9 | import type { AfterThrowAdvice } from './after-throw.type'; 10 | 11 | /** 12 | * Execution context passed to advices of type {@link AfterThrowAdvice} 13 | */ 14 | export interface AfterThrowContext< 15 | T extends PointcutKind = PointcutKind, 16 | X = unknown, 17 | > { 18 | /** The annotation contexts **/ 19 | readonly annotations: AnnotationsByRefSelector< 20 | ToAnnotationKind 21 | >['annotations']; 22 | /** The 'this' instance bound to the current execution context **/ 23 | readonly instance: X; 24 | /** the arguments originally passed to the joinpoint **/ 25 | readonly args: unknown[]; 26 | /** The error originally thrown by the joinpoint **/ 27 | readonly error: Error | unknown; 28 | /** The symbol targeted by this advice (class, method, property or parameter **/ 29 | readonly target: AdviceTarget; 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/advices/after-throw/after-throw.type.ts: -------------------------------------------------------------------------------- 1 | import { AdviceKind } from '../../advice/advice-type.type'; 2 | import type { AdviceContext } from '../../advice/advice.context'; 3 | import { Pointcut } from '../../pointcut/pointcut'; 4 | import type { PointcutKind } from '../../pointcut/pointcut-kind.type'; 5 | 6 | export type AfterThrowPointcut = 7 | Pointcut; 8 | 9 | export type AfterThrowAdvice< 10 | T extends PointcutKind = PointcutKind, 11 | X = unknown, 12 | > = { 13 | name: string; 14 | pointcuts: AfterThrowPointcut[]; 15 | } & ((ctxt: AdviceContext, thrownError: Error) => T | null | undefined); 16 | -------------------------------------------------------------------------------- /packages/core/src/advices/after/after.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { PointcutExpression } from '../../pointcut/pointcut-expression.type'; 3 | import { _CORE_ANNOTATION_FACTORY } from '../../utils'; 4 | /* eslint-disable @typescript-eslint/no-unused-vars */ 5 | 6 | /** 7 | * Annotation to declare an advice to be applied after a method. 8 | * The After annotation is used to apply after advice to a method. 9 | * After advice is executed after the target method completes its execution normally or with an error. 10 | * It is commonly used to perform cleanup tasks, logging, or additional actions that need to be executed 11 | * after the method's execution. 12 | * @param pointcutExp - The pointcut expression specifying the join points where the after advice should be applied. 13 | * @returns An ES decorator representing the After annotation. 14 | * 15 | * @example 16 | * ```ts 17 | * import { After, on } from '@aspectjs/core'; 18 | * 19 | * class MyClass { 20 | * @After(on.classes.withAnnotations(SomeAnnotation)) 21 | * myMethod() { 22 | * // Method implementation 23 | * } 24 | * } 25 | * ``` 26 | */ 27 | 28 | export const After = _CORE_ANNOTATION_FACTORY.create( 29 | AnnotationKind.METHOD, 30 | 'After', 31 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 32 | // @ts-ignore 33 | function (...pointcutExps: PointcutExpression[]) {}, 34 | ); 35 | -------------------------------------------------------------------------------- /packages/core/src/advices/after/after.context.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { AnnotationsByRefSelector } from '@aspectjs/common'; 3 | import type { 4 | PointcutKind, 5 | ToAnnotationKind, 6 | } from '../../pointcut/pointcut-kind.type'; 7 | import type { AdviceTarget } from './../../advice/advice-target.type'; 8 | 9 | import type { AfterAdvice } from './after.type'; 10 | /** 11 | * Execution context passed to advices of type {@link AfterAdvice} 12 | */ 13 | export interface AfterContext< 14 | T extends PointcutKind = PointcutKind, 15 | X = object, 16 | > { 17 | /** The annotations contexts **/ 18 | readonly annotations: AnnotationsByRefSelector< 19 | ToAnnotationKind 20 | >['annotations']; 21 | /** The 'this' instance bound to the current execution context **/ 22 | readonly instance: X; 23 | /** the arguments originally passed to the joinpoint **/ 24 | readonly args: any[]; 25 | /** The symbol targeted by this advice (class, method, property or parameter **/ 26 | readonly target: AdviceTarget; 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/advices/after/after.type.ts: -------------------------------------------------------------------------------- 1 | import { AdviceKind } from '../../advice/advice-type.type'; 2 | import type { AdviceContext } from '../../advice/advice.context'; 3 | import { Pointcut } from '../../pointcut/pointcut'; 4 | import type { PointcutKind } from '../../pointcut/pointcut-kind.type'; 5 | 6 | export type AfterPointcut = Pointcut< 7 | AdviceKind.AFTER, 8 | T 9 | >; 10 | 11 | export type AfterAdvice = { 12 | /** 13 | * The name of this advice 14 | */ 15 | name: string; 16 | /** 17 | * The set of pointcuts this advice stops at 18 | */ 19 | pointcuts: AfterPointcut[]; 20 | } & (( 21 | /** 22 | * The advice context 23 | */ 24 | ctxt: AdviceContext, 25 | ) => void); 26 | -------------------------------------------------------------------------------- /packages/core/src/advices/around/around.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { PointcutExpression } from '../../pointcut/pointcut-expression.type'; 3 | import { _CORE_ANNOTATION_FACTORY } from '../../utils'; 4 | /* eslint-disable @typescript-eslint/no-unused-vars */ 5 | 6 | /** 7 | * Annotation to declare an advice to be applied around a method. 8 | * 9 | * The Around annotation is used to apply around advice to a method. 10 | * Around advice is executed before and after the target method's execution. 11 | * It allows full control over the method's execution by intercepting the method invocation. 12 | * It is commonly used for method interception, performance monitoring, or transaction management. 13 | * 14 | * @param pointcutExp - The pointcut expression specifying the join points where the around advice should be applied. 15 | * @returns An ES decorator representing the Around annotation. 16 | * 17 | * @example 18 | * ```typescript 19 | * import { Around, on } from '@aspectjs/core'; 20 | * 21 | * class MyClass { 22 | * @Around(on.classes.withAnnotations(SomeAnnotation)) 23 | * applyAroundAdvice(ctxt: AroundContext) { 24 | * // Method implementation 25 | * } 26 | * } 27 | * ``` 28 | */ 29 | export const Around = _CORE_ANNOTATION_FACTORY.create( 30 | AnnotationKind.METHOD, 31 | 'Around', 32 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 33 | // @ts-ignore 34 | function (...pointcutExps: PointcutExpression[]) {}, 35 | ); 36 | -------------------------------------------------------------------------------- /packages/core/src/advices/around/around.context.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { AnnotationsByRefSelector } from '@aspectjs/common'; 3 | import type { 4 | PointcutKind, 5 | ToAnnotationKind, 6 | } from '../../pointcut/pointcut-kind.type'; 7 | import type { AdviceTarget } from './../../advice/advice-target.type'; 8 | import type { JoinPoint } from './../../advice/joinpoint'; 9 | 10 | import type { AroundAdvice } from './around.type'; 11 | 12 | /** 13 | * Execution context passed to advices of type {@link AroundAdvice} 14 | */ 15 | export interface AroundContext< 16 | T extends PointcutKind = PointcutKind, 17 | X = object, 18 | > { 19 | /** The annotation contexts **/ 20 | readonly annotations: AnnotationsByRefSelector< 21 | ToAnnotationKind 22 | >['annotations']; 23 | /** The 'this' instance bound to the current execution context **/ 24 | readonly instance: X; 25 | /** the arguments originally passed to the joinpoint **/ 26 | readonly args: unknown[]; 27 | /** Hold the original function, bound to its execution context and it original parameters **/ 28 | readonly joinpoint: JoinPoint; 29 | /** The symbol targeted by this advice (class, method, property or parameter **/ 30 | readonly target: AdviceTarget; 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/advices/around/around.type.ts: -------------------------------------------------------------------------------- 1 | import { AdviceKind } from '../../advice/advice-type.type'; 2 | import type { AdviceContext } from '../../advice/advice.context'; 3 | import type { JoinPoint } from '../../advice/joinpoint'; 4 | import { Pointcut } from '../../pointcut/pointcut'; 5 | import type { PointcutKind } from '../../pointcut/pointcut-kind.type'; 6 | 7 | export type AroundPointcut = Pointcut< 8 | AdviceKind.AROUND, 9 | T 10 | >; 11 | 12 | export type AroundAdvice = { 13 | name: string; 14 | pointcuts: AroundPointcut[]; 15 | } & ((ctxt: AdviceContext, joinPoint: JoinPoint, args: any[]) => any); 16 | -------------------------------------------------------------------------------- /packages/core/src/advices/before/before.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { PointcutExpression } from '../../pointcut/pointcut-expression.type'; 3 | import { _CORE_ANNOTATION_FACTORY } from '../../utils'; 4 | /* eslint-disable @typescript-eslint/no-unused-vars */ 5 | 6 | /** 7 | * Annotation to declare an advice to be applied before a joinpoint. 8 | * 9 | * The Before annotation is used to apply before advice 10 | * to a joinpoint. Before advice is executed before the target method starts its execution. 11 | * It is commonly used to perform setup tasks, parameter validation, or additional actions 12 | * that need to be executed before the method's execution. 13 | * 14 | * @param pointcutExp (required): The pointcut expression 15 | * specifying the join points where the before advice should be applied. 16 | * 17 | * @returns An ES decorator representing the Before annotation. 18 | * 19 | * @example 20 | * ```ts 21 | * import { Before, on } from '@aspectjs/core'; 22 | * 23 | * class MyClass { 24 | * @Before(on.classes.withAnnotations(SomeAnnotation)) 25 | * myMethod() { 26 | * // Method implementation 27 | * } 28 | * } 29 | * ``` 30 | */ 31 | export const Before = _CORE_ANNOTATION_FACTORY.create( 32 | AnnotationKind.METHOD, 33 | 'Before', 34 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 35 | // @ts-ignore 36 | function (...pointcutExps: PointcutExpression[]) {}, 37 | ); 38 | -------------------------------------------------------------------------------- /packages/core/src/advices/before/before.context.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { AnnotationsByRefSelector } from '@aspectjs/common'; 3 | import type { 4 | PointcutKind, 5 | ToAnnotationKind, 6 | } from '../../pointcut/pointcut-kind.type'; 7 | import type { AdviceTarget } from './../../advice/advice-target.type'; 8 | 9 | import type { BeforeAdvice } from './before.type'; 10 | 11 | /** 12 | * Execution context passed to advices of type {@link BeforeAdvice} 13 | */ 14 | export interface BeforeContext< 15 | T extends PointcutKind = PointcutKind, 16 | X = object, 17 | > { 18 | /** The annotation contexts **/ 19 | readonly annotations: AnnotationsByRefSelector< 20 | ToAnnotationKind 21 | >['annotations']; 22 | /** The 'this' instance bound to the current execution context */ 23 | readonly instance: T extends PointcutKind.CLASS ? never : X; 24 | /** the arguments originally passed to the joinpoint */ 25 | readonly args: unknown[]; 26 | /** The symbol targeted by this advice (class, method, property or parameter */ 27 | readonly target: AdviceTarget; 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/advices/before/before.type.ts: -------------------------------------------------------------------------------- 1 | import { AdviceKind } from '../../advice/advice-type.type'; 2 | import type { AdviceContext } from '../../advice/advice.context'; 3 | import { Pointcut } from '../../pointcut/pointcut'; 4 | import type { PointcutKind } from '../../pointcut/pointcut-kind.type'; 5 | 6 | export type BeforePointcut = Pointcut< 7 | AdviceKind.BEFORE, 8 | T 9 | >; 10 | 11 | export type BeforeAdvice = { 12 | name: string; 13 | pointcuts: BeforePointcut[]; 14 | } & ((ctxt: AdviceContext) => void); 15 | -------------------------------------------------------------------------------- /packages/core/src/advices/compile/compile.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { PointcutExpression } from '../../pointcut/pointcut-expression.type'; 3 | import { _CORE_ANNOTATION_FACTORY } from '../../utils'; 4 | /* eslint-disable @typescript-eslint/no-unused-vars */ 5 | 6 | /** 7 | * Annotation to declare an advice to be applied at compile time, when a symnol is read bu the interpreter. 8 | * 9 | * The Compile annotation is used to apply compile advice 10 | * to a symbol. Compile advices are executed once, when the decorators are evaluated by the interpreter. 11 | * 12 | * @param pointcutExp (required): The pointcut expression 13 | * specifying the join points where the compile advice should be applied. 14 | * 15 | * @returns An ES decorator representing the Compile annotation. 16 | * 17 | * @example 18 | * ```ts 19 | * import { Compile, on } from '@aspectjs/core'; 20 | * 21 | * class MyClass { 22 | * @Compile(on.classes.withAnnotations(SomeAnnotation)) 23 | * myMethod() { 24 | * // Method implementation 25 | * } 26 | * } 27 | * ``` 28 | */ 29 | export const Compile = _CORE_ANNOTATION_FACTORY.create( 30 | AnnotationKind.METHOD, 31 | 'Compile', 32 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 33 | // @ts-ignore 34 | function (...pointcutExp: PointcutExpression[]) {}, 35 | ); 36 | -------------------------------------------------------------------------------- /packages/core/src/advices/compile/compile.context.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import type { AnnotationsByRefSelector } from '@aspectjs/common'; 3 | import type { 4 | PointcutKind, 5 | ToAnnotationKind, 6 | } from '../../pointcut/pointcut-kind.type'; 7 | import type { AdviceTarget } from './../../advice/advice-target.type'; 8 | 9 | import type { CompileAdvice } from './compile.type'; 10 | 11 | /** 12 | * Execution context passed to advices of type {@link CompileAdvice} 13 | */ 14 | export interface CompileContext< 15 | T extends PointcutKind = PointcutKind, 16 | X = unknown, 17 | > { 18 | /** The annotation contexts **/ 19 | readonly annotations: AnnotationsByRefSelector< 20 | ToAnnotationKind 21 | >['annotations']; 22 | /** The symbol targeted by this advice (class, method, property or parameter **/ 23 | readonly target: AdviceTarget; 24 | 25 | readonly args: never; 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/advices/compile/compile.type.ts: -------------------------------------------------------------------------------- 1 | import { AdviceKind } from '../../advice/advice-type.type'; 2 | import type { AdviceContext } from '../../advice/advice.context'; 3 | import type { Pointcut } from '../../pointcut/pointcut'; 4 | import type { PointcutKind } from '../../pointcut/pointcut-kind.type'; 5 | 6 | export type CompilePointcut = Pointcut< 7 | AdviceKind.COMPILE, 8 | T 9 | >; 10 | 11 | export type CompileAdvice< 12 | T extends PointcutKind = PointcutKind, 13 | X = unknown, 14 | > = { 15 | name: string; 16 | pointcuts: CompilePointcut[]; 17 | } & ((ctxt: AdviceContext) => T extends PointcutKind.CLASS 18 | ? // eslint-disable-next-line @typescript-eslint/ban-types 19 | undefined | Function 20 | : PropertyDescriptor); 21 | -------------------------------------------------------------------------------- /packages/core/src/advices/early-declaration.spec.ts: -------------------------------------------------------------------------------- 1 | import { _setReflectContext, AnnotationFactory } from '@aspectjs/common'; 2 | import { ReflectTestingContext } from '@aspectjs/common/testing'; 3 | import { Aspect, Before, getWeaver, on } from '@aspectjs/core'; 4 | 5 | describe('@Before property advice', () => { 6 | const af = new AnnotationFactory('test'); 7 | 8 | beforeEach(() => _setReflectContext(new ReflectTestingContext())); 9 | describe('when the property is declared before enabling the weaver', () => { 10 | it('calls the advice before the property is get', () => { 11 | const EarlyAnnotation = af.create( 12 | 'EarlyAnnotation', 13 | function EarlyAnnotation() {}, 14 | ); 15 | 16 | class A { 17 | @EarlyAnnotation() 18 | declare prop: string; 19 | } 20 | 21 | const advice = jest.fn(() => {}); 22 | @Aspect() 23 | class EarlyAnnotationAspect { 24 | @Before(on.properties.withAnnotations(EarlyAnnotation)) 25 | advice() { 26 | advice(); 27 | } 28 | } 29 | const earlyAnnotationAspect = new EarlyAnnotationAspect(); 30 | getWeaver().enable(earlyAnnotationAspect); 31 | 32 | new A().prop; 33 | 34 | expect(advice).toHaveBeenCalled(); 35 | console.log(advice.mock.calls[0]); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/core/src/annotations/order.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { Annotation } from '@aspectjs/common'; 3 | import { _CORE_ANNOTATION_FACTORY } from '../utils'; 4 | 5 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 6 | // @ts-ignore 7 | let _Annotation: Annotation; // fixes force import @aspectjs/common in .d.ts generation 8 | // https://github.com/microsoft/TypeScript/issues/54743 9 | 10 | const OrderAnnotation = _CORE_ANNOTATION_FACTORY.create( 11 | 'Order', 12 | function ( 13 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 14 | // @ts-ignore 15 | precedence: number, 16 | ) {}, 17 | ); 18 | 19 | Object.defineProperties(OrderAnnotation, { 20 | LOWEST_PRECEDENCE: { 21 | writable: false, 22 | value: Infinity, 23 | }, 24 | HIGHEST_PRECEDENCE: { 25 | writable: false, 26 | value: -Infinity, 27 | }, 28 | }); 29 | 30 | export type OrderType = typeof OrderAnnotation & { 31 | LOWEST_PRECEDENCE: number; 32 | HIGHEST_PRECEDENCE: number; 33 | }; 34 | 35 | export const Order = OrderAnnotation as OrderType; 36 | -------------------------------------------------------------------------------- /packages/core/src/aspect/aspect-metadata.type.ts: -------------------------------------------------------------------------------- 1 | export interface AspectOptions { 2 | readonly id?: string; 3 | } 4 | export interface AspectMetadata { 5 | readonly id: string; 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/aspect/aspect.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { AnnotationKind } from '@aspectjs/common'; 4 | import type { Weaver } from './../weaver/weaver'; 5 | 6 | import { _CORE_ANNOTATION_FACTORY } from '../utils'; 7 | 8 | import type { AspectOptions } from './aspect-metadata.type'; 9 | 10 | /** 11 | * Use the `@Aspect()` annotation on a class to mark that class as an aspect. 12 | * The aspect could then be enabled with the {@link Weaver.enable} method. 13 | * 14 | * @example 15 | * ```ts 16 | * @Aspect() 17 | * class MyAspect { 18 | * } 19 | * 20 | * getWeaver().enable(new MyAspect()); 21 | * ``` 22 | */ 23 | export const Aspect = _CORE_ANNOTATION_FACTORY.create( 24 | AnnotationKind.CLASS, 25 | 'Aspect', 26 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 27 | // @ts-ignore 28 | function (id: string | AspectOptions = {}) {}, 29 | ); 30 | -------------------------------------------------------------------------------- /packages/core/src/aspect/aspect.spec.ts: -------------------------------------------------------------------------------- 1 | import { configureTesting } from '@aspectjs/common/testing'; 2 | 3 | import { AspectError } from '../errors/aspect.error'; 4 | import { JitWeaver } from '../jit/jit-weaver'; 5 | import { WeaverModule } from '../weaver/weaver.module'; 6 | import { Aspect } from './aspect.annotation'; 7 | import { getAspectMetadata } from './aspect.type'; 8 | 9 | /* eslint-disable @typescript-eslint/no-unused-vars */ 10 | 11 | describe('@Aspect() annotation', () => { 12 | let weaver: JitWeaver; 13 | beforeEach(() => { 14 | weaver = configureTesting(WeaverModule).get(JitWeaver); 15 | jest.spyOn(weaver as JitWeaver, 'enhance'); 16 | }); 17 | 18 | describe('annotated on a class', () => { 19 | it('calls "Weaver.enhance"', () => { 20 | expect(weaver.enhance).not.toHaveBeenCalled(); 21 | try { 22 | @Aspect() 23 | class TestAspect {} 24 | } catch (e) { 25 | // noop 26 | } 27 | 28 | expect(weaver.enhance).toHaveBeenCalled(); 29 | }); 30 | 31 | describe('given no id', () => { 32 | it('assigns a random id', () => { 33 | @Aspect() 34 | class AAspect {} 35 | 36 | @Aspect() 37 | class BAspect {} 38 | 39 | expect(getAspectMetadata(AAspect).id).toBeDefined(); 40 | expect(getAspectMetadata(AAspect).id).not.toEqual( 41 | getAspectMetadata(BAspect).id, 42 | ); 43 | }); 44 | }); 45 | describe('on a subclass', () => { 46 | it('overrides the id of the parent class', () => { 47 | @Aspect('AAspect') 48 | class AAspect {} 49 | 50 | @Aspect('BAspect') 51 | class BAspect {} 52 | 53 | expect(getAspectMetadata(AAspect).id).toEqual('AAspect'); 54 | expect(getAspectMetadata(BAspect).id).toEqual('BAspect'); 55 | }); 56 | }); 57 | xdescribe('twice', () => { 58 | it('throws as AspectError', () => { 59 | jest.spyOn(console, 'error').mockImplementation(() => {}); 60 | expect(() => { 61 | @Aspect() 62 | @Aspect() 63 | class TestAspect {} 64 | }).toThrowError(AspectError); 65 | }); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /packages/core/src/errors/advice.error.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationTarget } from '@aspectjs/common'; 2 | 3 | import { Advice } from '../advice/advice.type'; 4 | import { AspectType } from '../aspect/aspect.type'; 5 | import { AspectError } from './aspect.error'; 6 | 7 | /** 8 | * Error thrown when an advice has an unexpected behavior (eg: returns a value that is not permitted) 9 | */ 10 | export class AdviceError extends AspectError { 11 | constructor( 12 | /** 13 | * The aspect that caused the error 14 | */ 15 | public readonly aspect: AspectType, 16 | /** 17 | * The advice that caused the error 18 | */ 19 | public readonly advice: Advice, 20 | /** 21 | * The target on which the advice was applied 22 | */ 23 | public readonly target: AnnotationTarget, 24 | /** 25 | * The error message 26 | */ 27 | message: string, 28 | ) { 29 | super( 30 | aspect, 31 | `Error applying advice ${advice} on ${target.label}: ${message}`, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/errors/aspect.error.ts: -------------------------------------------------------------------------------- 1 | import { AspectType, getAspectMetadata } from '../aspect/aspect.type'; 2 | import { WeavingError } from './weaving.error'; 3 | 4 | export class AspectError extends WeavingError { 5 | constructor(aspect: AspectType, msg: string) { 6 | super(`[${getAspectMetadata(aspect).id}]: ${msg}`); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/src/errors/weaving.error.ts: -------------------------------------------------------------------------------- 1 | import { ReflectError } from '@aspectjs/common'; 2 | 3 | /** 4 | * Error thrown during the weaving process meaning the weaver has illegal state. 5 | */ 6 | export class WeavingError extends ReflectError {} 7 | -------------------------------------------------------------------------------- /packages/core/src/jit/jit-weaver.hook.ts: -------------------------------------------------------------------------------- 1 | import { DecoratorHook, reflectContext } from '@aspectjs/common'; 2 | import { assert } from '@aspectjs/common/utils'; 3 | import { _BindableAnnotationTarget } from '../utils/annotation-mixin-target'; 4 | import { _CompilationState } from '../weaver/compilation-state.provider'; 5 | import { JitWeaver } from './jit-weaver'; 6 | 7 | export const CALL_JIT_WEAVER_HOOK: DecoratorHook = { 8 | name: '@aspectjs::weaver.enhance', 9 | order: 200, 10 | createDecorator: function (reflect, context) { 11 | const weaver = reflect.get(JitWeaver); 12 | 13 | return function (..._decoratorArgs: any) { 14 | const target = context.target as _BindableAnnotationTarget; 15 | assert(typeof target._bind === 'function'); 16 | const state = reflectContext().get(_CompilationState); 17 | 18 | // already enhanced, skip calling the weaver. 19 | // this might happen when doing a mixin between two annotations. 20 | 21 | // dynamically add the annotation to advices selection filter 22 | if (state.status === _CompilationState.Status.PENDING) { 23 | assert(!!state.advices); 24 | // add the current annotation to the list of known annotations for advices 25 | state.advices!.filters.annotations ??= []; 26 | state.advices!.filters.annotations.push(context.ref); 27 | return; 28 | } else { 29 | const decoree = weaver.enhance(target); 30 | return decoree; 31 | } 32 | }; 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /packages/core/src/jit/joinpoint.factory.ts: -------------------------------------------------------------------------------- 1 | import { AroundAdvice } from '../advices/around/around.type'; 2 | 3 | import type { JoinPoint } from '../advice/joinpoint'; 4 | import type { AroundContext } from './../advices/around/around.context'; 5 | 6 | import type { PointcutKind } from '../pointcut/pointcut-kind.type'; 7 | export class JoinPointFactory { 8 | create( 9 | _advice: AroundAdvice, 10 | ctxt: AroundContext, 11 | fn: (...args: unknown[]) => R, 12 | ): JoinPoint { 13 | return function (...args: unknown[]) { 14 | return fn.apply(ctxt.instance, args); 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/src/pointcut/pointcut-expression.factory.ts: -------------------------------------------------------------------------------- 1 | import type { Annotation, AnnotationRef } from '@aspectjs/common'; 2 | import { PointcutExpression } from './pointcut-expression.type'; 3 | import { PointcutKind } from './pointcut-kind.type'; 4 | 5 | class PointcutExpressionFactory { 6 | constructor(private readonly type: T) {} 7 | 8 | withAnnotations( 9 | ...annotations: (Annotation | AnnotationRef)[] 10 | ): PointcutExpression { 11 | return new PointcutExpression({ 12 | kind: this.type, 13 | annotations, 14 | }); 15 | } 16 | 17 | // withAllAnnotations(..._annotations: Annotation[]): PointcutExpression { 18 | // // TODO: implement PointcutExpressionFactory.withAllAnnotations 19 | // throw new Error('not implemented'); 20 | // } 21 | } 22 | 23 | class PropertyPointcutFactory extends PointcutExpressionFactory { 24 | readonly setter: PointcutExpressionFactory; 25 | constructor() { 26 | super(PointcutKind.GET_PROPERTY); 27 | this.setter = new PointcutExpressionFactory(PointcutKind.SET_PROPERTY); 28 | } 29 | } 30 | 31 | export const on = { 32 | classes: new PointcutExpressionFactory(PointcutKind.CLASS), 33 | methods: new PointcutExpressionFactory(PointcutKind.METHOD), 34 | parameters: new PointcutExpressionFactory(PointcutKind.PARAMETER), 35 | properties: new PropertyPointcutFactory(), 36 | any: new PointcutExpressionFactory(PointcutKind.ANY), 37 | }; 38 | -------------------------------------------------------------------------------- /packages/core/src/pointcut/pointcut-kind.type.ts: -------------------------------------------------------------------------------- 1 | import type { AnnotationKind } from '@aspectjs/common'; 2 | 3 | export enum PointcutKind { 4 | CLASS = 'class', 5 | METHOD = 'method', 6 | GET_PROPERTY = 'get property', 7 | SET_PROPERTY = 'set property', 8 | PARAMETER = 'parameter', 9 | ANY = 'any', 10 | } 11 | 12 | export type ToAnnotationKind = T extends 13 | | PointcutKind.GET_PROPERTY 14 | | PointcutKind.SET_PROPERTY 15 | ? AnnotationKind.PROPERTY 16 | : T extends PointcutKind.CLASS 17 | ? AnnotationKind.CLASS 18 | : T extends PointcutKind.METHOD 19 | ? AnnotationKind.METHOD 20 | : T extends PointcutKind.PARAMETER 21 | ? AnnotationKind.PARAMETER 22 | : any; 23 | -------------------------------------------------------------------------------- /packages/core/src/pointcut/pointcut.ts: -------------------------------------------------------------------------------- 1 | import type { AnnotationRef } from '@aspectjs/common'; 2 | import { assert } from '@aspectjs/common/utils'; 3 | import { AdviceKind } from '../advice/advice-type.type'; 4 | import type { PointcutExpression } from './pointcut-expression.type'; 5 | import type { PointcutKind } from './pointcut-kind.type'; 6 | 7 | interface PointcutInit< 8 | P extends AdviceKind, 9 | T extends PointcutKind = PointcutKind, 10 | > { 11 | readonly kind: P; 12 | readonly expression: PointcutExpression; 13 | } 14 | 15 | export class Pointcut< 16 | P extends AdviceKind = AdviceKind, 17 | T extends PointcutKind = PointcutKind, 18 | > { 19 | readonly kind: T; 20 | readonly annotations: AnnotationRef[]; 21 | readonly name: string; 22 | readonly adviceKind: AdviceKind; 23 | private readonly _expr: PointcutExpression; 24 | 25 | constructor(pointcutInit: PointcutInit) { 26 | this._expr = pointcutInit.expression; 27 | this.adviceKind = pointcutInit.kind; 28 | this.kind = this._expr.kind as T; 29 | this.annotations = this._expr.annotations; 30 | this.name = this._expr.name; 31 | } 32 | 33 | [Symbol.toPrimitive] = () => this.toString(); 34 | 35 | toString(): string { 36 | return `${this.adviceKind}(${this._expr})`; 37 | } 38 | 39 | isAssignableFrom(pointcut: Pointcut): boolean { 40 | return ( 41 | pointcut.kind === this.kind && this.adviceKind === pointcut.adviceKind 42 | ); 43 | } 44 | 45 | merge(pointcut: Pointcut): this { 46 | assert(this.isAssignableFrom(pointcut)); 47 | 48 | if (!this.annotations.length) { 49 | // pointcut already targets all annotations 50 | return this; 51 | } else if (!pointcut.annotations.length) { 52 | // pointcut now targets all annotations 53 | this.annotations.splice(0, this.annotations.length); 54 | return this; 55 | } 56 | 57 | // merge annotations 58 | const annotations = [ 59 | ...new Set([...this.annotations, ...pointcut.annotations]), 60 | ]; 61 | 62 | this.annotations.push(...annotations.slice(annotations.length - 1)); 63 | 64 | return this; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/core/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './advice/advice.context'; 2 | // export * from './advice/advice.type'; 3 | export * from './advice/advice-type.type'; 4 | export * from './advice/joinpoint'; 5 | export * from './advices/after-return/after-return.annotation'; 6 | export * from './advices/after-return/after-return.context'; 7 | export * from './advices/after-return/after-return.type'; 8 | export * from './advices/after-throw/after-throw.annotation'; 9 | export * from './advices/after-throw/after-throw.context'; 10 | export * from './advices/after-throw/after-throw.type'; 11 | export * from './advices/after/after.annotation'; 12 | export * from './advices/after/after.context'; 13 | export * from './advices/after/after.type'; 14 | export * from './advices/around/around.annotation'; 15 | export * from './advices/around/around.context'; 16 | export * from './advices/around/around.type'; 17 | export * from './advices/before/before.annotation'; 18 | export * from './advices/before/before.context'; 19 | export * from './advices/before/before.type'; 20 | export * from './advices/compile/compile.annotation'; 21 | export * from './advices/compile/compile.context'; 22 | export * from './advices/compile/compile.type'; 23 | 24 | export * from './annotations/order.annotation'; 25 | export * from './aspect/aspect-metadata.type'; 26 | export * from './aspect/aspect.annotation'; 27 | export * from './aspect/aspect.type'; 28 | export * from './errors/advice.error'; 29 | export * from './errors/aspect.error'; 30 | export * from './errors/weaving.error'; 31 | export * from './pointcut/pointcut'; 32 | export * from './pointcut/pointcut-expression.factory'; 33 | export * from './pointcut/pointcut-expression.type'; 34 | export * from './pointcut/pointcut-kind.type'; 35 | export * from './utils/annotation-mixin'; 36 | export * from './weaver/context/weaver.context.global'; 37 | export * from './weaver/weaver'; 38 | export * from './weaver/weaver.module'; 39 | -------------------------------------------------------------------------------- /packages/core/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationFactory } from '@aspectjs/common'; 2 | 3 | export const _CORE_ANNOTATION_FACTORY = new AnnotationFactory('aspectjs.core'); 4 | -------------------------------------------------------------------------------- /packages/core/src/utils/annotation-mixin-target.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind, AnnotationTarget } from '@aspectjs/common'; 2 | 3 | export type _BindableAnnotationTarget< 4 | T extends AnnotationKind = AnnotationKind, 5 | X = unknown, 6 | > = AnnotationTarget & { 7 | /** 8 | * Binds a value to this AnnotationTarget. The value is either the class instance for ClassAnnotationTarget, the property value for PropertyAnnotationTarget, MethodAnnotationTarget or ParameterAnnotationTarget. 9 | * In addition, in case of ParameterAnnotationTarget, the bind() method accepts a 2nd argument to bind the parameter value. 10 | * 11 | * @param instance The class instance to bind this target to. 12 | * @param value If target is a ParameterAnnotationTarget, args is the array of parameters given to the currently called function 13 | */ 14 | _bind(instance: X, args?: unknown[]): AnnotationTarget; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/core/src/weaver/canvas/canvas-strategy.type.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ConstructorType, 3 | MethodPropertyDescriptor, 4 | } from '@aspectjs/common/utils'; 5 | import { MutableAdviceContext } from '../../advice/mutable-advice.context'; 6 | 7 | import type { JoinPoint } from '../../advice/joinpoint'; 8 | import type { PointcutKind } from '../../pointcut/pointcut-kind.type'; 9 | 10 | export type CompiledSymbol< 11 | T extends PointcutKind = PointcutKind, 12 | X = unknown, 13 | > = T extends PointcutKind.CLASS 14 | ? ConstructorType 15 | : T extends PointcutKind.METHOD | PointcutKind.PARAMETER 16 | ? MethodPropertyDescriptor 17 | : T extends PointcutKind.GET_PROPERTY | PointcutKind.SET_PROPERTY 18 | ? PropertyDescriptor 19 | : never; 20 | 21 | export interface WeaverCanvasStrategy< 22 | T extends PointcutKind = PointcutKind, 23 | X = unknown, 24 | > { 25 | compile(ctxt: MutableAdviceContext): CompiledSymbol | undefined; 26 | 27 | before(ctxt: MutableAdviceContext): void; 28 | 29 | callJoinpoint( 30 | ctxt: MutableAdviceContext, 31 | originalSymbol: CompiledSymbol, 32 | ): unknown; 33 | 34 | afterReturn(ctxt: MutableAdviceContext): unknown; 35 | 36 | afterThrow(ctxt: MutableAdviceContext): unknown; 37 | 38 | after(ctxt: MutableAdviceContext): void; 39 | 40 | around(ctxt: MutableAdviceContext): JoinPoint; 41 | 42 | link?( 43 | ctxt: MutableAdviceContext, 44 | compiledSymbol: CompiledSymbol, 45 | joinpoint: (...args: any[]) => T, 46 | ): CompiledSymbol; 47 | } 48 | -------------------------------------------------------------------------------- /packages/core/src/weaver/compilation-state.provider.ts: -------------------------------------------------------------------------------- 1 | import { AdvicesSelection } from '../advice/registry/advices-selection.model'; 2 | 3 | enum CompilationStatus { 4 | DONE = 'DONE', 5 | PENDING = 'PENDING', 6 | } 7 | export class _CompilationState { 8 | static readonly __providerName = '_CompilationState'; 9 | static readonly Status = CompilationStatus; 10 | 11 | advices?: AdvicesSelection; 12 | status = CompilationStatus.DONE; 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/weaver/context/weaver.context.global.ts: -------------------------------------------------------------------------------- 1 | import { WeaverModule } from '../weaver.module'; 2 | 3 | import { reflectContext } from '@aspectjs/common'; 4 | import { JitWeaver } from '../../jit/jit-weaver'; 5 | import { Weaver } from '../weaver'; 6 | import type { WeaverContext } from './weaver.context'; 7 | 8 | // force WeaverModule to add its annotation hooks even before getting called 9 | // to take a chance to compile annotated symbols even when weaverContext is called lately 10 | reflectContext().registerModules(WeaverModule); 11 | 12 | /** 13 | * @internal 14 | */ 15 | export const weaverContext = (): WeaverContext => { 16 | return reflectContext().registerModules(WeaverModule); 17 | }; 18 | 19 | /** 20 | * Get the weaver instance. 21 | * 22 | * @returns {Weaver} The weaver instance. 23 | */ 24 | export function getWeaver(): Weaver { 25 | return weaverContext().get(JitWeaver); 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/weaver/context/weaver.context.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | configureTesting, 3 | ReflectTestingContext, 4 | } from '@aspectjs/common/testing'; 5 | 6 | import { AdviceRegistry } from '../../advice/registry/advice.registry'; 7 | import { AspectRegistry } from '../../aspect/aspect.registry'; 8 | import { WeaverModule } from '../weaver.module'; 9 | 10 | describe('weaverContext()', () => { 11 | let context!: ReflectTestingContext; 12 | beforeEach(() => { 13 | context = configureTesting(WeaverModule); 14 | }); 15 | it('registers a reflect provider for AspectRegistry', () => { 16 | expect(context.has(AspectRegistry)).toBeTruthy(); 17 | }); 18 | 19 | it('registers a reflect provider for AdviceRegistry', () => { 20 | expect(context.has(AdviceRegistry)).toBeTruthy(); 21 | }); 22 | 23 | it('registers a reflect provider for Weaver', () => { 24 | expect(context.has('Weaver')).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/core/src/weaver/context/weaver.context.ts: -------------------------------------------------------------------------------- 1 | import { ReflectContext } from '@aspectjs/common'; 2 | 3 | export class WeaverContext extends ReflectContext {} 4 | -------------------------------------------------------------------------------- /packages/core/src/weaver/weaver.ts: -------------------------------------------------------------------------------- 1 | import { ConstructorType } from '@aspectjs/common/utils'; 2 | import type { AspectType } from '../aspect/aspect.type'; 3 | 4 | /** 5 | * A Weaver is some kind of compiler that connects the joinpoints to the corresponding advices. 6 | * @public 7 | */ 8 | export interface Weaver { 9 | /** 10 | * Enable some aspects. 11 | * @param aspects - the aspects to enable 12 | */ 13 | enable(...aspects: AspectType[]): this; 14 | 15 | /** 16 | * Find an aspect among enabled aspects given an aspect id or constructor. 17 | * @param aspect - The aspect id or constructor to find. 18 | * @returns The aspects maching the given id, or undefined 19 | */ 20 | getAspect( 21 | aspect: string | ConstructorType, 22 | ): (T & AspectType) | undefined; 23 | 24 | /** 25 | * Find all aspects among enabled aspects given. 26 | * @returns all aspects registered 27 | */ 28 | getAspects(): AspectType[]; 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "include": ["./src/**/*.ts", "./index.ts"], 5 | "compilerOptions": { 6 | "baseUrl": ".", 7 | "lib": ["ES2015.Reflect", "ES2015", "ES2021"], 8 | 9 | "noUnusedParameters": false 10 | }, 11 | "exclude": ["./dist"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "exclude": ["jest.config.ts", "./**/*.spec.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.spec.json", 4 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules/ 3 | src/.vuepress/.cache/ 4 | src/.vuepress/.temp/ 5 | src/.vuepress/dist/ 6 | src/api 7 | 8 | 9 | dist 10 | -------------------------------------------------------------------------------- /packages/docs/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: node:22-alpine 2 | 3 | pages: 4 | stage: deploy 5 | script: 6 | - mv dist/ public 7 | artifacts: 8 | untracked: true 9 | when: on_success 10 | expire_in: 1 days 11 | paths: 12 | - 'public' 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/docs/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "npm start", 9 | "runtimeExecutable": "npm", 10 | "runtimeArgs": ["start"], 11 | "request": "launch", 12 | "skipFiles": ["/**"], 13 | "type": "node" 14 | }, 15 | { 16 | "name": "Launch typedoc", 17 | "runtimeExecutable": "npm", 18 | "runtimeArgs": ["run", "typedoc"], 19 | "request": "launch", 20 | "skipFiles": ["/**"], 21 | "type": "node" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /packages/docs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "grammarly.selectors": [ 3 | { 4 | "language": "markdown", 5 | "scheme": "file" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/docs/TODO.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | - [] import with cdn / es / cjs (eg: https://github.com/apvarun/toastify-js/blob/master/README.md#installation) 3 | ## Aspects 4 | - [] **Context** 5 | - [] aspect inheritance 6 | 7 | ## Advanced Usage 8 | - [] TargetFactory.of() 9 | - [] AnnotationContextRegistry.find() -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aspectjs/docs", 3 | "private": true, 4 | "version": "0.5.4", 5 | "license": "MIT", 6 | "type": "module", 7 | "scripts": { 8 | "start": "npm run docs:dev", 9 | "clean": "rimraf ./src/.vuepress/.cache ./src/.vuepress/.temp", 10 | "docs:build": "npm run typedoc && vuepress build src --dest dist", 11 | "docs:clean-dev": "vuepress dev src --clean-cache", 12 | "typedoc": "tsx ./src/.vuepress/run-typedoc.ts", 13 | "docs:dev": "vuepress dev src", 14 | "docs:update-package": "npx vp-update" 15 | }, 16 | "devDependencies": { 17 | "@vuepress/bundler-vite": "^2.0.0-rc.14", 18 | "jsdom": "^25.0.0", 19 | "katex": "^0.16.11", 20 | "mathjax-full": "^3.2.2", 21 | "reveal.js": "^5.1.0", 22 | "tsx": "^4.19.1", 23 | "typedoc": "^0.26.7", 24 | "typedoc-plugin-markdown": "^4.2.7", 25 | "vue": "^3.5.5", 26 | "vuepress": "^2.0.0-beta.67", 27 | "vuepress-plugin-search-pro": "2.0.0-rc.52", 28 | "vuepress-theme-hope": "2.0.0-rc.52" 29 | }, 30 | "dependencies": { 31 | "find-up": "^7.0.0", 32 | "glob": "^11.0.0", 33 | "json5": "^2.2.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/config.ts: -------------------------------------------------------------------------------- 1 | import viteBundler from '@vuepress/bundler-vite'; 2 | import { defineUserConfig } from 'vuepress'; 3 | import { LOCALES } from './locales'; 4 | import theme from './theme.js'; 5 | 6 | export default defineUserConfig({ 7 | base: '/', 8 | 9 | locales: LOCALES, 10 | 11 | theme, 12 | bundler: viteBundler({ 13 | viteOptions: {}, 14 | vuePluginOptions: {}, 15 | }), 16 | 17 | // Enable it with pwa 18 | // shouldPrefetch: false, 19 | }); 20 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/custom-blocks/custom-md.ts: -------------------------------------------------------------------------------- 1 | import MarkdownIt from 'markdown-it'; 2 | import { customQuoteBlocks } from './quote-blocks'; 3 | export function extendsMarkdown(md: MarkdownIt) { 4 | return md.use( 5 | customQuoteBlocks([ 6 | 'warning', 7 | 'info', 8 | 'error', 9 | 'danger', 10 | 'boom', 11 | 'tip', 12 | 'question', 13 | 'success', 14 | ]), 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/custom-blocks/quote-blocks.ts: -------------------------------------------------------------------------------- 1 | import MarkdownIt from 'markdown-it'; 2 | import { JSDOM } from 'jsdom'; 3 | 4 | export function customQuoteBlocks( 5 | blockNames: string[] 6 | ): MarkdownIt.PluginWithOptions { 7 | return (md: MarkdownIt) => { 8 | const customBlockQuotes: any = blockNames.reduce((acc, cssClass) => { 9 | return { 10 | ...acc, 11 | [cssClass]: `custom-block ${cssClass}`, 12 | }; 13 | }, {}); 14 | const origMd = new MarkdownIt(); 15 | 16 | origMd.renderer.rules.blockquote_open = md.renderer.rules.blockquote_open; 17 | 18 | md.renderer.rules.blockquote_open = function ( 19 | tokens, 20 | index, 21 | options, 22 | env, 23 | self 24 | ) { 25 | let inlineIndex = index + 1; 26 | while ( 27 | tokens[inlineIndex].type !== 'inline' && 28 | tokens[inlineIndex].type !== 'blockquote_close' 29 | ) { 30 | inlineIndex++; 31 | } 32 | 33 | if (tokens[inlineIndex].type === 'inline') { 34 | const el = JSDOM.fragment( 35 | `${origMd.renderer.render( 36 | tokens[inlineIndex].children!, 37 | options, 38 | env 39 | )}` 40 | ); 41 | 42 | const stereotypeNode = el.firstElementChild as HTMLElement; 43 | if (stereotypeNode?.tagName.toUpperCase() === 'STRONG') { 44 | if (customBlockQuotes[stereotypeNode.innerHTML]) { 45 | el.removeChild(stereotypeNode); 46 | tokens[index].attrJoin( 47 | 'class', 48 | customBlockQuotes[stereotypeNode.innerHTML] 49 | ); 50 | } 51 | } 52 | } 53 | 54 | return self.renderToken(tokens, index, options); 55 | }; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/locales.ts: -------------------------------------------------------------------------------- 1 | export const LOCALES = { 2 | '/': { 3 | lang: 'en-US', 4 | title: 'AspectJS documentation', 5 | description: 'Documentation for the AspectJS framework', 6 | }, 7 | // '/fr/': { 8 | // lang: 'fr-FR', 9 | // title: 'AspectJS documentation', 10 | // description: 'Documentation for the AspectJS framework', 11 | // }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/md/utils.ts: -------------------------------------------------------------------------------- 1 | import { createMarkdown } from '@vuepress/markdown'; 2 | import { path } from '@vuepress/utils'; 3 | import { existsSync, readFileSync, readdirSync } from 'fs'; 4 | 5 | import { ROOT_DIR } from '../utils'; 6 | 7 | export function listMdDirs(root = '.') { 8 | return readdirSync(path.join(ROOT_DIR, root), { 9 | withFileTypes: true, 10 | }) 11 | .filter((dirent) => dirent.isDirectory()) 12 | .map((dirent) => dirent.name) 13 | .filter((d) => listMdFiles(path.join(root, d), false).length); 14 | } 15 | 16 | export function findIndexMd(dir: string, filename = 'README.md') { 17 | const file = path.join(ROOT_DIR, dir, filename); 18 | return existsSync(file) ? path.relative(ROOT_DIR, file) : undefined; 19 | } 20 | 21 | export function extractInfo(filename: string) { 22 | let env: any = {}; 23 | 24 | createMarkdown({ 25 | headers: { 26 | level: [1, 2, 3, 4], 27 | }, 28 | }).render(readFileSync(path.join(ROOT_DIR, filename)).toString(), env); 29 | 30 | const title = 31 | env.headers.sort((h1: any, h2: any) => h1.level - h2.level)[0]?.title ?? 32 | filename; 33 | 34 | const icon = env.frontmatter?.icon ?? ''; 35 | return { title, icon }; 36 | } 37 | 38 | export function listMdFiles(dirname: string, filterIgnored = true): string[] { 39 | const dir = path.join(ROOT_DIR, dirname); 40 | return readdirSync(dir, { withFileTypes: true }) 41 | .filter((f) => f.isFile() && f.name.endsWith('.md')) 42 | .map((f) => f.name) 43 | .filter((filepath: string) => !path.basename(filepath).startsWith('_')) 44 | .filter((f) => { 45 | if (!filterIgnored) { 46 | return true; 47 | } 48 | const env = {} as any; 49 | createMarkdown({}).render( 50 | readFileSync(path.join(dir, f)).toString(), 51 | env, 52 | ); 53 | return env.frontmatter.index !== false; 54 | }) 55 | .sort((s1, s2) => 56 | s1.localeCompare(s2, 'en', { 57 | numeric: true, 58 | ignorePunctuation: true, 59 | }), 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/navbar/navbar.plugin.ts: -------------------------------------------------------------------------------- 1 | import chokidar from 'chokidar'; 2 | import { App, PluginFunction } from 'vuepress'; 3 | 4 | function computeNavbar(app: App) { 5 | app; 6 | } 7 | export const customNavbar: () => PluginFunction = () => { 8 | return (app) => { 9 | return { 10 | name: 'custom-navbar', 11 | onInitialized: (app) => { 12 | // app.siteData.title = m3Context.name; 13 | // app.siteData.description = m3Context.description; 14 | }, 15 | define: (app) => {}, 16 | 17 | onWatched: (app, watchers, reload) => { 18 | const pagesWatcher = chokidar.watch(app.options.pagePatterns, { 19 | cwd: app.dir.source(), 20 | ignoreInitial: true, 21 | }); 22 | pagesWatcher.on('add', async (filePathRelative) => { 23 | reload(); 24 | // computeNavbar(app); 25 | }); 26 | // pagesWatcher.on('change', async (filePathRelative) => { 27 | // reload(); 28 | // computeNavbar(app); 29 | // }); 30 | pagesWatcher.on('unlink', async (filePathRelative) => { 31 | reload(); 32 | computeNavbar(app); 33 | }); 34 | 35 | watchers.push(pagesWatcher); 36 | }, 37 | 38 | extendsPage: (page) => {}, 39 | }; 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicolasThierion/aspectjs/dda0d35f73d111072855a9ac284f2a6119d361c9/packages/docs/src/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicolasThierion/aspectjs/dda0d35f73d111072855a9ac284f2a6119d361c9/packages/docs/src/.vuepress/public/logo.png -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/run-typedoc.ts: -------------------------------------------------------------------------------- 1 | import { findUpSync } from 'find-up'; 2 | import { dirname, join } from 'path'; 3 | import * as url from 'url'; 4 | import { configureTypedoc } from './typedoc/typedoc.js'; 5 | 6 | const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); 7 | const rootFolder = dirname( 8 | findUpSync('package.json', { 9 | cwd: join(dirname(findUpSync('package.json', { cwd: __dirname })!), '..'), 10 | })!, 11 | ); 12 | 13 | export const TYPEDOC_ENTRYPOINTS = [ 14 | `${rootFolder}/packages/core/index.ts`, 15 | `${rootFolder}/packages/common/index.ts`, 16 | `${rootFolder}/packages/memo/index.ts`, 17 | `${rootFolder}/packages/persistence/index.ts`, 18 | ]; 19 | 20 | await configureTypedoc(TYPEDOC_ENTRYPOINTS).generateDocs(); 21 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/sidebar/sidebar-sorter.ts: -------------------------------------------------------------------------------- 1 | import { SidebarInfo } from 'vuepress-theme-hope'; 2 | 3 | export function sidebarSorter( 4 | infoA: SidebarInfo | any, 5 | infoB: SidebarInfo | any, 6 | ) { 7 | if (infoA.filename === 'README.md') { 8 | return 1; 9 | } 10 | const rx = /^(?\d+).*$/; 11 | 12 | if (infoA.type === 'file' && infoB.type === 'dir') { 13 | return -1; 14 | } else if (infoA.type === 'dir' && infoB.type === 'file') { 15 | return 1; 16 | } 17 | const orderA = 18 | infoA.order ?? 19 | (infoA.filename ?? infoA.dirname).match(rx)?.groups?.order ?? 20 | Infinity; 21 | 22 | const orderB = 23 | infoB.order ?? 24 | (infoB.filename ?? infoB.dirname).match(rx)?.groups?.order ?? 25 | Infinity; 26 | 27 | if (orderA === orderB) { 28 | return (infoA.filename ?? infoA.dirname).localeCompare( 29 | infoB.filename ?? infoB.dirname, 30 | 'en', 31 | { 32 | numeric: true, 33 | ignorePunctuation: true, 34 | }, 35 | ); 36 | } 37 | return +orderA - +orderB; 38 | } 39 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/styles/config.scss: -------------------------------------------------------------------------------- 1 | // you can change config here 2 | $colors: #c0392b, #d35400, #f39c12, #27ae60, #16a085, #2980b9, #8e44ad, #2c3e50, 3 | #7f8c8d !default; 4 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './blocks.scss'; 2 | // place your custom styles here 3 | 4 | // html[data-theme='light'] #app { 5 | // --code-color: #686868; 6 | // --code-line-color: rgba(63, 64, 66, 0.67); 7 | // --code-c-bg: #f0f5ee; 8 | // --code-border-color: #e2ebde; 9 | // --code-highlight-line-color: #d3def5; 10 | // } 11 | 12 | html[data-theme='light'] #app { 13 | --code-color: #686868; 14 | --code-line-color: rgba(63, 64, 66, 0.67); 15 | --code-c-bg: #f1f5ed; 16 | --code-tabs-c-bg: #dce0dc; 17 | --code-highlight-line-color: #d3e6d3; 18 | } 19 | 20 | html #app { 21 | h1, 22 | h2, 23 | h3, 24 | h4, 25 | h5, 26 | h6 { 27 | i, 28 | .font-icon.icon { 29 | color: var(--theme-color-darker); 30 | } 31 | color: var(--theme-color-dark); 32 | } 33 | .nav-item a, 34 | .nav-item button, 35 | .nav-link.active, 36 | .vp-breadcrumb a, 37 | #toc .toc-item.active .toc-link { 38 | color: var(--theme-color-dark); 39 | 40 | i, 41 | .font-icon.icon { 42 | color: var(--theme-color-darker); 43 | } 44 | } 45 | 46 | .theme-hope-content a { 47 | color: var(--theme-color-dark); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/styles/palette.scss: -------------------------------------------------------------------------------- 1 | // you can change colors here 2 | $theme-color: #a7c859; 3 | $theme-color-dark: #88ae52; 4 | $theme-color-darker: #6a8a5b; 5 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/typedoc/README.typedoc.md: -------------------------------------------------------------------------------- 1 | --- 2 | index: false 3 | icon: file-alt 4 | order: 0 5 | --- 6 | 7 | # API Documentation 8 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/typedoc/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicolasThierion/aspectjs/dda0d35f73d111072855a9ac284f2a6119d361c9/packages/docs/src/.vuepress/typedoc/index.ts -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/typedoc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../../tsconfig.json", 3 | "compilerOptions": { 4 | "esModuleInterop": true, 5 | "noUnusedParameters": false, 6 | "noUnusedLocals": false, 7 | // "module": "ES2020", 8 | "allowImportingTsExtensions": true, 9 | "emitDeclarationOnly": true, 10 | "declaration": true, 11 | "lib": ["DOM", "ESNext", "ES2015"], 12 | "noImplicitAny": false, 13 | // "moduleResolution": "nodenext", 14 | "downlevelIteration": true 15 | }, 16 | "include": ["../../../../../**/index.ts", "../../../../../**/public_api.ts"], 17 | "exclude": [ 18 | "../../../../../*.spec.ts", 19 | "../../../../../node_modules/**/*", 20 | "../../../../docs" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/docs/src/.vuepress/utils.ts: -------------------------------------------------------------------------------- 1 | import { dirname, join } from 'path'; 2 | import { fileURLToPath } from 'url'; 3 | 4 | export const ROOT_DIR = join(fileURLToPath(dirname(import.meta.url)), '..'); 5 | -------------------------------------------------------------------------------- /packages/docs/src/00.guide/005.core-concepts/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | index: false 3 | icon: cog 4 | category: 5 | - Guide 6 | --- 7 | 8 | # Core concepts 9 | -------------------------------------------------------------------------------- /packages/docs/src/00.guide/006.advanced-usage/060.reflect-context.md: -------------------------------------------------------------------------------- 1 | --- 2 | index: false 3 | icon: magnifying-glass 4 | category: 5 | - Advanced usage 6 | --- 7 | 8 | # Reflect context 9 | -------------------------------------------------------------------------------- /packages/docs/src/00.guide/006.advanced-usage/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | index: false 3 | icon: plus 4 | category: 5 | - Advanced usage 6 | --- 7 | 8 | # Advanced usage 9 | -------------------------------------------------------------------------------- /packages/docs/src/00.guide/010.examples/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Examples 3 | icon: certificate 4 | index: false 5 | --- 6 | 7 | # Examples 8 | -------------------------------------------------------------------------------- /packages/docs/src/00.guide/20.context.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: thumbtack 3 | index: false 4 | --- 5 | 6 | # Aspect Context 7 | 8 | - Use the `Context` 9 | -------------------------------------------------------------------------------- /packages/docs/src/00.guide/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | index: false 3 | icon: star 4 | category: 5 | - Guide 6 | --- 7 | 8 | # Guide 9 | -------------------------------------------------------------------------------- /packages/docs/src/20.projects/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | index: false 3 | icon: plus 4 | --- 5 | 6 | # Projects 7 | -------------------------------------------------------------------------------- /packages/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "NodeNext", 5 | "moduleResolution": "nodenext", 6 | "lib": ["DOM", "ESNext"], 7 | "declaration": true, 8 | "emitDeclarationOnly": true, 9 | "allowImportingTsExtensions": true, 10 | "esModuleInterop": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/httyped-client/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /packages/httyped-client/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - ci-templates/node.yml 3 | 4 | httyped-client:lint: 5 | variables: 6 | CWD: packages/httyped-client 7 | extends: .node:lint 8 | stage: test 9 | needs: 10 | - node:prepare 11 | 12 | httyped-client:unit: 13 | extends: .node:test:unit 14 | variables: 15 | CWD: packages/httyped-client 16 | stage: test 17 | needs: 18 | - node:prepare 19 | 20 | httyped-client:build: 21 | extends: .node:build 22 | variables: 23 | CWD: packages/httyped-client 24 | stage: compile 25 | needs: 26 | - node:prepare 27 | 28 | # httyped-client:publish:gitlab: 29 | # extends: .node:gitlab:publish 30 | # stage: publish 31 | # variables: 32 | # CWD: packages/httyped-client 33 | # PACKAGE_DIR: ./dist 34 | 35 | httyped-client:publish:npm: 36 | extends: .node:npm:publish 37 | stage: release 38 | variables: 39 | CWD: packages/httyped-client 40 | PACKAGE_DIR: ./dist 41 | rules: 42 | - if: $CI_COMMIT_REF_NAME =~ /^v\d+\.\d+\.\d+.*$/ 43 | when: manual 44 | -------------------------------------------------------------------------------- /packages/httyped-client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "jest.jestCommandLine": "../../node_modules/.bin/jest" 3 | // "jest.rootPath": "../../", 4 | // "jest.jestCommandLine": "npm run test:unit" 5 | } 6 | -------------------------------------------------------------------------------- /packages/httyped-client/TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - [] `@AddRequestHandler()` 4 | - [] `@AddResponseHandler()` 5 | - [] `@PathVariable()` 6 | - [] `@QueryParam()` 7 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/address.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Mapper } from '../src/types/mapper.type'; 2 | import { Address } from './address.model'; 3 | 4 | export const ADDRESS_MAPPER: Mapper = { 5 | typeHint: Address, 6 | map: (obj: any) => { 7 | return Object.setPrototypeOf(obj, Address.prototype); 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/address.model.ts: -------------------------------------------------------------------------------- 1 | export class Address { 2 | street!: string; 3 | suite!: string; 4 | city!: string; 5 | zipcode!: string; 6 | 7 | print() { 8 | return `${this.street}, ${this.suite}, ${this.zipcode} ${this.city}`; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/client-factory.global.ts: -------------------------------------------------------------------------------- 1 | import { HttypedClientFactory } from 'httyped-client'; 2 | import { ADDRESS_MAPPER } from './address.mapper'; 3 | import { POST_MAPPER } from './post.mapper'; 4 | import { USER_MAPPER } from './user.mapper'; 5 | 6 | export const httyped = new HttypedClientFactory({ 7 | baseUrl: 'https://jsonplaceholder.typicode.com', 8 | }) 9 | .addRequestHandler((r) => console.log(`[${r.method}] ${r.url}`)) 10 | .addResponseBodyMappers(USER_MAPPER, POST_MAPPER, ADDRESS_MAPPER); 11 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/company.model..ts: -------------------------------------------------------------------------------- 1 | export class Company { 2 | name!: string; 3 | catchPhrase!: string; 4 | bs!: string; 5 | } 6 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/main.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import 'whatwg-fetch'; 3 | import { HttypedClientFactory } from '../src/public_api'; 4 | import { User } from './user.model'; 5 | import { UsersApi } from './users.api'; 6 | import { UsersClient } from './users.client'; 7 | 8 | async function main() { 9 | const usersClient = new UsersClient(); 10 | 11 | const user = (await usersClient.find({ username: 'Bret' }))[0]!; 12 | console.log(user); 13 | // const posts = (await usersClient.id(1).posts())!; 14 | // console.log(posts); 15 | 16 | const cf = new HttypedClientFactory({ 17 | baseUrl: 'http://localhost:3000', 18 | }).addRequestBodyMappers({ 19 | typeHint: User, 20 | map: (obj: User) => { 21 | obj.name = obj.name.toUpperCase(); 22 | return obj; 23 | }, 24 | }); 25 | 26 | const localUserApi = cf.create(UsersApi); 27 | await localUserApi.create(user); 28 | } 29 | 30 | main(); 31 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "private": "true", 4 | "type": "module" 5 | } 6 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/post.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Mapper } from '../src/types/mapper.type'; 2 | import { Post } from './post.model'; 3 | 4 | export const POST_MAPPER: Mapper = { 5 | typeHint: Post, 6 | map: (obj: any) => { 7 | return Object.setPrototypeOf(obj, Post.prototype); 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/post.model.ts: -------------------------------------------------------------------------------- 1 | export class Post { 2 | userId!: number; 3 | id!: number; 4 | title!: string; 5 | body!: string; 6 | } 7 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/posts.api.ts: -------------------------------------------------------------------------------- 1 | import { abstract } from '@aspectjs/common/utils'; 2 | import { TypeHint } from '../src/annotations/type.annotation'; 3 | import { Get, HttypedClient } from '../src/public_api'; 4 | import { Post } from './post.model'; 5 | 6 | @HttypedClient('posts') 7 | export abstract class PostsApi { 8 | @Get() 9 | @TypeHint([Post]) 10 | getAll() { 11 | return abstract(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/user.mapper.ts: -------------------------------------------------------------------------------- 1 | import { Mapper, MapperContext } from '../src/types/mapper.type'; 2 | import { Address } from './address.model'; 3 | import { httyped } from './client-factory.global'; 4 | import { Post } from './post.model'; 5 | import { User } from './user.model'; 6 | import { UsersApi } from './users.api'; 7 | 8 | export const USER_MAPPER: Mapper = { 9 | typeHint: User, 10 | map: async (obj: any, context: MapperContext) => { 11 | obj.address = 12 | context.mappers.findMapper(Address)?.map(obj.address, context) ?? 13 | obj.address; 14 | 15 | if (!obj.posts) { 16 | obj.posts = await httyped.create(UsersApi).getUsersPosts(obj.id); 17 | } else { 18 | obj.posts = 19 | (await context.mappers.findMapper(Post)?.map(obj.posts, context)) ?? 20 | obj.posts; 21 | } 22 | 23 | return Object.setPrototypeOf(obj, User.prototype); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/user.model.ts: -------------------------------------------------------------------------------- 1 | import { Address } from './address.model'; 2 | import { Company } from './company.model.'; 3 | 4 | export class User { 5 | id!: number; 6 | name!: string; 7 | username!: string; 8 | email!: string; 9 | address!: Address; 10 | 11 | phone!: string; 12 | website!: string; 13 | company!: Company; 14 | } 15 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/users.api.ts: -------------------------------------------------------------------------------- 1 | import { abstract } from '@aspectjs/common/utils'; 2 | import { Body } from '../src/annotations/body.annotation'; 3 | import { Get } from '../src/annotations/fetch/get.annotation'; 4 | import { Post } from '../src/annotations/fetch/post.annotation'; 5 | import { HttypedClient } from '../src/annotations/http-client.annotation'; 6 | import { PathVariable } from '../src/annotations/path-variable.annotation'; 7 | import { RequestParams } from '../src/annotations/request-params.annotation'; 8 | import { Post as _Post } from './post.model'; 9 | import { User } from './user.model'; 10 | 11 | @HttypedClient('users') 12 | export abstract class UsersApi { 13 | @Get() 14 | async find(@RequestParams() search?: { username?: string }) { 15 | return abstract([User]); 16 | } 17 | 18 | @Get(':id') 19 | async getById(@PathVariable('id') id: number) { 20 | return abstract(User); 21 | } 22 | 23 | @Get(':id/posts') 24 | async getUsersPosts(@PathVariable('id') userId: number) { 25 | return abstract([_Post]); 26 | } 27 | 28 | @Post() 29 | async create( 30 | @Body() 31 | user: User, 32 | ) { 33 | return abstract(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/httyped-client/demo/users.client.ts: -------------------------------------------------------------------------------- 1 | import { httyped } from './client-factory.global'; 2 | import { UsersApi } from './users.api'; 3 | 4 | export class UsersClient { 5 | private readonly usersApi = httyped.create(UsersApi); 6 | 7 | find(search?: { username?: string }) { 8 | return this.usersApi.find(search); 9 | } 10 | 11 | id(id: number) { 12 | return { 13 | posts: () => this.usersApi.getUsersPosts(id), 14 | find: () => this.usersApi.getById(id), 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/httyped-client/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/public_api'; 2 | -------------------------------------------------------------------------------- /packages/httyped-client/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { JestConfigWithTsJest } from 'ts-jest'; 2 | import config from '../../jest.config'; 3 | 4 | export default { 5 | ...config, 6 | collectCoverageFrom: [`./**/*.{js,ts}`], 7 | } satisfies JestConfigWithTsJest; 8 | -------------------------------------------------------------------------------- /packages/httyped-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "httyped-client", 3 | "version": "0.5.4", 4 | "description": "An AOP powered typesafe http client, inspired by the Retrofit library for java", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/NicolasThierion/aspectjs.git", 8 | "directory": "packages/httyped-client" 9 | }, 10 | "scripts": { 11 | "test": "npm run clean && npm run test:lint && npm run test:unit", 12 | "test:lint": "eslint \"src/**/*.{ts,js,json}\"", 13 | "test:unit": "jest --runInBand --collectCoverage", 14 | "pack": "npm pack ./dist", 15 | "lint": "eslint \"src/**/*.{ts,js,json}\" --fix", 16 | "build": "rollup --config ./rollup.config.cjs", 17 | "clean": "rimraf ./dist && rimraf ./*.tgz" 18 | }, 19 | "author": "Nicolas Thierion ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/NicolasThierion/aspectjs/issues" 23 | }, 24 | "homepage": "https://aspectjs.gitlab.io/", 25 | "type": "module", 26 | "sideEffects": false, 27 | "types": "./index.d.ts", 28 | "exports": { 29 | ".": { 30 | "types": "./index.d.ts", 31 | "fesm2020": "./fesm2020/httyped-client.mjs", 32 | "esm2020": "./esm2020/index.mjs", 33 | "esm": "./esm2020/index.mjs", 34 | "require": "./cjs/index.cjs", 35 | "import": "./fesm2020/httyped-client.mjs", 36 | "unpkg": "./umd/httyped-client.umd.min.js" 37 | } 38 | }, 39 | "module": "./fesm2020/httyped-client.mjs", 40 | "main": "./umd/httyped-client.umd.js", 41 | "devDependencies": { 42 | "jest": "^29.5.0", 43 | "tsx": "^4.19.0", 44 | "whatwg-fetch": "^3.6.2" 45 | }, 46 | "peerDependencies": { 47 | "@aspectjs/common": "*", 48 | "@aspectjs/core": "*" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/httyped-client/rollup.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const { createConfig } = require('../../rollup.config.cjs'); 4 | module.exports = createConfig(__dirname); 5 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/annotation-factory.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationFactory } from '@aspectjs/common'; 2 | 3 | export const ASPECTJS_HTTP_ANNOTATION_FACTORY = new AnnotationFactory( 4 | 'aspectjs.http', 5 | ); 6 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/body.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { AnnotationKind } from '@aspectjs/common'; 4 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from './annotation-factory'; 5 | 6 | export const Body = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 7 | AnnotationKind.PARAMETER, 8 | 'Body', 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | function (contentType?: string) {}, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/fetch/delete.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { AnnotationKind } from '@aspectjs/common'; 4 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from '../annotation-factory'; 5 | 6 | export const Delete = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Delete', 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | function (url?: string, init?: RequestInit) {}, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/fetch/fetch-annotations.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationContext, AnnotationKind } from '@aspectjs/common'; 2 | import { Delete } from './delete.annotation'; 3 | import { Get } from './get.annotation'; 4 | import { Head } from './head.annotation'; 5 | import { Options } from './options.annotation'; 6 | import { Patch } from './patch.annotation'; 7 | import { Post } from './post.annotation'; 8 | import { Put } from './put.annotation'; 9 | 10 | export type FetchAnnotationContext = AnnotationContext< 11 | AnnotationKind.METHOD, 12 | (typeof FETCH_ANNOTATIONS)[number] 13 | >; 14 | export const FETCH_ANNOTATIONS = [Get, Post, Put, Delete, Patch, Head, Options]; 15 | export type FetchAnnotationKind = 16 | | typeof Get 17 | | typeof Post 18 | | typeof Put 19 | | typeof Delete 20 | | typeof Patch 21 | | typeof Head 22 | | typeof Options; 23 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/fetch/get.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { AnnotationKind } from '@aspectjs/common'; 4 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from '../annotation-factory'; 5 | 6 | export const Get = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Get', 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | function (url?: string, init?: RequestInit) {}, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/fetch/head.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { AnnotationKind } from '@aspectjs/common'; 4 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from '../annotation-factory'; 5 | 6 | export const Head = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Head', 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | function (url?: string, init?: RequestInit) {}, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/fetch/options.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { AnnotationKind } from '@aspectjs/common'; 4 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from '../annotation-factory'; 5 | 6 | export const Options = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Option', 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | function (url?: string, init?: RequestInit) {}, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/fetch/patch.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { AnnotationKind } from '@aspectjs/common'; 4 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from '../annotation-factory'; 5 | 6 | export const Patch = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Patch', 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | function (url?: string, init?: RequestInit) {}, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/fetch/post.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { AnnotationKind } from '@aspectjs/common'; 4 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from '../annotation-factory'; 5 | 6 | export const Post = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Post', 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | function Post(url?: string, init?: RequestInit) {}, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/fetch/put.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { AnnotationKind } from '@aspectjs/common'; 4 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from '../annotation-factory'; 5 | 6 | export const Put = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Put', 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | function (url?: string, init?: RequestInit) {}, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/header.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from './annotation-factory'; 3 | export const Header = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 4 | 'Header', 5 | function ( 6 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 7 | // @ts-ignore 8 | headerName: string, 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | value: string | string[], 12 | ) {}, 13 | ); 14 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/headers.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from './annotation-factory'; 3 | export const Headers = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 4 | 'Headers', 5 | function ( 6 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 7 | // @ts-ignore 8 | headers: HeadersInit, 9 | ) {}, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/http-client.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { AnnotationKind } from '@aspectjs/common'; 4 | import { HttypedClientConfig } from '../client-factory/client-config.type'; 5 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from './annotation-factory'; 6 | export const HttypedClient = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 7 | AnnotationKind.CLASS, 8 | 'HttypedClient', 9 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 10 | // @ts-ignore 11 | function (config?: Partial | string) {}, 12 | ); 13 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/path-variable.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { AnnotationKind } from '@aspectjs/common'; 3 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from './annotation-factory'; 4 | export const PathVariable = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 5 | AnnotationKind.PARAMETER, 6 | 'PathVariable', 7 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 8 | // @ts-ignore 9 | function (name: string) {}, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/request-param.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { AnnotationKind } from '@aspectjs/common'; 3 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from './annotation-factory'; 4 | export const RequestParam = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 5 | AnnotationKind.PARAMETER, 6 | 'RequestParam', 7 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 8 | // @ts-ignore 9 | function (name: string) {}, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/request-params.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import { ASPECTJS_HTTP_ANNOTATION_FACTORY } from './annotation-factory'; 3 | export const RequestParams = ASPECTJS_HTTP_ANNOTATION_FACTORY.create( 4 | AnnotationKind.PARAMETER, 5 | 'RequestParams', 6 | function () {}, 7 | ); 8 | -------------------------------------------------------------------------------- /packages/httyped-client/src/annotations/type-hint.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { AnnotationFactory } from '@aspectjs/common'; 3 | import { TypeHintType } from './../types/type-hint.type'; 4 | export const TypeHint = new AnnotationFactory('aspectjs.utils').create( 5 | 'TypeHint', 6 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 7 | // @ts-ignore 8 | function (type: TypeHintType | TypeHintType[]) {}, 9 | ); 10 | -------------------------------------------------------------------------------- /packages/httyped-client/src/client-factory/client-config.type.ts: -------------------------------------------------------------------------------- 1 | import { FetchAdapter } from '../types/fetch-adapter.type'; 2 | import { MappersRegistry } from '../types/mapper.type'; 3 | import { RequestHandler } from '../types/request-handler.type'; 4 | import { ResponseHandler } from '../types/response-handler.type'; 5 | import { PathVariablesHandler } from './path-variables-handler.type'; 6 | import { RequestParamsHandler } from './request-param-handler.type'; 7 | 8 | /** 9 | * The configuration for an HttypedClient. 10 | */ 11 | export interface HttypedClientConfig { 12 | /** 13 | * The base url of all requests (eg: host plus base route) 14 | */ 15 | baseUrl: string; 16 | /** 17 | * A set of options passed to every requests 18 | * 19 | * default: {} 20 | */ 21 | requestInit?: RequestInit; 22 | /** 23 | * The fetch implementation to use. Default: native fetch 24 | */ 25 | fetchAdapter?: FetchAdapter; 26 | /** 27 | * A list of request handlers. 28 | */ 29 | requestHandlers?: RequestHandler[]; 30 | /** 31 | * A list of response handlers. 32 | */ 33 | responseHandlers?: ResponseHandler[]; 34 | /** 35 | * Responses body can be mapped with these mappers 36 | */ 37 | responseBodyMappers?: MappersRegistry; 38 | /** 39 | * Requests body can be mapped with these mappers 40 | */ 41 | requestBodyMappers?: MappersRegistry; 42 | /** 43 | * Used to customize how path variables are handled 44 | */ 45 | pathVariablesHandler?: PathVariablesHandler; 46 | /** 47 | * Used to customize how request parameters are handled 48 | */ 49 | requestParamsHandler?: RequestParamsHandler; 50 | } 51 | -------------------------------------------------------------------------------- /packages/httyped-client/src/client-factory/content-type-json-request-handler.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from '../types/request-handler.type'; 2 | 3 | export const CONTENT_TYPE_JSON_REQUEST_HANDLER: RequestHandler = ( 4 | req: Request, 5 | ) => { 6 | req.headers.set( 7 | 'Content-Type', 8 | req.headers.get('Content-Type') ?? 'application/json', 9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /packages/httyped-client/src/client-factory/request-param-handler.type.ts: -------------------------------------------------------------------------------- 1 | export type RequestParamsHandler = ( 2 | requestParams: [string, unknown][], 3 | ) => string; 4 | 5 | export class DefaultRequestParamHandler { 6 | stringify = (requestParams: [string, unknown][]): string => { 7 | return requestParams.length 8 | ? `?` + 9 | requestParams 10 | .map(([name, value]) => { 11 | return Array.isArray(value) 12 | ? value.map((v) => `${name}=${v}`).join('&') 13 | : `${name}=${value}`; 14 | }) 15 | .join('&') 16 | : ''; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/httyped-client/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './annotations/body.annotation'; 2 | export * from './annotations/fetch/delete.annotation'; 3 | export * from './annotations/fetch/fetch-annotations'; 4 | export * from './annotations/fetch/get.annotation'; 5 | export * from './annotations/fetch/head.annotation'; 6 | export * from './annotations/fetch/options.annotation'; 7 | export * from './annotations/fetch/patch.annotation'; 8 | export * from './annotations/fetch/post.annotation'; 9 | export * from './annotations/fetch/put.annotation'; 10 | export * from './annotations/header.annotation'; 11 | export * from './annotations/headers.annotation'; 12 | export * from './annotations/http-client.annotation'; 13 | export * from './annotations/path-variable.annotation'; 14 | export * from './annotations/request-param.annotation'; 15 | export * from './annotations/request-params.annotation'; 16 | export * from './annotations/type-hint.annotation'; 17 | export * from './aspects/abstract-aop-http-client.aspect'; 18 | export * from './aspects/httyped-client.aspect'; 19 | export * from './client-factory/client-config.type'; 20 | export * from './client-factory/client.factory'; 21 | export * from './client-factory/content-type-json-request-handler'; 22 | export * from './client-factory/default-json-response-handler'; 23 | export * from './client-factory/path-variables-handler.type'; 24 | export * from './client-factory/request-param-handler.type'; 25 | export * from './types/body-metadata.type'; 26 | export * from './types/fetch-adapter.type'; 27 | export * from './types/http-class-metadata.type'; 28 | export * from './types/http-endpoint-metadata.type'; 29 | export * from './types/mapper.error'; 30 | export * from './types/mapper.type'; 31 | export * from './types/request-handler.type'; 32 | export * from './types/response-handler.type'; 33 | export * from './types/type-hint.type'; 34 | -------------------------------------------------------------------------------- /packages/httyped-client/src/test-helpers/all-fetch-annotations.helper.ts: -------------------------------------------------------------------------------- 1 | import { Delete } from '../annotations/fetch/delete.annotation'; 2 | import { Get } from '../annotations/fetch/get.annotation'; 3 | import { Head } from '../annotations/fetch/head.annotation'; 4 | import { Options } from '../annotations/fetch/options.annotation'; 5 | import { Patch } from '../annotations/fetch/patch.annotation'; 6 | import { Post } from '../annotations/fetch/post.annotation'; 7 | import { Put } from '../annotations/fetch/put.annotation'; 8 | 9 | export const ALL_FETCH_ANNOTATIONS = [ 10 | { 11 | annotation: Get, 12 | annotationName: `${Get}`, 13 | method: 'get', 14 | }, 15 | { 16 | annotation: Post, 17 | annotationName: `${Post}`, 18 | method: 'post', 19 | }, 20 | { 21 | annotation: Put, 22 | annotationName: `${Put}`, 23 | method: 'put', 24 | }, 25 | { 26 | annotation: Delete, 27 | annotationName: `${Delete}`, 28 | method: 'delete', 29 | }, 30 | { 31 | annotation: Patch, 32 | annotationName: `${Patch}`, 33 | method: 'patch', 34 | }, 35 | { 36 | annotation: Options, 37 | annotationName: `${Options}`, 38 | method: 'option', 39 | }, 40 | { 41 | annotation: Head, 42 | annotationName: `${Head}`, 43 | method: 'head', 44 | }, 45 | ]; 46 | -------------------------------------------------------------------------------- /packages/httyped-client/src/types/body-metadata.type.ts: -------------------------------------------------------------------------------- 1 | import { TypeHintType } from './type-hint.type'; 2 | 3 | export interface BodyMetadata { 4 | value: T; 5 | typeHint: TypeHintType; 6 | } 7 | -------------------------------------------------------------------------------- /packages/httyped-client/src/types/fetch-adapter.type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines the HTTP backend to be used by the HttpClientAspect. 3 | */ 4 | export type FetchAdapter = typeof fetch; 5 | -------------------------------------------------------------------------------- /packages/httyped-client/src/types/http-class-metadata.type.ts: -------------------------------------------------------------------------------- 1 | export interface HttpClassMetadata { 2 | baseUrl?: string; 3 | requestInit?: RequestInit; 4 | } 5 | -------------------------------------------------------------------------------- /packages/httyped-client/src/types/http-endpoint-metadata.type.ts: -------------------------------------------------------------------------------- 1 | export interface HttpEndpointMetadata { 2 | url?: string; 3 | method: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'option'; 4 | requestInit?: RequestInit; 5 | } 6 | -------------------------------------------------------------------------------- /packages/httyped-client/src/types/mapper.error.ts: -------------------------------------------------------------------------------- 1 | import { TypeHintType } from './type-hint.type'; 2 | 3 | /** 4 | * Error that is thrown when something unexpected happens while mapping a Request Body or a Response Body. 5 | */ 6 | export class MapperError extends Error { 7 | constructor( 8 | readonly value: any, 9 | readonly typeHint: TypeHintType | TypeHintType[] | undefined, 10 | message: string, 11 | ) { 12 | super(message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/httyped-client/src/types/request-handler.type.ts: -------------------------------------------------------------------------------- 1 | export type RequestHandler = (request: Request) => void; 2 | -------------------------------------------------------------------------------- /packages/httyped-client/src/types/response-handler.type.ts: -------------------------------------------------------------------------------- 1 | import { AfterReturnContext, PointcutKind } from '@aspectjs/core'; 2 | import { HttypedClientConfig } from '../client-factory/client-config.type'; 3 | 4 | export type ResponseHandler = ( 5 | response: Response, 6 | config: Readonly>, 7 | ctxt: AfterReturnContext, 8 | ) => Promise; 9 | -------------------------------------------------------------------------------- /packages/httyped-client/src/types/type-hint.type.ts: -------------------------------------------------------------------------------- 1 | import { ConstructorType } from '@aspectjs/common/utils'; 2 | 3 | export type TypeHintType = string | ConstructorType; 4 | -------------------------------------------------------------------------------- /packages/httyped-client/src/url-canparse.polyfill.ts: -------------------------------------------------------------------------------- 1 | if (typeof URL.canParse !== 'function') { 2 | URL.canParse = function (url: string) { 3 | try { 4 | new URL(url); 5 | return true; 6 | } catch (e) { 7 | return false; 8 | } 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /packages/httyped-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "include": ["./src/**/*.ts", "./index.ts"], 5 | "compilerOptions": { 6 | "lib": ["DOM", "DOM.Iterable", "ES2021", "ES2015.Reflect"], 7 | "types": ["reflect-metadata", "jest"], 8 | "useDefineForClassFields": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/httyped-client/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "exclude": ["jest.config.ts", "./**/*.spec.ts"], 5 | "compilerOptions": { 6 | "types": ["reflect-metadata"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/httyped-client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.spec.json", 4 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/httyped-client/tsconfig.tsx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "inlineSourceMap": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/memo/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - ci-templates/node.yml 3 | 4 | memo:lint: 5 | variables: 6 | CWD: packages/memo 7 | extends: .node:lint 8 | stage: test 9 | needs: 10 | - node:prepare 11 | 12 | memo:unit: 13 | extends: .node:test:unit 14 | variables: 15 | CWD: packages/memo 16 | stage: test 17 | needs: 18 | - node:prepare 19 | 20 | memo:build: 21 | extends: .node:build 22 | variables: 23 | CWD: packages/memo 24 | stage: compile 25 | needs: 26 | - node:prepare 27 | 28 | memo:publish:gitlab: 29 | extends: .node:gitlab:publish 30 | stage: publish 31 | variables: 32 | CWD: packages/memo 33 | PACKAGE_DIR: ./dist 34 | 35 | memo:publish:npm: 36 | extends: .node:npm:publish 37 | stage: release 38 | variables: 39 | CWD: packages/memo 40 | PACKAGE_DIR: ./dist 41 | rules: 42 | - if: $CI_COMMIT_REF_NAME =~ /^v\d+\.\d+\.\d+.*$/ 43 | when: manual 44 | -------------------------------------------------------------------------------- /packages/memo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "jest.rootPath": "../../" 3 | // "jest.rootPath": "../../", 4 | // "jest.jestCommandLine": "npm run test:unit" 5 | } 6 | -------------------------------------------------------------------------------- /packages/memo/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/public_api'; 2 | -------------------------------------------------------------------------------- /packages/memo/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { JestConfigWithTsJest } from 'ts-jest'; 2 | import config from '../../jest.config'; 3 | import { join } from 'path'; 4 | 5 | export default { 6 | ...config, 7 | coverageDirectory: join(__dirname, 'dist', 'coverage'), 8 | collectCoverageFrom: [`./**/*.{js,ts}`], 9 | } satisfies JestConfigWithTsJest; 10 | -------------------------------------------------------------------------------- /packages/memo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aspectjs/memo", 3 | "version": "0.5.4", 4 | "description": "Memoize methods with a simple annotation", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/NicolasThierion/aspectjs.git", 8 | "directory": "packages/memo" 9 | }, 10 | "scripts": { 11 | "test": "npm run test:lint && npm run test:unit", 12 | "test:lint": "eslint \"src/**/*.{ts,js,json}\"", 13 | "test:unit": "npm run clean && jest --runInBand --collectCoverage", 14 | "pack": "npm pack ./dist", 15 | "lint": "eslint \"src/**/*.{ts,js,json}\" --fix", 16 | "build": "rollup --config ./rollup.config.cjs", 17 | "clean": "rimraf ./dist && rimraf ./*.tgz" 18 | }, 19 | "author": "Nicolas Thierion ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/NicolasThierion/aspectjs/issues" 23 | }, 24 | "homepage": "https://aspectjs.gitlab.io/", 25 | "type": "module", 26 | "sideEffects": false, 27 | "types": "./index.d.ts", 28 | "exports": { 29 | ".": { 30 | "types": "./index.d.ts", 31 | "fesm2020": "./esm/memo.mjs", 32 | "esm": "./esm/memo.mjs", 33 | "node": "./cjs/memo.cjs", 34 | "require": "./cjs/memo.cjs", 35 | "unpkg": "./umd/memo.umd.min.js" 36 | } 37 | }, 38 | "module": "./esm/memo.mjs", 39 | "main": "./umd/memo.umd.js", 40 | "dependencies": { 41 | "@emotion/hash": "^0.9.1" 42 | }, 43 | "devDependencies": { 44 | "jest": "^29.5.0" 45 | }, 46 | "peerDependencies": { 47 | "@aspectjs/core": "*", 48 | "@aspectjs/common": "*" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/memo/rollup.config.cjs: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const commonjs = require('@rollup/plugin-commonjs'); 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const nodeResolve = require('@rollup/plugin-node-resolve'); 5 | 6 | /* eslint-disable */ 7 | 8 | const external = ['@emotion/hash', 'json-stable-stringify', 'uid']; 9 | const { createConfig } = require('../../rollup.config.cjs'); 10 | module.exports = createConfig(__dirname, { 11 | // external, 12 | output: { 13 | preserveModules: false, 14 | globals: { 15 | '@emotion/hash': 'hash', 16 | 'json-stable-stringify': 'json-stable-stringify', 17 | uid: 'uid', 18 | }, 19 | }, 20 | plugins: [commonjs(), nodeResolve(external)], 21 | }); 22 | -------------------------------------------------------------------------------- /packages/memo/src/memo-annotation-factory.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationFactory } from '@aspectjs/common'; 2 | 3 | export const memoAnnotationFactory = new AnnotationFactory('aspectjs.memo'); 4 | -------------------------------------------------------------------------------- /packages/memo/src/memo.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { memoAnnotationFactory } from './memo-annotation-factory'; 3 | 4 | export interface MemoOptions { 5 | expires?: number | Date; 6 | } 7 | 8 | /** 9 | * Annotation to declare a method as memoizable. 10 | * 11 | * The `@Memo` annotation is used to mark a method as memoizable, meaning its return value will be cached 12 | * and subsequent calls to the method with the same arguments will return the cached result quickly 13 | * instead of executing the method again. 14 | * The memoization occurs when the {@link MemoAspect} aspect is enabled. 15 | * 16 | * @example 17 | * ```typescript 18 | * import { Memo } from '@aspectjs/core'; 19 | * 20 | * class MyClass { 21 | * @Memo() 22 | * expensiveCalculation(arg1: string, arg2: number): string { 23 | * // Perform expensive calculation 24 | * return result; 25 | * } 26 | * } 27 | * ``` 28 | */ 29 | export const Memo = memoAnnotationFactory.create( 30 | 'Memo', 31 | function ( 32 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 33 | // @ts-ignore 34 | // eslint-disable-next-line 35 | options?: MemoOptions, 36 | ) {}, 37 | ); 38 | -------------------------------------------------------------------------------- /packages/memo/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './memo.annotation'; 2 | export * from './memo.aspect'; 3 | -------------------------------------------------------------------------------- /packages/memo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "include": ["./**/*.ts", "./index.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/memo/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | }, 7 | "include": ["./src/**/*.ts", "index.ts"], 8 | "exclude": ["jest.config.ts", "./**/*.spec.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/memo/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.spec.json", 4 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - ci-templates/node.yml 3 | 4 | nestjs-httyped-client:lint: 5 | variables: 6 | CWD: packages/nestjs-httyped-client 7 | extends: .node:lint 8 | stage: test 9 | needs: 10 | - node:prepare 11 | 12 | nestjs-httyped-client:unit: 13 | extends: .node:test:unit 14 | variables: 15 | CWD: packages/nestjs-httyped-client 16 | stage: test 17 | needs: 18 | - node:prepare 19 | 20 | nestjs-httyped-client:build: 21 | extends: .node:build 22 | variables: 23 | CWD: packages/nestjs-httyped-client 24 | stage: compile 25 | needs: 26 | - node:prepare 27 | 28 | # nestjs-httyped-client:publish:gitlab: 29 | # extends: .node:gitlab:publish 30 | # stage: publish 31 | # variables: 32 | # CWD: packages/nestjs-httyped-client 33 | # PACKAGE_DIR: ./dist 34 | 35 | nestjs-httyped-client:publish:npm: 36 | extends: .node:npm:publish 37 | stage: release 38 | variables: 39 | CWD: packages/nestjs-httyped-client 40 | PACKAGE_DIR: ./dist 41 | rules: 42 | - if: $CI_COMMIT_REF_NAME =~ /^v\d+\.\d+\.\d+.*$/ 43 | when: manual 44 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "test http client", 11 | "runtimeExecutable": "npm", 12 | "runtimeVersion": "20", 13 | "outputCapture": "std", 14 | "cwd": "${workspaceFolder}", 15 | "console": "integratedTerminal", 16 | "runtimeArgs": ["run", "test:unit"] 17 | }, 18 | { 19 | "type": "node", 20 | "request": "launch", 21 | "name": "test with tsx", 22 | "runtimeExecutable": "tsx", 23 | "runtimeArgs": ["--tsconfig", "./tsconfig.tsx.json"], 24 | // "runtimeArgs": ["-r", "ts-node/register"], 25 | // "runtimeArgs": ["--loader", "ts-node/esm"], 26 | "console": "integratedTerminal", 27 | "outputCapture": "console", 28 | "skipFiles": ["/**"], 29 | "program": "${workspaceFolder}/test/main.ts" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "jest.jestCommandLine": "../../node_modules/.bin/jest", 3 | "workbench.colorCustomizations": { 4 | "activityBar.activeBackground": "#e2eeb3", 5 | "activityBar.background": "#e2eeb3", 6 | "activityBar.foreground": "#15202b", 7 | "activityBar.inactiveForeground": "#15202b99", 8 | "activityBarBadge.background": "#2fafd0", 9 | "activityBarBadge.foreground": "#15202b", 10 | "commandCenter.border": "#15202b99", 11 | "sash.hoverBorder": "#e2eeb3", 12 | "statusBar.background": "#d2e48a", 13 | "statusBar.foreground": "#15202b", 14 | "statusBarItem.hoverBackground": "#c2da61", 15 | "statusBarItem.remoteBackground": "#d2e48a", 16 | "statusBarItem.remoteForeground": "#15202b", 17 | "titleBar.activeBackground": "#d2e48a", 18 | "titleBar.activeForeground": "#15202b", 19 | "titleBar.inactiveBackground": "#d2e48a99", 20 | "titleBar.inactiveForeground": "#15202b99" 21 | }, 22 | "peacock.color": "#d2e48a" 23 | // "jest.rootPath": "../../", 24 | // "jest.jestCommandLine": "npm run test:unit" 25 | } 26 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - [] `@AddRequestHandler()` 4 | - [] `@AddResponseHandler()` 5 | - [] `@PathVariable()` 6 | - [] `@QueryParam()` 7 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/public_api'; 2 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { JestConfigWithTsJest } from 'ts-jest'; 2 | import config from '../../jest.config'; 3 | 4 | export default { 5 | ...config, 6 | collectCoverageFrom: [`./**/*.{js,ts}`], 7 | } satisfies JestConfigWithTsJest; 8 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-httyped-client", 3 | "version": "0.5.4", 4 | "description": "An AOP powered typesafe http client for your NestJS controllers, inspired by Retrofit library for java", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/NicolasThierion/aspectjs.git", 8 | "directory": "packages/nestjs-httyped-client" 9 | }, 10 | "scripts": { 11 | "test": "npm run clean && npm run test:lint && npm run test:unit", 12 | "test:lint": "eslint \"src/**/*.{ts,js,json}\"", 13 | "test:unit": "npm run clean && jest --runInBand --passWithNoTests", 14 | "pack": "npm pack ./dist", 15 | "lint": "eslint \"src/**/*.{ts,js,json}\" --fix", 16 | "build": "rollup --config ./rollup.config.cjs", 17 | "clean": "rimraf ./dist && rimraf ./*.tgz" 18 | }, 19 | "author": "Nicolas Thierion ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/NicolasThierion/aspectjs/issues" 23 | }, 24 | "homepage": "https://aspectjs.gitlab.io/", 25 | "type": "module", 26 | "sideEffects": false, 27 | "types": "./index.d.ts", 28 | "exports": { 29 | ".": { 30 | "types": "./index.d.ts", 31 | "fesm2020": "./fesm2020/nestjs-httyped-client.mjs", 32 | "esm2020": "./esm2020/index.mjs", 33 | "esm": "./esm2020/index.mjs", 34 | "require": "./cjs/index.cjs", 35 | "import": "./fesm2020/nestjs-httyped-client.mjs", 36 | "unpkg": "./umd/nestjs-httyped-client.umd.min.js" 37 | } 38 | }, 39 | "module": "./fesm2020/nestjs-httyped-client.mjs", 40 | "main": "./umd/nestjs-httyped-client.umd.min.js", 41 | "devDependencies": { 42 | "jest": "^29.5.0", 43 | "tsx": "^4.19.0", 44 | "whatwg-fetch": "^3.6.2" 45 | }, 46 | "dependencies": { 47 | "httyped-client": "*" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/rollup.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | const { createConfig } = require('../../rollup.config.cjs'); 4 | module.exports = createConfig(__dirname); 5 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/src/nestjs-client-factory.type.ts: -------------------------------------------------------------------------------- 1 | import { getWeaver } from '@aspectjs/core'; 2 | import { 3 | HttypedClientAspect, 4 | HttypedClientConfig, 5 | HttypedClientFactory, 6 | } from 'httyped-client'; 7 | import { NestClientAspect } from './nestjs-client.aspect'; 8 | 9 | export class NestClientFactory extends HttypedClientFactory { 10 | constructor(config?: HttypedClientConfig | HttypedClientFactory) { 11 | super(config); 12 | } 13 | 14 | protected override registerAspect(): HttypedClientAspect & object { 15 | let clientAspect = getWeaver().getAspect(NestClientAspect); 16 | 17 | if (clientAspect) { 18 | return clientAspect; 19 | } 20 | clientAspect = new NestClientAspect(); 21 | getWeaver().enable(clientAspect); 22 | return clientAspect; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/src/nestjs-client.annotation.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { AnnotationFactory } from '@aspectjs/common'; 3 | 4 | export const NestClient = new AnnotationFactory('nestjs-httyped-client').create( 5 | 'NestClient', 6 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 7 | // @ts-ignore 8 | function (host?: string) {}, 9 | ); 10 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './nestjs-client-factory.type'; 2 | export * from './nestjs-client.annotation'; 3 | export * from './nestjs-client.aspect'; 4 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "include": ["./src/**/*.ts", "./index.ts"], 5 | "compilerOptions": { 6 | "baseUrl": "./", 7 | "lib": ["DOM", "ES2021"], 8 | "useDefineForClassFields": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | }, 7 | "include": ["./src/**/*.ts", "index.ts"], 8 | "exclude": ["jest.config.ts", "./**/*.spec.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.spec.json", 4 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/nestjs-httyped-client/tsconfig.tsx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "inlineSourceMap": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/nestjs/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/nestjs/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - ci-templates/node.yml 3 | 4 | nestjs:lint: 5 | variables: 6 | CWD: packages/nestjs 7 | extends: .node:lint 8 | stage: test 9 | needs: 10 | - node:prepare 11 | 12 | nestjs:unit: 13 | extends: .node:test:unit 14 | variables: 15 | CWD: packages/nestjs 16 | stage: test 17 | needs: 18 | - node:prepare 19 | 20 | nestjs:build: 21 | extends: .node:build 22 | variables: 23 | CWD: packages/nestjs 24 | stage: compile 25 | needs: 26 | - node:prepare 27 | 28 | nestjs:publish:gitlab: 29 | extends: .node:gitlab:publish 30 | stage: publish 31 | variables: 32 | CWD: packages/nestjs 33 | PACKAGE_DIR: ./dist 34 | 35 | nestjs:publish:npm: 36 | extends: .node:npm:publish 37 | stage: release 38 | variables: 39 | CWD: packages/nestjs 40 | PACKAGE_DIR: ./dist 41 | rules: 42 | - if: $CI_COMMIT_REF_NAME =~ /^v\d+\.\d+\.\d+.*$/ 43 | when: manual 44 | -------------------------------------------------------------------------------- /packages/nestjs/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "name": "vscode-jest-tests", 10 | "request": "launch", 11 | // "args": [ 12 | // "--runInBand", 13 | // "--watchAll=false", 14 | // "--testNamePattern", 15 | // "${jest.testNamePattern}", 16 | // "--runTestsByPath", 17 | // "${jest.testFile}" 18 | // ], 19 | "cwd": "${workspaceFolder}", 20 | "console": "integratedTerminal", 21 | "internalConsoleOptions": "neverOpen", 22 | "runtimeExecutable": "npm", 23 | "runtimeArgs": ["run", "test:unit", "--", "--collectCoverage=false"] 24 | }, 25 | { 26 | "type": "node", 27 | "request": "launch", 28 | "name": "npm test", 29 | "runtimeExecutable": "npm", 30 | "cwd": "${workspaceFolder}", 31 | "runtimeVersion": "20", 32 | "runtimeArgs": ["run", "test:unit"], 33 | "console": "integratedTerminal" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/nestjs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#ffb199", 4 | "activityBar.background": "#ffb199", 5 | "activityBar.foreground": "#15202b", 6 | "activityBar.inactiveForeground": "#15202b99", 7 | "activityBarBadge.background": "#009f26", 8 | "activityBarBadge.foreground": "#e7e7e7", 9 | "commandCenter.border": "#15202b99", 10 | "sash.hoverBorder": "#ffb199", 11 | "statusBar.background": "#ff8a66", 12 | "statusBar.foreground": "#15202b", 13 | "statusBarItem.hoverBackground": "#ff6333", 14 | "statusBarItem.remoteBackground": "#ff8a66", 15 | "statusBarItem.remoteForeground": "#15202b", 16 | "titleBar.activeBackground": "#ff8a66", 17 | "titleBar.activeForeground": "#15202b", 18 | "titleBar.inactiveBackground": "#ff8a6699", 19 | "titleBar.inactiveForeground": "#15202b99" 20 | }, 21 | "peacock.color": "#ff8a66", 22 | "jest.jestCommandLine": "../../node_modules/.bin/jest" 23 | } 24 | -------------------------------------------------------------------------------- /packages/nestjs/README.md: -------------------------------------------------------------------------------- 1 | # @aspectjs/nestjs 2 | 3 | ## 💡 Why ? 4 | 5 | NestJS heavily relies on [experimental decorators](https://github.com/tc39/proposal-decorators) to provide a clean and modern design architecture based on AOP. 6 | This package offers a conversion of some NestJS decorators into **AspectJS annotations**, allowing you to repurpose these annotations with external aspects. 7 | 8 | By themselves, the annotations do nothing, but they require the activation of third-party aspects to introduce actual behavior. 9 | 10 | ## ⚙️ Installation 11 | 12 | ```bash 13 | npm i @aspectjs/nestjs 14 | ``` 15 | 16 | ## 📜 Documentation 17 | 18 | ### `@aspectjs/nestjs/common` 19 | 20 | This package binds the following annotations from `@nestjs/common`: 21 | 22 | | Annotation | 23 | |-----------------| 24 | | `Body()` | 25 | | `Delete()` | 26 | | `Get()` | 27 | | `Head()` | 28 | | `Header()` | 29 | | `Headers()` | 30 | | `Injectable()` | 31 | | `Options()` | 32 | | `Param()` | 33 | | `Patch()` | 34 | | `Post()` | 35 | | `Put()` | 36 | | `Query()` | -------------------------------------------------------------------------------- /packages/nestjs/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/public_api'; 2 | -------------------------------------------------------------------------------- /packages/nestjs/common/register/index.ts: -------------------------------------------------------------------------------- 1 | import { getWeaver } from '@aspectjs/core'; 2 | import { NestCommonAspect } from './src/nestjs-common.aspect'; 3 | 4 | getWeaver().enable(new NestCommonAspect()); 5 | -------------------------------------------------------------------------------- /packages/nestjs/common/register/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './nestjs-common.aspect'; 2 | -------------------------------------------------------------------------------- /packages/nestjs/common/register/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../tsconfig.json", 4 | "include": ["./src/**/*.ts", "./index.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotation-factory.global.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationFactory } from '@aspectjs/common'; 2 | 3 | export const NESTJS_COMMON_ANNOTATION_FACTORY = new AnnotationFactory( 4 | 'aspectjs.nestjs.common', 5 | ); 6 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/body.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Body as NBody, PipeTransform, Type } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Body = NESTJS_COMMON_ANNOTATION_FACTORY.create< 7 | AnnotationKind.PARAMETER, 8 | | (() => void) 9 | | ((...pipes: (Type | PipeTransform)[]) => void) 10 | | (( 11 | property: string, 12 | ...pipes: (Type | PipeTransform)[] 13 | ) => void) 14 | >(AnnotationKind.PARAMETER, 'Body', function Body() {} as ReplaceReturnType< 15 | typeof NBody, 16 | void 17 | >); 18 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/controller.annotation.ts: -------------------------------------------------------------------------------- 1 | import { Annotation, AnnotationKind } from '@aspectjs/common'; 2 | import type { 3 | ControllerOptions, 4 | Controller as NController, 5 | } from '@nestjs/common'; 6 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 7 | import { ReplaceReturnType } from '../type.utils'; 8 | 9 | export const Controller: Annotation< 10 | AnnotationKind.CLASS, 11 | | (() => void) 12 | | ((prefix: string | string[]) => void) 13 | | ((options: ControllerOptions) => void) 14 | > = NESTJS_COMMON_ANNOTATION_FACTORY.create( 15 | 'Controller', 16 | function () {} as ReplaceReturnType, 17 | ) as any; 18 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/delete.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Delete as NDelete } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Delete = NESTJS_COMMON_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Delete', 9 | function () {} as ReplaceReturnType, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/get.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Get as NGet } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Get = NESTJS_COMMON_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Get', 9 | function () {} as ReplaceReturnType, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/head.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Head as NHead } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Head = NESTJS_COMMON_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Head', 9 | function () {} as ReplaceReturnType, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/header.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Header as NHeader } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Header = NESTJS_COMMON_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Header', 9 | function () {} as ReplaceReturnType, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/headers.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Headers as NHeaders } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Headers = NESTJS_COMMON_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Headers', 9 | function () {} as ReplaceReturnType, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/injectable.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Injectable as NInjectable } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Injectable = NESTJS_COMMON_ANNOTATION_FACTORY.create( 7 | AnnotationKind.CLASS, 8 | 'Injectable', 9 | function () {} as ReplaceReturnType, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/options.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Options as NOptions } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Options = NESTJS_COMMON_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Options', 9 | function () {} as ReplaceReturnType, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/param.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Param as NParam, PipeTransform, Type } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Param = NESTJS_COMMON_ANNOTATION_FACTORY.create< 7 | AnnotationKind.PARAMETER, 8 | | (() => void) 9 | | ((...pipes: (Type | PipeTransform)[]) => void) 10 | | (( 11 | property: string, 12 | ...pipes: (Type | PipeTransform)[] 13 | ) => void) 14 | >(AnnotationKind.PARAMETER, function Param() {} as ReplaceReturnType< 15 | typeof NParam, 16 | void 17 | > as any); 18 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/patch.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Patch as NPatch } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Patch = NESTJS_COMMON_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Patch', 9 | function () {} as ReplaceReturnType, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/post.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Post as NPost } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Post = NESTJS_COMMON_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Post', 9 | function () {} as ReplaceReturnType, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/put.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Put as NPut } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Put = NESTJS_COMMON_ANNOTATION_FACTORY.create( 7 | AnnotationKind.METHOD, 8 | 'Put', 9 | function () {} as ReplaceReturnType, 10 | ); 11 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/annotations/query.annotation.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationKind } from '@aspectjs/common'; 2 | import type { Query as NQuery, PipeTransform, Type } from '@nestjs/common'; 3 | import { NESTJS_COMMON_ANNOTATION_FACTORY } from '../annotation-factory.global'; 4 | import { ReplaceReturnType } from '../type.utils'; 5 | 6 | export const Query = NESTJS_COMMON_ANNOTATION_FACTORY.create< 7 | AnnotationKind.PARAMETER, 8 | | (() => void) 9 | | ((...pipes: (Type | PipeTransform)[]) => void) 10 | | (( 11 | property: string, 12 | ...pipes: (Type | PipeTransform)[] 13 | ) => void) 14 | >(function Query() {} as ReplaceReturnType as any); 15 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './annotations/body.annotation'; 2 | export * from './annotations/controller.annotation'; 3 | export * from './annotations/delete.annotation'; 4 | export * from './annotations/get.annotation'; 5 | export * from './annotations/head.annotation'; 6 | export * from './annotations/header.annotation'; 7 | export * from './annotations/injectable.annotation'; 8 | export * from './annotations/options.annotation'; 9 | export * from './annotations/param.annotation'; 10 | export * from './annotations/patch.annotation'; 11 | export * from './annotations/post.annotation'; 12 | export * from './annotations/put.annotation'; 13 | export * from './annotations/query.annotation'; 14 | -------------------------------------------------------------------------------- /packages/nestjs/common/src/type.utils.ts: -------------------------------------------------------------------------------- 1 | export type ReplaceReturnType any, TNewReturn> = ( 2 | ...a: Parameters 3 | ) => TNewReturn; 4 | -------------------------------------------------------------------------------- /packages/nestjs/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../tsconfig.json", 4 | "include": ["./src/**/*.ts", "./index.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/nestjs/common/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../tsconfig.lib.json", 4 | "include": ["./**/*.ts"], 5 | "compilerOptions": { 6 | "baseUrl": "." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/nestjs/index.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /packages/nestjs/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { JestConfigWithTsJest } from 'ts-jest'; 2 | import { createJestConfig } from '../../jest.config'; 3 | 4 | export default { 5 | ...createJestConfig({ 6 | rootDir: __dirname, 7 | }), 8 | collectCoverageFrom: [`./**/*.{js,ts}`], 9 | } satisfies JestConfigWithTsJest; 10 | -------------------------------------------------------------------------------- /packages/nestjs/rollup.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const { createConfig } = require('../../rollup.config.cjs'); 3 | const external = ['@nestjs/common']; 4 | module.exports = createConfig(__dirname, { 5 | external, 6 | }); 7 | -------------------------------------------------------------------------------- /packages/nestjs/src/public_api.ts: -------------------------------------------------------------------------------- 1 | // do not remove, or nestjs/common .d.ts won't build ¯\_(ツ)_/¯ 2 | -------------------------------------------------------------------------------- /packages/nestjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "include": ["./src/**/*.ts", "./index.ts"], 5 | "compilerOptions": { 6 | "baseUrl": ".", 7 | "types": ["jest", "node"] 8 | }, 9 | "exclude": ["./dist", "./**/*.spec.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/nestjs/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "exclude": ["**/jest.config.ts", "**/*.spec.ts"], 5 | "compilerOptions": { 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/nestjs/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.spec.json", 4 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"], 5 | "compilerOptions": { 6 | "baseUrl": ".", 7 | "types": ["jest", "node"], 8 | "emitDecoratorMetadata": true, 9 | "lib": [], 10 | "module": "nodenext" 11 | }, 12 | "exclude": ["./dist"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/persistence/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/persistence/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - ci-templates/node.yml 3 | 4 | persistence:lint: 5 | variables: 6 | CWD: packages/persistence 7 | extends: .node:lint 8 | stage: test 9 | needs: 10 | - node:prepare 11 | 12 | persistence:unit: 13 | extends: .node:test:unit 14 | variables: 15 | CWD: packages/persistence 16 | stage: test 17 | needs: 18 | - node:prepare 19 | 20 | persistence:build: 21 | extends: .node:build 22 | variables: 23 | CWD: packages/persistence 24 | stage: compile 25 | needs: 26 | - node:prepare 27 | 28 | persistence:publish:gitlab: 29 | extends: .node:gitlab:publish 30 | stage: publish 31 | variables: 32 | CWD: packages/persistence 33 | PACKAGE_DIR: ./dist 34 | 35 | persistence:publish:npm: 36 | extends: .node:npm:publish 37 | stage: release 38 | variables: 39 | CWD: packages/persistence 40 | PACKAGE_DIR: ./dist 41 | rules: 42 | - if: $CI_COMMIT_REF_NAME =~ /^v\d+\.\d+\.\d+.*$/ 43 | when: manual 44 | -------------------------------------------------------------------------------- /packages/persistence/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "name": "vscode-jest-tests", 10 | "request": "launch", 11 | // "args": [ 12 | // "--runInBand", 13 | // "--watchAll=false", 14 | // "--testNamePattern", 15 | // "${jest.testNamePattern}", 16 | // "--runTestsByPath", 17 | // "${jest.testFile}" 18 | // ], 19 | "cwd": "${workspaceFolder}", 20 | "console": "integratedTerminal", 21 | "internalConsoleOptions": "neverOpen", 22 | "runtimeExecutable": "npm", 23 | "runtimeArgs": ["run", "test:unit", "--", "--collectCoverage=false"] 24 | }, 25 | { 26 | "type": "node", 27 | "request": "launch", 28 | "name": "npm test", 29 | "runtimeExecutable": "npm", 30 | "cwd": "${workspaceFolder}", 31 | "runtimeVersion": "20", 32 | "runtimeArgs": ["run", "test:unit"], 33 | "console": "integratedTerminal" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/persistence/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#ffb199", 4 | "activityBar.background": "#ffb199", 5 | "activityBar.foreground": "#15202b", 6 | "activityBar.inactiveForeground": "#15202b99", 7 | "activityBarBadge.background": "#009f26", 8 | "activityBarBadge.foreground": "#e7e7e7", 9 | "commandCenter.border": "#15202b99", 10 | "sash.hoverBorder": "#ffb199", 11 | "statusBar.background": "#ff8a66", 12 | "statusBar.foreground": "#15202b", 13 | "statusBarItem.hoverBackground": "#ff6333", 14 | "statusBarItem.remoteBackground": "#ff8a66", 15 | "statusBarItem.remoteForeground": "#15202b", 16 | "titleBar.activeBackground": "#ff8a66", 17 | "titleBar.activeForeground": "#15202b", 18 | "titleBar.inactiveBackground": "#ff8a6699", 19 | "titleBar.inactiveForeground": "#15202b99" 20 | }, 21 | "peacock.color": "#ff8a66", 22 | "jest.jestCommandLine": "../../node_modules/.bin/jest" 23 | } 24 | -------------------------------------------------------------------------------- /packages/persistence/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/public_api'; 2 | -------------------------------------------------------------------------------- /packages/persistence/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { JestConfigWithTsJest } from 'ts-jest'; 2 | import { createJestConfig } from '../../jest.config'; 3 | 4 | export default { 5 | ...createJestConfig({ 6 | rootDir: __dirname, 7 | }), 8 | collectCoverageFrom: [`./**/*.{js,ts}`], 9 | } satisfies JestConfigWithTsJest; 10 | -------------------------------------------------------------------------------- /packages/persistence/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@aspectjs/persistence", 3 | "version": "0.5.4", 4 | "description": "ORM annotations", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/NicolasThierion/aspectjs.git", 8 | "directory": "packages/persistence" 9 | }, 10 | "scripts": { 11 | "test": "npm run test:lint && npm run test:unit", 12 | "test:lint": "eslint \"src/**/*.{ts,js,json}\"", 13 | "test:unit": "npm run clean && jest --runInBand --collectCoverage", 14 | "pack": "npm pack ./dist", 15 | "lint": "eslint \"*/**/*.{ts,js,json}\" --fix", 16 | "build": "rollup --config ./rollup.config.cjs", 17 | "clean": "rimraf dist && rimraf ./*.tgz" 18 | }, 19 | "author": "Nicolas Thierion ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/NicolasThierion/aspectjs/issues" 23 | }, 24 | "homepage": "https://aspectjs.gitlab.io/", 25 | "type": "module", 26 | "sideEffects": false, 27 | "exports": { 28 | ".": { 29 | "types": "./index.d.ts", 30 | "esm2020": "./esm2020/persistence.mjs", 31 | "esm": "./esm2020/persistence.mjs", 32 | "require": "./cjs/index.cjs", 33 | "import": "./esm2020/persistence.mjs" 34 | }, 35 | "./typeorm": { 36 | "types": "./typeorm/index.d.ts", 37 | "esm2020": "./esm2020/typeorm/index.mjs", 38 | "esm": "./esm2020/typeorm/index.mjs", 39 | "require": "./cjs/typeorm/_index.cjs", 40 | "import": "./esm2020/typeorm/index.mjs" 41 | } 42 | }, 43 | "module": "./esm2020/persistence.mjs", 44 | "main": "./cjs/persistence.cjs", 45 | "peerDependencies": { 46 | "typeorm": "^0.3.17", 47 | "@aspectjs/core": "*", 48 | "@aspectjs/common": "*" 49 | }, 50 | "devDependencies": { 51 | "pg": "^8.11.2", 52 | "portfinder": "^1.0.32", 53 | "typeorm": "^0.3.17" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/persistence/rollup.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const { createConfig } = require('../../rollup.config.cjs'); 3 | 4 | module.exports = createConfig(__dirname, { 5 | input: ['./index.ts', './typeorm/_index.ts'], 6 | external: ['node:async_hooks'], 7 | }); 8 | -------------------------------------------------------------------------------- /packages/persistence/src/annotation-factory.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationFactory } from '@aspectjs/common'; 2 | 3 | export const ORM_ANNOTATION_FACTORY = new AnnotationFactory('aspectjs'); 4 | -------------------------------------------------------------------------------- /packages/persistence/src/datasource.registry.ts: -------------------------------------------------------------------------------- 1 | export abstract class DatasourceRegistry { 2 | private registry = new Map(); 3 | register(datasource: T, name = 'default'): void { 4 | this.registry.set(name, datasource); 5 | } 6 | get(name = 'default'): T { 7 | const ds = this.registry.get(name); 8 | 9 | if (!ds) { 10 | throw new TypeError(`No datasource registered for ${name}`); 11 | } 12 | 13 | return ds; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/persistence/src/persistence-error.ts: -------------------------------------------------------------------------------- 1 | export class AspectJsPersistenceError extends Error {} 2 | -------------------------------------------------------------------------------- /packages/persistence/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './persistence-error'; 2 | export * from './transactional/annotations/transactional.annotation'; 3 | export * from './transactional/transaction'; 4 | export * from './transactional/transaction-manager'; 5 | export * from './transactional/transactional-error'; 6 | export * from './transactional/transactional.aspect'; 7 | -------------------------------------------------------------------------------- /packages/persistence/src/test-helpers/init-db.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | 3 | import portfinder from 'portfinder'; 4 | 5 | import path from 'path'; 6 | 7 | export interface Db { 8 | id: number; 9 | port: number; 10 | host: string; 11 | username: string; 12 | password: string; 13 | database: string; 14 | teardown: () => void; 15 | } 16 | const baseContainerName = 'aspectjs_orm_db'; 17 | let id = 0; 18 | 19 | export interface DbOptions { 20 | database?: string; 21 | port?: number; 22 | } 23 | export async function initDb(dbOptions?: DbOptions): Promise { 24 | const port = await portfinder.getPortPromise({ 25 | port: 25432, 26 | }); 27 | const options = { 28 | database: `aspectjs_orm_db`, 29 | port: port, 30 | ...dbOptions, 31 | }; 32 | const containerName = `${baseContainerName}_${new Date() 33 | .valueOf() 34 | .toString()}`; 35 | const password = 'password'; 36 | const username = 'username'; 37 | const initdb = path.join(__dirname, 'initdb'); 38 | const dockerCmd = 39 | `docker run -d --rm ` + 40 | `-v ${initdb}:/docker-entrypoint-initdb.d:ro ` + 41 | `-p ${options.port}:5432 ` + 42 | `-e POSTGRES_PASSWORD=${password} ` + 43 | `-e POSTGRES_USER=${username} -e POSTGRES_DB=${options.database} ` + 44 | `--name ${containerName} ` + 45 | `postgres && sleep 5`; 46 | execSync(dockerCmd, { 47 | stdio: 'inherit', 48 | }); 49 | 50 | const db = { 51 | id, 52 | port, 53 | host: 'localhost', 54 | username, 55 | password, 56 | database: options.database, 57 | teardown: () => { 58 | execSync(`docker rm -f ${containerName} `, { stdio: 'inherit' }); 59 | }, 60 | } satisfies Db; 61 | id++; 62 | return db; 63 | } 64 | -------------------------------------------------------------------------------- /packages/persistence/src/test-helpers/initdb/00.user-post.sql: -------------------------------------------------------------------------------- 1 | -- Create the User table 2 | CREATE TABLE User_ ( 3 | id SERIAL PRIMARY KEY, 4 | username VARCHAR(50) UNIQUE NOT NULL, 5 | firstname VARCHAR(50) NOT NULL, 6 | lastname VARCHAR(50) NOT NULL 7 | ); 8 | 9 | -- Create the Post table 10 | CREATE TABLE Post ( 11 | id SERIAL PRIMARY KEY, 12 | content TEXT NOT NULL, 13 | user_id INT REFERENCES User_(id) ON DELETE CASCADE 14 | ); 15 | -------------------------------------------------------------------------------- /packages/persistence/src/test-helpers/typeorm/init-ds.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from 'typeorm'; 2 | import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions'; 3 | import { Post } from './post.entity'; 4 | import { User } from './user.entity'; 5 | 6 | export const createDataSource = async ( 7 | options: Partial, 8 | ) => { 9 | const dataSource = new DataSource({ 10 | type: 'postgres', 11 | host: 'localhost', 12 | port: 5432, 13 | entities: [User, Post], 14 | synchronize: true, 15 | ...options, 16 | }); 17 | 18 | await dataSource.initialize(); 19 | await dataSource.query('DELETE FROM User_'); 20 | await dataSource.query('DELETE FROM Post'); 21 | return dataSource; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/persistence/src/test-helpers/typeorm/post.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { User } from './user.entity'; 3 | 4 | @Entity() 5 | export class Post { 6 | @PrimaryGeneratedColumn() 7 | id?: number; 8 | 9 | @Column() 10 | content?: string = 'Hello'; 11 | 12 | @ManyToOne(() => User, (user) => user.posts) 13 | user?: User; 14 | } 15 | -------------------------------------------------------------------------------- /packages/persistence/src/test-helpers/typeorm/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Post } from './post.entity'; 3 | 4 | @Entity({ 5 | name: 'user_', 6 | }) 7 | export class User { 8 | @PrimaryGeneratedColumn() 9 | id?: number; 10 | 11 | @Column({ unique: true }) 12 | username?: string = 'JohnDoe'; 13 | 14 | @Column() 15 | firstname?: string = 'John'; 16 | 17 | @Column() 18 | lastname?: string = 'Doe'; 19 | 20 | @OneToMany(() => Post, (post) => post.user) 21 | posts?: Post[]; 22 | } 23 | -------------------------------------------------------------------------------- /packages/persistence/src/transactional/annotations/transactional.annotation.ts: -------------------------------------------------------------------------------- 1 | import { ORM_ANNOTATION_FACTORY } from '../../annotation-factory'; 2 | 3 | export const Transactional = ORM_ANNOTATION_FACTORY.create('Transactional'); 4 | -------------------------------------------------------------------------------- /packages/persistence/src/transactional/transaction-manager.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from './transaction'; 2 | 3 | export interface DataSourceDefinition { 4 | name: string; 5 | dataSource: T; 6 | } 7 | 8 | export interface TransactionManager { 9 | hasTransaction(): boolean; 10 | createTransaction(): Promise; 11 | configure(ds: DataSourceDefinition[]): this; 12 | } 13 | -------------------------------------------------------------------------------- /packages/persistence/src/transactional/transaction.ts: -------------------------------------------------------------------------------- 1 | export interface Transaction { 2 | rollback(): Promise; 3 | commit(): Promise; 4 | close(): Promise; 5 | run(fn: (transaction: Transaction) => Promise | T): Promise; 6 | } 7 | -------------------------------------------------------------------------------- /packages/persistence/src/transactional/transactional-error.ts: -------------------------------------------------------------------------------- 1 | import { AspectJsPersistenceError } from '../persistence-error'; 2 | 3 | export class TransactionalError extends AspectJsPersistenceError {} 4 | -------------------------------------------------------------------------------- /packages/persistence/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { AnnotationFactory } from '@aspectjs/common'; 2 | 3 | export const _CORE_ANNOTATION_FACTORY = new AnnotationFactory('aspectjs.core'); 4 | -------------------------------------------------------------------------------- /packages/persistence/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "include": ["./src/**/*.ts", "./index.ts"], 5 | "compilerOptions": { 6 | "baseUrl": ".", 7 | "skipLibCheck": true, 8 | "types": ["jest", "node"], 9 | "lib": ["DOM", "ES2021", "ES2019.Array"] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/persistence/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "exclude": ["**/jest.config.ts", "**/*.spec.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/persistence/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.spec.json", 4 | "include": ["./**/*.spec.ts", "./**/*.ts", "./**/*.d.ts"], 5 | "compilerOptions": { 6 | "baseUrl": ".", 7 | "types": ["jest", "node"], 8 | "lib": ["DOM", "ES2018", "ES2019.Array"] 9 | }, 10 | "exclude": ["./dist"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/persistence/typeorm/_index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/public_api'; 2 | -------------------------------------------------------------------------------- /packages/persistence/typeorm/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './typeorm-datasource-definition'; 2 | export * from './typeorm-transaction-manager'; 3 | export * from './typeorm-transactional.aspect'; 4 | -------------------------------------------------------------------------------- /packages/persistence/typeorm/src/typeorm-datasource-definition.ts: -------------------------------------------------------------------------------- 1 | import { DataSourceDefinition } from '@aspectjs/persistence'; 2 | import { DataSource } from 'typeorm'; 3 | 4 | export interface TypeOrmDataSourceDefinition 5 | extends DataSourceDefinition {} 6 | -------------------------------------------------------------------------------- /packages/persistence/typeorm/src/typeorm-transaction-manager.spec.ts: -------------------------------------------------------------------------------- 1 | import { configureTesting } from '@aspectjs/common/testing'; 2 | import { getWeaver } from '@aspectjs/core'; 3 | import { DataSource } from 'typeorm'; 4 | import { Db, initDb } from '../../src/test-helpers/init-db'; 5 | import { createDataSource } from '../../src/test-helpers/typeorm/init-ds'; 6 | import { Transactional } from '../../src/transactional/annotations/transactional.annotation'; 7 | import { TypeOrmTransactionManager } from './typeorm-transaction-manager'; 8 | import { TypeOrmTransactionalAspect } from './typeorm-transactional.aspect'; 9 | 10 | // disabled for now as we have not managed to run docker container in a gitlab runner 11 | xdescribe('TypeOrmTransactionManager', () => { 12 | let ds: DataSource; 13 | let db: Db; 14 | let transactionManager: TypeOrmTransactionManager; 15 | beforeAll(async () => { 16 | db = await initDb(); 17 | }); 18 | 19 | beforeEach(async () => { 20 | ds = await createDataSource(db); 21 | configureTesting(); 22 | transactionManager = new TypeOrmTransactionManager(); 23 | getWeaver().enable( 24 | new TypeOrmTransactionalAspect(transactionManager).configure(ds), 25 | ); 26 | }); 27 | afterAll(async () => { 28 | db.teardown(); 29 | }); 30 | 31 | describe('.hasTransaction()', () => { 32 | describe('when called from a @Transactional method', () => { 33 | it('returns true', async () => { 34 | class T { 35 | @Transactional() 36 | m() { 37 | expect(transactionManager.hasTransaction()).toBe(true); 38 | return Promise.resolve(); 39 | } 40 | } 41 | 42 | await new T().m(); 43 | }); 44 | }); 45 | describe('when called from outside a @Transactional method', () => { 46 | it('returns false', () => { 47 | expect(transactionManager.hasTransaction()).toBe(false); 48 | }); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/persistence/typeorm/src/typeorm-transaction-registry.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '@aspectjs/persistence'; 2 | import { EntityManager } from 'typeorm'; 3 | 4 | export class TransactionsRegistry { 5 | private readonly transactions = new Map< 6 | string, 7 | { transaction: Transaction; managers: Map } 8 | >(); 9 | 10 | registerTransactions(transactionId: string, transaction: Transaction) { 11 | this.transactions.set(transactionId, { 12 | transaction: transaction, 13 | managers: new Map(), 14 | }); 15 | } 16 | 17 | registerManager( 18 | transactionId: string, 19 | name: string, 20 | manager: EntityManager, 21 | ): void { 22 | const managers = this.transactions.get(transactionId)!.managers; 23 | managers.set(name, manager); 24 | } 25 | 26 | remove(transactionId: string): void { 27 | this.transactions.delete(transactionId); 28 | } 29 | 30 | getTransaction(transactionId: string) { 31 | return this.transactions.get(transactionId)?.transaction; 32 | } 33 | 34 | hasTransaction(transactionId: string): boolean { 35 | return !!this.transactions.get(transactionId); 36 | } 37 | 38 | getManager( 39 | transactionId: string | undefined, 40 | name: string, 41 | ): EntityManager | undefined { 42 | return transactionId 43 | ? this.transactions.get(transactionId)?.managers.get(name) 44 | : undefined; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/persistence/typeorm/src/typeorm-transactional.aspect.ts: -------------------------------------------------------------------------------- 1 | import { TransactionalAspect, TransactionManager } from '@aspectjs/persistence'; 2 | import { DataSource } from 'typeorm'; 3 | import { TypeOrmDataSourceDefinition } from './typeorm-datasource-definition'; 4 | import { TypeOrmTransactionManager } from './typeorm-transaction-manager'; 5 | 6 | export class TypeOrmTransactionalAspect extends TransactionalAspect { 7 | declare readonly transactionManager: TransactionManager; 8 | constructor( 9 | transactionManager: TransactionManager = new TypeOrmTransactionManager(), 10 | ) { 11 | super(transactionManager); 12 | } 13 | 14 | override configure( 15 | ...dataSources: (DataSource | TypeOrmDataSourceDefinition)[] 16 | ): this; 17 | override configure(...dataSources: TypeOrmDataSourceDefinition[]): this; 18 | override configure( 19 | ...dataSources: (DataSource | TypeOrmDataSourceDefinition)[] 20 | ) { 21 | return super.configure(...createDataSourceDefinition(dataSources)); 22 | } 23 | } 24 | 25 | function createDataSourceDefinition( 26 | dataSources: (DataSource | TypeOrmDataSourceDefinition)[], 27 | ): TypeOrmDataSourceDefinition[] { 28 | return dataSources.map((ds) => { 29 | return (ds as TypeOrmDataSourceDefinition).dataSource 30 | ? (ds as TypeOrmDataSourceDefinition) 31 | : ({ 32 | dataSource: ds as DataSource, 33 | name: 'default', 34 | } satisfies TypeOrmDataSourceDefinition); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /packages/persistence/typeorm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../tsconfig.json", 4 | "include": ["./src/**/*.ts", "./_index.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/persistence/typeorm/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "exclude": ["**/jest.config.ts", "**/*.spec.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /rollup.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | require('ts-node').register({ 4 | compilerOptions: { 5 | module: 'CommonJS', 6 | moduleResolution: 'Node', 7 | allowImportingTsExtensions: true, 8 | rootDir: '.', 9 | }, 10 | 11 | // and other tsconfig.json options as you like 12 | }); 13 | 14 | const { createConfig } = require('./rollup.config.ts'); 15 | 16 | module.exports = { 17 | createConfig, 18 | }; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "include": ["rollup.config.ts"], 4 | 5 | "compilerOptions": { 6 | "module": "ESNext", 7 | "moduleResolution": "Node", 8 | "baseUrl": ".", 9 | "target": "ESNext", 10 | 11 | // "useDefineForClassFields": false, 12 | "experimentalDecorators": true, 13 | "downlevelIteration": true, 14 | "importHelpers": true, 15 | // Module settings. 16 | // "allowSyntheticDefaultImports": true, 17 | "esModuleInterop": true, 18 | "isolatedModules": true, 19 | "resolveJsonModule": true, 20 | 21 | // Strictness and quality settings. 22 | "alwaysStrict": true, 23 | "exactOptionalPropertyTypes": false, 24 | "forceConsistentCasingInFileNames": true, 25 | "noFallthroughCasesInSwitch": true, 26 | "noImplicitOverride": true, 27 | "noImplicitReturns": true, 28 | "noPropertyAccessFromIndexSignature": true, 29 | "noUncheckedIndexedAccess": true, 30 | "noUnusedLocals": true, 31 | "noUnusedParameters": true, 32 | "strict": true, 33 | 34 | // Type-checking settings. 35 | "lib": ["ES2015.Reflect", "ES2015", "ES2021"], 36 | 37 | "skipDefaultLibCheck": true, 38 | "skipLibCheck": true, 39 | "paths": { 40 | "@aspectjs/common": ["./packages/common/src/public_api.ts"], 41 | "@aspectjs/common/testing": [ 42 | "./packages/common/testing/src/public_api.ts" 43 | ], 44 | "@aspectjs/common/utils": ["./packages/common/utils/src/public_api.ts"], 45 | "@aspectjs/core": ["./packages/core/src/public_api.ts"], 46 | "@aspectjs/core/testing": ["./packages/core/testing/src/public_api.ts"], 47 | "@aspectjs/nestjs/common": ["./packages/nestjs/common/src/public_api.ts"], 48 | "@aspectjs/persistence": ["./packages/persistence/src/public_api.ts"], 49 | 50 | "httyped-client": ["./packages/httyped-client/src/public_api.ts"], 51 | "nestjs-httyped-client": [ 52 | "./packages/nestjs-httyped-client/src/public_api.ts" 53 | ] 54 | } 55 | }, 56 | "ts-node": { 57 | "moduleTypes": { 58 | "rollup.config.ts": "cjs" 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "experimentalDecorators": true, 6 | "emitDecoratorMetadata": true, 7 | "outDir": "./out-tsc/spec", 8 | "lib": ["DOM", "ES2021"], 9 | "types": ["jest", "node", "reflect-metadata"], 10 | "noUnusedLocals": false, 11 | "noUnusedParameters": false 12 | }, 13 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"], 14 | "ts-node": { 15 | "moduleTypes": { 16 | "**": "cjs" 17 | } 18 | } 19 | } 20 | --------------------------------------------------------------------------------