├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── apps └── examples │ ├── angular │ ├── list-e2e │ │ ├── .eslintrc.json │ │ ├── cypress.json │ │ ├── project.json │ │ ├── src │ │ │ ├── fixtures │ │ │ │ └── example.json │ │ │ ├── integration │ │ │ │ └── app.spec.ts │ │ │ └── support │ │ │ │ ├── app.po.ts │ │ │ │ ├── commands.ts │ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── list │ │ ├── .eslintrc.json │ │ ├── jest.config.ts │ │ ├── project.json │ │ ├── src │ │ │ ├── app │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.scss │ │ │ │ ├── app.component.spec.ts │ │ │ │ ├── app.component.ts │ │ │ │ └── app.module.ts │ │ │ ├── assets │ │ │ │ └── .gitkeep │ │ │ ├── environments │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ ├── polyfills.ts │ │ │ ├── styles.scss │ │ │ └── test-setup.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.editor.json │ │ ├── tsconfig.json │ │ └── tsconfig.spec.json │ ├── tic-tac-toe-e2e │ │ ├── .eslintrc.json │ │ ├── cypress.json │ │ ├── project.json │ │ ├── src │ │ │ ├── fixtures │ │ │ │ └── example.json │ │ │ ├── integration │ │ │ │ └── app.spec.ts │ │ │ └── support │ │ │ │ ├── app.po.ts │ │ │ │ ├── commands.ts │ │ │ │ └── index.ts │ │ └── tsconfig.json │ └── tic-tac-toe │ │ ├── .eslintrc.json │ │ ├── jest.config.ts │ │ ├── project.json │ │ ├── src │ │ ├── app │ │ │ ├── app.component.html │ │ │ ├── app.component.scss │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ └── app.module.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.scss │ │ └── test-setup.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.editor.json │ │ ├── tsconfig.json │ │ └── tsconfig.spec.json │ ├── react │ ├── list-e2e │ │ ├── .eslintrc.json │ │ ├── cypress.json │ │ ├── project.json │ │ ├── src │ │ │ ├── fixtures │ │ │ │ └── example.json │ │ │ ├── integration │ │ │ │ └── app.spec.ts │ │ │ └── support │ │ │ │ ├── app.po.ts │ │ │ │ ├── commands.ts │ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── list │ │ ├── .babelrc │ │ ├── .eslintrc.json │ │ ├── jest.config.ts │ │ ├── project.json │ │ ├── src │ │ │ ├── app │ │ │ │ ├── CustomListImpl.tsx │ │ │ │ ├── List.tsx │ │ │ │ ├── app.module.scss │ │ │ │ ├── app.spec.tsx │ │ │ │ ├── app.tsx │ │ │ │ ├── logo.svg │ │ │ │ └── star.svg │ │ │ ├── assets │ │ │ │ └── .gitkeep │ │ │ ├── environments │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── polyfills.ts │ │ │ └── styles.scss │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ └── tsconfig.spec.json │ ├── tic-tac-toe-e2e │ │ ├── .eslintrc.json │ │ ├── cypress.json │ │ ├── project.json │ │ ├── src │ │ │ ├── fixtures │ │ │ │ └── example.json │ │ │ ├── integration │ │ │ │ └── app.spec.ts │ │ │ └── support │ │ │ │ ├── app.po.ts │ │ │ │ ├── commands.ts │ │ │ │ └── index.ts │ │ └── tsconfig.json │ └── tic-tac-toe │ │ ├── .babelrc │ │ ├── .eslintrc.json │ │ ├── jest.config.ts │ │ ├── project.json │ │ ├── src │ │ ├── app │ │ │ ├── TicTacToe.tsx │ │ │ ├── app.module.scss │ │ │ ├── app.spec.tsx │ │ │ ├── app.tsx │ │ │ ├── logo.svg │ │ │ └── star.svg │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.tsx │ │ ├── polyfills.ts │ │ └── styles.scss │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ └── tsconfig.spec.json │ └── vanilla │ ├── list-e2e │ ├── .eslintrc.json │ ├── cypress.json │ ├── project.json │ ├── src │ │ ├── fixtures │ │ │ └── example.json │ │ ├── integration │ │ │ └── app.spec.ts │ │ └── support │ │ │ ├── app.po.ts │ │ │ ├── commands.ts │ │ │ └── index.ts │ └── tsconfig.json │ ├── list │ ├── .babelrc │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── app │ │ │ ├── app.element.css │ │ │ └── app.element.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.css │ │ └── test-setup.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json │ ├── tic-tac-toe-e2e │ ├── .eslintrc.json │ ├── cypress.json │ ├── project.json │ ├── src │ │ ├── fixtures │ │ │ └── example.json │ │ ├── integration │ │ │ └── app.spec.ts │ │ └── support │ │ │ ├── app.po.ts │ │ │ ├── commands.ts │ │ │ └── index.ts │ └── tsconfig.json │ └── tic-tac-toe │ ├── .babelrc │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ ├── app │ │ ├── app.element.scss │ │ └── app.element.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ └── test-setup.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── babel.config.json ├── derxjs-logo.jpg ├── derxjs-react-logo.png ├── jest.config.ts ├── jest.preset.js ├── libs └── examples │ ├── list-view-model-implementation │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── custom-implementation.ts │ │ │ ├── implementation-using-reducer-package.ts │ │ │ ├── list-view-model-implementation.spec.ts │ │ │ └── types.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json │ └── tic-tac-toe-view-model-implementation │ ├── .babelrc │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── project.json │ ├── src │ ├── index.ts │ └── lib │ │ ├── random-ai.ts │ │ ├── types.ts │ │ ├── using-reducer-package.spec.ts │ │ ├── using-reducer-package.ts │ │ ├── view-model.spec.ts │ │ └── view-model.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── migrations.json ├── nx.json ├── package-lock.json ├── package.json ├── packages ├── .gitkeep ├── react │ ├── .babelrc │ ├── .eslintrc.json │ ├── LICENSE.md │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ └── DeRxJSComponent.tsx │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── reducer │ ├── .babelrc │ ├── .eslintrc.json │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE.md │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ └── reducer.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json └── view-model │ ├── .babelrc │ ├── .eslintrc.json │ ├── .npmignore │ ├── LICENSE.md │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ ├── index.ts │ └── lib │ │ └── view-model.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── separation-of-tasks.png ├── the-derxjs-view-model-pattern.png ├── tools ├── scripts │ ├── bump-version.ts │ ├── publish-all.ts │ └── publish.ts ├── tsconfig.tools.json └── workspace-plugin │ ├── .eslintrc.json │ ├── generators.json │ ├── jest.config.ts │ ├── package.json │ ├── project.json │ ├── src │ ├── generators │ │ └── package │ │ │ ├── index.ts │ │ │ └── schema.json │ └── index.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json └── tsconfig.base.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nx/javascript"], 32 | "rules": {} 33 | }, 34 | { 35 | "files": "*.json", 36 | "parser": "jsonc-eslint-parser", 37 | "rules": {} 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "firsttris.vscode-jest-runner" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": ["json"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @derxjs 2 | 3 | Because your state management code should be domain-agnostic. 4 | 5 |

6 | 7 |

8 | 9 | ## Packages 10 | 11 | | Package | Installation | 12 | | ------------------ | --------------------------- | 13 | | @derxjs/view-model | `npm i @derxjs/view-model ` | 14 | | @derxjs/reducer | `npm i @derxjs/reducer ` | 15 | | @derxjs/react | `npm i @derxjs/react ` | 16 | 17 | ## Usage 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 38 | 43 | 44 | 45 | 46 | 51 | 56 | 61 | 62 |
ExampleVanillaReactAngular
Simple List Component 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 |
Intermediate Tic Tac Toe Component 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 57 | 58 | 59 | 60 |
63 | 64 | ## Why @derxjs 65 | 66 | ### Domain-agnostic state-management 67 | 68 | Your state management code should not depend on which framework or tools your project happens to be using at the time. 69 | 70 | `@derxjs/view-model` is all about first-principles thinking and problem-solving. The pattern enforced by this package requires you to break down your system - regardless of scope - to some set of inputs (preferably represented as RxJS Observables!) and expose a single Observable of your ViewModel as an output. 71 | 72 | Future packages on the roadmap will provide utilities for implementing this pattern (`@derxjs/reducer` 👀), as well as utilities for plugging it into popular front-end frameworks (`@derxjs/react` 👀). 73 | 74 | ### Separation of concerns 75 | 76 | We solved this a long time ago. Programming to interfaces lets us put a joint in our workflow that allows for parallel work to be completed by multiple developers and lets your team play to their strengths. 77 | 78 |

79 | 80 |

81 | 82 | This allows for easy transitions into other implementations, frameworks, as well as implementing the facade, adapter, and proxy patterns from the Gang of Four. 83 | 84 | ### Complimentary to all existing state-management libraries 85 | 86 | We're not here to take a shot at the king ([👑](https://ngrx.io/)👀) - we're just here to help out where we can! 87 | 88 | The `@derxjs/view-model` package is designed to work with any other state management frameworks that can expose state or events as an Observable, making it a great compliment to any existing code in your codebase. 89 | 90 | ### Future-Proof Code 91 | 92 | Domain-agnostic first-principles-based code will never go out of style 🌲. 93 | 94 | As long as JavaScript is the language of the web, your state-management code will be valid. 95 | 96 | Go ahead, change to that trendy new framework. Your @derxjs code will still work just fine :). 97 | 98 | ### Simplicity && Elegence 99 | 100 | The `DeRxJSViewModel` type is the `E = mc^2` of state management. 101 | 102 | Deceptively simple, but elegant enough to encompass any && all of your state management requirements. 103 | 104 |

105 | 106 |

107 | 108 | ### TDD made awesome with timeline testing 109 | 110 | Embrace TDD, using timeline testing to test your code with a whole new dimension of precision. 111 | 112 | On the roadmap for `@derxjs` is a timeline test generation GUI tool that will take your Typescript interface code, and allow you to "draw" hypothetical timelines of events from your inputs - specifying what the output timeline for each hypothetical should look like. 113 | 114 | This tool will generate `.spec.ts` files that you can paste directly into your repos for easy TDD, and coding the way we were meant to. 115 | 116 | ## @derxjs Roadmap 117 | 118 | - @derxjs/view-model package ✅ 119 | - [Article on TDD and implementing DeRxJS View Models](https://dev.to/zackderose/the-derxjsviewmodel-pattern-the-e-mc-2-of-state-management-part-1-3dka) ✅ 120 | - [Article on using DeRxJS View Models in different Frameworks](https://dev.to/zackderose/the-derxjsviewmodel-pattern-the-emc2-of-state-management-part-2-2i73) ✅ 121 | - @derxjs/reducer package (TBD; [beta available now](https://www.npmjs.com/package/@derxjs/reducer)) 🚧 122 | - @derxjs/react package (TBD; [beta available now](https://www.npmjs.com/package/@derxjs/react)) 🚧 123 | - Timeline Test Code Generation Tool (TBD) 124 | - @derxjs/selector package (TBD) 125 | - Ai-Driven DeRxJS Code Generation (TBD) 126 | -------------------------------------------------------------------------------- /apps/examples/angular/list-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/angular/list-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "supportFile": "./src/support/index.ts", 7 | "pluginsFile": false, 8 | "video": true, 9 | "videosFolder": "../../../../dist/cypress/apps/examples/angular/list-e2e/videos", 10 | "screenshotsFolder": "../../../../dist/cypress/apps/examples/angular/list-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/examples/angular/list-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-angular-list-e2e", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/examples/angular/list-e2e/src", 5 | "projectType": "application", 6 | "targets": { 7 | "e2e": { 8 | "executor": "@nx/cypress:cypress", 9 | "options": { 10 | "cypressConfig": "apps/examples/angular/list-e2e/cypress.json", 11 | "devServerTarget": "examples-angular-list:serve:development" 12 | }, 13 | "configurations": { 14 | "production": { 15 | "devServerTarget": "examples-angular-list:serve:production" 16 | } 17 | } 18 | }, 19 | "lint": { 20 | "executor": "@nx/linter:eslint", 21 | "outputs": ["{options.outputFile}"], 22 | "options": { 23 | "lintFilePatterns": ["apps/examples/angular/list-e2e/**/*.{js,ts}"] 24 | } 25 | } 26 | }, 27 | "tags": [], 28 | "implicitDependencies": ["examples-angular-list"] 29 | } 30 | -------------------------------------------------------------------------------- /apps/examples/angular/list-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/examples/angular/list-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('examples-angular-list', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to examples-angular-list!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/examples/angular/list-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/examples/angular/list-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /apps/examples/angular/list-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/examples/angular/list-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"], 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true 12 | }, 13 | "include": ["src/**/*.ts", "src/**/*.js"], 14 | "angularCompilerOptions": { 15 | "strictInjectionParameters": true, 16 | "strictInputAccessModifiers": true, 17 | "strictTemplates": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/examples/angular/list/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "derxjs", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "derxjs", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /apps/examples/angular/list/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'examples-angular-list', 4 | preset: '../../../../jest.preset.js', 5 | setupFilesAfterEnv: ['/src/test-setup.ts'], 6 | globals: {}, 7 | coverageDirectory: '../../../../coverage/apps/examples/angular/list', 8 | transform: { 9 | '^.+.(ts|mjs|js|html)$': [ 10 | 'jest-preset-angular', 11 | { 12 | tsconfig: '/tsconfig.spec.json', 13 | stringifyContentPathRegex: '\\.(html|svg)$', 14 | }, 15 | ], 16 | }, 17 | transformIgnorePatterns: ['node_modules/(?!.*.mjs$)'], 18 | snapshotSerializers: [ 19 | 'jest-preset-angular/build/serializers/no-ng-attributes', 20 | 'jest-preset-angular/build/serializers/ng-snapshot', 21 | 'jest-preset-angular/build/serializers/html-comment', 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /apps/examples/angular/list/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-angular-list", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/examples/angular/list/src", 6 | "prefix": "derxjs", 7 | "targets": { 8 | "build": { 9 | "executor": "@angular-devkit/build-angular:browser", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/apps/examples/angular/list", 13 | "index": "apps/examples/angular/list/src/index.html", 14 | "main": "apps/examples/angular/list/src/main.ts", 15 | "polyfills": "apps/examples/angular/list/src/polyfills.ts", 16 | "tsConfig": "apps/examples/angular/list/tsconfig.app.json", 17 | "inlineStyleLanguage": "scss", 18 | "assets": [ 19 | "apps/examples/angular/list/src/favicon.ico", 20 | "apps/examples/angular/list/src/assets" 21 | ], 22 | "styles": ["apps/examples/angular/list/src/styles.scss"], 23 | "scripts": [] 24 | }, 25 | "configurations": { 26 | "production": { 27 | "budgets": [ 28 | { 29 | "type": "initial", 30 | "maximumWarning": "500kb", 31 | "maximumError": "1mb" 32 | }, 33 | { 34 | "type": "anyComponentStyle", 35 | "maximumWarning": "2kb", 36 | "maximumError": "4kb" 37 | } 38 | ], 39 | "fileReplacements": [ 40 | { 41 | "replace": "apps/examples/angular/list/src/environments/environment.ts", 42 | "with": "apps/examples/angular/list/src/environments/environment.prod.ts" 43 | } 44 | ], 45 | "outputHashing": "all" 46 | }, 47 | "development": { 48 | "buildOptimizer": false, 49 | "optimization": false, 50 | "vendorChunk": true, 51 | "extractLicenses": false, 52 | "sourceMap": true, 53 | "namedChunks": true 54 | } 55 | }, 56 | "defaultConfiguration": "production" 57 | }, 58 | "serve": { 59 | "executor": "@angular-devkit/build-angular:dev-server", 60 | "configurations": { 61 | "production": { 62 | "browserTarget": "examples-angular-list:build:production" 63 | }, 64 | "development": { 65 | "browserTarget": "examples-angular-list:build:development" 66 | } 67 | }, 68 | "defaultConfiguration": "development" 69 | }, 70 | "extract-i18n": { 71 | "executor": "@angular-devkit/build-angular:extract-i18n", 72 | "options": { 73 | "browserTarget": "examples-angular-list:build" 74 | } 75 | }, 76 | "lint": { 77 | "executor": "@nx/linter:eslint", 78 | "options": { 79 | "lintFilePatterns": [ 80 | "apps/examples/angular/list/src/**/*.ts", 81 | "apps/examples/angular/list/src/**/*.html" 82 | ] 83 | } 84 | }, 85 | "test": { 86 | "executor": "@nx/jest:jest", 87 | "outputs": ["{workspaceRoot}/coverage/apps/examples/angular/list"], 88 | "options": { 89 | "jestConfig": "apps/examples/angular/list/jest.config.ts", 90 | "passWithNoTests": true 91 | } 92 | } 93 | }, 94 | "tags": [] 95 | } 96 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |

@derxj/view-model Angular Usage Example

3 | 4 |
5 | 6 | 7 |
8 | 9 |
    10 |
  • {{ item }}
  • 11 |
12 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Remove template code below 3 | */ 4 | :host { 5 | display: block; 6 | font-family: sans-serif; 7 | min-width: 300px; 8 | max-width: 600px; 9 | margin: 50px auto; 10 | } 11 | 12 | .gutter-left { 13 | margin-left: 9px; 14 | } 15 | 16 | .col-span-2 { 17 | grid-column: span 2; 18 | } 19 | 20 | .flex { 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | } 25 | 26 | header { 27 | background-color: #143055; 28 | color: white; 29 | padding: 5px; 30 | border-radius: 3px; 31 | } 32 | 33 | main { 34 | padding: 0 36px; 35 | } 36 | 37 | p { 38 | text-align: center; 39 | } 40 | 41 | h1 { 42 | text-align: center; 43 | margin-left: 18px; 44 | font-size: 24px; 45 | } 46 | 47 | h2 { 48 | text-align: center; 49 | font-size: 20px; 50 | margin: 40px 0 10px 0; 51 | } 52 | 53 | .resources { 54 | text-align: center; 55 | list-style: none; 56 | padding: 0; 57 | display: grid; 58 | grid-gap: 9px; 59 | grid-template-columns: 1fr 1fr; 60 | } 61 | 62 | .resource { 63 | color: #0094ba; 64 | height: 36px; 65 | background-color: rgba(0, 0, 0, 0); 66 | border: 1px solid rgba(0, 0, 0, 0.12); 67 | border-radius: 4px; 68 | padding: 3px 9px; 69 | text-decoration: none; 70 | } 71 | 72 | .resource:hover { 73 | background-color: rgba(68, 138, 255, 0.04); 74 | } 75 | 76 | pre { 77 | padding: 9px; 78 | border-radius: 4px; 79 | background-color: black; 80 | color: #eee; 81 | } 82 | 83 | details { 84 | border-radius: 4px; 85 | color: #333; 86 | background-color: rgba(0, 0, 0, 0); 87 | border: 1px solid rgba(0, 0, 0, 0.12); 88 | padding: 3px 9px; 89 | margin-bottom: 9px; 90 | } 91 | 92 | summary { 93 | cursor: pointer; 94 | outline: none; 95 | height: 36px; 96 | line-height: 36px; 97 | } 98 | 99 | .github-star-container { 100 | margin-top: 12px; 101 | line-height: 20px; 102 | } 103 | 104 | .github-star-container a { 105 | display: flex; 106 | align-items: center; 107 | text-decoration: none; 108 | color: #333; 109 | } 110 | 111 | .github-star-badge { 112 | color: #24292e; 113 | display: flex; 114 | align-items: center; 115 | font-size: 12px; 116 | padding: 3px 10px; 117 | border: 1px solid rgba(27, 31, 35, 0.2); 118 | border-radius: 3px; 119 | background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%); 120 | margin-left: 4px; 121 | font-weight: 600; 122 | } 123 | 124 | .github-star-badge:hover { 125 | background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%); 126 | border-color: rgba(27, 31, 35, 0.35); 127 | background-position: -0.5em; 128 | } 129 | .github-star-badge .material-icons { 130 | height: 16px; 131 | width: 16px; 132 | margin-right: 4px; 133 | } 134 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async () => { 6 | await TestBed.configureTestingModule({ 7 | declarations: [AppComponent], 8 | }).compileComponents(); 9 | }); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { UntypedFormControl } from '@angular/forms'; 3 | import { 4 | customListImpl$ as listViewModel$, 5 | // reducerListImpl$ as listViewModel$, 6 | ListViewModel, 7 | } from '@derxjs/examples/list-view-model-implementation'; 8 | import { Observable, Observer } from 'rxjs'; 9 | 10 | @Component({ 11 | selector: 'derxjs-root', 12 | templateUrl: './app.component.html', 13 | styleUrls: ['./app.component.scss'], 14 | }) 15 | export class AppComponent { 16 | textInputFormControl = new UntypedFormControl(); 17 | private _pushObserver!: Observer; 18 | private _popObserver!: Observer; 19 | viewModel$: Observable; 20 | 21 | constructor() { 22 | const push$ = new Observable( 23 | (observer) => (this._pushObserver = observer) 24 | ); 25 | const pop$ = new Observable( 26 | (observer) => (this._popObserver = observer) 27 | ); 28 | this.viewModel$ = listViewModel$({ push$, pop$, initialValue: [] }) as any; 29 | } 30 | 31 | push() { 32 | this._pushObserver.next(this.textInputFormControl.value); 33 | this.textInputFormControl.reset(); 34 | } 35 | 36 | pop() { 37 | this._popObserver.next(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | 5 | import { AppComponent } from './app.component'; 6 | 7 | @NgModule({ 8 | declarations: [AppComponent], 9 | imports: [BrowserModule, FormsModule, ReactiveFormsModule], 10 | providers: [], 11 | bootstrap: [AppComponent], 12 | }) 13 | export class AppModule {} 14 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/angular/list/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/examples/angular/list/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/angular/list/src/favicon.ico -------------------------------------------------------------------------------- /apps/examples/angular/list/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ExamplesAngularList 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * IE11 requires the following for NgClass support on SVG elements 23 | */ 24 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 25 | 26 | /** 27 | * Web Animations `@angular/platform-browser/animations` 28 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 29 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 30 | */ 31 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 32 | 33 | /** 34 | * By default, zone.js will patch all possible macroTask and DomEvents 35 | * user can disable parts of macroTask/DomEvents patch by setting following flags 36 | * because those flags need to be set before `zone.js` being loaded, and webpack 37 | * will put import in the top of bundle, so user need to create a separate file 38 | * in this directory (for example: zone-flags.ts), and put the following flags 39 | * into that file, and then add the following code before importing zone.js. 40 | * import './zone-flags'; 41 | * 42 | * The flags allowed in zone-flags.ts are listed here. 43 | * 44 | * The following flags will work for all browsers. 45 | * 46 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 47 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 48 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 49 | * 50 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 51 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 52 | * 53 | * (window as any).__Zone_enable_cross_context_check = true; 54 | * 55 | */ 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js'; // Included with Angular CLI. 61 | 62 | /*************************************************************************************************** 63 | * APPLICATION IMPORTS 64 | */ 65 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /apps/examples/angular/list/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting, 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | 9 | getTestBed().resetTestEnvironment(); 10 | getTestBed().initTestEnvironment( 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting(), 13 | { teardown: { destroyAfterEach: false } } 14 | ); 15 | -------------------------------------------------------------------------------- /apps/examples/angular/list/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "types": [], 6 | "target": "ES2022", 7 | "useDefineForClassFields": false 8 | }, 9 | "files": ["src/main.ts", "src/polyfills.ts"], 10 | "include": ["src/**/*.d.ts"], 11 | "exclude": ["jest.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /apps/examples/angular/list/tsconfig.editor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "compilerOptions": { 5 | "types": ["jest", "node"] 6 | }, 7 | "exclude": ["jest.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/examples/angular/list/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | }, 12 | { 13 | "path": "./tsconfig.editor.json" 14 | } 15 | ], 16 | "compilerOptions": { 17 | "forceConsistentCasingInFileNames": true, 18 | "strict": true, 19 | "noImplicitReturns": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "target": "es2020" 22 | }, 23 | "angularCompilerOptions": { 24 | "strictInjectionParameters": true, 25 | "strictInputAccessModifiers": true, 26 | "strictTemplates": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/examples/angular/list/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts", "jest.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "supportFile": "./src/support/index.ts", 7 | "pluginsFile": false, 8 | "video": true, 9 | "videosFolder": "../../../../dist/cypress/apps/examples/angular/tic-tac-toe-e2e/videos", 10 | "screenshotsFolder": "../../../../dist/cypress/apps/examples/angular/tic-tac-toe-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-angular-tic-tac-toe-e2e", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/examples/angular/tic-tac-toe-e2e/src", 5 | "projectType": "application", 6 | "targets": { 7 | "e2e": { 8 | "executor": "@nx/cypress:cypress", 9 | "options": { 10 | "cypressConfig": "apps/examples/angular/tic-tac-toe-e2e/cypress.json", 11 | "devServerTarget": "examples-angular-tic-tac-toe:serve:development" 12 | }, 13 | "configurations": { 14 | "production": { 15 | "devServerTarget": "examples-angular-tic-tac-toe:serve:production" 16 | } 17 | } 18 | }, 19 | "lint": { 20 | "executor": "@nx/linter:eslint", 21 | "outputs": ["{options.outputFile}"], 22 | "options": { 23 | "lintFilePatterns": [ 24 | "apps/examples/angular/tic-tac-toe-e2e/**/*.{js,ts}" 25 | ] 26 | } 27 | } 28 | }, 29 | "tags": [], 30 | "implicitDependencies": ["examples-angular-tic-tac-toe"] 31 | } 32 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('examples-angular-tic-tac-toe', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to examples-angular-tic-tac-toe!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"], 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true 12 | }, 13 | "include": ["src/**/*.ts", "src/**/*.js"], 14 | "angularCompilerOptions": { 15 | "strictInjectionParameters": true, 16 | "strictInputAccessModifiers": true, 17 | "strictTemplates": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "derxjs", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "derxjs", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'examples-angular-tic-tac-toe', 4 | preset: '../../../../jest.preset.js', 5 | setupFilesAfterEnv: ['/src/test-setup.ts'], 6 | globals: {}, 7 | coverageDirectory: '../../../../coverage/apps/examples/angular/tic-tac-toe', 8 | transform: { 9 | '^.+.(ts|mjs|js|html)$': [ 10 | 'jest-preset-angular', 11 | { 12 | tsconfig: '/tsconfig.spec.json', 13 | stringifyContentPathRegex: '\\.(html|svg)$', 14 | }, 15 | ], 16 | }, 17 | transformIgnorePatterns: ['node_modules/(?!.*.mjs$)'], 18 | snapshotSerializers: [ 19 | 'jest-preset-angular/build/serializers/no-ng-attributes', 20 | 'jest-preset-angular/build/serializers/ng-snapshot', 21 | 'jest-preset-angular/build/serializers/html-comment', 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-angular-tic-tac-toe", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/examples/angular/tic-tac-toe/src", 6 | "prefix": "derxjs", 7 | "targets": { 8 | "build": { 9 | "executor": "@angular-devkit/build-angular:browser", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/apps/examples/angular/tic-tac-toe", 13 | "index": "apps/examples/angular/tic-tac-toe/src/index.html", 14 | "main": "apps/examples/angular/tic-tac-toe/src/main.ts", 15 | "polyfills": "apps/examples/angular/tic-tac-toe/src/polyfills.ts", 16 | "tsConfig": "apps/examples/angular/tic-tac-toe/tsconfig.app.json", 17 | "inlineStyleLanguage": "scss", 18 | "assets": [ 19 | "apps/examples/angular/tic-tac-toe/src/favicon.ico", 20 | "apps/examples/angular/tic-tac-toe/src/assets" 21 | ], 22 | "styles": ["apps/examples/angular/tic-tac-toe/src/styles.scss"], 23 | "scripts": [] 24 | }, 25 | "configurations": { 26 | "production": { 27 | "budgets": [ 28 | { 29 | "type": "initial", 30 | "maximumWarning": "500kb", 31 | "maximumError": "1mb" 32 | }, 33 | { 34 | "type": "anyComponentStyle", 35 | "maximumWarning": "2kb", 36 | "maximumError": "4kb" 37 | } 38 | ], 39 | "fileReplacements": [ 40 | { 41 | "replace": "apps/examples/angular/tic-tac-toe/src/environments/environment.ts", 42 | "with": "apps/examples/angular/tic-tac-toe/src/environments/environment.prod.ts" 43 | } 44 | ], 45 | "outputHashing": "all" 46 | }, 47 | "development": { 48 | "buildOptimizer": false, 49 | "optimization": false, 50 | "vendorChunk": true, 51 | "extractLicenses": false, 52 | "sourceMap": true, 53 | "namedChunks": true 54 | } 55 | }, 56 | "defaultConfiguration": "production" 57 | }, 58 | "serve": { 59 | "executor": "@angular-devkit/build-angular:dev-server", 60 | "configurations": { 61 | "production": { 62 | "browserTarget": "examples-angular-tic-tac-toe:build:production" 63 | }, 64 | "development": { 65 | "browserTarget": "examples-angular-tic-tac-toe:build:development" 66 | } 67 | }, 68 | "defaultConfiguration": "development" 69 | }, 70 | "extract-i18n": { 71 | "executor": "@angular-devkit/build-angular:extract-i18n", 72 | "options": { 73 | "browserTarget": "examples-angular-tic-tac-toe:build" 74 | } 75 | }, 76 | "lint": { 77 | "executor": "@nx/linter:eslint", 78 | "options": { 79 | "lintFilePatterns": [ 80 | "apps/examples/angular/tic-tac-toe/src/**/*.ts", 81 | "apps/examples/angular/tic-tac-toe/src/**/*.html" 82 | ] 83 | } 84 | }, 85 | "test": { 86 | "executor": "@nx/jest:jest", 87 | "outputs": ["{workspaceRoot}/coverage/apps/examples/angular/tic-tac-toe"], 88 | "options": { 89 | "jestConfig": "apps/examples/angular/tic-tac-toe/jest.config.ts", 90 | "passWithNoTests": true 91 | } 92 | } 93 | }, 94 | "tags": [] 95 | } 96 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |

Tic Tac Toe

2 | 3 |

{{ vm.turn }}

4 |
5 |
6 | 7 |
8 | 11 |
12 |
13 |
14 |
15 | 16 |
17 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Remove template code below 3 | */ 4 | :host { 5 | display: grid; 6 | font-family: sans-serif; 7 | min-width: 300px; 8 | max-width: 600px; 9 | margin: 50px auto; 10 | align-content: center; 11 | justify-items: center; 12 | } 13 | 14 | .border { 15 | background-color: black; 16 | width: 200px; 17 | text-align: center; 18 | } 19 | 20 | .board { 21 | display: grid; 22 | grid-template-columns: fit-content(40px) fit-content(40px) fit-content(40px); 23 | grid-template-rows: fit-content(40px) fit-content(40px) fit-content(40px); 24 | grid-column-gap: 10px; 25 | grid-row-gap: 10px; 26 | & > div { 27 | background-color: white; 28 | padding: 10px; 29 | } 30 | margin: 0; 31 | button { 32 | text-align: center; 33 | height: 40px; 34 | width: 40px; 35 | } 36 | } 37 | 38 | .reset { 39 | margin-top: 40px; 40 | } 41 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async () => { 6 | await TestBed.configureTestingModule({ 7 | declarations: [AppComponent], 8 | }).compileComponents(); 9 | }); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | 17 | it('should render title', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | fixture.detectChanges(); 20 | const compiled = fixture.nativeElement as HTMLElement; 21 | expect(compiled.querySelector('h1')?.textContent).toContain('Tic Tac Toe'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { 3 | ticTacToeViewModel$, 4 | randomAi, 5 | SpaceCoordinates, 6 | BoardIndex, 7 | } from '@derxjs/examples/tic-tac-toe-view-model-implementation'; 8 | import { Observable, Observer } from 'rxjs'; 9 | 10 | @Component({ 11 | selector: 'derxjs-root', 12 | templateUrl: './app.component.html', 13 | styleUrls: ['./app.component.scss'], 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class AppComponent { 17 | userResetClickObserver!: Observer; 18 | userResetClickEvents$ = new Observable( 19 | (observer) => (this.userResetClickObserver = observer) 20 | ); 21 | userSpaceClickObserver!: Observer; 22 | userSpaceClickEvents$ = new Observable( 23 | (observer) => (this.userSpaceClickObserver = observer) 24 | ); 25 | vm$ = ticTacToeViewModel$({ 26 | ai: randomAi, 27 | userSpaceClickEvents$: this.userSpaceClickEvents$, 28 | userResetClickEvents$: this.userResetClickEvents$, 29 | }); 30 | rows: BoardIndex[] = [0, 1, 2]; 31 | 32 | handleSpaceClick(coordinates: SpaceCoordinates) { 33 | this.userSpaceClickObserver.next(coordinates); 34 | } 35 | 36 | handleResetClick() { 37 | this.userResetClickObserver.next(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | 4 | import { AppComponent } from './app.component'; 5 | 6 | @NgModule({ 7 | declarations: [AppComponent], 8 | imports: [BrowserModule], 9 | providers: [], 10 | bootstrap: [AppComponent], 11 | }) 12 | export class AppModule {} 13 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/angular/tic-tac-toe/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/angular/tic-tac-toe/src/favicon.ico -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ExamplesAngularTicTacToe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * IE11 requires the following for NgClass support on SVG elements 23 | */ 24 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 25 | 26 | /** 27 | * Web Animations `@angular/platform-browser/animations` 28 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 29 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 30 | */ 31 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 32 | 33 | /** 34 | * By default, zone.js will patch all possible macroTask and DomEvents 35 | * user can disable parts of macroTask/DomEvents patch by setting following flags 36 | * because those flags need to be set before `zone.js` being loaded, and webpack 37 | * will put import in the top of bundle, so user need to create a separate file 38 | * in this directory (for example: zone-flags.ts), and put the following flags 39 | * into that file, and then add the following code before importing zone.js. 40 | * import './zone-flags'; 41 | * 42 | * The flags allowed in zone-flags.ts are listed here. 43 | * 44 | * The following flags will work for all browsers. 45 | * 46 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 47 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 48 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 49 | * 50 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 51 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 52 | * 53 | * (window as any).__Zone_enable_cross_context_check = true; 54 | * 55 | */ 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js'; // Included with Angular CLI. 61 | 62 | /*************************************************************************************************** 63 | * APPLICATION IMPORTS 64 | */ 65 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting, 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | 9 | getTestBed().resetTestEnvironment(); 10 | getTestBed().initTestEnvironment( 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting(), 13 | { teardown: { destroyAfterEach: false } } 14 | ); 15 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "types": [], 6 | "target": "ES2022", 7 | "useDefineForClassFields": false 8 | }, 9 | "files": ["src/main.ts", "src/polyfills.ts"], 10 | "include": ["src/**/*.d.ts"], 11 | "exclude": ["jest.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/tsconfig.editor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "compilerOptions": { 5 | "types": ["jest", "node"] 6 | }, 7 | "exclude": ["jest.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | }, 12 | { 13 | "path": "./tsconfig.editor.json" 14 | } 15 | ], 16 | "compilerOptions": { 17 | "forceConsistentCasingInFileNames": true, 18 | "strict": true, 19 | "noImplicitReturns": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "lib": ["ESNext", "dom"], 22 | "target": "es2020" 23 | }, 24 | "angularCompilerOptions": { 25 | "strictInjectionParameters": true, 26 | "strictInputAccessModifiers": true, 27 | "strictTemplates": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/examples/angular/tic-tac-toe/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts", "jest.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/react/list-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/react/list-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "supportFile": "./src/support/index.ts", 7 | "pluginsFile": false, 8 | "video": true, 9 | "videosFolder": "../../../../dist/cypress/apps/examples/react/list-e2e/videos", 10 | "screenshotsFolder": "../../../../dist/cypress/apps/examples/react/list-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/examples/react/list-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-react-list-e2e", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/examples/react/list-e2e/src", 5 | "projectType": "application", 6 | "targets": { 7 | "e2e": { 8 | "executor": "@nx/cypress:cypress", 9 | "options": { 10 | "cypressConfig": "apps/examples/react/list-e2e/cypress.json", 11 | "devServerTarget": "examples-react-list:serve" 12 | }, 13 | "configurations": { 14 | "production": { 15 | "devServerTarget": "examples-react-list:serve:production" 16 | } 17 | } 18 | }, 19 | "lint": { 20 | "executor": "@nx/linter:eslint", 21 | "outputs": ["{options.outputFile}"], 22 | "options": { 23 | "lintFilePatterns": ["apps/examples/react/list-e2e/**/*.{js,ts}"] 24 | } 25 | } 26 | }, 27 | "tags": [], 28 | "implicitDependencies": ["examples-react-list"] 29 | } 30 | -------------------------------------------------------------------------------- /apps/examples/react/list-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/examples/react/list-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('examples-react-list', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to examples-react-list!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/examples/react/list-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/examples/react/list-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /apps/examples/react/list-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/examples/react/list-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/react/list/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nx/react/babel", 5 | { 6 | "runtime": "automatic" 7 | } 8 | ] 9 | ], 10 | "plugins": [] 11 | } 12 | -------------------------------------------------------------------------------- /apps/examples/react/list/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nx/react", "../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/examples/react/list/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'examples-react-list', 4 | preset: '../../../../jest.preset.js', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], 8 | }, 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 10 | coverageDirectory: '../../../../coverage/apps/examples/react/list', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/examples/react/list/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-react-list", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/examples/react/list/src", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/webpack:webpack", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/apps/examples/react/list", 12 | "index": "apps/examples/react/list/src/index.html", 13 | "main": "apps/examples/react/list/src/main.tsx", 14 | "polyfills": "apps/examples/react/list/src/polyfills.ts", 15 | "tsConfig": "apps/examples/react/list/tsconfig.app.json", 16 | "assets": [ 17 | "apps/examples/react/list/src/favicon.ico", 18 | "apps/examples/react/list/src/assets" 19 | ], 20 | "styles": ["apps/examples/react/list/src/styles.scss"], 21 | "scripts": [], 22 | "webpackConfig": "@nx/react/plugins/webpack" 23 | }, 24 | "configurations": { 25 | "production": { 26 | "fileReplacements": [ 27 | { 28 | "replace": "apps/examples/react/list/src/environments/environment.ts", 29 | "with": "apps/examples/react/list/src/environments/environment.prod.ts" 30 | } 31 | ], 32 | "optimization": true, 33 | "outputHashing": "all", 34 | "sourceMap": false, 35 | "extractCss": true, 36 | "namedChunks": false, 37 | "extractLicenses": true, 38 | "vendorChunk": false, 39 | "budgets": [ 40 | { 41 | "type": "initial", 42 | "maximumWarning": "500kb", 43 | "maximumError": "1mb" 44 | } 45 | ] 46 | } 47 | } 48 | }, 49 | "serve": { 50 | "executor": "@nx/webpack:dev-server", 51 | "options": { 52 | "buildTarget": "examples-react-list:build", 53 | "hmr": true 54 | }, 55 | "configurations": { 56 | "production": { 57 | "buildTarget": "examples-react-list:build:production", 58 | "hmr": false 59 | }, 60 | "development": { 61 | "buildTarget": "examples-react-list:build:development" 62 | } 63 | }, 64 | "defaultConfiguration": "development" 65 | }, 66 | "lint": { 67 | "executor": "@nx/linter:eslint", 68 | "outputs": ["{options.outputFile}"], 69 | "options": { 70 | "lintFilePatterns": ["apps/examples/react/list/**/*.{ts,tsx,js,jsx}"] 71 | } 72 | }, 73 | "test": { 74 | "executor": "@nx/jest:jest", 75 | "outputs": ["{workspaceRoot}/coverage/apps/examples/react/list"], 76 | "options": { 77 | "jestConfig": "apps/examples/react/list/jest.config.ts", 78 | "passWithNoTests": true 79 | } 80 | } 81 | }, 82 | "tags": [] 83 | } 84 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/app/CustomListImpl.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ListViewModel, 3 | ListViewModelInputs, 4 | } from '@derxjs/examples/list-view-model-implementation'; 5 | import { DeRxJSViewModel } from '@derxjs/view-model'; 6 | import { useEffect, useState } from 'react'; 7 | import { Observable, Observer } from 'rxjs'; 8 | 9 | let pushObserver: Observer; 10 | let popObserver: Observer; 11 | const push$ = new Observable((observer) => (pushObserver = observer)); 12 | const pop$ = new Observable((observer) => (popObserver = observer)); 13 | 14 | export const CustomListImpl = ( 15 | viewModel: DeRxJSViewModel, 16 | component: ({ 17 | viewModel, 18 | pushClick, 19 | popClick, 20 | }: { 21 | viewModel: ListViewModel; 22 | pushClick: (x: string) => void; 23 | popClick: () => void; 24 | }) => any 25 | ) => { 26 | const [viewModelValue, setViewModel] = useState([] as ListViewModel); 27 | const pushClick = (value: string) => pushObserver.next(value); 28 | const popClick = () => popObserver.next(); 29 | useEffect(() => { 30 | const subscription = viewModel({ push$, pop$, initialValue: [] }).subscribe( 31 | (x) => setViewModel(x) 32 | ); 33 | return () => subscription.unsubscribe(); 34 | }, []); 35 | return component({ viewModel: viewModelValue, popClick, pushClick }); 36 | }; 37 | 38 | export function ListView({ 39 | viewModel, 40 | pushClick, 41 | popClick, 42 | }: { 43 | viewModel: ListViewModel; 44 | pushClick: (x: string) => void; 45 | popClick: () => void; 46 | }) { 47 | const handlePush = (e: any) => { 48 | e.preventDefault(); 49 | const textInput = document.getElementById('textInput')! as HTMLInputElement; 50 | const formValue = textInput.value; 51 | pushClick(formValue); 52 | textInput.value = ''; 53 | }; 54 | 55 | return ( 56 |
57 |
58 | 59 | 60 |
61 |
    62 | {viewModel.map((item, i) => ( 63 |
  • {item}
  • 64 | ))} 65 |
66 | 67 |
68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/app/List.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ListViewModel, 3 | customListImpl$ as listViewModel$, 4 | ListViewModelInputs, 5 | } from '@derxjs/examples/list-view-model-implementation'; 6 | import { useRef } from 'react'; 7 | import { DeRxJSComponent } from '@derxjs/react'; 8 | 9 | export const List = ({ initialValue }: { initialValue: string[] }) => { 10 | return DeRxJSComponent>( 11 | { 12 | component: ListView as any, 13 | viewModel$: listViewModel$, 14 | initialValue, 15 | triggerMap: { 16 | pop: 'pop$', 17 | push: 'push$', 18 | }, 19 | inputs: { initialValue }, 20 | } 21 | ); 22 | }; 23 | 24 | export interface ListProps { 25 | initialState: T[]; 26 | push: (toPush: T) => void; 27 | pop: () => void; 28 | } 29 | 30 | function ListView({ 31 | state, 32 | triggers, 33 | }: { 34 | state: ListViewModel; 35 | triggers: { 36 | push: (x: string) => void; 37 | pop: () => void; 38 | }; 39 | }) { 40 | const textInputElement = useRef(null); 41 | const handlePush = (e: any) => { 42 | e.preventDefault(); 43 | const textInput = textInputElement.current!; 44 | const formValue = textInput.value; 45 | triggers.push(formValue); 46 | textInput.value = ''; 47 | }; 48 | return ( 49 | <> 50 |
51 | 52 | 53 |
54 |
    55 | {state.map((item, i) => ( 56 |
  • {item}
  • 57 | ))} 58 |
59 | 60 | 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/app/app.module.scss: -------------------------------------------------------------------------------- 1 | .app { 2 | font-family: sans-serif; 3 | min-width: 300px; 4 | max-width: 600px; 5 | margin: 50px auto; 6 | } 7 | 8 | .app :global(.gutter-left) { 9 | margin-left: 9px; 10 | } 11 | 12 | .app :global(.col-span-2) { 13 | grid-column: span 2; 14 | } 15 | 16 | .app :global(.flex) { 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | } 21 | 22 | .app :global(header) { 23 | background-color: #143055; 24 | color: white; 25 | padding: 5px; 26 | border-radius: 3px; 27 | } 28 | 29 | .app :global(main) { 30 | padding: 0 36px; 31 | } 32 | 33 | .app :global(p) { 34 | text-align: center; 35 | } 36 | 37 | .app :global(h1) { 38 | text-align: center; 39 | margin-left: 18px; 40 | font-size: 24px; 41 | } 42 | 43 | .app :global(h2) { 44 | text-align: center; 45 | font-size: 20px; 46 | margin: 40px 0 10px 0; 47 | } 48 | 49 | .app :global(.resources) { 50 | text-align: center; 51 | list-style: none; 52 | padding: 0; 53 | display: grid; 54 | grid-gap: 9px; 55 | grid-template-columns: 1fr 1fr; 56 | } 57 | 58 | .app :global(.resource) { 59 | color: #0094ba; 60 | height: 36px; 61 | background-color: rgba(0, 0, 0, 0); 62 | border: 1px solid rgba(0, 0, 0, 0.12); 63 | border-radius: 4px; 64 | padding: 3px 9px; 65 | text-decoration: none; 66 | } 67 | 68 | .app :global(.resource:hover) { 69 | background-color: rgba(68, 138, 255, 0.04); 70 | } 71 | 72 | .app :global(pre) { 73 | padding: 9px; 74 | border-radius: 4px; 75 | background-color: black; 76 | color: #eee; 77 | } 78 | 79 | .app :global(details) { 80 | border-radius: 4px; 81 | color: #333; 82 | background-color: rgba(0, 0, 0, 0); 83 | border: 1px solid rgba(0, 0, 0, 0.12); 84 | padding: 3px 9px; 85 | margin-bottom: 9px; 86 | } 87 | 88 | .app :global(summary) { 89 | outline: none; 90 | height: 36px; 91 | line-height: 36px; 92 | } 93 | 94 | .app :global(.github-star-container) { 95 | margin-top: 12px; 96 | line-height: 20px; 97 | } 98 | 99 | .app :global(.github-star-container a) { 100 | display: flex; 101 | align-items: center; 102 | text-decoration: none; 103 | color: #333; 104 | } 105 | 106 | .app :global(.github-star-badge) { 107 | color: #24292e; 108 | display: flex; 109 | align-items: center; 110 | font-size: 12px; 111 | padding: 3px 10px; 112 | border: 1px solid rgba(27, 31, 35, 0.2); 113 | border-radius: 3px; 114 | background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%); 115 | margin-left: 4px; 116 | font-weight: 600; 117 | } 118 | 119 | .app :global(.github-star-badge:hover) { 120 | background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%); 121 | border-color: rgba(27, 31, 35, 0.35); 122 | background-position: -0.5em; 123 | } 124 | .app :global(.github-star-badge .material-icons) { 125 | height: 16px; 126 | width: 16px; 127 | margin-right: 4px; 128 | } 129 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/app/app.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | 3 | import App from './app'; 4 | 5 | describe('App', () => { 6 | it('should render successfully', () => { 7 | const { baseElement } = render(); 8 | 9 | expect(baseElement).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/app/app.tsx: -------------------------------------------------------------------------------- 1 | import { customListImpl$ } from '@derxjs/examples/list-view-model-implementation'; 2 | import { CustomListImpl, ListView } from './CustomListImpl'; 3 | import { List } from './List'; 4 | 5 | export const App = () => { 6 | return ( 7 | <> 8 |

@derxj/view-model React Usage Example

9 | 10 |
11 | 12 |
13 | {CustomListImpl(customListImpl$, ListView)} 14 | 15 | ); 16 | }; 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/app/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/app/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/react/list/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/examples/react/list/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // When building for production, this file is replaced with `environment.prod.ts`. 3 | 4 | export const environment = { 5 | production: false, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/react/list/src/favicon.ico -------------------------------------------------------------------------------- /apps/examples/react/list/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | List 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | 4 | import App from './app/app'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. 3 | * 4 | * See: https://github.com/zloirock/core-js#babel 5 | */ 6 | import 'core-js/stable'; 7 | import 'regenerator-runtime/runtime'; 8 | -------------------------------------------------------------------------------- /apps/examples/react/list/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /apps/examples/react/list/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "../../../../node_modules/@nx/react/typings/cssmodule.d.ts", 9 | "../../../../node_modules/@nx/react/typings/image.d.ts" 10 | ], 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.test.ts", 14 | "**/*.spec.tsx", 15 | "**/*.test.tsx", 16 | "jest.config.ts" 17 | ], 18 | "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] 19 | } 20 | -------------------------------------------------------------------------------- /apps/examples/react/list/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true 12 | }, 13 | "files": [], 14 | "include": [], 15 | "references": [ 16 | { 17 | "path": "./tsconfig.app.json" 18 | }, 19 | { 20 | "path": "./tsconfig.spec.json" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /apps/examples/react/list/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.tsx", 12 | "**/*.test.tsx", 13 | "**/*.spec.js", 14 | "**/*.test.js", 15 | "**/*.spec.jsx", 16 | "**/*.test.jsx", 17 | "**/*.d.ts", 18 | "jest.config.ts" 19 | ], 20 | "files": [ 21 | "../../../../node_modules/@nx/react/typings/cssmodule.d.ts", 22 | "../../../../node_modules/@nx/react/typings/image.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "supportFile": "./src/support/index.ts", 7 | "pluginsFile": false, 8 | "video": true, 9 | "videosFolder": "../../../../dist/cypress/apps/examples/react/tic-tac-toe-e2e/videos", 10 | "screenshotsFolder": "../../../../dist/cypress/apps/examples/react/tic-tac-toe-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-react-tic-tac-toe-e2e", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/examples/react/tic-tac-toe-e2e/src", 5 | "projectType": "application", 6 | "targets": { 7 | "e2e": { 8 | "executor": "@nx/cypress:cypress", 9 | "options": { 10 | "cypressConfig": "apps/examples/react/tic-tac-toe-e2e/cypress.json", 11 | "devServerTarget": "examples-react-tic-tac-toe:serve" 12 | }, 13 | "configurations": { 14 | "production": { 15 | "devServerTarget": "examples-react-tic-tac-toe:serve:production" 16 | } 17 | } 18 | }, 19 | "lint": { 20 | "executor": "@nx/linter:eslint", 21 | "outputs": ["{options.outputFile}"], 22 | "options": { 23 | "lintFilePatterns": ["apps/examples/react/tic-tac-toe-e2e/**/*.{js,ts}"] 24 | } 25 | } 26 | }, 27 | "tags": [], 28 | "implicitDependencies": ["examples-react-tic-tac-toe"] 29 | } 30 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('examples-react-tic-tac-toe', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to examples-react-tic-tac-toe!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nx/react/babel", 5 | { 6 | "runtime": "automatic" 7 | } 8 | ] 9 | ], 10 | "plugins": [] 11 | } 12 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nx/react", "../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'examples-react-tic-tac-toe', 4 | preset: '../../../../jest.preset.js', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], 8 | }, 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 10 | coverageDirectory: '../../../../coverage/apps/examples/react/tic-tac-toe', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-react-tic-tac-toe", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/examples/react/tic-tac-toe/src", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/webpack:webpack", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/apps/examples/react/tic-tac-toe", 12 | "index": "apps/examples/react/tic-tac-toe/src/index.html", 13 | "main": "apps/examples/react/tic-tac-toe/src/main.tsx", 14 | "polyfills": "apps/examples/react/tic-tac-toe/src/polyfills.ts", 15 | "tsConfig": "apps/examples/react/tic-tac-toe/tsconfig.app.json", 16 | "assets": [ 17 | "apps/examples/react/tic-tac-toe/src/favicon.ico", 18 | "apps/examples/react/tic-tac-toe/src/assets" 19 | ], 20 | "styles": ["apps/examples/react/tic-tac-toe/src/styles.scss"], 21 | "scripts": [], 22 | "webpackConfig": "@nx/react/plugins/webpack" 23 | }, 24 | "configurations": { 25 | "production": { 26 | "fileReplacements": [ 27 | { 28 | "replace": "apps/examples/react/tic-tac-toe/src/environments/environment.ts", 29 | "with": "apps/examples/react/tic-tac-toe/src/environments/environment.prod.ts" 30 | } 31 | ], 32 | "optimization": true, 33 | "outputHashing": "all", 34 | "sourceMap": false, 35 | "extractCss": true, 36 | "namedChunks": false, 37 | "extractLicenses": true, 38 | "vendorChunk": false, 39 | "budgets": [ 40 | { 41 | "type": "initial", 42 | "maximumWarning": "500kb", 43 | "maximumError": "1mb" 44 | } 45 | ] 46 | } 47 | } 48 | }, 49 | "serve": { 50 | "executor": "@nx/webpack:dev-server", 51 | "options": { 52 | "buildTarget": "examples-react-tic-tac-toe:build", 53 | "hmr": true 54 | }, 55 | "configurations": { 56 | "production": { 57 | "buildTarget": "examples-react-tic-tac-toe:build:production", 58 | "hmr": false 59 | }, 60 | "development": { 61 | "buildTarget": "examples-react-tic-tac-toe:build:development" 62 | } 63 | }, 64 | "defaultConfiguration": "development" 65 | }, 66 | "lint": { 67 | "executor": "@nx/linter:eslint", 68 | "outputs": ["{options.outputFile}"], 69 | "options": { 70 | "lintFilePatterns": [ 71 | "apps/examples/react/tic-tac-toe/**/*.{ts,tsx,js,jsx}" 72 | ] 73 | } 74 | }, 75 | "test": { 76 | "executor": "@nx/jest:jest", 77 | "outputs": ["{workspaceRoot}/coverage/apps/examples/react/tic-tac-toe"], 78 | "options": { 79 | "jestConfig": "apps/examples/react/tic-tac-toe/jest.config.ts", 80 | "passWithNoTests": true 81 | } 82 | } 83 | }, 84 | "tags": [] 85 | } 86 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/app/TicTacToe.tsx: -------------------------------------------------------------------------------- 1 | import { DeRxJSComponent } from '@derxjs/react'; 2 | import { 3 | TicTacToeViewModelInputs, 4 | TicTacToeViewModel, 5 | SpaceCoordinates, 6 | SpaceContent, 7 | ticTacToeViewModel$, 8 | createInitialViewModel, 9 | randomAi, 10 | } from '@derxjs/examples/tic-tac-toe-view-model-implementation'; 11 | 12 | export const TicTacToe = () => { 13 | return DeRxJSComponent< 14 | TicTacToeViewModelInputs, 15 | TicTacToeViewModel, 16 | TicTacToeProps 17 | >({ 18 | viewModel$: ticTacToeViewModel$, 19 | component: TicTacToeView as any, 20 | initialValue: createInitialViewModel(), 21 | triggerMap: { 22 | spaceClick: 'userSpaceClickEvents$', 23 | resetClick: 'userResetClickEvents$', 24 | }, 25 | inputs: { 26 | ai: randomAi, 27 | }, 28 | }); 29 | }; 30 | 31 | export interface TicTacToeProps { 32 | spaceClick: (spaceCoordinates: SpaceCoordinates) => void; 33 | resetClick: () => void; 34 | } 35 | 36 | interface SpaceProps { 37 | contents: SpaceContent; 38 | spaceCoordinates: SpaceCoordinates; 39 | clickHandler: (spaceCoordinates: SpaceCoordinates) => void; 40 | } 41 | const Space = ({ contents, clickHandler, spaceCoordinates }: SpaceProps) => ( 42 |
43 | 46 |
47 | ); 48 | 49 | function TicTacToeView({ 50 | state, 51 | triggers, 52 | }: { 53 | state: TicTacToeViewModel; 54 | triggers: TicTacToeProps; 55 | }) { 56 | return ( 57 | <> 58 |

{state.turn}

59 |
60 |
61 | {([0, 1, 2] as const) 62 | .map((row) => ([0, 1, 2] as const).map((column) => [row, column])) 63 | .flat() 64 | .map(([row, column]) => ( 65 | 71 | ))} 72 |
73 |
74 | 77 | 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/app/app.module.scss: -------------------------------------------------------------------------------- 1 | .app { 2 | font-family: sans-serif; 3 | min-width: 300px; 4 | max-width: 600px; 5 | margin: 50px auto; 6 | } 7 | 8 | .app :global(.gutter-left) { 9 | margin-left: 9px; 10 | } 11 | 12 | .app :global(.col-span-2) { 13 | grid-column: span 2; 14 | } 15 | 16 | .app :global(.flex) { 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | } 21 | 22 | .app :global(header) { 23 | background-color: #143055; 24 | color: white; 25 | padding: 5px; 26 | border-radius: 3px; 27 | } 28 | 29 | .app :global(main) { 30 | padding: 0 36px; 31 | } 32 | 33 | .app :global(p) { 34 | text-align: center; 35 | } 36 | 37 | .app :global(h1) { 38 | text-align: center; 39 | margin-left: 18px; 40 | font-size: 24px; 41 | } 42 | 43 | .app :global(h2) { 44 | text-align: center; 45 | font-size: 20px; 46 | margin: 40px 0 10px 0; 47 | } 48 | 49 | .app :global(.resources) { 50 | text-align: center; 51 | list-style: none; 52 | padding: 0; 53 | display: grid; 54 | grid-gap: 9px; 55 | grid-template-columns: 1fr 1fr; 56 | } 57 | 58 | .app :global(.resource) { 59 | color: #0094ba; 60 | height: 36px; 61 | background-color: rgba(0, 0, 0, 0); 62 | border: 1px solid rgba(0, 0, 0, 0.12); 63 | border-radius: 4px; 64 | padding: 3px 9px; 65 | text-decoration: none; 66 | } 67 | 68 | .app :global(.resource:hover) { 69 | background-color: rgba(68, 138, 255, 0.04); 70 | } 71 | 72 | .app :global(pre) { 73 | padding: 9px; 74 | border-radius: 4px; 75 | background-color: black; 76 | color: #eee; 77 | } 78 | 79 | .app :global(details) { 80 | border-radius: 4px; 81 | color: #333; 82 | background-color: rgba(0, 0, 0, 0); 83 | border: 1px solid rgba(0, 0, 0, 0.12); 84 | padding: 3px 9px; 85 | margin-bottom: 9px; 86 | } 87 | 88 | .app :global(summary) { 89 | outline: none; 90 | height: 36px; 91 | line-height: 36px; 92 | } 93 | 94 | .app :global(.github-star-container) { 95 | margin-top: 12px; 96 | line-height: 20px; 97 | } 98 | 99 | .app :global(.github-star-container a) { 100 | display: flex; 101 | align-items: center; 102 | text-decoration: none; 103 | color: #333; 104 | } 105 | 106 | .app :global(.github-star-badge) { 107 | color: #24292e; 108 | display: flex; 109 | align-items: center; 110 | font-size: 12px; 111 | padding: 3px 10px; 112 | border: 1px solid rgba(27, 31, 35, 0.2); 113 | border-radius: 3px; 114 | background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%); 115 | margin-left: 4px; 116 | font-weight: 600; 117 | } 118 | 119 | .app :global(.github-star-badge:hover) { 120 | background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%); 121 | border-color: rgba(27, 31, 35, 0.35); 122 | background-position: -0.5em; 123 | } 124 | .app :global(.github-star-badge .material-icons) { 125 | height: 16px; 126 | width: 16px; 127 | margin-right: 4px; 128 | } 129 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/app/app.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | 3 | import App from './app'; 4 | 5 | describe('App', () => { 6 | it('should render successfully', () => { 7 | const { baseElement } = render(); 8 | 9 | expect(baseElement).toBeTruthy(); 10 | }); 11 | 12 | it('should have a greeting as the title', () => { 13 | const { getByText } = render(); 14 | 15 | expect(getByText('Tic Tac Toe')).toBeTruthy(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/app/app.tsx: -------------------------------------------------------------------------------- 1 | import styles from './app.module.scss'; 2 | 3 | import { TicTacToe } from './TicTacToe'; 4 | 5 | export function App() { 6 | return ( 7 | <> 8 |

Tic Tac Toe

9 | 10 | 11 | ); 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/app/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/app/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/react/tic-tac-toe/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // When building for production, this file is replaced with `environment.prod.ts`. 3 | 4 | export const environment = { 5 | production: false, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/react/tic-tac-toe/src/favicon.ico -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TicTacToe 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | 4 | import App from './app/app'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. 3 | * 4 | * See: https://github.com/zloirock/core-js#babel 5 | */ 6 | import 'core-js/stable'; 7 | import 'regenerator-runtime/runtime'; 8 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Remove template code below 3 | */ 4 | .app { 5 | display: grid; 6 | font-family: sans-serif; 7 | min-width: 300px; 8 | max-width: 600px; 9 | margin: 50px auto; 10 | align-content: center; 11 | justify-items: center; 12 | } 13 | 14 | .border { 15 | background-color: black; 16 | width: 200px; 17 | text-align: center; 18 | } 19 | 20 | .board { 21 | display: grid; 22 | grid-template-columns: fit-content(40px) fit-content(40px) fit-content(40px); 23 | grid-template-rows: fit-content(40px) fit-content(40px) fit-content(40px); 24 | grid-column-gap: 10px; 25 | grid-row-gap: 10px; 26 | & > div { 27 | background-color: white; 28 | padding: 10px; 29 | } 30 | margin: 0; 31 | button { 32 | text-align: center; 33 | height: 40px; 34 | width: 40px; 35 | } 36 | } 37 | 38 | .reset { 39 | margin-top: 40px; 40 | } 41 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "../../../../node_modules/@nx/react/typings/cssmodule.d.ts", 9 | "../../../../node_modules/@nx/react/typings/image.d.ts" 10 | ], 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.test.ts", 14 | "**/*.spec.tsx", 15 | "**/*.test.tsx", 16 | "jest.config.ts" 17 | ], 18 | "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] 19 | } 20 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "lib": ["ESNext", "DOM"] 13 | }, 14 | "files": [], 15 | "include": [], 16 | "references": [ 17 | { 18 | "path": "./tsconfig.app.json" 19 | }, 20 | { 21 | "path": "./tsconfig.spec.json" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /apps/examples/react/tic-tac-toe/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.tsx", 12 | "**/*.test.tsx", 13 | "**/*.spec.js", 14 | "**/*.test.js", 15 | "**/*.spec.jsx", 16 | "**/*.test.jsx", 17 | "**/*.d.ts", 18 | "jest.config.ts" 19 | ], 20 | "files": [ 21 | "../../../../node_modules/@nx/react/typings/cssmodule.d.ts", 22 | "../../../../node_modules/@nx/react/typings/image.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "supportFile": "./src/support/index.ts", 7 | "pluginsFile": false, 8 | "video": true, 9 | "videosFolder": "../../../../dist/cypress/apps/examples/vanilla/list-e2e/videos", 10 | "screenshotsFolder": "../../../../dist/cypress/apps/examples/vanilla/list-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-vanilla-list-e2e", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/examples/vanilla/list-e2e/src", 5 | "projectType": "application", 6 | "targets": { 7 | "e2e": { 8 | "executor": "@nx/cypress:cypress", 9 | "options": { 10 | "cypressConfig": "apps/examples/vanilla/list-e2e/cypress.json", 11 | "devServerTarget": "examples-vanilla-list:serve" 12 | }, 13 | "configurations": { 14 | "production": { 15 | "devServerTarget": "examples-vanilla-list:serve:production" 16 | } 17 | } 18 | }, 19 | "lint": { 20 | "executor": "@nx/linter:eslint", 21 | "outputs": ["{options.outputFile}"], 22 | "options": { 23 | "lintFilePatterns": ["apps/examples/vanilla/list-e2e/**/*.{js,ts}"] 24 | } 25 | } 26 | }, 27 | "tags": [], 28 | "implicitDependencies": ["examples-vanilla-list"] 29 | } 30 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('examples-vanilla-list', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to examples-vanilla-list!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@nrwl/js/babel" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'examples-vanilla-list', 4 | preset: '../../../../jest.preset.js', 5 | setupFilesAfterEnv: ['/src/test-setup.ts'], 6 | globals: {}, 7 | transform: { 8 | '^.+\\.[tj]s$': [ 9 | 'ts-jest', 10 | { 11 | tsconfig: '/tsconfig.spec.json', 12 | }, 13 | ], 14 | }, 15 | moduleFileExtensions: ['ts', 'js', 'html'], 16 | coverageDirectory: '../../../../coverage/apps/examples/vanilla/list', 17 | }; 18 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-vanilla-list", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/examples/vanilla/list/src", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/webpack:webpack", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/apps/examples/vanilla/list", 13 | "index": "apps/examples/vanilla/list/src/index.html", 14 | "main": "apps/examples/vanilla/list/src/main.ts", 15 | "polyfills": "apps/examples/vanilla/list/src/polyfills.ts", 16 | "tsConfig": "apps/examples/vanilla/list/tsconfig.app.json", 17 | "assets": [ 18 | "apps/examples/vanilla/list/src/favicon.ico", 19 | "apps/examples/vanilla/list/src/assets" 20 | ], 21 | "styles": ["apps/examples/vanilla/list/src/styles.css"], 22 | "scripts": [] 23 | }, 24 | "configurations": { 25 | "production": { 26 | "fileReplacements": [ 27 | { 28 | "replace": "apps/examples/vanilla/list/src/environments/environment.ts", 29 | "with": "apps/examples/vanilla/list/src/environments/environment.prod.ts" 30 | } 31 | ], 32 | "optimization": true, 33 | "outputHashing": "all", 34 | "sourceMap": false, 35 | "extractCss": true, 36 | "namedChunks": false, 37 | "extractLicenses": true, 38 | "vendorChunk": false, 39 | "budgets": [ 40 | { 41 | "type": "initial", 42 | "maximumWarning": "2mb", 43 | "maximumError": "5mb" 44 | } 45 | ] 46 | } 47 | } 48 | }, 49 | "serve": { 50 | "executor": "@nx/webpack:dev-server", 51 | "options": { 52 | "buildTarget": "examples-vanilla-list:build" 53 | }, 54 | "configurations": { 55 | "production": { 56 | "buildTarget": "examples-vanilla-list:build:production" 57 | }, 58 | "development": { 59 | "buildTarget": "examples-vanilla-list:build:development" 60 | } 61 | }, 62 | "defaultConfiguration": "development" 63 | }, 64 | "lint": { 65 | "executor": "@nx/linter:eslint", 66 | "outputs": ["{options.outputFile}"], 67 | "options": { 68 | "lintFilePatterns": ["apps/examples/vanilla/list/**/*.ts"] 69 | } 70 | }, 71 | "test": { 72 | "executor": "@nx/jest:jest", 73 | "outputs": ["{workspaceRoot}/coverage/apps/examples/vanilla/list"], 74 | "options": { 75 | "jestConfig": "apps/examples/vanilla/list/jest.config.ts", 76 | "passWithNoTests": true 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/app/app.element.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Remove template code below 3 | */ 4 | body > *:first-child { 5 | display: block; 6 | font-family: sans-serif; 7 | min-width: 300px; 8 | max-width: 600px; 9 | margin: 50px auto; 10 | } 11 | 12 | .gutter-left { 13 | margin-left: 9px; 14 | } 15 | 16 | .col-span-2 { 17 | grid-column: span 2; 18 | } 19 | 20 | .flex { 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | } 25 | 26 | header { 27 | background-color: #143055; 28 | color: white; 29 | padding: 5px; 30 | border-radius: 3px; 31 | } 32 | 33 | main { 34 | padding: 0 36px; 35 | } 36 | 37 | p { 38 | text-align: center; 39 | } 40 | 41 | h1 { 42 | text-align: center; 43 | margin-left: 18px; 44 | font-size: 24px; 45 | } 46 | 47 | h2 { 48 | text-align: center; 49 | font-size: 20px; 50 | margin: 40px 0 10px 0; 51 | } 52 | 53 | .resources { 54 | text-align: center; 55 | list-style: none; 56 | padding: 0; 57 | display: grid; 58 | grid-gap: 9px; 59 | grid-template-columns: 1fr 1fr; 60 | } 61 | 62 | .resource { 63 | color: #0094ba; 64 | height: 36px; 65 | background-color: rgba(0, 0, 0, 0); 66 | border: 1px solid rgba(0, 0, 0, 0.12); 67 | border-radius: 4px; 68 | padding: 3px 9px; 69 | text-decoration: none; 70 | } 71 | 72 | .resource:hover { 73 | background-color: rgba(68, 138, 255, 0.04); 74 | } 75 | 76 | pre { 77 | padding: 9px; 78 | border-radius: 4px; 79 | background-color: black; 80 | color: #eee; 81 | } 82 | 83 | details { 84 | border-radius: 4px; 85 | color: #333; 86 | background-color: rgba(0, 0, 0, 0); 87 | border: 1px solid rgba(0, 0, 0, 0.12); 88 | padding: 3px 9px; 89 | margin-bottom: 9px; 90 | } 91 | 92 | summary { 93 | cursor: pointer; 94 | outline: none; 95 | height: 36px; 96 | line-height: 36px; 97 | } 98 | 99 | .github-star-container { 100 | margin-top: 12px; 101 | line-height: 20px; 102 | } 103 | 104 | .github-star-container a { 105 | display: flex; 106 | align-items: center; 107 | text-decoration: none; 108 | color: #333; 109 | } 110 | 111 | .github-star-badge { 112 | color: #24292e; 113 | display: flex; 114 | align-items: center; 115 | font-size: 12px; 116 | padding: 3px 10px; 117 | border: 1px solid rgba(27, 31, 35, 0.2); 118 | border-radius: 3px; 119 | background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%); 120 | margin-left: 4px; 121 | font-weight: 600; 122 | } 123 | 124 | .github-star-badge:hover { 125 | background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%); 126 | border-color: rgba(27, 31, 35, 0.35); 127 | background-position: -0.5em; 128 | } 129 | .github-star-badge .material-icons { 130 | height: 16px; 131 | width: 16px; 132 | margin-right: 4px; 133 | } 134 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/app/app.element.ts: -------------------------------------------------------------------------------- 1 | import { fromEvent } from 'rxjs'; 2 | import { map, tap } from 'rxjs/operators'; 3 | import './app.element.css'; 4 | import { reducerListImpl$ as listViewModel$ } from '@derxjs/examples/list-view-model-implementation'; 5 | // import { customListImpl$ as listViewModel$ } from '@derxjs/examples/list-view-model-implementation'; 6 | 7 | const appDiv: HTMLElement = document.getElementById('app'); 8 | const formId = `push-form`; 9 | const textInputId = `text-input`; 10 | const pushButtonId = `push-button`; 11 | const popButtonId = `pop-button`; 12 | const listId = `list`; 13 | appDiv.innerHTML = `

@derxjs/view-model Usage Example

14 | 15 |
16 | 17 | 18 |
19 | 20 |
    21 | 22 | 23 | `; 24 | 25 | const listTarget = document.getElementById(listId); 26 | const pushForm = document.getElementById(formId) as HTMLFormElement; 27 | const textInput = document.getElementById(textInputId) as HTMLInputElement; 28 | const pushButton = document.getElementById(pushButtonId); 29 | const popButton = document.getElementById(popButtonId); 30 | 31 | pushForm.onsubmit = (event) => { 32 | event.preventDefault(); 33 | }; 34 | 35 | const push$ = fromEvent(pushButton, 'click').pipe( 36 | map(() => textInput.value), 37 | tap(() => (textInput.value = '')) 38 | ); 39 | const pop$ = fromEvent(popButton, 'click').pipe(map(() => undefined)); 40 | 41 | listViewModel$({ 42 | push$, 43 | pop$, 44 | initialValue: [], 45 | }).subscribe((list) => { 46 | listTarget.innerHTML = list.map((item) => `
  • ${item}
  • `).join(''); 47 | }); 48 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/vanilla/list/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // When building for production, this file is replaced with `environment.prod.ts`. 3 | 4 | export const environment = { 5 | production: false, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/vanilla/list/src/favicon.ico -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | List 6 | 7 | 8 | 9 | 10 | 11 | 12 |
    13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/main.ts: -------------------------------------------------------------------------------- 1 | import './app/app.element.ts'; 2 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. 3 | * 4 | * See: https://github.com/zloirock/core-js#babel 5 | */ 6 | import 'core-js/stable'; 7 | import 'regenerator-runtime/runtime'; 8 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/src/test-setup.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/vanilla/list/src/test-setup.ts -------------------------------------------------------------------------------- /apps/examples/vanilla/list/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/examples/vanilla/list/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts", "jest.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "supportFile": "./src/support/index.ts", 7 | "pluginsFile": false, 8 | "video": true, 9 | "videosFolder": "../../../../dist/cypress/apps/examples/vanilla/tic-tac-toe-e2e/videos", 10 | "screenshotsFolder": "../../../../dist/cypress/apps/examples/vanilla/tic-tac-toe-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-vanilla-tic-tac-toe-e2e", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/examples/vanilla/tic-tac-toe-e2e/src", 5 | "projectType": "application", 6 | "targets": { 7 | "e2e": { 8 | "executor": "@nx/cypress:cypress", 9 | "options": { 10 | "cypressConfig": "apps/examples/vanilla/tic-tac-toe-e2e/cypress.json", 11 | "devServerTarget": "examples-vanilla-tic-tac-toe:serve" 12 | }, 13 | "configurations": { 14 | "production": { 15 | "devServerTarget": "examples-vanilla-tic-tac-toe:serve:production" 16 | } 17 | } 18 | }, 19 | "lint": { 20 | "executor": "@nx/linter:eslint", 21 | "outputs": ["{options.outputFile}"], 22 | "options": { 23 | "lintFilePatterns": [ 24 | "apps/examples/vanilla/tic-tac-toe-e2e/**/*.{js,ts}" 25 | ] 26 | } 27 | } 28 | }, 29 | "tags": [], 30 | "implicitDependencies": ["examples-vanilla-tic-tac-toe"] 31 | } 32 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('examples-vanilla-tic-tac-toe', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to examples-vanilla-tic-tac-toe!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@nrwl/js/babel" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'examples-vanilla-tic-tac-toe', 4 | preset: '../../../../jest.preset.js', 5 | setupFilesAfterEnv: ['/src/test-setup.ts'], 6 | globals: {}, 7 | transform: { 8 | '^.+\\.[tj]s$': [ 9 | 'ts-jest', 10 | { 11 | tsconfig: '/tsconfig.spec.json', 12 | }, 13 | ], 14 | }, 15 | moduleFileExtensions: ['ts', 'js', 'html'], 16 | coverageDirectory: '../../../../coverage/apps/examples/vanilla/tic-tac-toe', 17 | }; 18 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-vanilla-tic-tac-toe", 3 | "$schema": "../../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/examples/vanilla/tic-tac-toe/src", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/webpack:webpack", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/apps/examples/vanilla/tic-tac-toe", 13 | "index": "apps/examples/vanilla/tic-tac-toe/src/index.html", 14 | "main": "apps/examples/vanilla/tic-tac-toe/src/main.ts", 15 | "polyfills": "apps/examples/vanilla/tic-tac-toe/src/polyfills.ts", 16 | "tsConfig": "apps/examples/vanilla/tic-tac-toe/tsconfig.app.json", 17 | "assets": [ 18 | "apps/examples/vanilla/tic-tac-toe/src/favicon.ico", 19 | "apps/examples/vanilla/tic-tac-toe/src/assets" 20 | ], 21 | "styles": ["apps/examples/vanilla/tic-tac-toe/src/styles.scss"], 22 | "scripts": [] 23 | }, 24 | "configurations": { 25 | "production": { 26 | "fileReplacements": [ 27 | { 28 | "replace": "apps/examples/vanilla/tic-tac-toe/src/environments/environment.ts", 29 | "with": "apps/examples/vanilla/tic-tac-toe/src/environments/environment.prod.ts" 30 | } 31 | ], 32 | "optimization": true, 33 | "outputHashing": "all", 34 | "sourceMap": false, 35 | "extractCss": true, 36 | "namedChunks": false, 37 | "extractLicenses": true, 38 | "vendorChunk": false, 39 | "budgets": [ 40 | { 41 | "type": "initial", 42 | "maximumWarning": "2mb", 43 | "maximumError": "5mb" 44 | } 45 | ] 46 | } 47 | } 48 | }, 49 | "serve": { 50 | "executor": "@nx/webpack:dev-server", 51 | "options": { 52 | "buildTarget": "examples-vanilla-tic-tac-toe:build" 53 | }, 54 | "configurations": { 55 | "production": { 56 | "buildTarget": "examples-vanilla-tic-tac-toe:build:production" 57 | }, 58 | "development": { 59 | "buildTarget": "examples-vanilla-tic-tac-toe:build:development" 60 | } 61 | }, 62 | "defaultConfiguration": "development" 63 | }, 64 | "lint": { 65 | "executor": "@nx/linter:eslint", 66 | "outputs": ["{options.outputFile}"], 67 | "options": { 68 | "lintFilePatterns": ["apps/examples/vanilla/tic-tac-toe/**/*.ts"] 69 | } 70 | }, 71 | "test": { 72 | "executor": "@nx/jest:jest", 73 | "outputs": ["{workspaceRoot}/coverage/apps/examples/vanilla/tic-tac-toe"], 74 | "options": { 75 | "jestConfig": "apps/examples/vanilla/tic-tac-toe/jest.config.ts", 76 | "passWithNoTests": true 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/app/app.element.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/vanilla/tic-tac-toe/src/app/app.element.scss -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/app/app.element.ts: -------------------------------------------------------------------------------- 1 | import { 2 | randomAi, 3 | SpaceCoordinates, 4 | ticTacToeViewModel$, 5 | } from '@derxjs/examples/tic-tac-toe-view-model-implementation'; 6 | import { fromEvent, merge, Observable } from 'rxjs'; 7 | import { map } from 'rxjs/operators'; 8 | import './app.element.scss'; 9 | 10 | const appDiv: HTMLElement = document.getElementById('app'); 11 | const turnTextId = 'turn-text'; 12 | 13 | appDiv.innerHTML = ` 14 |

    Tic Tac Toe

    15 |

    16 |
    17 |
    18 | ${([0, 1, 2] as const) 19 | .map((row) => ([0, 1, 2] as const).map((column) => [row, column])) 20 | .flat() 21 | .map( 22 | ([row, column]) => ` 23 |
    24 | 25 |
    26 | ` 27 | ) 28 | .join('')} 29 |
    30 |
    31 | 34 | `; 35 | 36 | const turnTextElement = document.getElementById(turnTextId); 37 | const spaceButtonElements: Record> = ( 38 | [0, 1, 2] as const 39 | ) 40 | .map((row) => ([0, 1, 2] as const).map((column) => [row, column])) 41 | .flat() 42 | .reduce((acc, [row, column]) => { 43 | const id = `row:${row}::column:${column}`; 44 | const buttonElement = document.getElementById(id) as HTMLButtonElement; 45 | acc[id] = fromEvent(buttonElement, 'click').pipe( 46 | map(() => ({ row, column })) 47 | ); 48 | return acc; 49 | }, {} as Record>); 50 | 51 | const userSpaceClickEvents$ = merge(...Object.values(spaceButtonElements)); 52 | const userResetClickEvents$ = fromEvent( 53 | document.getElementById('reset'), 54 | 'click' 55 | ).pipe(map(() => undefined)); 56 | const vm$ = ticTacToeViewModel$({ 57 | userSpaceClickEvents$, 58 | userResetClickEvents$, 59 | ai: randomAi, 60 | }); 61 | vm$.subscribe(({ board, turn }) => { 62 | turnTextElement.textContent = turn; 63 | ([0, 1, 2] as const) 64 | .map((row) => ([0, 1, 2] as const).map((column) => [row, column])) 65 | .flat() 66 | .forEach(([row, column]) => { 67 | const buttonElement = document.getElementById( 68 | `row:${row}::column:${column}` 69 | ); 70 | buttonElement.textContent = board[row][column].toUpperCase(); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/vanilla/tic-tac-toe/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // When building for production, this file is replaced with `environment.prod.ts`. 3 | 4 | export const environment = { 5 | production: false, 6 | }; 7 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/vanilla/tic-tac-toe/src/favicon.ico -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TicTacToe 6 | 7 | 8 | 9 | 10 | 11 | 12 |
    13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/main.ts: -------------------------------------------------------------------------------- 1 | import './app/app.element.ts'; 2 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. 3 | * 4 | * See: https://github.com/zloirock/core-js#babel 5 | */ 6 | import 'core-js/stable'; 7 | import 'regenerator-runtime/runtime'; 8 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Remove template code below 3 | */ 4 | #app { 5 | display: grid; 6 | font-family: sans-serif; 7 | min-width: 300px; 8 | max-width: 600px; 9 | margin: 50px auto; 10 | align-content: center; 11 | justify-items: center; 12 | } 13 | 14 | .border { 15 | background-color: black; 16 | width: 200px; 17 | text-align: center; 18 | } 19 | 20 | .board { 21 | display: grid; 22 | grid-template-columns: fit-content(40px) fit-content(40px) fit-content(40px); 23 | grid-template-rows: fit-content(40px) fit-content(40px) fit-content(40px); 24 | grid-column-gap: 10px; 25 | grid-row-gap: 10px; 26 | & > div { 27 | background-color: white; 28 | padding: 10px; 29 | } 30 | margin: 0; 31 | button { 32 | text-align: center; 33 | height: 40px; 34 | width: 40px; 35 | } 36 | } 37 | 38 | .reset { 39 | margin-top: 40px; 40 | } 41 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/src/test-setup.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/apps/examples/vanilla/tic-tac-toe/src/test-setup.ts -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "lib": ["DOM", "ESNext"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/examples/vanilla/tic-tac-toe/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts", "jest.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "babelrcRoots": ["*"] 3 | } 4 | -------------------------------------------------------------------------------- /derxjs-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/derxjs-logo.jpg -------------------------------------------------------------------------------- /derxjs-react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/derxjs-react-logo.png -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | const { getJestProjects } = require('@nx/jest'); 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nx/jest/preset').default; 2 | 3 | module.exports = { 4 | ...nxPreset, 5 | /* TODO: Update to latest Jest snapshotFormat 6 | * By default Nx has kept the older style of Jest Snapshot formats 7 | * to prevent breaking of any existing tests with snapshots. 8 | * It's recommend you update to the latest format. 9 | * You can do this by removing snapshotFormat property 10 | * and running tests with --update-snapshot flag. 11 | * Example: "nx affected --targets=test --update-snapshot" 12 | * More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format 13 | */ 14 | snapshotFormat: { escapeString: true, printBasicPrototype: true }, 15 | }; 16 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/js/babel", 5 | { 6 | "useBuiltIns": "usage" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/README.md: -------------------------------------------------------------------------------- 1 | # examples-list-view-model-implementation 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test examples-list-view-model-implementation` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'examples-list-view-model-implementation', 4 | preset: '../../../jest.preset.js', 5 | globals: {}, 6 | transform: { 7 | '^.+\\.[tj]sx?$': [ 8 | 'ts-jest', 9 | { 10 | tsconfig: '/tsconfig.spec.json', 11 | }, 12 | ], 13 | }, 14 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 15 | coverageDirectory: 16 | '../../../coverage/libs/examples/list-view-model-implementation', 17 | }; 18 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-list-view-model-implementation", 3 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "libs/examples/list-view-model-implementation/src", 5 | "projectType": "library", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nx/linter:eslint", 9 | "outputs": ["{options.outputFile}"], 10 | "options": { 11 | "lintFilePatterns": [ 12 | "libs/examples/list-view-model-implementation/**/*.ts" 13 | ] 14 | } 15 | }, 16 | "test": { 17 | "executor": "@nx/jest:jest", 18 | "outputs": [ 19 | "{workspaceRoot}/coverage/libs/examples/list-view-model-implementation" 20 | ], 21 | "options": { 22 | "jestConfig": "libs/examples/list-view-model-implementation/jest.config.ts", 23 | "passWithNoTests": true 24 | } 25 | } 26 | }, 27 | "tags": ["type:example"] 28 | } 29 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/src/index.ts: -------------------------------------------------------------------------------- 1 | export { listViewModel$ as customListImpl$ } from './lib/custom-implementation'; 2 | export { listViewModel$ as reducerListImpl$ } from './lib/implementation-using-reducer-package'; 3 | export * from './lib/types'; 4 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/src/lib/custom-implementation.ts: -------------------------------------------------------------------------------- 1 | import { DeRxJSViewModel } from '@derxjs/view-model'; 2 | import { merge } from 'rxjs'; 3 | import { map, scan, startWith } from 'rxjs/operators'; 4 | import { ListAction, ListViewModel, ListViewModelInputs } from './types'; 5 | 6 | export const listViewModel$: DeRxJSViewModel< 7 | ListViewModelInputs, 8 | ListViewModel 9 | > = ({ push$, pop$, initialValue }) => { 10 | return merge( 11 | push$.pipe(map((item) => ({ type: 'push' as const, item }))), 12 | pop$.pipe(map(() => ({ type: 'pop' as const }))) 13 | ).pipe(scan(reducer, initialValue), startWith(initialValue)); 14 | }; 15 | 16 | function reducer(state: ListViewModel, action: ListAction): ListViewModel { 17 | switch (action.type) { 18 | case 'push': { 19 | return [...state, action.item]; 20 | } 21 | case 'pop': { 22 | const newState = [...state]; 23 | newState.pop(); 24 | return newState; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/src/lib/implementation-using-reducer-package.ts: -------------------------------------------------------------------------------- 1 | import { actionize, createDeRxJSReducer } from '@derxjs/reducer'; 2 | import { DeRxJSViewModel } from '@derxjs/view-model'; 3 | import { map } from 'rxjs/operators'; 4 | import { ListAction, ListViewModel, ListViewModelInputs } from './types'; 5 | 6 | export const listViewModel$: DeRxJSViewModel< 7 | ListViewModelInputs, 8 | ListViewModel 9 | > = ({ push$, pop$, initialValue }) => { 10 | const state = createDeRxJSReducer({ 11 | reducer, 12 | incomingObservables: { 13 | push: push$.pipe( 14 | map((item) => ({ item })), 15 | actionize('push') 16 | ), 17 | pop: pop$.pipe( 18 | map(() => undefined), 19 | actionize('pop') 20 | ), 21 | }, // when out of beta, this should be: 22 | // incomingObservables: [push$.pipe(actionize('push'), pop$.pipe(actionize('pop')))] 23 | effects: [], 24 | sideEffects: [], 25 | initialState: initialValue, 26 | }); 27 | return state; 28 | }; 29 | 30 | function reducer(state: ListViewModel, action: ListAction): ListViewModel { 31 | switch (action.type) { 32 | case 'push': { 33 | return [...state, action.item]; 34 | } 35 | case 'pop': { 36 | const newState = [...state]; 37 | newState.pop(); 38 | return newState; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/src/lib/list-view-model-implementation.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestScheduler } from 'rxjs/testing'; 2 | import { listViewModel$ as customImpl$ } from './custom-implementation'; 3 | import { listViewModel$ as reducerPackageImpl$ } from './implementation-using-reducer-package'; 4 | import { DeRxJSListViewModel } from './types'; 5 | 6 | const implementations: DeRxJSListViewModel[] = [ 7 | customImpl$, 8 | reducerPackageImpl$, 9 | ]; 10 | 11 | const testSuite = (impl$: DeRxJSListViewModel) => { 12 | describe('viewModel', () => { 13 | let testScheduler: TestScheduler; 14 | 15 | beforeEach(() => { 16 | testScheduler = new TestScheduler((actual, expected) => 17 | expect(actual).toEqual(expected) 18 | ); 19 | }); 20 | 21 | test('with no user events, the list stays empty', () => { 22 | const initialValue = []; 23 | testScheduler.run(({ cold, expectObservable }) => { 24 | const push$ = cold('------'); 25 | const pop$ = cold('------'); 26 | const expected = cold('a-----', { a: [] }); 27 | const result = impl$({ 28 | push$, 29 | pop$, 30 | initialValue, 31 | }); 32 | expectObservable(result).toEqual(expected); 33 | }); 34 | }); 35 | 36 | test('push pop push push pop push push pop pop pop pop pop', () => { 37 | const initialValue = []; 38 | testScheduler.run(({ cold, expectObservable }) => { 39 | const push$ = cold('--a-----b-c----d-e-f', { 40 | a: 'a', 41 | b: 'b', 42 | c: 'c', 43 | d: 'd', 44 | e: 'e', 45 | f: 'f', 46 | }); 47 | const pop$ = cold(' -----z------z--------z-z-z-z'); 48 | const expected = cold(' a-b--c--d-e-f--g-h-i-j-k-l-m', { 49 | a: [], 50 | b: ['a'], 51 | c: [], 52 | d: ['b'], 53 | e: ['b', 'c'], 54 | f: ['b'], 55 | g: ['b', 'd'], 56 | h: ['b', 'd', 'e'], 57 | i: ['b', 'd', 'e', 'f'], 58 | j: ['b', 'd', 'e'], 59 | k: ['b', 'd'], 60 | l: ['b'], 61 | m: [], 62 | }); 63 | const result = impl$({ 64 | push$, 65 | pop$, 66 | initialValue, 67 | }); 68 | expectObservable(result).toEqual(expected); 69 | }); 70 | }); 71 | }); 72 | }; 73 | 74 | // note: the DeRxJSViewModel pattern allows us to use the same exact 75 | // test suite to test either implementation 76 | implementations.forEach(testSuite); 77 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/src/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { DeRxJSViewModel } from 'packages/view-model/src/lib/view-model'; 2 | import { Observable } from 'rxjs'; 3 | 4 | export type DeRxJSListViewModel = DeRxJSViewModel< 5 | ListViewModelInputs, 6 | ListViewModel 7 | >; 8 | 9 | export interface ListViewModelInputs { 10 | push$: Observable; 11 | pop$: Observable; 12 | initialValue: string[]; 13 | } 14 | 15 | export type ListViewModel = string[]; 16 | 17 | export type ListAction = { type: 'push'; item: string } | { type: 'pop' }; 18 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "declaration": true, 6 | "types": [] 7 | }, 8 | "include": ["**/*.ts"], 9 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/examples/list-view-model-implementation/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.tsx", 12 | "**/*.test.tsx", 13 | "**/*.spec.js", 14 | "**/*.test.js", 15 | "**/*.spec.jsx", 16 | "**/*.test.jsx", 17 | "**/*.d.ts", 18 | "jest.config.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/js/babel", 5 | { 6 | "useBuiltIns": "usage" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/README.md: -------------------------------------------------------------------------------- 1 | # examples-tic-tac-toe-view-model-implementation 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test examples-tic-tac-toe-view-model-implementation` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'examples-tic-tac-toe-view-model-implementation', 4 | preset: '../../../jest.preset.js', 5 | globals: {}, 6 | transform: { 7 | '^.+\\.[tj]sx?$': [ 8 | 'ts-jest', 9 | { 10 | tsconfig: '/tsconfig.spec.json', 11 | }, 12 | ], 13 | }, 14 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 15 | coverageDirectory: 16 | '../../../coverage/libs/examples/tic-tac-toe-view-model-implementation', 17 | }; 18 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples-tic-tac-toe-view-model-implementation", 3 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "libs/examples/tic-tac-toe-view-model-implementation/src", 5 | "projectType": "library", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nx/linter:eslint", 9 | "outputs": ["{options.outputFile}"], 10 | "options": { 11 | "lintFilePatterns": [ 12 | "libs/examples/tic-tac-toe-view-model-implementation/**/*.ts" 13 | ] 14 | } 15 | }, 16 | "test": { 17 | "executor": "@nx/jest:jest", 18 | "outputs": [ 19 | "{workspaceRoot}/coverage/libs/examples/tic-tac-toe-view-model-implementation" 20 | ], 21 | "options": { 22 | "jestConfig": "libs/examples/tic-tac-toe-view-model-implementation/jest.config.ts", 23 | "passWithNoTests": true 24 | } 25 | } 26 | }, 27 | "tags": [] 28 | } 29 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/src/index.ts: -------------------------------------------------------------------------------- 1 | export { ticTacToeViewModel$, createInitialViewModel } from './lib/view-model'; 2 | export * from './lib/types'; 3 | export * from './lib/random-ai'; 4 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/src/lib/random-ai.ts: -------------------------------------------------------------------------------- 1 | import { SpaceCoordinates } from '..'; 2 | import { TicTacToeAi } from './types'; 3 | 4 | export const randomAi: TicTacToeAi = ({ board }) => { 5 | const openSpots: SpaceCoordinates[] = []; 6 | for (let row = 0; row <= 2; row++) { 7 | for (let col = 0; col <= 2; col++) { 8 | if (board[row][col] === '') { 9 | openSpots.push({ row, column: col } as SpaceCoordinates); 10 | } 11 | } 12 | } 13 | const index = Math.floor(Math.random() * openSpots.length); 14 | return openSpots[index]; 15 | }; 16 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/src/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | export interface TicTacToeViewModelInputs { 4 | userSpaceClickEvents$: Observable; 5 | userResetClickEvents$: Observable; 6 | ai: TicTacToeAi; 7 | } 8 | 9 | export type TicTacToeAi = (params: AiParams) => SpaceCoordinates; 10 | interface AiParams { 11 | board: Board; 12 | aiLetter: 'o' | 'x'; 13 | } 14 | 15 | export interface TicTacToeViewModel { 16 | board: Board; 17 | turn: Turn; 18 | } 19 | 20 | export type Turn = 21 | | 'your turn' 22 | | `computer's turn` 23 | | 'game over - you win' 24 | | 'game over - you lose' 25 | | `game over - it's a tie`; 26 | 27 | export type SpaceContent = 'x' | 'o' | ''; 28 | 29 | export type Board = [ 30 | [SpaceContent, SpaceContent, SpaceContent], 31 | [SpaceContent, SpaceContent, SpaceContent], 32 | [SpaceContent, SpaceContent, SpaceContent] 33 | ]; 34 | 35 | export type BoardIndex = 0 | 1 | 2; 36 | 37 | export interface SpaceCoordinates { 38 | row: BoardIndex; 39 | column: BoardIndex; 40 | } 41 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "compilerOptions": { 6 | "lib": ["esnext", "dom"] 7 | }, 8 | "references": [ 9 | { 10 | "path": "./tsconfig.lib.json" 11 | }, 12 | { 13 | "path": "./tsconfig.spec.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "declaration": true, 6 | "types": [] 7 | }, 8 | "include": ["**/*.ts"], 9 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/examples/tic-tac-toe-view-model-implementation/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.tsx", 12 | "**/*.test.tsx", 13 | "**/*.spec.js", 14 | "**/*.test.js", 15 | "**/*.spec.jsx", 16 | "**/*.test.jsx", 17 | "**/*.d.ts", 18 | "jest.config.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasksRunnerOptions": { 3 | "default": { 4 | "runner": "nx-cloud", 5 | "options": { 6 | "cacheableOperations": ["build", "lint", "test", "e2e"], 7 | "accessToken": "MTc5YzBiOGQtZWExOS00YzJjLTk5YjAtYmYzYmIxNGZlZGU4fHJlYWQtd3JpdGU=", 8 | "parallel": 1 9 | } 10 | } 11 | }, 12 | "extends": "nx/presets/npm.json", 13 | "npmScope": "derxjs", 14 | "affected": { 15 | "defaultBase": "master" 16 | }, 17 | "workspaceLayout": { 18 | "libsDir": "libs" 19 | }, 20 | "cli": { 21 | "defaultCollection": "@nx/angular" 22 | }, 23 | "generators": { 24 | "@nx/angular:application": { 25 | "style": "scss", 26 | "linter": "eslint", 27 | "unitTestRunner": "jest", 28 | "e2eTestRunner": "cypress" 29 | }, 30 | "@nx/angular:library": { 31 | "linter": "eslint", 32 | "unitTestRunner": "jest" 33 | }, 34 | "@nx/angular:component": { 35 | "style": "scss" 36 | }, 37 | "@nx/web:application": { 38 | "style": "css", 39 | "linter": "eslint", 40 | "unitTestRunner": "jest", 41 | "e2eTestRunner": "cypress" 42 | }, 43 | "@nx/web:library": { 44 | "style": "css", 45 | "linter": "eslint", 46 | "unitTestRunner": "jest" 47 | }, 48 | "@nx/react": { 49 | "application": { 50 | "style": "scss", 51 | "linter": "eslint", 52 | "babel": true 53 | }, 54 | "component": { 55 | "style": "scss" 56 | }, 57 | "library": { 58 | "style": "scss", 59 | "linter": "eslint" 60 | } 61 | } 62 | }, 63 | "defaultProject": "examples-vanilla-list", 64 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 65 | "targetDefaults": { 66 | "lint": { 67 | "inputs": ["default", "{workspaceRoot}/.eslintrc.json"] 68 | }, 69 | "e2e": { 70 | "inputs": ["default", "^default"] 71 | }, 72 | "test": { 73 | "inputs": ["default", "^default", "{workspaceRoot}/jest.preset.js"] 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "derxjs", 3 | "version": "1.4.1", 4 | "license": "MIT", 5 | "scripts": { 6 | "publish-packages": "npx ts-node tools/scripts/publish-all.ts" 7 | }, 8 | "private": true, 9 | "dependencies": { 10 | "@angular/animations": "16.1.9", 11 | "@angular/common": "16.1.9", 12 | "@angular/compiler": "16.1.9", 13 | "@angular/core": "16.1.9", 14 | "@angular/forms": "16.1.9", 15 | "@angular/platform-browser": "16.1.9", 16 | "@angular/platform-browser-dynamic": "16.1.9", 17 | "@angular/router": "16.1.9", 18 | "@swc/helpers": "~0.5.0", 19 | "core-js": "^3.6.5", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0", 22 | "react-is": "18.2.0", 23 | "regenerator-runtime": "0.13.7", 24 | "rxjs": "7.8.1", 25 | "tslib": "^2.3.0", 26 | "zone.js": "0.13.1" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "16.1.8", 30 | "@angular-devkit/core": "16.1.8", 31 | "@angular-devkit/schematics": "16.1.8", 32 | "@angular-eslint/eslint-plugin": "16.0.3", 33 | "@angular-eslint/eslint-plugin-template": "16.0.3", 34 | "@angular-eslint/template-parser": "16.0.3", 35 | "@angular/compiler-cli": "16.1.9", 36 | "@angular/language-service": "16.1.9", 37 | "@nrwl/js": "16.6.0", 38 | "@nx/cypress": "16.6.0", 39 | "@nx/devkit": "16.6.0", 40 | "@nx/eslint-plugin": "16.6.0", 41 | "@nx/jest": "16.6.0", 42 | "@nx/js": "16.6.0", 43 | "@nx/linter": "16.6.0", 44 | "@nx/plugin": "16.6.0", 45 | "@nx/react": "16.6.0", 46 | "@nx/web": "16.6.0", 47 | "@nx/workspace": "16.6.0", 48 | "@schematics/angular": "16.1.8", 49 | "@swc-node/register": "~1.4.2", 50 | "@swc/cli": "~0.1.62", 51 | "@swc/core": "~1.3.51", 52 | "@testing-library/react": "14.0.0", 53 | "@types/jest": "29.4.4", 54 | "@types/node": "18.14.2", 55 | "@types/prompt": "^1.1.1", 56 | "@types/react": "18.2.14", 57 | "@types/react-dom": "18.2.6", 58 | "@typescript-eslint/eslint-plugin": "5.62.0", 59 | "@typescript-eslint/parser": "5.62.0", 60 | "babel-jest": "29.4.3", 61 | "cypress": "^8.3.0", 62 | "eslint": "8.15.0", 63 | "eslint-config-prettier": "8.1.0", 64 | "eslint-plugin-cypress": "^2.10.3", 65 | "eslint-plugin-import": "2.27.5", 66 | "eslint-plugin-jsx-a11y": "6.7.1", 67 | "eslint-plugin-react": "7.32.2", 68 | "eslint-plugin-react-hooks": "4.6.0", 69 | "jest": "29.4.3", 70 | "jest-environment-jsdom": "^29.4.1", 71 | "jest-preset-angular": "13.1.1", 72 | "nx": "16.6.0", 73 | "nx-cloud": "16.3.0", 74 | "prettier": "2.6.2", 75 | "prompt": "^1.2.0", 76 | "ts-jest": "29.1.1", 77 | "ts-node": "10.9.1", 78 | "typescript": "5.1.6", 79 | "webpack-sources": "^3.2.1", 80 | "@nx/angular": "16.6.0" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/packages/.gitkeep -------------------------------------------------------------------------------- /packages/react/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/js/babel", 5 | { 6 | "useBuiltIns": "usage" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/react/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/react/LICENSE.md: -------------------------------------------------------------------------------- 1 | **Note: This API is Subject to Change!** 2 | _(@derxjs/view-model is stable however)_ 3 | 4 | # @derxjs/react 5 | 6 | Because your state management code should be domain-agnostic. 7 | 8 |

    9 | 10 |

    11 | 12 | ## Installation 13 | 14 | ```bash 15 | npm i @derxjs/react 16 | ``` 17 | 18 | ## Usage 19 | 20 | - More docs to come for 1.0.0 release 21 | 22 | ## Why @derxjs 23 | 24 | ### Domain-agnostic state-management 25 | 26 | Your state management code should not depend on which framework or tools your project happens to be using at the time. 27 | 28 | `@derxjs/view-model` is all about first-principles thinking and problem-solving. The pattern enforced by this package requires you to break down the inputs of your system - regardless of scope - to some set of inputs, represented as RxJS Observables, and 29 | 30 | Future packages on the roadmap will provide utilities for implementing this pattern (`@derxjs/reducer` 👀), as well as ultilities for plugging this pattern into popular front-end frameworks (`@derxjs/react` 👀). 31 | 32 | ### Separation of concerns 33 | 34 | We solved this a long time ago. Programming to interfaces allows us to put a joint in our wrokflows that allows for parallel work to be completed by multiple developers, and lets your team play to their strengths. 35 | 36 |

    37 | 38 |

    39 | 40 | This allows for easy transitions into other implementations, frameworks, as well as implementing the facade, adapter, and proxy patterns from the Gang of Four. 41 | 42 | ### Complimentary to all existing state-management libraries 43 | 44 | We're not here to take a shot at the king ([👑](https://ngrx.io/)👀) - we're just here to help out where we can! 45 | 46 | The `@derxjs/view-model` package is designed to work with with any other state management frameworks that can expose state or events as an Observable, making it a great compliment to any and all existing code in your codebase. 47 | 48 | ### Future-Proof Code 49 | 50 | Domain-agnostic first-principles-based code will never go out of style 🌲. 51 | 52 | As long as JavaScript is the language of the web, your state-management code will be valid. 53 | 54 | Go ahead, change to that trendy new framework. Your @derxjs code will still work just fine :). 55 | 56 | ### Simplicity && Elegence 57 | 58 | The `DeRxJSViewModel` type is the `E = mc^2` of state management. 59 | 60 | Deceptively simple, but elegant enough to encompass any && all of your state management requirements. 61 | 62 |

    63 | 64 |

    65 | 66 | ### TDD made awesome with timeline testing 67 | 68 | Embrace TDD, using timeline testing to test your code with a whole new dimension of precision. 69 | 70 | On the roadmap for `@derxjs` is a timeline test generation GUI tool that will take your Typescript interface code, and allow you to "draw" hypothetical timelines of events from your inputs - specifying what the output timeline for each hypothetical should look like. 71 | 72 | This tool will generate `.spec.ts` files that you can paste directly into your repos for easy TDD, and coding the way we were meant to. 73 | 74 | ## @derxjs Roadmap 75 | 76 | - @derxjs/view-model package ✅ 77 | - Article on TDD and implementing DeRxJS View Models (10/8/2021) 78 | - Article on using DeRxJS View Models in different Frameworks (10/15/2021) 79 | - @derxjs/reducer package (TBD; beta available now [you're looking at it!]) 80 | - Timeline Test Code Generation Tool (TBD) 81 | - @derxjs/selector package (TBD) 82 | - @derxjs/react package (TBD) 83 | - Ai-Driven DeRxJS Code Generation (TBD) 84 | -------------------------------------------------------------------------------- /packages/react/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: '..-packages-react', 4 | preset: '../../jest.preset.js', 5 | globals: {}, 6 | transform: { 7 | '^.+\\.[tj]sx?$': [ 8 | 'ts-jest', 9 | { 10 | tsconfig: '/tsconfig.spec.json', 11 | }, 12 | ], 13 | }, 14 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 15 | coverageDirectory: '../../coverage/libs/../packages/react', 16 | }; 17 | -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@derxjs/react", 3 | "version": "1.4.1", 4 | "main": "src/index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/ZackDeRose/derxjs.git" 11 | }, 12 | "keywords": [ 13 | "rxjs", 14 | "state-management" 15 | ], 16 | "author": "Zack DeRose", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/ZackDeRose/derxjs/issues" 20 | }, 21 | "homepage": "https://github.com/ZackDeRose/derxjs#readme", 22 | "dependencies": { 23 | "rxjs": "^7.4.0", 24 | "typescript": "4.7.3", 25 | "@derxjs/view-model": "^1.4.0", 26 | "react": "18.2.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/react/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/react/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/js:tsc", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/packages/react", 12 | "main": "packages/react/src/index.ts", 13 | "tsConfig": "packages/react/tsconfig.lib.json", 14 | "assets": ["packages/react/*.md"], 15 | "updateBuildableProjectDepsInPackageJson": true 16 | }, 17 | "outputPath": "packages/react/dist" 18 | }, 19 | "lint": { 20 | "executor": "@nx/linter:eslint", 21 | "outputs": ["{options.outputFile}"], 22 | "options": { 23 | "lintFilePatterns": ["packages/react/**/*.ts"] 24 | } 25 | }, 26 | "test": { 27 | "executor": "@nx/jest:jest", 28 | "outputs": ["{workspaceRoot}/coverage/packages/react"], 29 | "options": { 30 | "jestConfig": "packages/react/jest.config.ts", 31 | "passWithNoTests": true 32 | } 33 | }, 34 | "publish": { 35 | "executor": "nx:run-commands", 36 | "options": { 37 | "commands": [ 38 | "npx ts-node tools/scripts/bump-version.ts react {args.version}", 39 | "nx build react", 40 | "npx ts-node tools/scripts/publish.ts react" 41 | ], 42 | "parallel": false 43 | } 44 | } 45 | }, 46 | "tags": [] 47 | } 48 | -------------------------------------------------------------------------------- /packages/react/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/DeRxJSComponent'; 2 | -------------------------------------------------------------------------------- /packages/react/src/lib/DeRxJSComponent.tsx: -------------------------------------------------------------------------------- 1 | import { DeRxJSViewModel } from '@derxjs/view-model'; 2 | import { useEffect, useRef, useState } from 'react'; 3 | import { Observable, Observer } from 'rxjs'; 4 | 5 | export const DeRxJSComponent = ({ 6 | viewModel$, 7 | component, 8 | triggerMap, 9 | initialValue, 10 | inputs, 11 | }: DeRxJSViewModelComponentProps) => { 12 | const [state, setState] = useState(initialValue); 13 | 14 | const triggers = useRef( 15 | {} as Partial void>> 16 | ); 17 | 18 | /** Subscribe */ 19 | useEffect(() => { 20 | const observers: Partial>> = {}; 21 | const observables: Partial>> = {}; 22 | for (const [triggerKey, inputKey] of Object.entries(triggerMap)) { 23 | const inputName = inputKey as keyof InputType; 24 | const triggerName = triggerKey as keyof PropType; 25 | 26 | observables[inputName] = new Observable((observer) => { 27 | observers[triggerName] = observer; 28 | triggers.current[triggerName] = (x: any) => observer.next(x); 29 | }) as any; 30 | } 31 | const subscription = viewModel$({ 32 | ...inputs, 33 | ...observables, 34 | } as InputType).subscribe(setState); 35 | return () => subscription.unsubscribe(); 36 | }, []); 37 | return component({ 38 | state: state || initialValue, 39 | triggers: triggers.current, 40 | }); 41 | }; 42 | 43 | export interface DeRxJSViewModelComponentProps< 44 | InputType, 45 | ViewModelType, 46 | PropType 47 | > { 48 | viewModel$: DeRxJSViewModel; 49 | component: (x: { 50 | state: ViewModelType; 51 | triggers: Partial void>>; 52 | }) => JSX.Element; 53 | triggerMap: Partial>; 54 | // valueMap: Partial>; 55 | initialValue: ViewModelType; 56 | inputs: Partial; 57 | } 58 | -------------------------------------------------------------------------------- /packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "compilerOptions": { 6 | "module": "commonjs", 7 | "jsx": "react-jsx" 8 | }, 9 | "references": [ 10 | { 11 | "path": "./tsconfig.lib.json" 12 | }, 13 | { 14 | "path": "./tsconfig.spec.json" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/react/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": [] 7 | }, 8 | "include": ["**/*.ts"], 9 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/react/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.tsx", 12 | "**/*.test.tsx", 13 | "**/*.spec.js", 14 | "**/*.test.js", 15 | "**/*.spec.jsx", 16 | "**/*.test.jsx", 17 | "**/*.d.ts", 18 | "jest.config.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/reducer/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/js/babel", 5 | { 6 | "useBuiltIns": "usage" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/reducer/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/reducer/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /packages/reducer/.npmignore: -------------------------------------------------------------------------------- 1 | jest.config.js 2 | project.json 3 | .eslint.json 4 | -------------------------------------------------------------------------------- /packages/reducer/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Zachary DeRose 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 | -------------------------------------------------------------------------------- /packages/reducer/README.md: -------------------------------------------------------------------------------- 1 | **Note: This API is Subject to Change!** 2 | _(@derxjs/view-model is stable however)_ 3 | 4 | # @derxjs/reducer 5 | 6 | Because your state management code should be domain-agnostic. 7 | 8 |

    9 | 10 |

    11 | 12 | ## Installation 13 | 14 | ```bash 15 | npm i @derxjs/reducer 16 | ``` 17 | 18 | ## Usage 19 | 20 | - [Example Tic Tac Toe App on Github (fully tested with Timeline Tests)](https://github.com/ZackDeRose/tic-tac-toe-rxjs-view-model/blob/main/libs/view-model/src/lib/using-reducer-package.ts) 21 | - See the [@derxjs/view-model package](https://github.com/ZackDeRose/view-model) for general usage of the @derxjs/view-model pattern 22 | - More docs to come for 1.0.0 release 23 | 24 | ## Why @derxjs 25 | 26 | ### Domain-agnostic state-management 27 | 28 | Your state management code should not depend on which framework or tools your project happens to be using at the time. 29 | 30 | `@derxjs/view-model` is all about first-principles thinking and problem-solving. The pattern enforced by this package requires you to break down the inputs of your system - regardless of scope - to some set of inputs, represented as RxJS Observables, and 31 | 32 | Future packages on the roadmap will provide utilities for implementing this pattern (`@derxjs/reducer` 👀), as well as ultilities for plugging this pattern into popular front-end frameworks (`@derxjs/react` 👀). 33 | 34 | ### Separation of concerns 35 | 36 | We solved this a long time ago. Programming to interfaces allows us to put a joint in our wrokflows that allows for parallel work to be completed by multiple developers, and lets your team play to their strengths. 37 | 38 |

    39 | 40 |

    41 | 42 | This allows for easy transitions into other implementations, frameworks, as well as implementing the facade, adapter, and proxy patterns from the Gang of Four. 43 | 44 | ### Complimentary to all existing state-management libraries 45 | 46 | We're not here to take a shot at the king ([👑](https://ngrx.io/)👀) - we're just here to help out where we can! 47 | 48 | The `@derxjs/view-model` package is designed to work with with any other state management frameworks that can expose state or events as an Observable, making it a great compliment to any and all existing code in your codebase. 49 | 50 | ### Future-Proof Code 51 | 52 | Domain-agnostic first-principles-based code will never go out of style 🌲. 53 | 54 | As long as JavaScript is the language of the web, your state-management code will be valid. 55 | 56 | Go ahead, change to that trendy new framework. Your @derxjs code will still work just fine :). 57 | 58 | ### Simplicity && Elegence 59 | 60 | The `DeRxJSViewModel` type is the `E = mc^2` of state management. 61 | 62 | Deceptively simple, but elegant enough to encompass any && all of your state management requirements. 63 | 64 |

    65 | 66 |

    67 | 68 | ### TDD made awesome with timeline testing 69 | 70 | Embrace TDD, using timeline testing to test your code with a whole new dimension of precision. 71 | 72 | On the roadmap for `@derxjs` is a timeline test generation GUI tool that will take your Typescript interface code, and allow you to "draw" hypothetical timelines of events from your inputs - specifying what the output timeline for each hypothetical should look like. 73 | 74 | This tool will generate `.spec.ts` files that you can paste directly into your repos for easy TDD, and coding the way we were meant to. 75 | 76 | ## @derxjs Roadmap 77 | 78 | - @derxjs/view-model package ✅ 79 | - [Article on TDD and implementing DeRxJS View Models](https://dev.to/zackderose/the-derxjsviewmodel-pattern-the-e-mc-2-of-state-management-part-1-3dka) ✅ 80 | - [Article on using DeRxJS View Models in different Frameworks](https://dev.to/zackderose/the-derxjsviewmodel-pattern-the-emc2-of-state-management-part-2-2i73) ✅ 81 | - @derxjs/reducer package (TBD; [beta available now](https://www.npmjs.com/package/@derxjs/reducer)) 🚧 82 | - @derxjs/react package (TBD; [beta available now](https://www.npmjs.com/package/@derxjs/react)) 🚧 83 | - Timeline Test Code Generation Tool (TBD) 84 | - @derxjs/selector package (TBD) 85 | - Ai-Driven DeRxJS Code Generation (TBD) 86 | -------------------------------------------------------------------------------- /packages/reducer/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'reducer', 4 | preset: '../../jest.preset.js', 5 | globals: {}, 6 | transform: { 7 | '^.+\\.[tj]sx?$': [ 8 | 'ts-jest', 9 | { 10 | tsconfig: '/tsconfig.spec.json', 11 | }, 12 | ], 13 | }, 14 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 15 | coverageDirectory: '../../coverage/packages/reducer', 16 | }; 17 | -------------------------------------------------------------------------------- /packages/reducer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@derxjs/reducer", 3 | "version": "1.4.1", 4 | "description": "Reducer util library for DeRxJSViewModel Pattern", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ZackDeRose/derxjs.git" 12 | }, 13 | "keywords": [ 14 | "rxjs", 15 | "state-management" 16 | ], 17 | "author": "Zack DeRose", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/ZackDeRose/derxjs/issues" 21 | }, 22 | "homepage": "https://github.com/ZackDeRose/derxjs#readme", 23 | "dependencies": { 24 | "rxjs": "^7.4.0", 25 | "typescript": "4.7.3", 26 | "@derxjs/view-model": "^1.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/reducer/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reducer", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/reducer/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/js:tsc", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/packages/reducer", 12 | "main": "packages/reducer/src/index.ts", 13 | "tsConfig": "packages/reducer/tsconfig.lib.json", 14 | "assets": ["packages/reducer/*.md"], 15 | "updateBuildableProjectDepsInPackageJson": true 16 | } 17 | }, 18 | "lint": { 19 | "executor": "@nx/linter:eslint", 20 | "outputs": ["{options.outputFile}"], 21 | "options": { 22 | "lintFilePatterns": ["packages/reducer/**/*.ts"] 23 | } 24 | }, 25 | "test": { 26 | "executor": "@nx/jest:jest", 27 | "outputs": ["{workspaceRoot}/coverage/packages/reducer"], 28 | "options": { 29 | "jestConfig": "packages/reducer/jest.config.ts", 30 | "passWithNoTests": true 31 | } 32 | }, 33 | "publish": { 34 | "executor": "nx:run-commands", 35 | "options": { 36 | "commands": [ 37 | "npx ts-node tools/scripts/bump-version.ts reducer {args.version}", 38 | "nx build reducer", 39 | "npx ts-node tools/scripts/publish.ts reducer" 40 | ], 41 | "parallel": false 42 | } 43 | } 44 | }, 45 | "tags": ["package-type:implementation"] 46 | } 47 | -------------------------------------------------------------------------------- /packages/reducer/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/reducer'; 2 | -------------------------------------------------------------------------------- /packages/reducer/src/lib/reducer.ts: -------------------------------------------------------------------------------- 1 | import { merge, Observable, Subject, Subscription } from 'rxjs'; 2 | import { 3 | distinctUntilChanged, 4 | map, 5 | scan, 6 | share, 7 | startWith, 8 | } from 'rxjs/operators'; 9 | 10 | // export function createDeRxJSReducer: DeRxJSViewModel; 11 | export function createDeRxJSReducer({ 12 | reducer, 13 | effects, 14 | sideEffects, 15 | incomingObservables, 16 | teardownFn, 17 | initialState, 18 | }: DeRxJSReducerInputs< 19 | ViewModelType, 20 | ActionsUnionType 21 | >): Observable { 22 | const actionsSubject = new Subject(); 23 | const actions$ = merge(...Object.values(incomingObservables)).pipe( 24 | share({ connector: () => actionsSubject }) 25 | ); 26 | const state$ = actions$.pipe( 27 | scan(reducer, initialState), 28 | startWith(initialState), 29 | distinctUntilChanged() 30 | ); 31 | const subscriptions = new Subscription(); 32 | if (teardownFn) { 33 | const teardown = async () => { 34 | await teardownFn(); 35 | subscriptions.unsubscribe(); 36 | }; 37 | teardown(); 38 | } 39 | for (const effect of effects) { 40 | subscriptions.add( 41 | effect(state$, actionsSubject) 42 | .pipe(share({ connector: () => actionsSubject })) 43 | .subscribe() 44 | ); 45 | } 46 | for (const sideEffect of sideEffects) { 47 | subscriptions.add(sideEffect(state$, actionsSubject).subscribe()); 48 | } 49 | return state$; 50 | } 51 | 52 | export interface DeRxJSReducerInputs { 53 | reducer: (a: ViewModelType, b: ActionsUnionType) => ViewModelType; 54 | effects: (( 55 | state$: Observable, 56 | actions: Observable 57 | ) => Observable)[]; 58 | // | (() => ActionsUnionType) 59 | // | (() => Promise))[]; 60 | sideEffects: (( 61 | state$: Observable, 62 | actions: Observable 63 | ) => Observable)[]; 64 | // | (() => any) | (() => Promise); 65 | incomingObservables: { 66 | [x: string]: Observable; 67 | }; 68 | teardownFn?: () => Promise; 69 | initialState: ViewModelType; 70 | } 71 | 72 | export type Action = { 73 | type: K; 74 | } & T; 75 | 76 | // export type ActionCreator = ( 77 | // data?: T 78 | // ) => Action; 79 | 80 | // export function createAction( 81 | // type: K 82 | // ): ActionCreator { 83 | // const actionCreatorComposer = (x: K) => (data?: T) => ({ 84 | // type: x, 85 | // ...(data || {}), 86 | // }); 87 | // const actionCreator = actionCreatorComposer(type); 88 | // return actionCreator; 89 | // } 90 | 91 | export function actionize(actionName: K) { 92 | return function (inc$: Observable): Observable<{ type: K } & T> { 93 | return inc$.pipe(map((x) => ({ type: actionName, ...x }))); 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /packages/reducer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/reducer/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": [] 7 | }, 8 | "include": ["**/*.ts"], 9 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/reducer/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.tsx", 12 | "**/*.test.tsx", 13 | "**/*.spec.js", 14 | "**/*.test.js", 15 | "**/*.spec.jsx", 16 | "**/*.test.jsx", 17 | "**/*.d.ts", 18 | "jest.config.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/view-model/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/js/babel", 5 | { 6 | "useBuiltIns": "usage" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/view-model/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/view-model/.npmignore: -------------------------------------------------------------------------------- 1 | jest.config.js 2 | project.json 3 | .eslint.json 4 | -------------------------------------------------------------------------------- /packages/view-model/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Zachary DeRose 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 | -------------------------------------------------------------------------------- /packages/view-model/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'view-model', 4 | preset: '../../jest.preset.js', 5 | globals: {}, 6 | transform: { 7 | '^.+\\.[tj]sx?$': [ 8 | 'ts-jest', 9 | { 10 | tsconfig: '/tsconfig.spec.json', 11 | }, 12 | ], 13 | }, 14 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 15 | coverageDirectory: '../../coverage/packages/view-model', 16 | }; 17 | -------------------------------------------------------------------------------- /packages/view-model/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@derxjs/view-model", 3 | "version": "1.4.1", 4 | "description": "Official library for the DeRxJS Pattern", 5 | "scripts": { 6 | "test": "nx test view-model" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/ZackDeRose/derxjs.git" 11 | }, 12 | "keywords": [ 13 | "rxjs", 14 | "state", 15 | "management" 16 | ], 17 | "author": "Zack DeRose", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/ZackDeRose/derxjs/issues" 21 | }, 22 | "homepage": "https://github.com/ZackDeRose/derxjs#readme", 23 | "types": "index.ts", 24 | "dependencies": { 25 | "rxjs": "^7.4.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/view-model/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "view-model", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/view-model/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/js:tsc", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/packages/view-model", 12 | "main": "packages/view-model/src/index.ts", 13 | "tsConfig": "packages/view-model/tsconfig.lib.json", 14 | "assets": ["packages/view-model/*.md"], 15 | "updateBuildableProjectDepsInPackageJson": true 16 | } 17 | }, 18 | "lint": { 19 | "executor": "@nx/linter:eslint", 20 | "outputs": ["{options.outputFile}"], 21 | "options": { 22 | "lintFilePatterns": ["packages/view-model/**/*.ts"] 23 | } 24 | }, 25 | "test": { 26 | "executor": "@nx/jest:jest", 27 | "outputs": ["{workspaceRoot}/coverage/packages/view-model"], 28 | "options": { 29 | "jestConfig": "packages/view-model/jest.config.ts", 30 | "passWithNoTests": true 31 | } 32 | }, 33 | "publish": { 34 | "executor": "nx:run-commands", 35 | "options": { 36 | "commands": [ 37 | "npx ts-node tools/scripts/bump-version.ts view-model {args.version}", 38 | "nx build view-model", 39 | "npx ts-node tools/scripts/publish.ts view-model" 40 | ], 41 | "parallel": false 42 | } 43 | } 44 | }, 45 | "tags": [] 46 | } 47 | -------------------------------------------------------------------------------- /packages/view-model/src/index.ts: -------------------------------------------------------------------------------- 1 | export { DeRxJSViewModel } from './lib/view-model'; 2 | -------------------------------------------------------------------------------- /packages/view-model/src/lib/view-model.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | export type DeRxJSViewModel = ( 4 | inputs: InputType 5 | ) => Observable; 6 | -------------------------------------------------------------------------------- /packages/view-model/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/view-model/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": [], 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "include": ["**/*.ts"], 13 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "jest.config.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/view-model/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "**/*.spec.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.tsx", 12 | "**/*.test.tsx", 13 | "**/*.spec.js", 14 | "**/*.test.js", 15 | "**/*.spec.jsx", 16 | "**/*.test.jsx", 17 | "**/*.d.ts", 18 | "jest.config.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /separation-of-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/separation-of-tasks.png -------------------------------------------------------------------------------- /the-derxjs-view-model-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/the-derxjs-view-model-pattern.png -------------------------------------------------------------------------------- /tools/scripts/bump-version.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import { join } from 'path'; 3 | 4 | async function run() { 5 | const packageName = process.argv[2]; 6 | const versionNumber = process.argv[3]; 7 | bumpVersion(packageName, versionNumber); 8 | } 9 | 10 | run(); 11 | 12 | function bumpVersion(packageName: string, version: string) { 13 | const cwd = join('packages', packageName); 14 | execSync(`npm version ${version}`, { cwd }); 15 | } 16 | -------------------------------------------------------------------------------- /tools/scripts/publish-all.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import { readFileSync } from 'fs'; 3 | import prompt from 'prompt'; 4 | 5 | const releaseTypes = ['major', 'minor', 'patch'] as const; 6 | const packages = ['react', 'view-model', 'reducer'] as const; 7 | 8 | async function run() { 9 | if (!areWeOnMainBranch()) { 10 | console.error('not on main branch'); 11 | process.exit(1); 12 | } 13 | if (uncomittedChangesExist()) { 14 | console.error('uncommitted changes exist'); 15 | process.exit(1); 16 | } 17 | const releaseType = process.argv[2]; 18 | if (!isReleaseType(releaseType)) { 19 | console.error(`release type '${releaseType}' is not valid`); 20 | process.exit(1); 21 | } 22 | await promptForConfirmation(); 23 | const nextVersion = getCurrentVersion(releaseType); 24 | console.error(nextVersion); 25 | publishEachPackage(nextVersion); 26 | gitCommit(nextVersion); 27 | bumpRepoVersion(nextVersion); 28 | pushToGithub(); 29 | } 30 | 31 | run(); 32 | 33 | function getCurrentVersion(releaseType: ReleaseType): string { 34 | const packageVersion = JSON.parse( 35 | readFileSync('package.json').toString() 36 | ).version; 37 | const [major, minor, patch] = packageVersion.split('.'); 38 | return [ 39 | releaseType === 'major' ? `${+major + 1}` : major, 40 | releaseType === 'major' 41 | ? 0 42 | : releaseType === 'minor' 43 | ? `${+minor + 1}` 44 | : minor, 45 | releaseType === 'patch' ? `${+patch + 1}` : 0, 46 | ].join('.'); 47 | } 48 | 49 | function uncomittedChangesExist() { 50 | const statusMsg = execSync(`git status`).toString(); 51 | return !statusMsg.includes('nothing to commit, working tree clean'); 52 | } 53 | 54 | async function promptForConfirmation(): Promise { 55 | const { response } = await prompt.get([ 56 | { 57 | name: 'response', 58 | description: 'are you sure you want to bump the version and publish?', 59 | }, 60 | ]); 61 | if ((response as string).toLowerCase() === 'y') { 62 | return; 63 | ``; 64 | } 65 | console.error('User confirmation not provided'); 66 | process.exit(1); 67 | } 68 | 69 | function areWeOnMainBranch(): boolean { 70 | const output = execSync('git branch').toString(); 71 | return output.includes('* main'); 72 | } 73 | 74 | function gitCommit(version: string) { 75 | execSync(`git commit -am "Packages bumped for release: ${version}"`); 76 | } 77 | 78 | function bumpRepoVersion(version: string) { 79 | execSync(`npm version ${version}`); 80 | } 81 | 82 | type ReleaseType = typeof releaseTypes[number]; 83 | function isReleaseType(x: any): x is ReleaseType { 84 | return releaseTypes.includes(x); 85 | } 86 | 87 | function pushToGithub() { 88 | execSync('git push'); 89 | } 90 | 91 | function publishEachPackage(versionNumber: string) { 92 | for (const packageName of packages) { 93 | execSync( 94 | `nx run ${packageName}:publish --args="--version=${versionNumber}"` 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tools/scripts/publish.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import { join } from 'path'; 3 | import { existsSync } from 'fs'; 4 | 5 | async function run() { 6 | const packageName = process.argv[2]; 7 | publishToNpm(packageName); 8 | } 9 | 10 | run(); 11 | 12 | function publishToNpm(packageName: string) { 13 | const cwd = join('dist', 'packages', packageName); 14 | execSync(`npm publish --access public`, { cwd }); 15 | } 16 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tools/workspace-plugin/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | }, 17 | { 18 | "files": ["*.json"], 19 | "parser": "jsonc-eslint-parser", 20 | "rules": { 21 | "@nx/dependency-checks": "error" 22 | } 23 | }, 24 | { 25 | "files": ["./package.json", "./generators.json"], 26 | "parser": "jsonc-eslint-parser", 27 | "rules": { 28 | "@nx/nx-plugin-checks": "error" 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /tools/workspace-plugin/generators.json: -------------------------------------------------------------------------------- 1 | { 2 | "generators": { 3 | "package": { 4 | "implementation": "./src/generators/package", 5 | "schema": "./src/generators/package/schema.json", 6 | "description": "Generator package" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tools/workspace-plugin/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'workspace-plugin', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 7 | }, 8 | moduleFileExtensions: ['ts', 'js', 'html'], 9 | coverageDirectory: '../../coverage/tools/workspace-plugin', 10 | }; 11 | -------------------------------------------------------------------------------- /tools/workspace-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@derxjs/workspace-plugin", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "tslib": "^2.3.0" 6 | }, 7 | "type": "commonjs", 8 | "main": "./src/index.js", 9 | "typings": "./src/index.d.ts", 10 | "generators": "./generators.json" 11 | } 12 | -------------------------------------------------------------------------------- /tools/workspace-plugin/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workspace-plugin", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "tools/workspace-plugin/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/js:tsc", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/tools/workspace-plugin", 12 | "main": "tools/workspace-plugin/src/index.ts", 13 | "tsConfig": "tools/workspace-plugin/tsconfig.lib.json", 14 | "assets": [ 15 | { 16 | "input": "./tools/workspace-plugin/src", 17 | "glob": "**/!(*.ts)", 18 | "output": "./src" 19 | }, 20 | { 21 | "input": "./tools/workspace-plugin/src", 22 | "glob": "**/*.d.ts", 23 | "output": "./src" 24 | }, 25 | { 26 | "input": "./tools/workspace-plugin", 27 | "glob": "generators.json", 28 | "output": "." 29 | }, 30 | { 31 | "input": "./tools/workspace-plugin", 32 | "glob": "executors.json", 33 | "output": "." 34 | } 35 | ], 36 | "updateBuildableProjectDepsInPackageJson": true 37 | } 38 | }, 39 | "lint": { 40 | "executor": "@nx/linter:eslint", 41 | "outputs": ["{options.outputFile}"], 42 | "options": { 43 | "lintFilePatterns": [ 44 | "tools/workspace-plugin/**/*.ts", 45 | "tools/workspace-plugin/package.json", 46 | "tools/workspace-plugin/generators.json" 47 | ] 48 | } 49 | }, 50 | "test": { 51 | "executor": "@nx/jest:jest", 52 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 53 | "options": { 54 | "jestConfig": "tools/workspace-plugin/jest.config.ts", 55 | "passWithNoTests": true 56 | }, 57 | "configurations": { 58 | "ci": { 59 | "ci": true, 60 | "codeCoverage": true 61 | } 62 | } 63 | } 64 | }, 65 | "tags": [] 66 | } 67 | -------------------------------------------------------------------------------- /tools/workspace-plugin/src/generators/package/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "cli": "nx", 4 | "$id": "package", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string", 9 | "description": "Library name", 10 | "$default": { 11 | "$source": "argv", 12 | "index": 0 13 | } 14 | } 15 | }, 16 | "required": ["name"] 17 | } 18 | -------------------------------------------------------------------------------- /tools/workspace-plugin/src/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZackDeRose/derxjs/96e2497bf36e4bb110c9421b0f770361155df6d0/tools/workspace-plugin/src/index.ts -------------------------------------------------------------------------------- /tools/workspace-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | }, 6 | "files": [], 7 | "include": [], 8 | "references": [ 9 | { 10 | "path": "./tsconfig.lib.json" 11 | }, 12 | { 13 | "path": "./tsconfig.spec.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tools/workspace-plugin/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /tools/workspace-plugin/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@derxjs/examples/list-view-model-implementation": [ 19 | "libs/examples/list-view-model-implementation/src/index.ts" 20 | ], 21 | "@derxjs/examples/tic-tac-toe-view-model-implementation": [ 22 | "libs/examples/tic-tac-toe-view-model-implementation/src/index.ts" 23 | ], 24 | "@derxjs/react": ["packages/react/src/index.ts"], 25 | "@derxjs/reducer": ["packages/reducer/src/index.ts"], 26 | "@derxjs/view-model": ["packages/view-model/src/index.ts"], 27 | "@derxjs/workspace-plugin": ["tools/workspace-plugin/src/index.ts"] 28 | } 29 | }, 30 | "exclude": ["node_modules", "tmp"] 31 | } 32 | --------------------------------------------------------------------------------