From 33c0c604e1a7b0e64cfd8af87ab5c809dde8d71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?He=CC=84sperus?= Date: Sat, 21 Feb 2026 22:36:52 +0800 Subject: [PATCH 1/2] dev(whitepaper): shift to Deno and finish whitepaper --- .github/workflows/ci.yml | 30 ++ .gitignore | 1 - .oxfmtrc.json | 29 -- .oxlintrc.json | 118 ------- README.md | 46 +-- deno.json | 44 +++ deno.lock | 16 + package.json | 24 -- pnpm-lock.yaml | 528 ------------------------------ shell.nix | 23 +- skill/example/AGENTS.md | 16 - skill/example/AlertDispatch.ts | 10 +- skill/example/BaseModule.ts | 31 +- skill/example/CoreLogging.ts | 7 +- skill/example/README.md | 4 +- skill/example/index.ts | 2 +- skill/example/tsconfig.json | 15 - skill/example/types.ts | 24 +- skill/example/utilities.ts | 7 +- skill/maintenance.md | 44 +-- skill/start.md | 80 +++-- tsconfig.json | 17 - whitepaper.ipynb | 565 +++++++++++++++++++++++++++++---- 23 files changed, 724 insertions(+), 957 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .oxfmtrc.json delete mode 100644 .oxlintrc.json create mode 100644 deno.json create mode 100644 deno.lock delete mode 100644 package.json delete mode 100644 pnpm-lock.yaml delete mode 100644 skill/example/AGENTS.md delete mode 100644 skill/example/tsconfig.json delete mode 100644 tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3bc8fe5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, ready_for_review] + push: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + ci: + runs-on: ubuntu-slim + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Run checks + run: deno run check diff --git a/.gitignore b/.gitignore index ff8db1e..fa97eb4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ node_modules # dot files .vscode -.pnpm-global # Logs logs diff --git a/.oxfmtrc.json b/.oxfmtrc.json deleted file mode 100644 index b0a67d7..0000000 --- a/.oxfmtrc.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "./node_modules/oxfmt/configuration_schema.json", - "printWidth": 100, - "tabWidth": 4, - "useTabs": true, - "singleQuote": true, - "experimentalSortImports": { - "newlinesBetween": false, - "groups": [ - "type-import", - ["value-builtin", "value-external"], - "type-internal", - "value-internal", - ["type-parent", "type-sibling", "type-index"], - ["value-parent", "value-sibling", "value-index"], - "unknown" - ] - }, - "embeddedLanguageFormatting": "auto", - "overrides": [ - { - "files": ["*.md", "*.yaml", "*.yml"], - "options": { - "tabWidth": 2, - "useTabs": false - } - } - ] -} diff --git a/.oxlintrc.json b/.oxlintrc.json deleted file mode 100644 index 7ea656a..0000000 --- a/.oxlintrc.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "$schema": "./node_modules/oxlint/configuration_schema.json", - "plugins": ["unicorn", "typescript", "oxc", "import", "react", "vue"], - "categories": {}, - "rules": { - "constructor-super": "warn", - "for-direction": "warn", - "no-async-promise-executor": "warn", - "no-caller": "warn", - "no-class-assign": "warn", - "no-compare-neg-zero": "warn", - "no-cond-assign": "warn", - "no-const-assign": "warn", - "no-constant-binary-expression": "warn", - "no-constant-condition": "warn", - "no-control-regex": "warn", - "no-debugger": "warn", - "no-delete-var": "warn", - "no-dupe-class-members": "warn", - "no-dupe-else-if": "warn", - "no-dupe-keys": "warn", - "no-duplicate-case": "warn", - "no-empty-character-class": "warn", - "no-empty-pattern": "warn", - "no-empty-static-block": "warn", - "no-eval": "warn", - "no-ex-assign": "warn", - "no-extra-boolean-cast": "warn", - "no-func-assign": "warn", - "no-global-assign": "warn", - "no-import-assign": "warn", - "no-invalid-regexp": "warn", - "no-irregular-whitespace": "warn", - "no-loss-of-precision": "warn", - "no-new-native-nonconstructor": "warn", - "no-nonoctal-decimal-escape": "warn", - "no-obj-calls": "warn", - "no-self-assign": "warn", - "no-setter-return": "warn", - "no-shadow-restricted-names": "warn", - "no-sparse-arrays": "warn", - "no-this-before-super": "warn", - "no-unassigned-vars": "warn", - "no-unsafe-finally": "warn", - "no-unsafe-negation": "warn", - "no-unsafe-optional-chaining": "warn", - "no-unused-expressions": "warn", - "no-unused-labels": "warn", - "no-unused-private-class-members": "warn", - "no-unused-vars": "warn", - "no-useless-backreference": "warn", - "no-useless-catch": "warn", - "no-useless-escape": "warn", - "no-useless-rename": "warn", - "no-with": "warn", - "require-yield": "warn", - "use-isnan": "warn", - "valid-typeof": "warn", - "oxc/bad-array-method-on-arguments": "warn", - "oxc/bad-char-at-comparison": "warn", - "oxc/bad-comparison-sequence": "warn", - "oxc/bad-min-max-func": "warn", - "oxc/bad-object-literal-comparison": "warn", - "oxc/bad-replace-all-arg": "warn", - "oxc/const-comparisons": "warn", - "oxc/double-comparisons": "warn", - "oxc/erasing-op": "warn", - "oxc/missing-throw": "warn", - "oxc/number-arg-out-of-range": "warn", - "oxc/only-used-in-recursion": "warn", - "oxc/uninvoked-array-callback": "warn", - "typescript/no-non-null-assertion": "warn", - "typescript/no-explicit-any": "warn", - "typescript/await-thenable": "warn", - "typescript/no-array-delete": "warn", - "typescript/no-base-to-string": "warn", - "typescript/no-duplicate-enum-values": "warn", - "typescript/no-duplicate-type-constituents": "warn", - "typescript/no-extra-non-null-assertion": "warn", - "typescript/no-floating-promises": "warn", - "typescript/no-for-in-array": "warn", - "typescript/no-implied-eval": "warn", - "typescript/no-meaningless-void-operator": "warn", - "typescript/no-misused-new": "warn", - "typescript/no-misused-spread": "warn", - "typescript/no-non-null-asserted-optional-chain": "warn", - "typescript/no-redundant-type-constituents": "warn", - "typescript/no-this-alias": "off", - "typescript/no-unnecessary-parameter-property-assignment": "warn", - "typescript/no-unsafe-declaration-merging": "warn", - "typescript/no-unsafe-unary-minus": "warn", - "typescript/no-useless-empty-export": "warn", - "typescript/no-wrapper-object-types": "warn", - "typescript/prefer-as-const": "warn", - "typescript/require-array-sort-compare": "warn", - "typescript/restrict-template-expressions": "warn", - "typescript/triple-slash-reference": "warn", - "typescript/unbound-method": "allow", - "unicorn/no-await-in-promise-methods": "warn", - "unicorn/no-empty-file": "warn", - "unicorn/no-invalid-fetch-options": "warn", - "unicorn/no-invalid-remove-event-listener": "warn", - "unicorn/no-new-array": "warn", - "unicorn/no-single-promise-in-promise-methods": "warn", - "unicorn/no-thenable": "warn", - "unicorn/no-unnecessary-await": "warn", - "unicorn/no-useless-fallback-in-spread": "warn", - "unicorn/no-useless-length-check": "warn", - "unicorn/no-useless-spread": "warn", - "unicorn/prefer-set-size": "warn", - "unicorn/prefer-string-starts-ends-with": "warn" - }, - "env": { - "builtin": true - }, - "globals": {}, - "ignorePatterns": [] -} diff --git a/README.md b/README.md index affece4..df0fbfe 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@

Made by Humans Agent Skill Available + + ci +

@@ -40,9 +43,7 @@ SynthKernel provides a Jupyter Notebook whitepaper running TypeScript, you can f VSCode or any fork of it is recommended to view it. To correctly execute the whitepaper, you need to do some setup: -**Normal OS**: - -Install Git, Python3, Node.js, VSCode or its fork, and ensure you have installed the following extensions for VSCode: +Install Python3 and Deno (because it provides a Jupyter Notebook TypeScript kernel), ensure you have installed the following extensions for VSCode if you are using it: - `ms-toolsai.jupyter` - `bierner.markdown-mermaid` @@ -53,44 +54,11 @@ Then open your terminal at a proper directory (we will create some file in it) a # install Jupyter if you haven't pip3 install jupyterlab -# clone and enter this repp -git clone https://github.com/hesprs/synthkernel.git -cd synthkernel - -# install dependencies, use your preferred package manager -npm install - -# register TS kernel, you can also use pnpm or whatever -npx tslab install -``` - -Then open your VSCode in the cloned repo and open `whitepaper.ipynb`, choose `Select Kernel` -> `Jupyter Kernel...` -> `TypeScript`, now you can read the notebook. - -**Nix Approach**: - -Install Git, Python3, Node.js, VSCode or its fork, and ensure you have installed the following extensions for VSCode: - -- `ms-toolsai.jupyter` -- `bierner.markdown-mermaid` - -Then open your terminal at a proper directory (we will create some file in it) and run following commands: - -```sh -# clone and enter this repp -git clone https://github.com/hesprs/synthkernel.git -cd synthkernel - -# install dependencies, use your preferred package manager -npm install - -# enter nix shell -nix-shell shell.nix - -# run VSCode, if you are using Cursor or whatever forks, change accordingly -code . +# register TS kernel +deno jupyter --install ``` -Open `whitepaper.ipynb`, choose `Select Kernel` -> `Jupyter Kernel...` -> `TypeScript`, now you can read the notebook. +Then open `whitepaper.ipynb` in your viewer, choose the Deno kernel (in VSCode, it's `Select Kernel` -> `Jupyter Kernel...` -> `Deno`). Now you can read the notebook. ## 🤖 Agent Skill diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..d55ef60 --- /dev/null +++ b/deno.json @@ -0,0 +1,44 @@ +{ + "description": "A type-safe and composable meta architecture for modular monolith development.", + "keywords": [ + "modular-monolith", + "typescript", + "architecture", + "agent-skills", + "agentic-coding" + ], + "license": "CC BY-SA 4.0", + "author": "", + "type": "module", + "tasks": { + "run:example": "deno run skill/examples/example.ts", + "lint": "deno lint --fix && deno fmt", + "check": "deno lint && deno fmt --check" + }, + "imports": { + "@needle-di/core": "jsr:@needle-di/core@^1.1.1" + }, + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "verbatimModuleSyntax": true, + "lib": ["ESNext", "DOM", "DOM.Iterable"] + }, + "fmt": { + "indentWidth": 4, + "useTabs": true, + "singleQuote": true, + "proseWrap": "never" + }, + "lint": { + "rules": { + "tags": ["recommended"], + "exclude": [ + "ban-types", + "no-this-alias" + ] + } + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..7c2171c --- /dev/null +++ b/deno.lock @@ -0,0 +1,16 @@ +{ + "version": "5", + "specifiers": { + "jsr:@needle-di/core@^1.1.1": "1.1.1" + }, + "jsr": { + "@needle-di/core@1.1.1": { + "integrity": "e1e746a82f7a104f05db43ebbc6cc8457b56b67f7b5d7b746c0aa5fb9c838317" + } + }, + "workspace": { + "dependencies": [ + "jsr:@needle-di/core@^1.1.1" + ] + } +} diff --git a/package.json b/package.json deleted file mode 100644 index 8fa0301..0000000 --- a/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "synthkernel", - "private": true, - "description": "A type-safe and composable meta architecture for modular monolith development.", - "keywords": [], - "license": "CC BY-SA 4.0", - "author": "", - "type": "module", - "scripts": { - "lint": "oxlint --type-aware --fix && oxfmt", - "check": "tsc && oxfmt --check && oxlint --type-aware" - }, - "dependencies": { - "@needle-di/core": "^1.1.0" - }, - "devDependencies": { - "@types/node": "^25.2.3", - "oxfmt": "^0.32.0", - "oxlint": "^1.47.0", - "oxlint-tsgolint": "^0.13.0", - "typescript": "^5.9.3" - }, - "packageManager": "pnpm@10.28.0" -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 468388b..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,528 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@needle-di/core': - specifier: ^1.1.0 - version: 1.1.0 - devDependencies: - '@types/node': - specifier: ^25.2.3 - version: 25.2.3 - oxfmt: - specifier: ^0.32.0 - version: 0.32.0 - oxlint: - specifier: ^1.47.0 - version: 1.47.0(oxlint-tsgolint@0.13.0) - oxlint-tsgolint: - specifier: ^0.13.0 - version: 0.13.0 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - -packages: - - '@needle-di/core@1.1.0': - resolution: {integrity: sha512-2naqWoNdHjmO5sOo8i3B8aqTPOPP3RUSjPofhxPBbmqBUTwQEZWEcUJF8Ib4YU7IbcKqDZnrtGLw2OcnhZi2/A==} - - '@oxfmt/binding-android-arm-eabi@0.32.0': - resolution: {integrity: sha512-DpVyuVzgLH6/MvuB/YD3vXO9CN/o9EdRpA0zXwe/tagP6yfVSFkFWkPqTROdqp0mlzLH5Yl+/m+hOrcM601EbA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - - '@oxfmt/binding-android-arm64@0.32.0': - resolution: {integrity: sha512-w1cmNXf9zs0vKLuNgyUF3hZ9VUAS1hBmQGndYJv1OmcVqStBtRTRNxSWkWM0TMkrA9UbvIvM9gfN+ib4Wy6lkQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@oxfmt/binding-darwin-arm64@0.32.0': - resolution: {integrity: sha512-m6wQojz/hn94XdZugFPtdFbOvXbOSYEqPsR2gyLyID3BvcrC2QsJyT1o3gb4BZEGtZrG1NiKVGwDRLM0dHd2mg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@oxfmt/binding-darwin-x64@0.32.0': - resolution: {integrity: sha512-hN966Uh6r3Erkg2MvRcrJWaB6QpBzP15rxWK/QtkUyD47eItJLsAQ2Hrm88zMIpFZ3COXZLuN3hqgSlUtvB0Xw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@oxfmt/binding-freebsd-x64@0.32.0': - resolution: {integrity: sha512-g5UZPGt8tJj263OfSiDGdS54HPa0KgFfspLVAUivVSdoOgsk6DkwVS9nO16xQTDztzBPGxTvrby8WuufF0g86Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@oxfmt/binding-linux-arm-gnueabihf@0.32.0': - resolution: {integrity: sha512-F4ZY83/PVQo9ZJhtzoMqbmjqEyTVEZjbaw4x1RhzdfUhddB41ZB2Vrt4eZi7b4a4TP85gjPRHgQBeO0c1jbtaw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxfmt/binding-linux-arm-musleabihf@0.32.0': - resolution: {integrity: sha512-olR37eG16Lzdj9OBSvuoT5RxzgM5xfQEHm1OEjB3M7Wm4KWa5TDWIT13Aiy74GvAN77Hq1+kUKcGVJ/0ynf75g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxfmt/binding-linux-arm64-gnu@0.32.0': - resolution: {integrity: sha512-eZhk6AIjRCDeLoXYBhMW7qq/R1YyVi+tGnGfc3kp7AZQrMsFaWtP/bgdCJCTNXMpbMwymtVz0qhSQvR5w2sKcg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@oxfmt/binding-linux-arm64-musl@0.32.0': - resolution: {integrity: sha512-UYiqO9MlipntFbdbUKOIo84vuyzrK4TVIs7Etat91WNMFSW54F6OnHq08xa5ZM+K9+cyYMgQPXvYCopuP+LyKw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@oxfmt/binding-linux-ppc64-gnu@0.32.0': - resolution: {integrity: sha512-IDH/fxMv+HmKsMtsjEbXqhScCKDIYp38sgGEcn0QKeXMxrda67PPZA7HMfoUwEtFUG+jsO1XJxTrQsL+kQ90xQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - - '@oxfmt/binding-linux-riscv64-gnu@0.32.0': - resolution: {integrity: sha512-bQFGPDa0buYWJFeK2I7ah8wRZjrAgamaG2OAGv+Ua5UMYEnHxmHcv+r8lWUUrwP2oqQGvp1SB8JIVtBbYuAueQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - - '@oxfmt/binding-linux-riscv64-musl@0.32.0': - resolution: {integrity: sha512-3vFp9DW1ItEKWltADzCFqG5N7rYFToT4ztlhg8wALoo2E2VhveLD88uAF4FF9AxD9NhgHDGmPCV+WZl/Qlj8cQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - - '@oxfmt/binding-linux-s390x-gnu@0.32.0': - resolution: {integrity: sha512-Fub2y8S9ImuPzAzpbgkoz/EVTWFFBolxFZYCMRhRZc8cJZI2gl/NlZswqhvJd/U0Jopnwgm/OJ2x128vVzFFWA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - - '@oxfmt/binding-linux-x64-gnu@0.32.0': - resolution: {integrity: sha512-XufwsnV3BF81zO2ofZvhT4FFaMmLTzZEZnC9HpFz/quPeg9C948+kbLlZnsfjmp+1dUxKMCpfmRMqOfF4AOLsA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@oxfmt/binding-linux-x64-musl@0.32.0': - resolution: {integrity: sha512-u2f9tC2qYfikKmA2uGpnEJgManwmk0ZXWs5BB4ga4KDu2JNLdA3i634DGHeMLK9wY9+iRf3t7IYpgN3OVFrvDw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@oxfmt/binding-openharmony-arm64@0.32.0': - resolution: {integrity: sha512-5ZXb1wrdbZ1YFXuNXNUCePLlmLDy4sUt4evvzD4Cgumbup5wJgS9PIe5BOaLywUg9f1wTH6lwltj3oT7dFpIGA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@oxfmt/binding-win32-arm64-msvc@0.32.0': - resolution: {integrity: sha512-IGSMm/Agq+IA0++aeAV/AGPfjcBdjrsajB5YpM3j7cMcwoYgUTi/k2YwAmsHH3ueZUE98pSM/Ise2J7HtyRjOA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@oxfmt/binding-win32-ia32-msvc@0.32.0': - resolution: {integrity: sha512-H/9gsuqXmceWMsVoCPZhtJG2jLbnBeKr7xAXm2zuKpxLVF7/2n0eh7ocOLB6t+L1ARE76iORuUsRMnuGjj8FjQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - - '@oxfmt/binding-win32-x64-msvc@0.32.0': - resolution: {integrity: sha512-fF8VIOeligq+mA6KfKvWtFRXbf0EFy73TdR6ZnNejdJRM8VWN1e3QFhYgIwD7O8jBrQsd7EJbUpkAr/YlUOokg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@oxlint-tsgolint/darwin-arm64@0.13.0': - resolution: {integrity: sha512-OWQ3U+oDjjupmX0WU9oYyKF2iUOKDMLW/+zan0cd0vYIGId80xTRHHA8oXnREmK8dsMMP3nV3VXME3NH/hS0lw==} - cpu: [arm64] - os: [darwin] - - '@oxlint-tsgolint/darwin-x64@0.13.0': - resolution: {integrity: sha512-wZvgj+eVqNkCUjSq2ExlMdbGDpZfaw6J+YctQV1pkGFdn7Y9cySWdfwu5v/AW2JPsJbFMXJ8GAr+WoZbRapz2A==} - cpu: [x64] - os: [darwin] - - '@oxlint-tsgolint/linux-arm64@0.13.0': - resolution: {integrity: sha512-nwtf5BgHbAWSVwyIF00l6QpfyFcpDMp6D+3cpe6NTgBYMSSSC0Ip1gswUwzVccOPoQK48t+J6vHyURQ96M1KDg==} - cpu: [arm64] - os: [linux] - - '@oxlint-tsgolint/linux-x64@0.13.0': - resolution: {integrity: sha512-Rkzgj38eVoGSBuGDaCrALS4FM19+m1Qlv0hjB4MWvXUej014XkB5ze+svYE3HX+AAm1ey9QYj/CQzfz203FPIg==} - cpu: [x64] - os: [linux] - - '@oxlint-tsgolint/win32-arm64@0.13.0': - resolution: {integrity: sha512-Y+0hFqLT5M7UIvGvTR3QFK27l17FqXk6UwwpBFOcyBGJ5bLd1RaAPWjqTmcgPvdolA6FCMeW1pxZuNtKDlYd7A==} - cpu: [arm64] - os: [win32] - - '@oxlint-tsgolint/win32-x64@0.13.0': - resolution: {integrity: sha512-mXjTttzyyfl8d/XvxggmZFBq0pbQmRvHbjQEv70YECNaLEHG8j8WYUwLa641uudAnV1VoBI34pc7bmgJM7qhOA==} - cpu: [x64] - os: [win32] - - '@oxlint/binding-android-arm-eabi@1.47.0': - resolution: {integrity: sha512-UHqo3te9K/fh29brCuQdHjN+kfpIi9cnTPABuD5S9wb9ykXYRGTOOMVuSV/CK43sOhU4wwb2nT1RVjcbrrQjFw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [android] - - '@oxlint/binding-android-arm64@1.47.0': - resolution: {integrity: sha512-xh02lsTF1TAkR+SZrRMYHR/xCx8Wg2MAHxJNdHVpAKELh9/yE9h4LJeqAOBbIb3YYn8o/D97U9VmkvkfJfrHfw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@oxlint/binding-darwin-arm64@1.47.0': - resolution: {integrity: sha512-OSOfNJqabOYbkyQDGT5pdoL+05qgyrmlQrvtCO58M4iKGEQ/xf3XkkKj7ws+hO+k8Y4VF4zGlBsJlwqy7qBcHA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@oxlint/binding-darwin-x64@1.47.0': - resolution: {integrity: sha512-hP2bOI4IWNS+F6pVXWtRshSTuJ1qCRZgDgVUg6EBUqsRy+ExkEPJkx+YmIuxgdCduYK1LKptLNFuQLJP8voPbQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@oxlint/binding-freebsd-x64@1.47.0': - resolution: {integrity: sha512-F55jIEH5xmGu7S661Uho8vGiLFk0bY3A/g4J8CTKiLJnYu/PSMZ2WxFoy5Hji6qvFuujrrM9Q8XXbMO0fKOYPg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@oxlint/binding-linux-arm-gnueabihf@1.47.0': - resolution: {integrity: sha512-wxmOn/wns/WKPXUC1fo5mu9pMZPVOu8hsynaVDrgmmXMdHKS7on6bA5cPauFFN9tJXNdsjW26AK9lpfu3IfHBQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxlint/binding-linux-arm-musleabihf@1.47.0': - resolution: {integrity: sha512-KJTmVIA/GqRlM2K+ZROH30VMdydEU7bDTY35fNg3tOPzQRIs2deLZlY/9JWwdWo1F/9mIYmpbdCmPqtKhWNOPg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@oxlint/binding-linux-arm64-gnu@1.47.0': - resolution: {integrity: sha512-PF7ELcFg1GVlS0X0ZB6aWiXobjLrAKer3T8YEkwIoO8RwWiAMkL3n3gbleg895BuZkHVlJ2kPRUwfrhHrVkD1A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@oxlint/binding-linux-arm64-musl@1.47.0': - resolution: {integrity: sha512-4BezLRO5cu0asf0Jp1gkrnn2OHiXrPPPEfBTxq1k5/yJ2zdGGTmZxHD2KF2voR23wb8Elyu3iQawXo7wvIZq0Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@oxlint/binding-linux-ppc64-gnu@1.47.0': - resolution: {integrity: sha512-aI5ds9jq2CPDOvjeapiIj48T/vlWp+f4prkxs+FVzrmVN9BWIj0eqeJ/hV8WgXg79HVMIz9PU6deI2ki09bR1w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - - '@oxlint/binding-linux-riscv64-gnu@1.47.0': - resolution: {integrity: sha512-mO7ycp9Elvgt5EdGkQHCwJA6878xvo9tk+vlMfT1qg++UjvOMB8INsOCQIOH2IKErF/8/P21LULkdIrocMw9xA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - - '@oxlint/binding-linux-riscv64-musl@1.47.0': - resolution: {integrity: sha512-24D0wsYT/7hDFn3Ow32m3/+QT/1ZwrUhShx4/wRDAmz11GQHOZ1k+/HBuK/MflebdnalmXWITcPEy4BWTi7TCA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [riscv64] - os: [linux] - - '@oxlint/binding-linux-s390x-gnu@1.47.0': - resolution: {integrity: sha512-8tPzPne882mtML/uy3mApvdCyuVOpthJ7xUv3b67gVfz63hOOM/bwO0cysSkPyYYFDFRn6/FnUb7Jhmsesntvg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - - '@oxlint/binding-linux-x64-gnu@1.47.0': - resolution: {integrity: sha512-q58pIyGIzeffEBhEgbRxLFHmHfV9m7g1RnkLiahQuEvyjKNiJcvdHOwKH2BdgZxdzc99Cs6hF5xTa86X40WzPw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@oxlint/binding-linux-x64-musl@1.47.0': - resolution: {integrity: sha512-e7DiLZtETZUCwTa4EEHg9G+7g3pY+afCWXvSeMG7m0TQ29UHHxMARPaEQUE4mfKgSqIWnJaUk2iZzRPMRdga5g==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@oxlint/binding-openharmony-arm64@1.47.0': - resolution: {integrity: sha512-3AFPfQ0WKMleT/bKd7zsks3xoawtZA6E/wKf0DjwysH7wUiMMJkNKXOzYq1R/00G98JFgSU1AkrlOQrSdNNhlg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@oxlint/binding-win32-arm64-msvc@1.47.0': - resolution: {integrity: sha512-cLMVVM6TBxp+N7FldQJ2GQnkcLYEPGgiuEaXdvhgvSgODBk9ov3jed+khIXSAWtnFOW0wOnG3RjwqPh0rCuheA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@oxlint/binding-win32-ia32-msvc@1.47.0': - resolution: {integrity: sha512-VpFOSzvTnld77/Edje3ZdHgZWnlTb5nVWXyTgjD3/DKF/6t5bRRbwn3z77zOdnGy44xAMvbyAwDNOSeOdVUmRA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - - '@oxlint/binding-win32-x64-msvc@1.47.0': - resolution: {integrity: sha512-+q8IWptxXx2HMTM6JluR67284t0h8X/oHJgqpxH1siowxPMqZeIpAcWCUq+tY+Rv2iQK8TUugjZnSBQAVV5CmA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@types/node@25.2.3': - resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} - - oxfmt@0.32.0: - resolution: {integrity: sha512-KArQhGzt/Y8M1eSAX98Y8DLtGYYDQhkR55THUPY5VNcpFQ+9nRZkL3ULXhagHMD2hIvjy8JSeEQEP5/yYJSrLA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - oxlint-tsgolint@0.13.0: - resolution: {integrity: sha512-VUOWP5T9R9RwuPLKvNgvhsjdPFVhr2k8no8ea84+KhDtYPmk9L/3StNP3WClyPOKJOT8bFlO3eyhTKxXK9+Oog==} - hasBin: true - - oxlint@1.47.0: - resolution: {integrity: sha512-v7xkK1iv1qdvTxJGclM97QzN8hHs5816AneFAQ0NGji1BMUquhiDAhXpMwp8+ls16uRVJtzVHxP9pAAXblDeGA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - oxlint-tsgolint: '>=0.11.2' - peerDependenciesMeta: - oxlint-tsgolint: - optional: true - - tinypool@2.1.0: - resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} - engines: {node: ^20.0.0 || >=22.0.0} - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - -snapshots: - - '@needle-di/core@1.1.0': {} - - '@oxfmt/binding-android-arm-eabi@0.32.0': - optional: true - - '@oxfmt/binding-android-arm64@0.32.0': - optional: true - - '@oxfmt/binding-darwin-arm64@0.32.0': - optional: true - - '@oxfmt/binding-darwin-x64@0.32.0': - optional: true - - '@oxfmt/binding-freebsd-x64@0.32.0': - optional: true - - '@oxfmt/binding-linux-arm-gnueabihf@0.32.0': - optional: true - - '@oxfmt/binding-linux-arm-musleabihf@0.32.0': - optional: true - - '@oxfmt/binding-linux-arm64-gnu@0.32.0': - optional: true - - '@oxfmt/binding-linux-arm64-musl@0.32.0': - optional: true - - '@oxfmt/binding-linux-ppc64-gnu@0.32.0': - optional: true - - '@oxfmt/binding-linux-riscv64-gnu@0.32.0': - optional: true - - '@oxfmt/binding-linux-riscv64-musl@0.32.0': - optional: true - - '@oxfmt/binding-linux-s390x-gnu@0.32.0': - optional: true - - '@oxfmt/binding-linux-x64-gnu@0.32.0': - optional: true - - '@oxfmt/binding-linux-x64-musl@0.32.0': - optional: true - - '@oxfmt/binding-openharmony-arm64@0.32.0': - optional: true - - '@oxfmt/binding-win32-arm64-msvc@0.32.0': - optional: true - - '@oxfmt/binding-win32-ia32-msvc@0.32.0': - optional: true - - '@oxfmt/binding-win32-x64-msvc@0.32.0': - optional: true - - '@oxlint-tsgolint/darwin-arm64@0.13.0': - optional: true - - '@oxlint-tsgolint/darwin-x64@0.13.0': - optional: true - - '@oxlint-tsgolint/linux-arm64@0.13.0': - optional: true - - '@oxlint-tsgolint/linux-x64@0.13.0': - optional: true - - '@oxlint-tsgolint/win32-arm64@0.13.0': - optional: true - - '@oxlint-tsgolint/win32-x64@0.13.0': - optional: true - - '@oxlint/binding-android-arm-eabi@1.47.0': - optional: true - - '@oxlint/binding-android-arm64@1.47.0': - optional: true - - '@oxlint/binding-darwin-arm64@1.47.0': - optional: true - - '@oxlint/binding-darwin-x64@1.47.0': - optional: true - - '@oxlint/binding-freebsd-x64@1.47.0': - optional: true - - '@oxlint/binding-linux-arm-gnueabihf@1.47.0': - optional: true - - '@oxlint/binding-linux-arm-musleabihf@1.47.0': - optional: true - - '@oxlint/binding-linux-arm64-gnu@1.47.0': - optional: true - - '@oxlint/binding-linux-arm64-musl@1.47.0': - optional: true - - '@oxlint/binding-linux-ppc64-gnu@1.47.0': - optional: true - - '@oxlint/binding-linux-riscv64-gnu@1.47.0': - optional: true - - '@oxlint/binding-linux-riscv64-musl@1.47.0': - optional: true - - '@oxlint/binding-linux-s390x-gnu@1.47.0': - optional: true - - '@oxlint/binding-linux-x64-gnu@1.47.0': - optional: true - - '@oxlint/binding-linux-x64-musl@1.47.0': - optional: true - - '@oxlint/binding-openharmony-arm64@1.47.0': - optional: true - - '@oxlint/binding-win32-arm64-msvc@1.47.0': - optional: true - - '@oxlint/binding-win32-ia32-msvc@1.47.0': - optional: true - - '@oxlint/binding-win32-x64-msvc@1.47.0': - optional: true - - '@types/node@25.2.3': - dependencies: - undici-types: 7.16.0 - - oxfmt@0.32.0: - dependencies: - tinypool: 2.1.0 - optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.32.0 - '@oxfmt/binding-android-arm64': 0.32.0 - '@oxfmt/binding-darwin-arm64': 0.32.0 - '@oxfmt/binding-darwin-x64': 0.32.0 - '@oxfmt/binding-freebsd-x64': 0.32.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.32.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.32.0 - '@oxfmt/binding-linux-arm64-gnu': 0.32.0 - '@oxfmt/binding-linux-arm64-musl': 0.32.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.32.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.32.0 - '@oxfmt/binding-linux-riscv64-musl': 0.32.0 - '@oxfmt/binding-linux-s390x-gnu': 0.32.0 - '@oxfmt/binding-linux-x64-gnu': 0.32.0 - '@oxfmt/binding-linux-x64-musl': 0.32.0 - '@oxfmt/binding-openharmony-arm64': 0.32.0 - '@oxfmt/binding-win32-arm64-msvc': 0.32.0 - '@oxfmt/binding-win32-ia32-msvc': 0.32.0 - '@oxfmt/binding-win32-x64-msvc': 0.32.0 - - oxlint-tsgolint@0.13.0: - optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.13.0 - '@oxlint-tsgolint/darwin-x64': 0.13.0 - '@oxlint-tsgolint/linux-arm64': 0.13.0 - '@oxlint-tsgolint/linux-x64': 0.13.0 - '@oxlint-tsgolint/win32-arm64': 0.13.0 - '@oxlint-tsgolint/win32-x64': 0.13.0 - - oxlint@1.47.0(oxlint-tsgolint@0.13.0): - optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.47.0 - '@oxlint/binding-android-arm64': 1.47.0 - '@oxlint/binding-darwin-arm64': 1.47.0 - '@oxlint/binding-darwin-x64': 1.47.0 - '@oxlint/binding-freebsd-x64': 1.47.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.47.0 - '@oxlint/binding-linux-arm-musleabihf': 1.47.0 - '@oxlint/binding-linux-arm64-gnu': 1.47.0 - '@oxlint/binding-linux-arm64-musl': 1.47.0 - '@oxlint/binding-linux-ppc64-gnu': 1.47.0 - '@oxlint/binding-linux-riscv64-gnu': 1.47.0 - '@oxlint/binding-linux-riscv64-musl': 1.47.0 - '@oxlint/binding-linux-s390x-gnu': 1.47.0 - '@oxlint/binding-linux-x64-gnu': 1.47.0 - '@oxlint/binding-linux-x64-musl': 1.47.0 - '@oxlint/binding-openharmony-arm64': 1.47.0 - '@oxlint/binding-win32-arm64-msvc': 1.47.0 - '@oxlint/binding-win32-ia32-msvc': 1.47.0 - '@oxlint/binding-win32-x64-msvc': 1.47.0 - oxlint-tsgolint: 0.13.0 - - tinypool@2.1.0: {} - - typescript@5.9.3: {} - - undici-types@7.16.0: {} diff --git a/shell.nix b/shell.nix index 444a9f5..8dd5fa2 100644 --- a/shell.nix +++ b/shell.nix @@ -3,31 +3,14 @@ let in pkgs.mkShell { buildInputs = with pkgs; [ - nodePackages_latest.nodejs + deno python3 jupyter ]; shellHook = '' - echo "🚀 Loading TypeScript Jupyter Environment (npm local-global)..." - - # Define local global directory - export NPM_GLOBAL_DIR="$PWD/.npm-global" - - # Configure npm for this session only - npm config set prefix "$NPM_GLOBAL_DIR" - - # Add binaries to PATH - export PATH="$NPM_GLOBAL_DIR/bin:$PATH" - - # Install tslab if missing - if ! command -v tslab &> /dev/null; then - echo "📦 Installing tslab via npm (local global)..." - npm install -g tslab - fi - - echo "🔗 Registering TypeScript kernel..." - tslab install + echo "🔗 Registering Deno kernel..." + deno jupyter echo "✅ Environment ready!" ''; diff --git a/skill/example/AGENTS.md b/skill/example/AGENTS.md deleted file mode 100644 index 799168a..0000000 --- a/skill/example/AGENTS.md +++ /dev/null @@ -1,16 +0,0 @@ -## Project Architecture - -This project uses SynthKernel architecture. Typical practice consists a module loader class and module classes: - -- The module loader class manages module lifecycles, orchestrates types, and behaves as an facade at of your app logic. -- All module classes extend a `BaseModule` class, they define APIs, register lifecycle hooks, execute actual logic, augment the loader class and wire each other via dependency injection. -- Types are resolved via generics orchestration. -- Modules are composed to the loader to form an APP. - -**Structure**: - -```text -loader: PolisAlert Loader - manages module lifecycles, orchestrates types, exposes augmented API -- CoreLogging: Provides centralized logging and audit trail functionality -- AlertDispatch: Handles alert validation and transmission, depends on CoreLogging via DI -``` diff --git a/skill/example/AlertDispatch.ts b/skill/example/AlertDispatch.ts index 1f27351..5e0aac2 100644 --- a/skill/example/AlertDispatch.ts +++ b/skill/example/AlertDispatch.ts @@ -3,7 +3,7 @@ */ import type { BaseOptions } from './index.ts'; -import { BaseModule, type BaseArgs } from './BaseModule.ts'; +import { type BaseArgs, BaseModule } from './BaseModule.ts'; import { CoreLogging } from './CoreLogging.ts'; interface Options extends BaseOptions { @@ -48,14 +48,14 @@ export class AlertDispatch extends BaseModule { return false; } - await this.connectAlertService(message); + await this.connectAlertService(message); return true; }; - private connectAlertService = async (alert: string) => { - this.logging.log('INFO', `Dispatched: "${alert}"`); + private connectAlertService = async (alert: string) => { + this.logging.log('INFO', `Dispatched: "${alert}"`); - // Simulate async connection to alerting service, like an email api + // Simulate async connection to alerting service, like an email api await new Promise((resolve) => setTimeout(resolve, 10)); }; } diff --git a/skill/example/BaseModule.ts b/skill/example/BaseModule.ts index ca8d6fb..cca46e8 100644 --- a/skill/example/BaseModule.ts +++ b/skill/example/BaseModule.ts @@ -1,6 +1,11 @@ import type { Container } from '@needle-di/core'; import type { BaseOptions } from './index.ts'; -import type { General, GeneralObject, ModuleInput as MI, Orchestratable } from './types.ts'; +import type { + General, + GeneralObject, + ModuleInput as MI, + Orchestratable, +} from './types.ts'; import type { Hook } from './utilities.ts'; type ModuleInput = MI; @@ -8,9 +13,15 @@ export type GeneralModuleCtor = typeof BaseModule; export type BaseArgs = ConstructorParameters; export type Options = Orchestratable; -export type Augmentation = Orchestratable; +export type Augmentation = Orchestratable< + M, + '_Augmentation' +>; -export class BaseModule { +export class BaseModule< + O extends BaseOptions = BaseOptions, + A extends GeneralObject = {}, +> { declare private static readonly _BaseModuleBrand: unique symbol; declare _Augmentation: A; @@ -18,9 +29,9 @@ export class BaseModule void; + + container: Container; + augment: (aug: A) => void; constructor( container: Container, options: GeneralObject, @@ -28,10 +39,10 @@ export class BaseModule void, ) { - this.container = container; - this.augment = augment; - // we assign the above two lines for Node.js compatibility. If you have a TypeScript compiler you can remove them and simply write in the constructor: - // protected container: Container, + this.container = container; + this.augment = augment; + // we assign the above two lines for Node.js compatibility. If you have a TypeScript compiler you can remove them and simply write in the constructor: + // protected container: Container, // protected augment: (aug: A) => void, this.options = options as O; diff --git a/skill/example/CoreLogging.ts b/skill/example/CoreLogging.ts index f384280..a3068d7 100644 --- a/skill/example/CoreLogging.ts +++ b/skill/example/CoreLogging.ts @@ -3,7 +3,7 @@ */ import type { BaseOptions } from './index.ts'; -import { BaseModule, type BaseArgs } from './BaseModule.ts'; +import { type BaseArgs, BaseModule } from './BaseModule.ts'; // Helper to enforce hierarchy const LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 } as const; @@ -46,7 +46,6 @@ export class CoreLogging extends BaseModule { } log = (level: Level, message: string) => { - // IMPLEMENTED: Check log level hierarchy const currentLevel = LEVELS[level]; const minLevel = LEVELS[this.options.logLevel] ?? 0; if (currentLevel < minLevel) return; // Skip logging if below threshold @@ -60,6 +59,8 @@ export class CoreLogging extends BaseModule { this._logs.push(entry); // Always print if debug mode is forced in base options, otherwise respect level - if (this.options.debug || currentLevel >= minLevel) console.log(`[${level}] ${message}`); + if (this.options.debug || currentLevel >= minLevel) { + console.log(`[${level}] ${message}`); + } }; } diff --git a/skill/example/README.md b/skill/example/README.md index 5971c81..295b576 100644 --- a/skill/example/README.md +++ b/skill/example/README.md @@ -60,7 +60,5 @@ Modules subscribe to global events (`onStart`, `onDispose`) defined by the Loade ├── utilities.ts # Hook system implementation (makeHook) ├── CoreLogging.ts # Module: Handles logging state and audit trail ├── AlertDispatch.ts # Module: Handles validation and message dispatching -├── main.ts # Consumer: Example usage of PolisAlert -├── tsconfig.json # TypeScript config (requires allowImportingTsExtensions) -└── AGENTS.md # Architecture summary for AI agents +└── main.ts # Consumer: Example usage of PolisAlert ``` diff --git a/skill/example/index.ts b/skill/example/index.ts index bcf95e8..64c0b4c 100644 --- a/skill/example/index.ts +++ b/skill/example/index.ts @@ -11,7 +11,7 @@ */ import { Container } from '@needle-di/core'; -import type { GeneralModuleCtor, Options, Augmentation } from './BaseModule.ts'; +import type { Augmentation, GeneralModuleCtor, Options } from './BaseModule.ts'; import type { GeneralObject } from './types.ts'; import { AlertDispatch } from './AlertDispatch.ts'; import { CoreLogging } from './CoreLogging.ts'; diff --git a/skill/example/tsconfig.json b/skill/example/tsconfig.json deleted file mode 100644 index 364eb11..0000000 --- a/skill/example/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "allowImportingTsExtensions": true, - "lib": ["ESNext", "DOM", "DOM.Iterable"] - } -} diff --git a/skill/example/types.ts b/skill/example/types.ts index 454ee9d..f679e65 100644 --- a/skill/example/types.ts +++ b/skill/example/types.ts @@ -1,22 +1,26 @@ // #region [DO NOT MODIFY] SynthKernel Core Types -// oxlint-disable-next-line typescript/no-explicit-any +// deno-lint-ignore no-explicit-any export type General = any; export type GeneralArray = ReadonlyArray; export type GeneralObject = object; export type GeneralConstructor = new (...args: General[]) => General; -type UnionToIntersection = (U extends General ? (k: U) => void : never) extends ( - k: infer I, -) => void - ? I - : never; +type UnionToIntersection = + (U extends General ? (k: U) => void : never) extends ( + k: infer I, + ) => void ? I + : never; -type GeneralModuleInput = Array | Array; +type GeneralModuleInput = + | ReadonlyArray + | ReadonlyArray; -export type ModuleInput = Array | Array>; +export type ModuleInput = + | ReadonlyArray + | ReadonlyArray>; -type Instances = - T extends Array ? InstanceType : T[number]; +type Instances = T extends + ReadonlyArray ? InstanceType : T[number]; export type Orchestratable< T extends GeneralModuleInput, diff --git a/skill/example/utilities.ts b/skill/example/utilities.ts index c08f8f3..b47e2f1 100644 --- a/skill/example/utilities.ts +++ b/skill/example/utilities.ts @@ -13,17 +13,20 @@ export type Hook = { * Pass your arguments as the type parameter * @example const hook = makeHook(true) the hook will run in reverse order of subscription */ -export function makeHook(reverse: boolean = false) { +export function makeHook( + reverse: boolean = false, +) { const result: Hook = (...args: Args) => { if (reverse) { const items = Array.from(result.subs).reverse(); items.forEach((callback) => { callback(...args); }); - } else + } else { result.subs.forEach((callback) => { callback(...args); }); + } }; result.subs = new Set(); result.subscribe = (callback: MatchingFunc) => { diff --git a/skill/maintenance.md b/skill/maintenance.md index 0320718..161d830 100644 --- a/skill/maintenance.md +++ b/skill/maintenance.md @@ -38,46 +38,46 @@ To add a module, create a file named `(module name in PascalCase).ts`, and write import { BaseModule, type BaseOptions } from './index.ts'; // all orchestrations needed by this module that has base fields, change the path to the actual path where the loader exists // import all modules that will be used by this module by DI -import AnotherModule from './AnotherModule.ts' +import AnotherModule from './AnotherModule.ts'; // the types of orchestrations, must extend the base type if the orchestration has base fields, adjust according to your needs interface Options extends BaseOptions { - // all fields this module provides, adjust accordingly - option1: string; - option2?: boolean; + // all fields this module provides, adjust accordingly + option1: string; + option2?: boolean; } // the augmentation if the module needs, adjust the fields according to your needs interface Augmentation { - method: () => void; - property: boolean; + method: () => void; + property: boolean; } // change the module name to the intended one // pass type parameters in the correct order as defined in BaseModule // - if what you want to pass comes later, e.g., you only want to pass `Augmentation`, simply pass the base orchestration, like BaseModule export class Module extends BaseModule { - constructor(...args: BaseArgs) { - super(...args); + constructor(...args: BaseArgs) { + super(...args); - // if you need augmentation, you must call `this.augment` in your constructor and pass everything defined in your `Augmentation` interface. - this.augment({ - method: this.method, - property: this.property, - }); + // if you need augmentation, you must call `this.augment` in your constructor and pass everything defined in your `Augmentation` interface. + this.augment({ + method: this.method, + property: this.property, + }); - // subscribe to lifecycle hooks if needed, adjust accordingly - this.onStart(this.method); + // subscribe to lifecycle hooks if needed, adjust accordingly + this.onStart(this.method); - // use this.container.get() to inject a dependency - const dep = this.container.get(AnotherModule); + // use this.container.get() to inject a dependency + const dep = this.container.get(AnotherModule); - // ... freely implement your logic - } + // ... freely implement your logic + } - // ... write your logic, the module is your playground - method = () => {}; - property = false; + // ... write your logic, the module is your playground + method = () => {}; + property = false; } ``` diff --git a/skill/start.md b/skill/start.md index fc3b5e8..db7962a 100644 --- a/skill/start.md +++ b/skill/start.md @@ -10,9 +10,9 @@ Check your `tsconfig.json` which should contain following: ```json { - "compilerOptions": { - "allowImportingTsExtensions": true - } + "compilerOptions": { + "allowImportingTsExtensions": true + } } ``` @@ -62,18 +62,22 @@ export type GeneralArray = ReadonlyArray; export type GeneralObject = object; export type GeneralConstructor = new (...args: General[]) => General; -type UnionToIntersection = (U extends General ? (k: U) => void : never) extends ( - k: infer I, -) => void - ? I - : never; +type UnionToIntersection = + (U extends General ? (k: U) => void : never) extends ( + k: infer I, + ) => void ? I + : never; -type GeneralModuleInput = Array | Array; +type GeneralModuleInput = + | ReadonlyArray + | ReadonlyArray; -export type ModuleInput = Array | Array>; +export type ModuleInput = + | ReadonlyArray + | ReadonlyArray>; -type Instances = - T extends Array ? InstanceType : T[number]; +type Instances = T extends + ReadonlyArray ? InstanceType : T[number]; export type Orchestratable< T extends GeneralModuleInput, @@ -106,17 +110,20 @@ export type Hook = { * Pass your arguments as the type parameter * @example const hook = makeHook(true); // create a hook that runs subscriptions in reverse order */ -export function makeHook(reverse: boolean = false) { +export function makeHook( + reverse: boolean = false, +) { const result: Hook = (...args: Args) => { if (reverse) { const items = Array.from(result.subs).reverse(); items.forEach((callback) => { callback(...args); }); - } else + } else { result.subs.forEach((callback) => { callback(...args); }); + } }; result.subs = new Set(); result.subscribe = (callback: MatchingFunc) => { @@ -151,8 +158,8 @@ According to your answer to question 2, step 3, if your app needs orchestration // #region Base Orchestrations // example only, define according to your needs export interface BaseOptions { - load: 'lazy' | 'normal' | false; - container: HTMLElement; + load: 'lazy' | 'normal' | false; + container: HTMLElement; } // #endregion ``` @@ -163,7 +170,12 @@ Create `BaseModule.ts` per the file system convention and write following code, ```TypeScript import type { BaseOptions } from './index.ts'; // change or delete this to the path to `index.ts` (the loader) according to real base orchestration needs -import type { General, GeneralObject, ModuleInput as MI, Orchestratable } from './types.ts'; // change this to the real path of `types.ts` +import type { + General, + GeneralObject, + ModuleInput as MI, + Orchestratable, +} from './types.ts'; // change this to the real path of `types.ts` import type { Hook } from './utilities.ts'; // change this to the real hook type you are using (question 5) import type { Container } from '@needle-di/core'; // change or delete this according to your DI container needs (question) @@ -175,31 +187,37 @@ export type BaseArgs = ConstructorParameters; export type Options = Orchestratable; // add this or not depends on whether you need augmentation or not (question 4) -export type Augmentation = Orchestratable; +export type Augmentation = Orchestratable< + M, + '_Augmentation' +>; // add, change or delete type parameters according to your type orchestration and augmentation needs (question 2, 4) // with base orchestrations: (Generics) extends (your base orchestration) = (your base orchestration) // without base orchestrations or augmentation: (Generics) extends GeneralObject = {} -export class BaseModule { +export class BaseModule< + O extends BaseOptions = BaseOptions, + A extends GeneralObject = {}, +> { declare private static readonly _BaseModuleBrand: unique symbol; // nominal marker to ensure type safety - // if you need augmentation (question 4) + // if you need augmentation (question 4) declare _Augmentation: A; - // your orchestration fields (question 2) - // orchestrated field needs runtime access: (field name): (Generics) - // only type-level orchestration: declare (field name): (Generics) - options: O; + // your orchestration fields (question 2) + // orchestrated field needs runtime access: (field name): (Generics) + // only type-level orchestration: declare (field name): (Generics) + options: O; - // your lifecycle hooks, add, modify or delete according to your needs (question 3) + // your lifecycle hooks, add, modify or delete according to your needs (question 3) onStart: Hook['subscribe']; onRestart: Hook['subscribe']; onDispose: Hook['subscribe']; - // constructor parameters, which should include: - // - DI container if you need (question 1) - // - all lifecycle hooks if you need (question 3) - // - special augmentation function: `(aug: (Augmentation Generics)) => void` if you need (question 2) + // constructor parameters, which should include: + // - DI container if you need (question 1) + // - all lifecycle hooks if you need (question 3) + // - special augmentation function: `(aug: (Augmentation Generics)) => void` if you need (question 2) constructor( protected container: Container, options: GeneralObject, @@ -208,10 +226,10 @@ export class BaseModule void, ) { - // orchestration assignments (question 2) + // orchestration assignments (question 2) this.options = options as O; - // lifecycle hooks assignments (question 3), adjust according to the real hook subscribe function according to your needs + // lifecycle hooks assignments (question 3), adjust according to the real hook subscribe function according to your needs this.onStart = onStart.subscribe; this.onDispose = onDispose.subscribe; this.onRestart = onRestart.subscribe; diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index f26cae0..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "verbatimModuleSyntax": true, - "allowImportingTsExtensions": true, - "lib": ["ESNext", "DOM", "DOM.Iterable"] - }, - "include": ["skill/**/*", "whitepaper.ipynb"], - "exclude": ["./node_modules"] -} diff --git a/whitepaper.ipynb b/whitepaper.ipynb index 4eb3c2c..0ab95fb 100644 --- a/whitepaper.ipynb +++ b/whitepaper.ipynb @@ -9,7 +9,7 @@ "\n", "SynthKernel is a software architecture in TypeScript that enforces type safety, modularity and composability as well as making the APP easily extensible. It's rather a philosophy, a methodology and a set of standards than a bunch of static code.\n", "\n", - "SynthKernel leverages strengths in *Object-Oriented Programming*, *TypeScript Type System and Dynamic Nature*, *Facade Pattern*, and *Inversion of Control Principle*. It is born to fundamentally solve the problem of software unmaintainability by enforcing strict naming, file system, separation of concern and module composition standards without stifling logic flexibility. It also provides detailed patterns and templates on how to architect an application.\n", + "SynthKernel leverages strengths in _Object-Oriented Programming_, _TypeScript Type System and its Dynamic Nature_, _Facade Pattern_, and _Inversion of Control Principle_. It is born to fundamentally solve the problem of software unmaintainability by enforcing strict naming, file system, separation of concern and module composition standards without stifling logic flexibility. It also provides detailed patterns and templates on how to architect an application.\n", "\n", "## Architecture Overview\n", "\n", @@ -67,7 +67,7 @@ "\n", "Modules need a starting template so that they can implement their functions freely. A base module, often in companion with a loader, is inherited by all modules of the loader. The module should do some fixed tasks like receive DI container and lifecycle hooks passed from the loader.\n", "\n", - "More importantly, base modules serve as interfaces of type orchestration and augmentation declaration. And it also takes the role of type re-interpretation for some methods & properties of orchestrated types. This part will be elaborated later in [Type Orchestration](#type-orchestration) and [Augmentation](#augmentation).\n", + "More importantly, base modules serve as interfaces of type orchestration and augmentation declaration. And it also takes the role of type re-interpretation for some methods & properties of orchestrated types. This part will be elaborated below in [Type Orchestration](#type-orchestration) and [Augmentation](#augmentation).\n", "\n", "### Type Orchestration\n", "\n", @@ -79,25 +79,25 @@ "\n", "```TypeScript\n", "interface Config {\n", - " a?: number,\n", - "};\n", + "\ta?: number;\n", + "}\n", "```\n", "\n", "And module B has config like this:\n", "\n", "```TypeScript\n", "interface Config {\n", - " b: boolean,\n", - "};\n", + "\tb: boolean;\n", + "}\n", "```\n", "\n", "The final config will be:\n", "\n", "```TypeScript\n", "interface Config {\n", - " a?: number,\n", - " b: boolean,\n", - "};\n", + "\ta?: number;\n", + "\tb: boolean;\n", + "}\n", "```\n", "\n", "Moreover, it's common for the loader to pass some methods or properties that needs type mapping to the modules. For example, the loader receives the user defined configuration and then pass it to the modules. It cannot pass the config in exact type since the it is defined in generics. This creates a problem that the modules cannot receive the precise type even it they defined it themselves. To solve this, the type is intercepted by the base module, it augments the type using the module's type declaration. Then everything become accurately typed while is still modular and pluggable.\n", @@ -142,12 +142,27 @@ "1. `CoreLogging`: the logging module that provides configurable and classified logging capability.\n", "2. `AlertDispatch`: the alerting module that validates alerts and dispatches them to external services, like an email API.\n", "\n", + "The architecture of `PolisAlert` can be show as a very simple tree:\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " loader[PolisAlert Loader]\n", + " mod1[CoreLogging Module]\n", + " mod2[AlertDispatch Module]\n", + "\n", + " %% Connections\n", + " loader --> mod1\n", + " loader --> mod2\n", + "```\n", + "\n", + "### Let's Code\n", + "\n", "Firstly, we need to import what we will use next. All we need is a dependency injection library, here we choose a super lightweight solution called `@needle-di/core`." ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 14, "id": "e1f6ade0", "metadata": {}, "outputs": [ @@ -160,9 +175,9 @@ } ], "source": [ - "import { Container } from \"@needle-di/core\";\n", + "import { Container } from '@needle-di/core';\n", "\n", - "console.log(\"Needle DI imported!\");" + "console.log('Needle DI imported!');" ] }, { @@ -175,7 +190,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 15, "id": "7a728bf6", "metadata": {}, "outputs": [ @@ -193,18 +208,22 @@ "type GeneralObject = object;\n", "type GeneralConstructor = new (...args: General[]) => General;\n", "\n", - "type UnionToIntersection = (U extends General ? (k: U) => void : never) extends (\n", - "\tk: infer I,\n", - ") => void\n", - "\t? I\n", - "\t: never;\n", + "type UnionToIntersection =\n", + "\t(U extends General ? (k: U) => void : never) extends (\n", + "\t\tk: infer I,\n", + "\t) => void ? I\n", + "\t\t: never;\n", "\n", - "type GeneralModuleInput = Array | Array;\n", + "type GeneralModuleInput =\n", + "\t| ReadonlyArray\n", + "\t| ReadonlyArray;\n", "\n", - "type ModuleInput = Array | Array>;\n", + "type ModuleInput =\n", + "\t| ReadonlyArray\n", + "\t| ReadonlyArray>;\n", "\n", - "type Instances =\n", - "\tT extends Array ? InstanceType : T[number];\n", + "type Instances = T extends\n", + "\tReadonlyArray ? InstanceType : T[number];\n", "\n", "type Orchestratable<\n", "\tT extends GeneralModuleInput,\n", @@ -226,7 +245,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 16, "id": "4c683ba6", "metadata": {}, "outputs": [ @@ -239,18 +258,18 @@ } ], "source": [ - "type Object1 = {\n", - " orchestrate: {\n", - " a: string;\n", - " b: number;\n", - " }\n", + "type Object1 = {\n", + "\torchestrate: {\n", + "\t\ta: string;\n", + "\t\tb: number;\n", + "\t};\n", "};\n", "\n", "type Object2 = {\n", - " orchestrate: {\n", - " c?: boolean;\n", - " d: () => void;\n", - " }\n", + "\torchestrate: {\n", + "\t\tc?: boolean;\n", + "\t\td: () => void;\n", + "\t};\n", "};\n", "\n", "type Orchestrated = Orchestratable<[Object1, Object2], 'orchestrate'>;\n", @@ -265,7 +284,7 @@ "}\n", "*/\n", "\n", - "console.log('Actually running this cell has no real effect.')" + "console.log('Actually running this cell has no real effect.');" ] }, { @@ -273,12 +292,12 @@ "id": "6dedcbc1", "metadata": {}, "source": [ - "We also need a tiny utility function to make a hook that can be subscribed to:" + "We also need a tiny utility function to make a hook that can be subscribed to. This function creates another function with properties that store and manipulate the subscription list." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 17, "id": "2c93146c", "metadata": {}, "outputs": [ @@ -311,10 +330,11 @@ "\t\t\titems.forEach((callback) => {\n", "\t\t\t\tcallback(...args);\n", "\t\t\t});\n", - "\t\t} else\n", + "\t\t} else {\n", "\t\t\tresult.subs.forEach((callback) => {\n", "\t\t\t\tcallback(...args);\n", "\t\t\t});\n", + "\t\t}\n", "\t};\n", "\tresult.subs = new Set();\n", "\tresult.subscribe = (callback: MatchingFunc) => {\n", @@ -326,7 +346,7 @@ "\treturn result;\n", "}\n", "\n", - "console.log('Hook maker loaded!')" + "console.log('Hook maker loaded!');" ] }, { @@ -342,7 +362,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 18, "id": "9942803e", "metadata": {}, "outputs": [ @@ -360,7 +380,7 @@ "\tdebug?: boolean;\n", "}\n", "\n", - "console.log('Base options declared!')" + "console.log('Base options declared!');" ] }, { @@ -376,7 +396,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 19, "id": "b6d9dcf7", "metadata": {}, "outputs": [ @@ -389,7 +409,10 @@ } ], "source": [ - "class BaseModule {\n", + "class BaseModule<\n", + "\tO extends BaseOptions = BaseOptions,\n", + "\tA extends GeneralObject = {},\n", + "> {\n", "\tdeclare private static readonly _BaseModuleBrand: unique symbol; // this is a hint fot TypeScript to only identify objects that truly extends BaseModule can pass the type check\n", "\tdeclare _Augmentation: A;\n", "\n", @@ -397,9 +420,9 @@ "\n", "\tonStart: Hook['subscribe'];\n", "\tonDispose: Hook['subscribe'];\n", - " \n", - " container: Container;\n", - " augment: (aug: A) => void;\n", + "\n", + "\tcontainer: Container;\n", + "\taugment: (aug: A) => void;\n", "\tconstructor(\n", "\t\tcontainer: Container,\n", "\t\toptions: GeneralObject,\n", @@ -407,10 +430,10 @@ "\t\tonDispose: Hook,\n", "\t\taugment: (aug: A) => void,\n", "\t) {\n", - " this.container = container;\n", - " this.augment = augment;\n", - " // we assign the above two lines for Node.js compatibility. If you have a TypeScript compiler you can remove them and simply write in the constructor:\n", - " // protected container: Container,\n", + "\t\tthis.container = container;\n", + "\t\tthis.augment = augment;\n", + "\t\t// we assign the above two lines for Node.js compatibility. If you have a TypeScript compiler you can remove them and simply write in the constructor parameters:\n", + "\t\t// protected container: Container,\n", "\t\t// protected augment: (aug: A) => void,\n", "\n", "\t\tthis.options = options as O;\n", @@ -420,7 +443,7 @@ "\t}\n", "}\n", "\n", - "console.log('Base module loaded!')" + "console.log('Base module loaded!');" ] }, { @@ -436,7 +459,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 20, "id": "19643cdf", "metadata": {}, "outputs": [ @@ -454,37 +477,453 @@ "type BaseArgs = ConstructorParameters;\n", "\n", "// the granular orchestration types\n", - "type Options = Orchestratable;\n", - "type Augmentation = Orchestratable;\n", + "type OptionsOrchestratable = Orchestratable<\n", + "\tM,\n", + "\t'options'\n", + ">;\n", + "type AugmentationOrchestratable = Orchestratable<\n", + "\tM,\n", + "\t'_Augmentation'\n", + ">;\n", "\n", "console.log('Additional types loaded!');" ] }, { "cell_type": "markdown", - "id": "a9ab349a", + "id": "34337a0d", + "metadata": {}, + "source": [ + "Everything is prepared! We can proceed with the logging module. It needs to provide a logging method that determines whether to log according to the consumer's config, record down all the logs, and provide a way to retrieve them.\n", + "\n", + "So it will contribute two options:\n", + "\n", + "- `logLevel`: The log level above which the log will be shown in the console.\n", + "- `maxLogs`: The maximum number of logs to be stored, if the number of logs exceeds this number, the oldest logs will be deleted.\n", + "\n", + "Then it needs to augment the `log` method and `logs` getter to the loader for easy access." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "4af2f9af", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CoreLogging loaded!\n" + ] + } + ], + "source": [ + "const LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 } as const;\n", + "type Level = keyof typeof LEVELS;\n", + "\n", + "interface LoggingOptions extends BaseOptions {\n", + "\tlogLevel: Level;\n", + "\tmaxLogs?: number;\n", + "}\n", + "interface LoggingAugmentation {\n", + "\tlog: CoreLogging['log'];\n", + "\tlogs: ReadonlyArray;\n", + "}\n", + "\n", + "interface LogEntry {\n", + "\ttimestamp: number;\n", + "\tlevel: string;\n", + "\tmessage: string;\n", + "}\n", + "\n", + "class CoreLogging extends BaseModule {\n", + "\tprivate _logs: LogEntry[] = [];\n", + "\n", + "\tconstructor(...args: BaseArgs) {\n", + "\t\tsuper(...args);\n", + "\t\tconst self = this;\n", + "\t\tthis.augment({\n", + "\t\t\tlog: this.log,\n", + "\t\t\tget logs() {\n", + "\t\t\t\treturn Object.freeze([...self._logs]);\n", + "\t\t\t},\n", + "\t\t});\n", + "\t\tthis.onStart(() => {\n", + "\t\t\tthis.log('INFO', 'CoreLogging initialized');\n", + "\t\t});\n", + "\t\tthis.onDispose(() => {\n", + "\t\t\tthis.log('INFO', 'CoreLogging disposed');\n", + "\t\t\tthis._logs = [];\n", + "\t\t});\n", + "\t}\n", + "\n", + "\tlog = (level: Level, message: string) => {\n", + "\t\tconst currentLevel = LEVELS[level];\n", + "\t\tconst minLevel = LEVELS[this.options.logLevel] ?? 0;\n", + "\t\tif (currentLevel < minLevel) return; // Skip logging if below threshold\n", + "\t\tconst entry: LogEntry = {\n", + "\t\t\ttimestamp: Date.now(),\n", + "\t\t\tlevel,\n", + "\t\t\tmessage,\n", + "\t\t};\n", + "\t\tconst maxLogs = this.options.maxLogs ?? 1000;\n", + "\t\tif (this._logs.length >= maxLogs) this._logs.shift();\n", + "\t\tthis._logs.push(entry);\n", + "\n", + "\t\t// Always print if debug mode is forced in base options, otherwise respect level\n", + "\t\tif (this.options.debug || currentLevel >= minLevel) {\n", + "\t\t\tconsole.log(`[${level}] ${message}`);\n", + "\t\t}\n", + "\t};\n", + "}\n", + "\n", + "console.log('CoreLogging loaded!');" + ] + }, + { + "cell_type": "markdown", + "id": "4d89470e", + "metadata": {}, + "source": [ + "Based on the logging module, we can now design the alerting module. This module will be responsible for sending alerts to external services and record the alerts via the log module.\n", + "\n", + "Since we cannot truly use an external service in a Jupyter Notebook, we will use a mock service that simulates an asynchronous trigger. We also allows the user to specify the max and min length of the alert to mimic the real alert service volume.\n", + "\n", + "Moreover, the module will contribute a method `dispatchAlert` to the loader facade." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "267834c3", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AlertDispatch loaded!\n" + ] + } + ], + "source": [ + "interface AlertOptions extends BaseOptions {\n", + "\tminMessageLength: number;\n", + "\tmaxMessageLength: number;\n", + "}\n", + "\n", + "interface AlertAugmentation {\n", + "\tdispatchAlert: AlertDispatch['dispatchAlert'];\n", + "}\n", + "\n", + "class AlertDispatch extends BaseModule {\n", + "\tprivate logging: CoreLogging;\n", + "\n", + "\tconstructor(...args: BaseArgs) {\n", + "\t\tsuper(...args);\n", + "\t\tthis.augment({ dispatchAlert: this.dispatchAlert });\n", + "\t\tthis.logging = this.container.get(CoreLogging);\n", + "\t\tthis.onStart(() => {\n", + "\t\t\tthis.logging.log('INFO', 'AlertDispatch initialized');\n", + "\t\t});\n", + "\t\tthis.onDispose(() => {\n", + "\t\t\tthis.logging.log('INFO', 'AlertDispatch disposed');\n", + "\t\t});\n", + "\t}\n", + "\n", + "\tdispatchAlert = async (message: string): Promise => {\n", + "\t\tthis.logging.log('INFO', `Attempted dispatch: \"${message}\"`);\n", + "\t\tconst { minMessageLength, maxMessageLength } = this.options;\n", + "\t\tif (message.length < minMessageLength) {\n", + "\t\t\tthis.logging.log(\n", + "\t\t\t\t'ERROR',\n", + "\t\t\t\t`Validation failed: message too short (min: ${minMessageLength})`,\n", + "\t\t\t);\n", + "\t\t\treturn false;\n", + "\t\t}\n", + "\t\tif (message.length > maxMessageLength) {\n", + "\t\t\tthis.logging.log(\n", + "\t\t\t\t'ERROR',\n", + "\t\t\t\t`Validation failed: message too long (max: ${maxMessageLength})`,\n", + "\t\t\t);\n", + "\t\t\treturn false;\n", + "\t\t}\n", + "\n", + "\t\tawait this.connectAlertService(message);\n", + "\t\treturn true;\n", + "\t};\n", + "\n", + "\tprivate connectAlertService = async (alert: string) => {\n", + "\t\tthis.logging.log('INFO', `Dispatched: \"${alert}\"`);\n", + "\n", + "\t\t// Simulate async connection to alerting service, like an email api\n", + "\t\tawait new Promise((resolve) => setTimeout(resolve, 10));\n", + "\t};\n", + "}\n", + "\n", + "console.log('AlertDispatch loaded!');" + ] + }, + { + "cell_type": "markdown", + "id": "24357da1", "metadata": {}, "source": [ - "Finally, let's design the loader that can correctly load modules and provide what they need." + "All modules needed are implemented. The final step is to put them together and design the loader. Firstly, we gather all the orchestration results, you can hover above the types to see the orchestration definition." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "ed65b2ba", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Orchestration finished!\n" + ] + } + ], + "source": [ + "const allModules = [CoreLogging, AlertDispatch];\n", + "type AllModules = typeof allModules;\n", + "\n", + "// final orchestrated types\n", + "type AllOptions = OptionsOrchestratable;\n", + "type AllAugmentation = AugmentationOrchestratable;\n", + "\n", + "console.log('Orchestration finished!');" + ] + }, + { + "cell_type": "markdown", + "id": "c7be43ce", + "metadata": {}, + "source": [ + "The loader needs to provide all of the functionalities below:\n", + "\n", + "- application lifecycle hooks\n", + "- receive configuration\n", + "- being augmented to behave as a facade between the application and the consumer\n", + "- DI container that registers all of the modules" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "eae9b1fd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loader loaded!\n" + ] + } + ], + "source": [ + "class Loader {\n", + "\tprivate onDispose = makeHook(true);\n", + "\tprivate onStart = makeHook();\n", + "\n", + "\toptions: AllOptions;\n", + "\n", + "\tcontainer: Container;\n", + "\n", + "\tprivate augment = (aug: GeneralObject) => {\n", + "\t\tconst descriptors = Object.getOwnPropertyDescriptors(aug);\n", + "\t\tObject.defineProperties(this, descriptors);\n", + "\t};\n", + "\n", + "\tconstructor(options: AllOptions) {\n", + "\t\tthis.container = new Container();\n", + "\t\tthis.options = options;\n", + "\n", + "\t\tconst bind = (Module: GeneralModuleCtor) => {\n", + "\t\t\tthis.container.bind({\n", + "\t\t\t\tprovide: Module,\n", + "\t\t\t\tuseFactory: () =>\n", + "\t\t\t\t\tnew Module(\n", + "\t\t\t\t\t\tthis.container,\n", + "\t\t\t\t\t\tthis.options,\n", + "\t\t\t\t\t\tthis.onStart,\n", + "\t\t\t\t\t\tthis.onDispose,\n", + "\t\t\t\t\t\tthis.augment,\n", + "\t\t\t\t\t),\n", + "\t\t\t});\n", + "\t\t};\n", + "\t\tallModules.forEach(bind);\n", + "\t\tallModules.forEach((Module: GeneralModuleCtor) => {\n", + "\t\t\tthis.container.get(Module);\n", + "\t\t});\n", + "\t\tthis.onStart();\n", + "\t}\n", + "\n", + "\tdispose = () => {\n", + "\t\tthis.onDispose();\n", + "\t\tthis.container.unbindAll();\n", + "\t};\n", + "}\n", + "\n", + "console.log('Loader loaded!');" + ] + }, + { + "cell_type": "markdown", + "id": "6fc54279", + "metadata": {}, + "source": [ + "It's not enough if we stop at the loader, since although the augmentation has injected into the loader, TypeScript doesn't know about it in the types of the loader.\n", + "\n", + "The final step is to tell TypeScript about the augmentation." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "3e4f30c5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PolisAlert augmented!\n" + ] + } + ], + "source": [ + "type LoaderType = new (\n", + "\t...args: ConstructorParameters\n", + ") => Loader & AllAugmentation;\n", + "\n", + "// not every type assertion harms type safety, this one enhances type safety\n", + "const PolisAlert = Loader as LoaderType;\n", + "\n", + "console.log('PolisAlert augmented!');" + ] + }, + { + "cell_type": "markdown", + "id": "bb4d5fa4", + "metadata": {}, + "source": [ + "We are all done! Let's test it out!\n", + "\n", + "### Final Result\n", + "\n", + "Let us create a consumer script that uses `PolisAlert` to log and alert something:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "6752cedf", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[INFO] CoreLogging initialized\n", + "[INFO] AlertDispatch initialized\n", + "[INFO] Application started\n", + "[INFO] Attempted dispatch: \"Hello Polis\"\n", + "[INFO] Dispatched: \"Hello Polis\"\n", + "Dispatch result: true\n", + "Audit trail: [\n", + " {\n", + " timestamp: 1771683816299,\n", + " level: \"INFO\",\n", + " message: \"CoreLogging initialized\"\n", + " },\n", + " {\n", + " timestamp: 1771683816299,\n", + " level: \"INFO\",\n", + " message: \"AlertDispatch initialized\"\n", + " },\n", + " {\n", + " timestamp: 1771683816299,\n", + " level: \"INFO\",\n", + " message: \"Application started\"\n", + " },\n", + " {\n", + " timestamp: 1771683816299,\n", + " level: \"INFO\",\n", + " message: 'Attempted dispatch: \"Hello Polis\"'\n", + " },\n", + " {\n", + " timestamp: 1771683816299,\n", + " level: \"INFO\",\n", + " message: 'Dispatched: \"Hello Polis\"'\n", + " }\n", + "]\n", + "[INFO] AlertDispatch disposed\n", + "[INFO] CoreLogging disposed\n" + ] + } + ], + "source": [ + "// all options naturally orchestrated here with correct types\n", + "const app = new PolisAlert({\n", + "\tappName: 'PolisAlert',\n", + "\tdebug: true,\n", + "\tlogLevel: 'DEBUG',\n", + "\tmaxLogs: 500,\n", + "\tminMessageLength: 1,\n", + "\tmaxMessageLength: 280,\n", + "});\n", + "\n", + "// type-safe access to augmented methods\n", + "app.log('INFO', 'Application started');\n", + "\n", + "const success = await app.dispatchAlert('Hello Polis');\n", + "console.log('Dispatch result:', success);\n", + "\n", + "// access orchestrated state\n", + "console.log('Audit trail:', app.logs);\n", + "\n", + "// cleanup\n", + "app.dispose();" + ] + }, + { + "cell_type": "markdown", + "id": "2491565f", + "metadata": {}, + "source": [ + "Everything works as if you were using a monolith! Despite its highly modular nature.\n", + "\n", + "You can adjust the options or log and alert more in the cell above to see what will happen. You can even remove `AlertDispatch` module from `allModules`, the logger will still work fine, you simply won't use any alerts.\n", + "\n", + "## Final Words\n", + "\n", + "Isn't SynthKernel awesome? You've just seen a demo of how modules can work seamlessly together and removing a module even doesn't break anything. Everything is structured and clear, no more \"spaghetti\" created.\n", + "\n", + "Maybe you'll say \"Ah, I'd like to make `PolisAlert` just a monolith within a single file. There's too much boilerplate to do that in your way\".\n", + "\n", + "Yes, absolutely. For small scripts, `SynthKernel` is an overkill. But `PolisAlert` is a simple example whose scale is deliberately designed to be small. In real production, when you have tens of modules with 1000+ lines of code, you'll find how `SynthKernel` can make your coding much easier." ] } ], "metadata": { "kernelspec": { - "display_name": "TypeScript", + "display_name": "Deno", "language": "typescript", - "name": "tslab" + "name": "deno" }, "language_info": { - "codemirror_mode": { - "mode": "typescript", - "name": "javascript", - "typescript": true - }, + "codemirror_mode": "typescript", "file_extension": ".ts", - "mimetype": "text/typescript", + "mimetype": "text/x.typescript", "name": "typescript", - "version": "3.7.2" + "nbconvert_exporter": "script", + "pygments_lexer": "typescript", + "version": "5.9.2" } }, "nbformat": 4, From a821469b0ca846bf734c5361a0096c333adc1d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?He=CC=84sperus?= Date: Sat, 21 Feb 2026 22:51:01 +0800 Subject: [PATCH 2/2] fix lint --- deno.json => deno.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename deno.json => deno.jsonc (86%) diff --git a/deno.json b/deno.jsonc similarity index 86% rename from deno.json rename to deno.jsonc index d55ef60..46f0318 100644 --- a/deno.json +++ b/deno.jsonc @@ -13,7 +13,7 @@ "tasks": { "run:example": "deno run skill/examples/example.ts", "lint": "deno lint --fix && deno fmt", - "check": "deno lint && deno fmt --check" + "check": "deno lint && deno fmt --check --ignore=\"*.ipynb\"" // Deno always report Jupyter Notebooks as unformatted for no reason }, "imports": { "@needle-di/core": "jsr:@needle-di/core@^1.1.1"