├── .editorconfig ├── .gitignore ├── .npm-dev-registry.json ├── .prettierignore ├── .vscode └── extensions.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── assets ├── broken-heart.pdf ├── broken-heart.svg ├── cross-layer-class-inheritance.svg ├── f-plus-b-equals-love-1500x500.png ├── f-plus-b-equals-love-dark-mode-1000x420.png ├── f-plus-b-equals-love-dark-mode-1400x600.png ├── f-plus-b-equals-love-dark-mode-1500x500.png ├── f-plus-b-equals-love-dark-mode-1600x900.png ├── f-plus-b-equals-love.pdf ├── f-plus-b-equals-love.svg ├── frontend-backend.excalidraw ├── frontend-webapi-backend.excalidraw ├── hashtags │ ├── cross-layer.pdf │ ├── full-stack.pdf │ ├── javascript.pdf │ ├── low-level.pdf │ ├── no-web-api.pdf │ ├── object-oriented.pdf │ ├── typescript.pdf │ ├── unopinionated.pdf │ └── web-app.pdf ├── layr-favicon.png ├── layr-icon-for-rounded-profile-dark-mode.png ├── layr-icon-for-rounded-profile.pdf ├── layr-icon-for-rounded-profile.png ├── layr-icon.pdf ├── layr-logo-with-icon-dark-mode.pdf ├── layr-logo-with-icon.pdf ├── layr-logo-with-icon.svg ├── traditional-vs-liaison-architecture-dark-mode.svg ├── traditional-vs-liaison-architecture.svg ├── typical-stack-vs-layr-stack-light-mode.excalidraw └── typical-stack-vs-layr-stack.excalidraw ├── docs ├── build.js ├── contents │ ├── concepts │ │ └── coming-soon.md │ ├── guides │ │ └── coming-soon.md │ ├── index.json │ └── introduction │ │ ├── handling-authorization.md │ │ ├── hello-world │ │ ├── assets │ │ │ ├── screenshot-001.immutable.png │ │ │ ├── screenshot-002.immutable.png │ │ │ ├── screenshot-003.immutable.png │ │ │ └── screenshot-004.immutable.png │ │ └── hello-world.md │ │ ├── introduction.md │ │ └── storing-data.md └── package.json ├── examples ├── v1 │ ├── counter-with-create-react-app-ts │ │ ├── README.md │ │ ├── backend │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── components │ │ │ │ │ └── counter.ts │ │ │ │ ├── http-server.ts │ │ │ │ └── server.ts │ │ │ └── tsconfig.json │ │ ├── frontend │ │ │ ├── .gitignore │ │ │ ├── package.json │ │ │ ├── public │ │ │ │ ├── favicon.ico │ │ │ │ ├── index.html │ │ │ │ ├── logo192.png │ │ │ │ ├── logo512.png │ │ │ │ ├── manifest.json │ │ │ │ └── robots.txt │ │ │ ├── src │ │ │ │ ├── components │ │ │ │ │ └── counter.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── react-app-env.d.ts │ │ │ └── tsconfig.json │ │ └── package.json │ ├── counter-with-esbuild-ts │ │ ├── README.md │ │ ├── backend │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── components │ │ │ │ │ └── counter.ts │ │ │ │ ├── http-server.ts │ │ │ │ └── server.ts │ │ │ └── tsconfig.json │ │ ├── frontend │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── components │ │ │ │ │ └── counter.tsx │ │ │ │ ├── index.html │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ │ └── package.json │ ├── counter-with-http │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── jsconfig.json │ │ ├── package.json │ │ └── src │ │ │ ├── backend.js │ │ │ └── frontend.js │ ├── counter-with-parcel-ts │ │ ├── README.md │ │ ├── backend │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── components │ │ │ │ │ └── counter.ts │ │ │ │ ├── http-server.ts │ │ │ │ └── server.ts │ │ │ └── tsconfig.json │ │ ├── frontend │ │ │ ├── .gitignore │ │ │ ├── babel.config.js │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── components │ │ │ │ │ └── counter.tsx │ │ │ │ ├── index.html │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ │ └── package.json │ ├── counter-with-rollup-ts │ │ ├── README.md │ │ ├── backend │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── components │ │ │ │ │ └── counter.ts │ │ │ │ ├── http-server.ts │ │ │ │ └── server.ts │ │ │ └── tsconfig.json │ │ ├── frontend │ │ │ ├── babel.config.js │ │ │ ├── package.json │ │ │ ├── rollup.config.js │ │ │ ├── src │ │ │ │ ├── components │ │ │ │ │ └── counter.tsx │ │ │ │ ├── index.html │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ │ └── package.json │ ├── counter │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── jsconfig.json │ │ ├── package.json │ │ └── src │ │ │ ├── backend.js │ │ │ └── frontend.js │ ├── guestbook-cli-js │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── jsconfig.json │ │ ├── package.json │ │ └── src │ │ │ ├── backend.js │ │ │ └── frontend.js │ ├── guestbook-cli-ts │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── backend.ts │ │ │ └── frontend.ts │ │ └── tsconfig.json │ ├── guestbook-web-js │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── jsconfig.json │ │ ├── package.json │ │ ├── src │ │ │ ├── backend.js │ │ │ ├── frontend.js │ │ │ └── index.html │ │ └── webpack.config.js │ ├── guestbook-web-ts │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── backend.ts │ │ │ ├── frontend.tsx │ │ │ └── index.html │ │ ├── tsconfig.json │ │ └── webpack.config.js │ ├── guestbook-web-with-authorization-js │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── jsconfig.json │ │ ├── package.json │ │ ├── src │ │ │ ├── backend.js │ │ │ ├── frontend.js │ │ │ └── index.html │ │ └── webpack.config.js │ ├── guestbook-web-with-authorization-ts │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── backend.ts │ │ │ ├── frontend.tsx │ │ │ └── index.html │ │ ├── tsconfig.json │ │ └── webpack.config.js │ ├── guestbook-web-with-routes-js │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── jsconfig.json │ │ ├── package.json │ │ ├── src │ │ │ ├── backend.js │ │ │ ├── frontend.js │ │ │ └── index.html │ │ └── webpack.config.js │ ├── guestbook-web-with-routes-ts │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── backend.ts │ │ │ ├── frontend.tsx │ │ │ └── index.html │ │ ├── tsconfig.json │ │ └── webpack.config.js │ ├── hello-world-js │ │ ├── README.md │ │ ├── babel.config.json │ │ ├── jsconfig.json │ │ ├── package.json │ │ └── src │ │ │ ├── backend.js │ │ │ └── frontend.js │ └── hello-world-ts │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ ├── backend.ts │ │ └── frontend.ts │ │ └── tsconfig.json └── v2 │ ├── hello-world-js │ ├── .editorconfig │ ├── .gitignore │ ├── .prettierignore │ ├── .vscode │ │ ├── extensions.json │ │ └── settings.json │ ├── README.md │ ├── backend │ │ ├── boostr.config.mjs │ │ ├── boostr.config.private-example.mjs │ │ ├── jsconfig.json │ │ ├── package.json │ │ └── src │ │ │ ├── components │ │ │ └── application.js │ │ │ └── index.js │ ├── boostr.config.mjs │ ├── database │ │ ├── .gitignore │ │ ├── boostr.config.mjs │ │ └── boostr.config.private-example.mjs │ ├── frontend │ │ ├── boostr.config.mjs │ │ ├── jsconfig.json │ │ ├── package.json │ │ ├── public │ │ │ └── boostr-favicon-3NjLR7w1Mu8UAIqq05vVG3.immutable.png │ │ └── src │ │ │ ├── components │ │ │ └── application.jsx │ │ │ └── index.js │ └── package.json │ └── hello-world-ts │ ├── .editorconfig │ ├── .gitignore │ ├── .prettierignore │ ├── .vscode │ ├── extensions.json │ └── settings.json │ ├── README.md │ ├── backend │ ├── boostr.config.mjs │ ├── boostr.config.private-example.mjs │ ├── package.json │ ├── src │ │ ├── components │ │ │ └── application.ts │ │ └── index.ts │ └── tsconfig.json │ ├── boostr.config.mjs │ ├── database │ ├── .gitignore │ ├── boostr.config.mjs │ └── boostr.config.private-example.mjs │ ├── frontend │ ├── boostr.config.mjs │ ├── package.json │ ├── public │ │ └── boostr-favicon-3NjLR7w1Mu8UAIqq05vVG3.immutable.png │ ├── src │ │ ├── components │ │ │ └── application.tsx │ │ └── index.ts │ └── tsconfig.json │ └── package.json ├── lerna.json ├── nx.json ├── package.json ├── packages ├── aws-integration │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lambda-execution-queue-sender.ts │ │ └── lambda-handler.ts │ └── tsconfig.json ├── browser-navigator │ ├── README.md │ ├── package.json │ ├── src │ │ ├── browser-navigator.ts │ │ ├── index.ts │ │ └── utilities.ts │ └── tsconfig.json ├── component-client │ ├── README.md │ ├── package.json │ ├── src │ │ ├── component-client.test.ts │ │ ├── component-client.ts │ │ ├── index.ts │ │ └── utilities.ts │ └── tsconfig.json ├── component-express-middleware │ ├── README.md │ ├── package.json │ ├── src │ │ ├── component-express-middleware.ts │ │ └── index.ts │ └── tsconfig.json ├── component-http-client │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ │ ├── component-http-client.test.ts │ │ ├── component-http-client.ts │ │ └── index.ts │ └── tsconfig.json ├── component-http-server │ ├── README.md │ ├── package.json │ ├── src │ │ ├── component-http-server.test.ts │ │ ├── component-http-server.ts │ │ └── index.ts │ └── tsconfig.json ├── component-koa-middleware │ ├── README.md │ ├── package.json │ ├── src │ │ ├── component-koa-middleware.ts │ │ └── index.ts │ └── tsconfig.json ├── component-server │ ├── README.md │ ├── package.json │ ├── src │ │ ├── component-server.test.ts │ │ ├── component-server.ts │ │ ├── index.ts │ │ └── utilities.ts │ └── tsconfig.json ├── component │ ├── README.md │ ├── package.json │ ├── src │ │ ├── cloning.test.ts │ │ ├── cloning.ts │ │ ├── component.test.ts │ │ ├── component.ts │ │ ├── decorators.test.ts │ │ ├── decorators.ts │ │ ├── deserialization.test.ts │ │ ├── deserialization.ts │ │ ├── embedded-component.ts │ │ ├── forking.test.ts │ │ ├── forking.ts │ │ ├── identifiable-component.test.ts │ │ ├── identity-map.test.ts │ │ ├── identity-map.ts │ │ ├── index.ts │ │ ├── js-parser.ts │ │ ├── js-tests │ │ │ ├── decorators.test.js │ │ │ └── jsconfig.json │ │ ├── merging.test.ts │ │ ├── merging.ts │ │ ├── properties │ │ │ ├── attribute-selector.test.ts │ │ │ ├── attribute-selector.ts │ │ │ ├── attribute.test.ts │ │ │ ├── attribute.ts │ │ │ ├── identifier-attribute.test.ts │ │ │ ├── identifier-attribute.ts │ │ │ ├── index.ts │ │ │ ├── method.test.ts │ │ │ ├── method.ts │ │ │ ├── primary-identifier-attribute.test.ts │ │ │ ├── primary-identifier-attribute.ts │ │ │ ├── property.test.ts │ │ │ ├── property.ts │ │ │ ├── secondary-identifier-attribute.test.ts │ │ │ ├── secondary-identifier-attribute.ts │ │ │ └── value-types │ │ │ │ ├── any-value-type.ts │ │ │ │ ├── array-value-type.ts │ │ │ │ ├── boolean-value-type.ts │ │ │ │ ├── component-value-type.ts │ │ │ │ ├── date-value-type.ts │ │ │ │ ├── factory.test.ts │ │ │ │ ├── factory.ts │ │ │ │ ├── index.ts │ │ │ │ ├── number-value-type.ts │ │ │ │ ├── object-value-type.ts │ │ │ │ ├── regexp-value-type.ts │ │ │ │ ├── string-value-type.ts │ │ │ │ ├── value-type.test.ts │ │ │ │ └── value-type.ts │ │ ├── sanitization │ │ │ ├── index.ts │ │ │ ├── sanitizer-builders.test.ts │ │ │ ├── sanitizer-builders.ts │ │ │ ├── sanitizer.test.ts │ │ │ ├── sanitizer.ts │ │ │ ├── utilities.test.ts │ │ │ └── utilities.ts │ │ ├── serialization.test.ts │ │ ├── serialization.ts │ │ ├── utilities.test.ts │ │ ├── utilities.ts │ │ └── validation │ │ │ ├── index.ts │ │ │ ├── utilities.test.ts │ │ │ ├── utilities.ts │ │ │ ├── validator-builders.test.ts │ │ │ ├── validator-builders.ts │ │ │ ├── validator.test.ts │ │ │ └── validator.ts │ └── tsconfig.json ├── execution-queue │ ├── README.md │ ├── package.json │ ├── src │ │ ├── execution-queue.test.ts │ │ ├── execution-queue.ts │ │ └── index.ts │ └── tsconfig.json ├── integration-testing │ ├── README.md │ ├── package.json │ ├── src │ │ ├── client-server.test.ts │ │ ├── component-express-middleware.test.ts │ │ ├── component-koa-middleware.test.ts │ │ ├── counter.fixture.ts │ │ ├── http-client-server.test.ts │ │ ├── memory-navigator.test.ts │ │ ├── navigator.test.ts │ │ ├── storable.fixture.ts │ │ └── storable.test.ts │ └── tsconfig.json ├── memory-navigator │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── memory-navigator.ts │ └── tsconfig.json ├── memory-store │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── memory-store.ts │ └── tsconfig.json ├── mongodb-store │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── mongodb-store.test.ts │ │ └── mongodb-store.ts │ └── tsconfig.json ├── navigator │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── navigator.ts │ │ ├── utilities.test.ts │ │ └── utilities.ts │ └── tsconfig.json ├── observable │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── observable.test.ts │ │ └── observable.ts │ └── tsconfig.json ├── react-integration │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components.tsx │ │ ├── decorators.tsx │ │ ├── hooks.ts │ │ ├── index.ts │ │ └── plugins.tsx │ └── tsconfig.json ├── routable │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ │ ├── addressable.ts │ │ ├── decorators.test.ts │ │ ├── decorators.ts │ │ ├── index.ts │ │ ├── js-tests │ │ │ ├── decorators.test.js │ │ │ └── jsconfig.json │ │ ├── param.ts │ │ ├── pattern.ts │ │ ├── routable.test.ts │ │ ├── routable.ts │ │ ├── route.test.ts │ │ ├── route.ts │ │ ├── utilities.ts │ │ └── wrapper.ts │ └── tsconfig.json ├── storable │ ├── README.md │ ├── package.json │ ├── src │ │ ├── decorators.test.ts │ │ ├── decorators.ts │ │ ├── index-class.test.ts │ │ ├── index-class.ts │ │ ├── index.ts │ │ ├── js-tests │ │ │ ├── decorators.test.js │ │ │ └── jsconfig.json │ │ ├── operator.ts │ │ ├── properties │ │ │ ├── index.ts │ │ │ ├── storable-attribute.test.ts │ │ │ ├── storable-attribute.ts │ │ │ ├── storable-method.ts │ │ │ ├── storable-primary-identifier-attribute.ts │ │ │ ├── storable-property.ts │ │ │ └── storable-secondary-identifier-attribute.ts │ │ ├── query.ts │ │ ├── storable.ts │ │ ├── store-like.ts │ │ └── utilities.ts │ └── tsconfig.json ├── store │ ├── README.md │ ├── package.json │ ├── src │ │ ├── document.ts │ │ ├── expression.ts │ │ ├── index.ts │ │ ├── path.ts │ │ ├── store.test.ts │ │ ├── store.ts │ │ └── utilities.ts │ └── tsconfig.json ├── utilities │ ├── README.md │ ├── package.json │ ├── src │ │ ├── error.test.ts │ │ ├── error.ts │ │ ├── index.ts │ │ └── time.ts │ └── tsconfig.json └── with-roles │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ ├── decorators.test.ts │ ├── decorators.ts │ ├── index.ts │ ├── role.test.ts │ ├── role.ts │ ├── utilities.ts │ ├── with-roles.test.ts │ └── with-roles.ts │ └── tsconfig.json └── website ├── README.md ├── backend ├── boostr.config.mjs ├── boostr.config.private-template.mjs ├── package.json ├── src │ ├── components │ │ ├── application.ts │ │ ├── article.ts │ │ ├── entity.ts │ │ ├── newsletter.ts │ │ ├── user.ts │ │ └── with-author.ts │ ├── index.ts │ └── jwt.ts └── tsconfig.json ├── boostr.config.mjs ├── database ├── .gitignore ├── boostr.config.mjs └── boostr.config.private-template.mjs ├── frontend ├── boostr.config.mjs ├── package.json ├── public │ ├── docs │ │ ├── v1 │ │ │ ├── introduction │ │ │ │ ├── authorization-6eooNLZnaLZk4eJNHNGwUp-edited.immutable.md │ │ │ │ ├── data-storage-1YDg3LGZPF09H3opJUEDSp-edited.immutable.md │ │ │ │ ├── hello-world-7F76XnjXXBB7eeZTpEZwEX-edited.immutable.md │ │ │ │ ├── introduction-1NNtkbXN0zHvG6VR0jvPRK-edited.immutable.md │ │ │ │ ├── routing-4ubaWloMHuNNs0foagYUwi-edited.immutable.md │ │ │ │ └── web-app-5okryUyuriFFHXtFK9uZNY-edited.immutable.md │ │ │ └── reference │ │ │ │ ├── attribute-4P9dTivZ9HJ4Flr4Y8cTKv.immutable.md │ │ │ │ ├── attribute-selector-7FZWQpwLR7jpUHoBpvO9Et.immutable.md │ │ │ │ ├── aws-integration-3iPKbtjOS0uDgsZKk6Q0Kp.immutable.md │ │ │ │ ├── browser-router-6EIyPCgoPYsWmDmpyKMhce.immutable.md │ │ │ │ ├── component-6LvsJL2MA9RN6hmT0bBacd.immutable.md │ │ │ │ ├── component-client-1ISSjJNRL12I33addutRh4.immutable.md │ │ │ │ ├── component-express-middleware-6UieWJkEvLdXLYug4xuM16.immutable.md │ │ │ │ ├── component-http-client-6g8M9kRcBS4AQwFtAp7t9z.immutable.md │ │ │ │ ├── component-http-server-5VDR5fS2uD9iTkuHRSywjz.immutable.md │ │ │ │ ├── component-koa-middleware-402oMlpSmEIARQJLzn4qQg.immutable.md │ │ │ │ ├── component-server-e5RUuQXVcCIVLxHiNzCDO.immutable.md │ │ │ │ ├── embedded-component-4MiZBzspJQbUshU8Q93aJ8.immutable.md │ │ │ │ ├── identifier-attribute-6Jgrzmrlv4QJoBZVTYYDb9.immutable.md │ │ │ │ ├── identity-map-01fsjrVv6cSC6awmnNnn6c.immutable.md │ │ │ │ ├── index-1KeXILVBQNLQpRzBhIWjqB.immutable.md │ │ │ │ ├── memory-router-TGoBOhUXFPp3iDyKA9Sn0.immutable.md │ │ │ │ ├── memory-store-78uJRKSOEyr1WnXttCnd9u.immutable.md │ │ │ │ ├── method-3mc2TakviGDKFcdpxFaCIu.immutable.md │ │ │ │ ├── mongodb-store-1yTYtEyVy4ZLkF4A2QFaAh.immutable.md │ │ │ │ ├── observable-6JJJVlFHPe2kPQ4NhsJg7O.immutable.md │ │ │ │ ├── primary-identifier-attribute-6qTkOuHNN4Bq3EjC9JGVqr.immutable.md │ │ │ │ ├── property-5bbULbxC52tIPaMAcPEp27.immutable.md │ │ │ │ ├── query-Q04JuSZAcx8HGvYktmTlI.immutable.md │ │ │ │ ├── react-integration-3EgAB99IhnithzdqiytvwF.immutable.md │ │ │ │ ├── role-UodTLJi6Lg6UPXOKZdp8K.immutable.md │ │ │ │ ├── routable-3zt4vMLvzQR3aqOwoWd3xV.immutable.md │ │ │ │ ├── route-1rS9AS4eda7qe7lWTU4WKF.immutable.md │ │ │ │ ├── router-5zhzJL9MYM1yaHY6CXlkso.immutable.md │ │ │ │ ├── secondary-identifier-attribute-7BqK9EmnCMCHIAroYiQDth.immutable.md │ │ │ │ ├── storable-2maAYPX5kWufBfVolYVgdc.immutable.md │ │ │ │ ├── storable-attribute-7MiyqvA7PUf2E2YeoqA912.immutable.md │ │ │ │ ├── storable-method-1rnW3aKFlFYZ3kIJdCGXRv.immutable.md │ │ │ │ ├── storable-primary-identifier-attribute-74IwGTycdA7NVAybBly3t1.immutable.md │ │ │ │ ├── storable-property-5LuWR7uuQ1qdebK3iszZJO.immutable.md │ │ │ │ ├── storable-secondary-identifier-attribute-6CqhR0kLxoLhtWU85EcsZU.immutable.md │ │ │ │ ├── store-6BvlCSWkAb7smHsvoIGjv1.immutable.md │ │ │ │ ├── validator-2strlvnapeTZmbWT3o3VM1.immutable.md │ │ │ │ ├── value-type-23f3WjnZNDfFejs5ceJVcY.immutable.md │ │ │ │ └── with-roles-2PYi0037F3Mxg80b5YKb26.immutable.md │ │ └── v2 │ │ │ ├── concepts │ │ │ └── coming-soon-38q4cuyCWekHuz36oHyMKG.immutable.md │ │ │ ├── guides │ │ │ └── coming-soon-6TWZbDxh3EVysir57k1tga.immutable.md │ │ │ ├── introduction │ │ │ ├── handling-authorization-1sLAebuD81FLlSvJbAuJmZ.immutable.md │ │ │ ├── hello-world │ │ │ │ ├── assets │ │ │ │ │ ├── screenshot-001.immutable.png │ │ │ │ │ ├── screenshot-002.immutable.png │ │ │ │ │ ├── screenshot-003.immutable.png │ │ │ │ │ └── screenshot-004.immutable.png │ │ │ │ └── hello-world-2FHBCgFRhZX9qH9O1RkTlZ.immutable.md │ │ │ ├── introduction-1iLUBREWe2BB65qw9G4uzm.immutable.md │ │ │ └── storing-data-40jzWiVleR8ZE0fqfGTbO2.immutable.md │ │ │ └── reference │ │ │ ├── addressable-6n5npS1cCoPKLkFrYU0Pk7.immutable.md │ │ │ ├── attribute-4IRansPGIm4E3gunH3Ztlq.immutable.md │ │ │ ├── attribute-selector-76ksImknctJspHLg12sRpR.immutable.md │ │ │ ├── aws-integration-3Rt2txpqioYQsmijgSqqwF.immutable.md │ │ │ ├── browser-navigator-eYIxYKNFdRYiKTxIWAjqd.immutable.md │ │ │ ├── component-1zBrZVglO2cjDP9aO87pOR.immutable.md │ │ │ ├── component-client-69HLk6gKzg5DfWcVC2bQWS.immutable.md │ │ │ ├── component-express-middleware-7KGBerWY9jB9r9eOjwLmPq.immutable.md │ │ │ ├── component-http-client-2rHYFMcsawI16EvGk8372w.immutable.md │ │ │ ├── component-http-server-5aWTLSzVvm0WohfP6af7OP.immutable.md │ │ │ ├── component-koa-middleware-6aVaQsSgkzWt35XyqSMuqx.immutable.md │ │ │ ├── component-server-i0AsOVFbGCJUrJ9mJyjPh.immutable.md │ │ │ ├── embedded-component-3YdGqfLNxl8c001AvWxHZ6.immutable.md │ │ │ ├── identifier-attribute-2w8hckqCKGHPurgIGY5vhT.immutable.md │ │ │ ├── identity-map-7sLdA5rsLvflf8T8OsZfGO.immutable.md │ │ │ ├── index-1Uz5JX1XB7V67nfM6tvnSV.immutable.md │ │ │ ├── memory-navigator-5wC9TEZn2cen7LuV4cGPOj.immutable.md │ │ │ ├── memory-store-3QHbAhGolblHx4OY17rEP.immutable.md │ │ │ ├── method-MAFTDipVXsOXj4o2CJLNQ.immutable.md │ │ │ ├── mongodb-store-37qsYI8A5ctkFlHTDVIc3O.immutable.md │ │ │ ├── navigator-5vNC9hp62PFUvxh0PLQcJ9.immutable.md │ │ │ ├── observable-7alzx5bqV1gD2Aj60hWQ0L.immutable.md │ │ │ ├── primary-identifier-attribute-5EqFynrkNUTa9DxYYmbmOl.immutable.md │ │ │ ├── property-bs5Xm31fCQXQkoZL24prZ.immutable.md │ │ │ ├── query-146gEiaHLp06V3LqhxRCzC.immutable.md │ │ │ ├── react-integration-4BgvR6gvTuqLdpNymyUYS1.immutable.md │ │ │ ├── role-3zJ3w6whE8RucN9kAhS5Hv.immutable.md │ │ │ ├── routable-7zjaksbkNa8sbNfqNnEKUu.immutable.md │ │ │ ├── route-3O1kMMGl4yZc9LGxH38Z3G.immutable.md │ │ │ ├── sanitizer-3hc34yGhzbK1cupoMtP6Dv.immutable.md │ │ │ ├── secondary-identifier-attribute-27lBtqHBopmgIAWwmeoLBg.immutable.md │ │ │ ├── storable-3UMrfeCfFVaVttjxtmCvVy.immutable.md │ │ │ ├── storable-attribute-1EYIvaUK2WvHmV3OxG5IzJ.immutable.md │ │ │ ├── storable-method-1o1egWUfiZzxO6y7Sda4rb.immutable.md │ │ │ ├── storable-primary-identifier-attribute-7qq06XjPnNRXumxcngJvZv.immutable.md │ │ │ ├── storable-property-6Ooq1wLy5pGx7Jb1qRrRyf.immutable.md │ │ │ ├── storable-secondary-identifier-attribute-1TKIxZX9JZ2aGFU5iEmUSv.immutable.md │ │ │ ├── store-1mGBVGCiO4K5X9dBakik9Z.immutable.md │ │ │ ├── validator-2aTKblcN30ADdXqrI3bHXv.immutable.md │ │ │ ├── value-type-FqKdGxNpEPIDVR56kq4NN.immutable.md │ │ │ ├── with-roles-CcLPnrWKVYxnZXgNTqVYi.immutable.md │ │ │ └── wrapper-4uhJFGS18pbZTt9qdtLnL2.immutable.md │ └── layr-favicon-3vtu1VGUfUfDawVC0zL4Oz.immutable.png ├── src │ ├── assets │ │ ├── f-plus-b-equals-love.svg │ │ ├── layr-logo-with-icon-dark-mode.svg │ │ ├── page-not-found.png │ │ ├── something-wrong.png │ │ └── typical-stack-vs-layr-stack.png │ ├── components │ │ ├── application.tsx │ │ ├── article.tsx │ │ ├── blog.tsx │ │ ├── docs.tsx │ │ ├── home.tsx │ │ ├── newsletter.tsx │ │ └── user.tsx │ ├── custom.d.ts │ ├── docs.json │ ├── index.ts │ ├── markdown.tsx │ ├── styles.ts │ └── ui.tsx └── tsconfig.json ├── package.json └── redirection ├── package.json ├── public └── index.html └── simple-deployment.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | .npmrc 5 | package-lock.json 6 | dist 7 | build 8 | _private 9 | *.private.* 10 | -------------------------------------------------------------------------------- /.npm-dev-registry.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["./packages/*"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | build 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "editorconfig.editorconfig"] 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Manuel Vila 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. -------------------------------------------------------------------------------- /assets/broken-heart.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/broken-heart.pdf -------------------------------------------------------------------------------- /assets/f-plus-b-equals-love-1500x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/f-plus-b-equals-love-1500x500.png -------------------------------------------------------------------------------- /assets/f-plus-b-equals-love-dark-mode-1000x420.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/f-plus-b-equals-love-dark-mode-1000x420.png -------------------------------------------------------------------------------- /assets/f-plus-b-equals-love-dark-mode-1400x600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/f-plus-b-equals-love-dark-mode-1400x600.png -------------------------------------------------------------------------------- /assets/f-plus-b-equals-love-dark-mode-1500x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/f-plus-b-equals-love-dark-mode-1500x500.png -------------------------------------------------------------------------------- /assets/f-plus-b-equals-love-dark-mode-1600x900.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/f-plus-b-equals-love-dark-mode-1600x900.png -------------------------------------------------------------------------------- /assets/f-plus-b-equals-love.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/f-plus-b-equals-love.pdf -------------------------------------------------------------------------------- /assets/hashtags/cross-layer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/hashtags/cross-layer.pdf -------------------------------------------------------------------------------- /assets/hashtags/full-stack.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/hashtags/full-stack.pdf -------------------------------------------------------------------------------- /assets/hashtags/javascript.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/hashtags/javascript.pdf -------------------------------------------------------------------------------- /assets/hashtags/low-level.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/hashtags/low-level.pdf -------------------------------------------------------------------------------- /assets/hashtags/no-web-api.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/hashtags/no-web-api.pdf -------------------------------------------------------------------------------- /assets/hashtags/object-oriented.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/hashtags/object-oriented.pdf -------------------------------------------------------------------------------- /assets/hashtags/typescript.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/hashtags/typescript.pdf -------------------------------------------------------------------------------- /assets/hashtags/unopinionated.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/hashtags/unopinionated.pdf -------------------------------------------------------------------------------- /assets/hashtags/web-app.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/hashtags/web-app.pdf -------------------------------------------------------------------------------- /assets/layr-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/layr-favicon.png -------------------------------------------------------------------------------- /assets/layr-icon-for-rounded-profile-dark-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/layr-icon-for-rounded-profile-dark-mode.png -------------------------------------------------------------------------------- /assets/layr-icon-for-rounded-profile.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/layr-icon-for-rounded-profile.pdf -------------------------------------------------------------------------------- /assets/layr-icon-for-rounded-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/layr-icon-for-rounded-profile.png -------------------------------------------------------------------------------- /assets/layr-icon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/layr-icon.pdf -------------------------------------------------------------------------------- /assets/layr-logo-with-icon-dark-mode.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/layr-logo-with-icon-dark-mode.pdf -------------------------------------------------------------------------------- /assets/layr-logo-with-icon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/assets/layr-logo-with-icon.pdf -------------------------------------------------------------------------------- /docs/contents/concepts/coming-soon.md: -------------------------------------------------------------------------------- 1 | ### Concepts 2 | 3 | #### Coming Soon 4 | 5 | This section will introduce the basic concepts of Layr, such as: 6 | 7 | - Components 8 | - Controlled attributes and methods 9 | - Cross-layer inheritance 10 | - Storage 11 | - Routes and wrappers 12 | 13 | Unfortunately, writing documentation takes a lot of time, and the Layr project is currently handled by a [single person](https://mvila.me) who has to work for customers to make a living and is also starting a new [ambitious project](https://1place.app), which will be based on Layr. 14 | 15 | More documentation will come for sure, but please be patient. 16 | 17 | If you cannot wait and feel adventurous, you can figure out how to build an app with Layr by checking out: 18 | 19 | - The ["Hello, World!"](https://layrjs.com/docs/v2/introduction/hello-world) introductory app. 20 | - The pretty exhaustive ["Reference"](https://layrjs.com/docs/v2/reference) section. 21 | - Some [examples](https://layrjs.com/docs/v2/introduction/introduction#examples) of simple full-stack apps built with Layr. 22 | -------------------------------------------------------------------------------- /docs/contents/guides/coming-soon.md: -------------------------------------------------------------------------------- 1 | ### Guides 2 | 3 | #### Coming Soon 4 | 5 | This section will contain some guides explaining how to achieve the most common tasks and features with Layr, such as: 6 | 7 | - Local development 8 | - Layouts and pages 9 | - Data fetching and storage 10 | - Authentication 11 | - Testing 12 | - Deployment 13 | 14 | Unfortunately, writing documentation takes a lot of time, and the Layr project is currently handled by a [single person](https://mvila.me) who has to work for customers to make a living and is also starting a new [ambitious project](https://1place.app), which will be based on Layr. 15 | 16 | More documentation will come for sure, but please be patient. 17 | 18 | If you cannot wait and feel adventurous, you can figure out how to build an app with Layr by checking out: 19 | 20 | - The ["Hello, World!"](https://layrjs.com/docs/v2/introduction/hello-world) introductory app. 21 | - The pretty exhaustive ["Reference"](https://layrjs.com/docs/v2/reference) section. 22 | - Some [examples](https://layrjs.com/docs/v2/introduction/introduction#examples) of simple full-stack apps built with Layr. 23 | -------------------------------------------------------------------------------- /docs/contents/introduction/handling-authorization.md: -------------------------------------------------------------------------------- 1 | ### Handling Authorization 2 | 3 | #### Coming Soon 4 | 5 | This tutorial will expand the ["Hello, World!"](https://layrjs.com/docs/v2/introduction/hello-world) app to show you how Layr handles user authorization. 6 | 7 | Unfortunately, writing tutorials takes a lot of time, and the Layr project is currently handled by a [single person](https://mvila.me) who has to work for customers to make a living and is also starting a new [ambitious project](https://1place.app), which will be based on Layr. 8 | 9 | This tutorial will come for sure, but please be patient. 10 | 11 | If you cannot wait and feel adventurous, you can figure out how to handle authorization with Layr by checking out: 12 | 13 | - The [`WithRoles()`](https://layrjs.com/docs/v2/reference/with-roles) mixin in the "Reference" section. 14 | - Some examples of simple full-stack apps handling authorization with Layr: 15 | - [Layr Website (TS)](https://github.com/layrjs/layr/tree/master/website) 16 | - [CodebaseShow (TS)](https://github.com/codebaseshow/codebaseshow) 17 | - [RealWorld Example App (JS)](https://github.com/layrjs/react-layr-realworld-example-app) 18 | -------------------------------------------------------------------------------- /docs/contents/introduction/hello-world/assets/screenshot-001.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/docs/contents/introduction/hello-world/assets/screenshot-001.immutable.png -------------------------------------------------------------------------------- /docs/contents/introduction/hello-world/assets/screenshot-002.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/docs/contents/introduction/hello-world/assets/screenshot-002.immutable.png -------------------------------------------------------------------------------- /docs/contents/introduction/hello-world/assets/screenshot-003.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/docs/contents/introduction/hello-world/assets/screenshot-003.immutable.png -------------------------------------------------------------------------------- /docs/contents/introduction/hello-world/assets/screenshot-004.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/docs/contents/introduction/hello-world/assets/screenshot-004.immutable.png -------------------------------------------------------------------------------- /docs/contents/introduction/storing-data.md: -------------------------------------------------------------------------------- 1 | ### Storing Data 2 | 3 | #### Coming Soon 4 | 5 | This tutorial will expand the ["Hello, World!"](https://layrjs.com/docs/v2/introduction/hello-world) app to show you how Layr handles database storage. 6 | 7 | Unfortunately, writing tutorials takes a lot of time, and the Layr project is currently handled by a [single person](https://mvila.me) who has to work for customers to make a living and is also starting a new [ambitious project](https://1place.app), which will be based on Layr. 8 | 9 | This tutorial will come for sure, but please be patient. 10 | 11 | If you cannot wait and feel adventurous, you can figure out how to store data with Layr by checking out: 12 | 13 | - The [`Storable()`](https://layrjs.com/docs/v2/reference/storable) mixin in the "Reference" section. 14 | - Some [examples](https://layrjs.com/docs/v2/introduction/introduction#examples) of simple full-stack apps built with Layr. 15 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/docs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Layr documentation", 6 | "keywords": [], 7 | "author": "Manuel Vila ", 8 | "license": "MIT", 9 | "repository": "https://github.com/layrjs/layr/tree/master/docs", 10 | "scripts": { 11 | "build": "node ./build.js" 12 | }, 13 | "dependencies": { 14 | "@mvila/simple-doc": "^0.1.138", 15 | "fs-extra": "^11.1.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/README.md: -------------------------------------------------------------------------------- 1 | # Counter with CreateReactApp (TS) 2 | 3 | A simple example to introduce the core concepts of Layr. 4 | 5 | ## Install 6 | 7 | Install the npm dependencies with: 8 | 9 | ```sh 10 | npm install 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Running the app in development mode 16 | 17 | Execute the following command: 18 | 19 | ```sh 20 | npm run start 21 | ``` 22 | 23 | The app should then be available at http://localhost:3000. 24 | 25 | ### Debugging 26 | 27 | #### Client 28 | 29 | Add the following entry in the local storage of your browser: 30 | 31 | ``` 32 | | Key | Value | 33 | | ----- | --------- | 34 | | debug | layr:* | 35 | ``` 36 | 37 | #### Server 38 | 39 | Add the following environment variables when starting the app: 40 | 41 | ```sh 42 | DEBUG=layr:* DEBUG_DEPTH=10 43 | ``` 44 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-create-react-app-ts-backend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "nodemon --watch ./src --exec ts-node ./src/http-server.ts" 9 | }, 10 | "dependencies": { 11 | "@layr/component": "^1.0.0", 12 | "@layr/component-server": "^1.0.0", 13 | "tslib": "^2.0.3" 14 | }, 15 | "devDependencies": { 16 | "@layr/component-http-server": "^1.0.0", 17 | "@types/node": "^14.11.8", 18 | "nodemon": "^2.0.5", 19 | "ts-node": "^9.0.0", 20 | "typescript": "^4.0.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/backend/src/components/counter.ts: -------------------------------------------------------------------------------- 1 | import {Component, primaryIdentifier, attribute, method, expose} from '@layr/component'; 2 | 3 | export class Counter extends Component { 4 | @expose({get: true, set: true}) @primaryIdentifier() id!: string; 5 | 6 | @expose({get: true, set: true}) @attribute('number') value = 0; 7 | 8 | @expose({call: true}) @method() async increment() { 9 | this.value++; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/backend/src/http-server.ts: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPServer} from '@layr/component-http-server'; 2 | 3 | import {server} from './server'; 4 | 5 | const httpServer = new ComponentHTTPServer(server, {port: 3001}); 6 | httpServer.start(); 7 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/backend/src/server.ts: -------------------------------------------------------------------------------- 1 | import {ComponentServer} from '@layr/component-server'; 2 | 3 | import {Counter} from './components/counter'; 4 | 5 | export const server = new ComponentServer(Counter); 6 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "importHelpers": true, 10 | "strict": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "experimentalDecorators": true, 18 | "resolveJsonModule": true, 19 | "stripInternal": true, 20 | "skipLibCheck": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-create-react-app-ts-frontend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "react-scripts start", 9 | "build": "react-scripts build", 10 | "test": "react-scripts test", 11 | "eject": "react-scripts eject" 12 | }, 13 | "dependencies": { 14 | "@layr/component": "^1.0.6", 15 | "@layr/component-http-client": "^1.0.3", 16 | "@layr/react-integration": "^1.0.3", 17 | "@types/node": "^14.11.8", 18 | "@types/react": "^16.9.52", 19 | "@types/react-dom": "^16.9.8", 20 | "react": "^16.13.1", 21 | "react-dom": "^16.13.1", 22 | "react-scripts": "3.4.3", 23 | "typescript": "^4.0.3" 24 | }, 25 | "eslintConfig": { 26 | "extends": "react-app" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/examples/v1/counter-with-create-react-app-ts/frontend/public/favicon.ico -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/examples/v1/counter-with-create-react-app-ts/frontend/public/logo192.png -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/examples/v1/counter-with-create-react-app-ts/frontend/public/logo512.png -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/src/components/counter.tsx: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | import React from 'react'; 3 | import {view} from '@layr/react-integration'; 4 | 5 | import type {Counter as BackendCounter} from '../../../backend/src/components/counter'; 6 | 7 | export const getCounter = async () => { 8 | const client = new ComponentHTTPClient('http://localhost:3001'); 9 | 10 | const BackendCounterProxy = (await client.getComponent()) as typeof BackendCounter; 11 | 12 | class Counter extends BackendCounterProxy { 13 | @view() Main() { 14 | return ( 15 |
16 | 17 | 24 |
25 | ); 26 | } 27 | } 28 | 29 | return Counter; 30 | }; 31 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import {getCounter} from './components/counter'; 5 | 6 | (async () => { 7 | const Counter = await getCounter(); 8 | 9 | const counter = new Counter(); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | })(); 18 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "noEmit": true, 19 | "jsx": "react", 20 | "experimentalDecorators": true, 21 | "skipLibCheck": true 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/v1/counter-with-create-react-app-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-create-react-app-ts", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "postinstall": "(cd ./frontend && npm install) && (cd ./backend && npm install)", 9 | "start": "concurrently --names=frontend,backend --prefix-colors=green,blue --kill-others \"(cd ./frontend && npm run start)\" \"(cd ./backend && npm run start)\"", 10 | "update": "(cd ./frontend && npm update) && (cd ./backend && npm update)" 11 | }, 12 | "devDependencies": { 13 | "concurrently": "^5.3.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/README.md: -------------------------------------------------------------------------------- 1 | # Counter with esbuild (TS) 2 | 3 | A simple example to introduce the core concepts of Layr. 4 | 5 | ## Install 6 | 7 | Install the npm dependencies with: 8 | 9 | ```sh 10 | npm install 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Running the app in development mode 16 | 17 | Execute the following command: 18 | 19 | ```sh 20 | npm run start 21 | ``` 22 | 23 | The app should then be available at http://localhost:3000. 24 | 25 | ### Debugging 26 | 27 | #### Client 28 | 29 | Add the following entry in the local storage of your browser: 30 | 31 | ``` 32 | | Key | Value | 33 | | ----- | --------- | 34 | | debug | layr:* | 35 | ``` 36 | 37 | #### Server 38 | 39 | Add the following environment variables when starting the app: 40 | 41 | ```sh 42 | DEBUG=layr:* DEBUG_DEPTH=10 43 | ``` 44 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-esbuild-ts-backend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "esbuild ./src/http-server.ts --bundle --target=node12 --sourcemap --platform=node --external:koa --outfile=./build/bundle.js", 9 | "start": "nodemon --watch ./src --ext ts --exec 'npm run build && node -r source-map-support/register ./build/bundle.js'" 10 | }, 11 | "dependencies": { 12 | "@layr/component": "^1.1.2", 13 | "@layr/component-server": "^1.1.2", 14 | "tslib": "^2.1.0" 15 | }, 16 | "devDependencies": { 17 | "@layr/component-http-server": "^1.1.2", 18 | "@types/node": "^14.14.20", 19 | "esbuild": "^0.9.2", 20 | "nodemon": "^2.0.7", 21 | "source-map-support": "^0.5.19", 22 | "typescript": "^4.1.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/backend/src/components/counter.ts: -------------------------------------------------------------------------------- 1 | import {Component, primaryIdentifier, attribute, method, expose} from '@layr/component'; 2 | 3 | export class Counter extends Component { 4 | @expose({get: true, set: true}) @primaryIdentifier() id!: string; 5 | 6 | @expose({get: true, set: true}) @attribute('number') value = 0; 7 | 8 | @expose({call: true}) @method() async increment() { 9 | this.value++; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/backend/src/http-server.ts: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPServer} from '@layr/component-http-server'; 2 | 3 | import {server} from './server'; 4 | 5 | const httpServer = new ComponentHTTPServer(server, {port: 3001}); 6 | httpServer.start(); 7 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/backend/src/server.ts: -------------------------------------------------------------------------------- 1 | import {ComponentServer} from '@layr/component-server'; 2 | 3 | import {Counter} from './components/counter'; 4 | 5 | export const server = new ComponentServer(Counter); 6 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "importHelpers": true, 10 | "strict": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "experimentalDecorators": true, 18 | "resolveJsonModule": true, 19 | "stripInternal": true, 20 | "skipLibCheck": true, 21 | "isolatedModules": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-esbuild-ts-frontend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "copy": "mkdir -p ./build && cp ./src/index.html ./build/index.html", 9 | "watch": "npm run copy && esbuild ./src/index.tsx --bundle --target=es2017 --watch --minify --keep-names --sourcemap --define:process.env.NODE_ENV=\\\"development\\\" --outfile=./build/bundle.js", 10 | "serve": "serve --listen 3000 ./build", 11 | "start": "npm-run-all --parallel watch serve" 12 | }, 13 | "dependencies": { 14 | "@layr/component": "^1.1.2", 15 | "@layr/component-http-client": "^1.1.1", 16 | "@layr/react-integration": "^1.0.22", 17 | "react": "^16.13.1", 18 | "react-dom": "^16.13.1" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^14.14.20", 22 | "@types/react": "^16.14.2", 23 | "@types/react-dom": "^16.9.10", 24 | "esbuild": "^0.9.2", 25 | "npm-run-all": "^4.1.5", 26 | "serve": "^11.3.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/frontend/src/components/counter.tsx: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | import React from 'react'; 3 | import {view} from '@layr/react-integration'; 4 | 5 | import type {Counter as BackendCounter} from '../../../backend/src/components/counter'; 6 | 7 | export const getCounter = async () => { 8 | const client = new ComponentHTTPClient('http://localhost:3001'); 9 | 10 | const BackendCounterProxy = (await client.getComponent()) as typeof BackendCounter; 11 | 12 | class Counter extends BackendCounterProxy { 13 | @view() Main() { 14 | return ( 15 |
16 | 17 | 24 |
25 | ); 26 | } 27 | } 28 | 29 | return Counter; 30 | }; 31 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My Rollup Project 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import {getCounter} from './components/counter'; 5 | 6 | (async () => { 7 | const Counter = await getCounter(); 8 | 9 | const counter = new Counter(); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | })(); 18 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "importHelpers": true, 10 | "strict": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "experimentalDecorators": true, 18 | "resolveJsonModule": true, 19 | "stripInternal": true, 20 | "skipLibCheck": true, 21 | "isolatedModules": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/v1/counter-with-esbuild-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-rollup-ts", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "postinstall": "(cd ./frontend && npm install) && (cd ./backend && npm install)", 9 | "start": "concurrently --names=frontend,backend --prefix-colors=green,blue --kill-others \"(cd ./frontend && npm run start)\" \"(cd ./backend && npm run start)\"", 10 | "update": "(cd ./frontend && npm update) && (cd ./backend && npm update)" 11 | }, 12 | "devDependencies": { 13 | "concurrently": "^5.3.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/v1/counter-with-http/README.md: -------------------------------------------------------------------------------- 1 | # Counter with HTTP 2 | 3 | A simple example to introduce the core concepts of Layr. 4 | 5 | ## Usage 6 | 7 | Install the npm dependencies with: 8 | 9 | ```sh 10 | npm install 11 | ``` 12 | 13 | Run the backend with: 14 | 15 | ```sh 16 | npx babel-node ./src/backend.js 17 | ``` 18 | 19 | Then, in another terminal, run the frontend with: 20 | 21 | ```sh 22 | npx babel-node ./src/frontend.js 23 | ``` 24 | -------------------------------------------------------------------------------- /examples/v1/counter-with-http/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", {"targets": {"node": "10"}}]], 3 | "plugins": [ 4 | ["@babel/plugin-proposal-decorators", {"legacy": true}], 5 | ["@babel/plugin-proposal-class-properties"] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/v1/counter-with-http/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "CommonJS", 5 | "checkJs": false, 6 | "experimentalDecorators": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/v1/counter-with-http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-http", 3 | "version": "1.0.0", 4 | "description": "A simple example to introduce the core concepts of Layr", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.11.1", 13 | "@babel/node": "^7.10.5", 14 | "@babel/plugin-proposal-class-properties": "^7.10.4", 15 | "@babel/plugin-proposal-decorators": "^7.10.5", 16 | "@babel/preset-env": "^7.11.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/v1/counter-with-http/src/backend.js: -------------------------------------------------------------------------------- 1 | import {Component, primaryIdentifier, attribute, method, expose} from '@layr/component'; 2 | import {ComponentHTTPServer} from '@layr/component-http-server'; 3 | 4 | class Counter extends Component { 5 | // We need a primary identifier so a Counter instance 6 | // can be transported between the frontend and the backend 7 | // while keeping it's identity 8 | @expose({get: true, set: true}) @primaryIdentifier() id; 9 | 10 | // The counter's value is exposed to the frontend 11 | @expose({get: true, set: true}) @attribute('number') value = 0; 12 | 13 | // And the "business logic" is exposed as well 14 | @expose({call: true}) @method() increment() { 15 | this.value++; 16 | } 17 | } 18 | 19 | // We serve the Counter through a ComponentHTTPServer 20 | const server = new ComponentHTTPServer(Counter, {port: 3210}); 21 | server.start(); 22 | -------------------------------------------------------------------------------- /examples/v1/counter-with-http/src/frontend.js: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | 3 | (async () => { 4 | // We create a client that is connected to the backend's server 5 | const client = new ComponentHTTPClient('http://localhost:3210'); 6 | 7 | // We get the backend's Counter component 8 | const BackendCounter = await client.getComponent(); 9 | 10 | // We extends the backend's Counter component so we can override the increment() method 11 | class Counter extends BackendCounter { 12 | async increment() { 13 | await super.increment(); // The backend's `increment()` method is invoked 14 | console.log(this.value); // Some additional code is executed in the frontend 15 | } 16 | } 17 | 18 | // Lastly, we consume the Counter 19 | const counter = new Counter(); 20 | await counter.increment(); 21 | })(); 22 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/README.md: -------------------------------------------------------------------------------- 1 | # Counter with Parcel (TS) 2 | 3 | A simple example to introduce the core concepts of Layr. 4 | 5 | ## Install 6 | 7 | Install the npm dependencies with: 8 | 9 | ```sh 10 | npm install 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Running the app in development mode 16 | 17 | Execute the following command: 18 | 19 | ```sh 20 | npm run start 21 | ``` 22 | 23 | The app should then be available at http://localhost:1234. 24 | 25 | ### Debugging 26 | 27 | #### Client 28 | 29 | Add the following entry in the local storage of your browser: 30 | 31 | ``` 32 | | Key | Value | 33 | | ----- | --------- | 34 | | debug | layr:* | 35 | ``` 36 | 37 | #### Server 38 | 39 | Add the following environment variables when starting the app: 40 | 41 | ```sh 42 | DEBUG=layr:* DEBUG_DEPTH=10 43 | ``` 44 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-parcel-ts-backend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "nodemon --watch ./src --exec ts-node ./src/http-server.ts" 9 | }, 10 | "dependencies": { 11 | "@layr/component": "^1.1.2", 12 | "@layr/component-server": "^1.1.2", 13 | "tslib": "^2.1.0" 14 | }, 15 | "devDependencies": { 16 | "@layr/component-http-server": "^1.1.2", 17 | "@types/node": "^14.14.20", 18 | "nodemon": "^2.0.5", 19 | "ts-node": "^9.1.1", 20 | "typescript": "^4.1.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/backend/src/components/counter.ts: -------------------------------------------------------------------------------- 1 | import {Component, primaryIdentifier, attribute, method, expose} from '@layr/component'; 2 | 3 | export class Counter extends Component { 4 | @expose({get: true, set: true}) @primaryIdentifier() id!: string; 5 | 6 | @expose({get: true, set: true}) @attribute('number') value = 0; 7 | 8 | @expose({call: true}) @method() async increment() { 9 | this.value++; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/backend/src/http-server.ts: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPServer} from '@layr/component-http-server'; 2 | 3 | import {server} from './server'; 4 | 5 | const httpServer = new ComponentHTTPServer(server, {port: 1235}); 6 | httpServer.start(); 7 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/backend/src/server.ts: -------------------------------------------------------------------------------- 1 | import {ComponentServer} from '@layr/component-server'; 2 | 3 | import {Counter} from './components/counter'; 4 | 5 | export const server = new ComponentServer(Counter); 6 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "importHelpers": true, 10 | "strict": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "experimentalDecorators": true, 18 | "resolveJsonModule": true, 19 | "stripInternal": true, 20 | "skipLibCheck": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | /.parcel-cache 2 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | api.cache(true); 3 | 4 | const presets = [ 5 | ['@babel/preset-typescript'], 6 | [ 7 | '@babel/preset-env', 8 | { 9 | targets: {chrome: '55', safari: '11', firefox: '54'}, 10 | loose: true, 11 | modules: false 12 | } 13 | ], 14 | ['@babel/preset-react'] 15 | ]; 16 | 17 | const plugins = [ 18 | ['@babel/plugin-proposal-decorators', {legacy: true}], 19 | ['@babel/plugin-proposal-class-properties', {loose: true}] 20 | ]; 21 | 22 | return {presets, plugins}; 23 | }; 24 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-parcel-ts-frontend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "parcel serve ./src/index.html" 9 | }, 10 | "dependencies": { 11 | "@layr/component": "^1.1.2", 12 | "@layr/component-http-client": "^1.1.1", 13 | "@layr/react-integration": "^1.0.22", 14 | "react": "^16.13.1", 15 | "react-dom": "^16.13.1" 16 | }, 17 | "devDependencies": { 18 | "@babel/plugin-proposal-class-properties": "^7.10.4", 19 | "@babel/plugin-proposal-decorators": "^7.12.12", 20 | "@babel/preset-env": "^7.12.11", 21 | "@babel/preset-react": "^7.12.10", 22 | "@babel/preset-typescript": "^7.12.7", 23 | "@types/node": "^14.14.20", 24 | "@types/react": "^16.14.2", 25 | "@types/react-dom": "^16.9.10", 26 | "parcel": "next" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/frontend/src/components/counter.tsx: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | import React from 'react'; 3 | import {view} from '@layr/react-integration'; 4 | 5 | import type {Counter as BackendCounter} from '../../../backend/src/components/counter'; 6 | 7 | export const getCounter = async () => { 8 | const client = new ComponentHTTPClient('http://localhost:1235'); 9 | 10 | const BackendCounterProxy = (await client.getComponent()) as typeof BackendCounter; 11 | 12 | class Counter extends BackendCounterProxy { 13 | @view() Main() { 14 | return ( 15 |
16 | 17 | 24 |
25 | ); 26 | } 27 | } 28 | 29 | return Counter; 30 | }; 31 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My Parcel Project 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import {getCounter} from './components/counter'; 5 | 6 | (async () => { 7 | const Counter = await getCounter(); 8 | 9 | const counter = new Counter(); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | })(); 18 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "importHelpers": true, 10 | "strict": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "experimentalDecorators": true, 18 | "resolveJsonModule": true, 19 | "stripInternal": true, 20 | "skipLibCheck": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/v1/counter-with-parcel-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-parcel-ts", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "postinstall": "(cd ./frontend && npm install) && (cd ./backend && npm install)", 9 | "start": "concurrently --names=frontend,backend --prefix-colors=green,blue --kill-others \"(cd ./frontend && npm run start)\" \"(cd ./backend && npm run start)\"", 10 | "update": "(cd ./frontend && npm update) && (cd ./backend && npm update)" 11 | }, 12 | "devDependencies": { 13 | "concurrently": "^5.3.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/README.md: -------------------------------------------------------------------------------- 1 | # Counter with Rollup (TS) 2 | 3 | A simple example to introduce the core concepts of Layr. 4 | 5 | ## Install 6 | 7 | Install the npm dependencies with: 8 | 9 | ```sh 10 | npm install 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Running the app in development mode 16 | 17 | Execute the following command: 18 | 19 | ```sh 20 | npm run start 21 | ``` 22 | 23 | The app should then be available at http://localhost:3000. 24 | 25 | ### Debugging 26 | 27 | #### Client 28 | 29 | Add the following entry in the local storage of your browser: 30 | 31 | ``` 32 | | Key | Value | 33 | | ----- | --------- | 34 | | debug | layr:* | 35 | ``` 36 | 37 | #### Server 38 | 39 | Add the following environment variables when starting the app: 40 | 41 | ```sh 42 | DEBUG=layr:* DEBUG_DEPTH=10 43 | ``` 44 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-rollup-ts-backend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "nodemon --watch ./src --exec ts-node ./src/http-server.ts" 9 | }, 10 | "dependencies": { 11 | "@layr/component": "^1.1.2", 12 | "@layr/component-server": "^1.1.2", 13 | "tslib": "^2.1.0" 14 | }, 15 | "devDependencies": { 16 | "@layr/component-http-server": "^1.1.2", 17 | "@types/node": "^14.14.20", 18 | "nodemon": "^2.0.5", 19 | "ts-node": "^9.1.1", 20 | "typescript": "^4.1.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/backend/src/components/counter.ts: -------------------------------------------------------------------------------- 1 | import {Component, primaryIdentifier, attribute, method, expose} from '@layr/component'; 2 | 3 | export class Counter extends Component { 4 | @expose({get: true, set: true}) @primaryIdentifier() id!: string; 5 | 6 | @expose({get: true, set: true}) @attribute('number') value = 0; 7 | 8 | @expose({call: true}) @method() async increment() { 9 | this.value++; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/backend/src/http-server.ts: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPServer} from '@layr/component-http-server'; 2 | 3 | import {server} from './server'; 4 | 5 | const httpServer = new ComponentHTTPServer(server, {port: 3001}); 6 | httpServer.start(); 7 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/backend/src/server.ts: -------------------------------------------------------------------------------- 1 | import {ComponentServer} from '@layr/component-server'; 2 | 3 | import {Counter} from './components/counter'; 4 | 5 | export const server = new ComponentServer(Counter); 6 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "importHelpers": true, 10 | "strict": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "experimentalDecorators": true, 18 | "resolveJsonModule": true, 19 | "stripInternal": true, 20 | "skipLibCheck": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | api.cache(true); 3 | 4 | const presets = [ 5 | ['@babel/preset-typescript'], 6 | [ 7 | '@babel/preset-env', 8 | { 9 | targets: {chrome: '55', safari: '11', firefox: '54'}, 10 | loose: true, 11 | modules: false 12 | } 13 | ], 14 | ['@babel/preset-react'] 15 | ]; 16 | 17 | const plugins = [ 18 | ['@babel/plugin-proposal-decorators', {legacy: true}], 19 | ['@babel/plugin-proposal-class-properties', {loose: true}] 20 | ]; 21 | 22 | return {presets, plugins}; 23 | }; 24 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/frontend/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from '@rollup/plugin-node-resolve'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import babel from '@rollup/plugin-babel'; 4 | import globals from 'rollup-plugin-node-globals'; 5 | 6 | export default { 7 | input: 'src/index.tsx', 8 | output: { 9 | file: 'build/bundle.js', 10 | format: 'iife', 11 | sourcemap: true 12 | }, 13 | plugins: [ 14 | resolve({browser: true, extensions: ['.mjs', '.js', '.json', '.node', '.ts', '.tsx']}), 15 | commonjs(), 16 | globals(), 17 | babel({extensions: ['ts', 'tsx'], babelHelpers: 'bundled'}) 18 | ] 19 | }; 20 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/frontend/src/components/counter.tsx: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | import React from 'react'; 3 | import {view} from '@layr/react-integration'; 4 | 5 | import type {Counter as BackendCounter} from '../../../backend/src/components/counter'; 6 | 7 | export const getCounter = async () => { 8 | const client = new ComponentHTTPClient('http://localhost:3001'); 9 | 10 | const BackendCounterProxy = (await client.getComponent()) as typeof BackendCounter; 11 | 12 | class Counter extends BackendCounterProxy { 13 | @view() Main() { 14 | return ( 15 |
16 | 17 | 24 |
25 | ); 26 | } 27 | } 28 | 29 | return Counter; 30 | }; 31 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My Rollup Project 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import {getCounter} from './components/counter'; 5 | 6 | (async () => { 7 | const Counter = await getCounter(); 8 | 9 | const counter = new Counter(); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | })(); 18 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "importHelpers": true, 10 | "strict": true, 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "experimentalDecorators": true, 18 | "resolveJsonModule": true, 19 | "stripInternal": true, 20 | "skipLibCheck": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/v1/counter-with-rollup-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-with-rollup-ts", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "postinstall": "(cd ./frontend && npm install) && (cd ./backend && npm install)", 9 | "start": "concurrently --names=frontend,backend --prefix-colors=green,blue --kill-others \"(cd ./frontend && npm run start)\" \"(cd ./backend && npm run start)\"", 10 | "update": "(cd ./frontend && npm update) && (cd ./backend && npm update)" 11 | }, 12 | "devDependencies": { 13 | "concurrently": "^5.3.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/v1/counter/README.md: -------------------------------------------------------------------------------- 1 | # Counter 2 | 3 | A simple example to introduce the core concepts of Layr. 4 | 5 | ## Usage 6 | 7 | Install the npm dependencies with: 8 | 9 | ```sh 10 | npm install 11 | ``` 12 | 13 | Run the example with: 14 | 15 | ```sh 16 | npx babel-node ./src/frontend.js 17 | ``` 18 | -------------------------------------------------------------------------------- /examples/v1/counter/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", {"targets": {"node": "10"}}]], 3 | "plugins": [ 4 | ["@babel/plugin-proposal-decorators", {"legacy": true}], 5 | ["@babel/plugin-proposal-class-properties"] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/v1/counter/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "CommonJS", 5 | "checkJs": false, 6 | "experimentalDecorators": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/v1/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter", 3 | "version": "1.0.0", 4 | "description": "A simple example to introduce the core concepts of Layr", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-client": "^1.0.0", 9 | "@layr/component-server": "^1.0.0" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.11.1", 13 | "@babel/node": "^7.10.5", 14 | "@babel/plugin-proposal-class-properties": "^7.10.4", 15 | "@babel/plugin-proposal-decorators": "^7.10.5", 16 | "@babel/preset-env": "^7.11.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/v1/counter/src/backend.js: -------------------------------------------------------------------------------- 1 | import {Component, primaryIdentifier, attribute, method, expose} from '@layr/component'; 2 | import {ComponentServer} from '@layr/component-server'; 3 | 4 | class Counter extends Component { 5 | // We need a primary identifier so a Counter instance 6 | // can be transported between the frontend and the backend 7 | // while keeping it's identity 8 | @expose({get: true, set: true}) @primaryIdentifier() id; 9 | 10 | // The counter's value is exposed to the frontend 11 | @expose({get: true, set: true}) @attribute('number') value = 0; 12 | 13 | // And the "business logic" is exposed as well 14 | @expose({call: true}) @method() increment() { 15 | this.value++; 16 | } 17 | } 18 | 19 | // We serve the Counter through a ComponentServer 20 | export const server = new ComponentServer(Counter); 21 | -------------------------------------------------------------------------------- /examples/v1/counter/src/frontend.js: -------------------------------------------------------------------------------- 1 | import {ComponentClient} from '@layr/component-client'; 2 | 3 | import {server} from './backend'; 4 | 5 | // We create a client that is connected to the backend's server 6 | const client = new ComponentClient(server); 7 | 8 | // We get the backend's Counter component 9 | const BackendCounter = client.getComponent(); 10 | 11 | // We extends the backend's Counter component so we can override the increment() method 12 | class Counter extends BackendCounter { 13 | increment() { 14 | super.increment(); // The backend's `increment()` method is invoked 15 | console.log(this.value); // Some additional code is executed in the frontend 16 | } 17 | } 18 | 19 | // Lastly, we consume the Counter 20 | const counter = new Counter(); 21 | counter.increment(); 22 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-js/README.md: -------------------------------------------------------------------------------- 1 | # Guestbook CLI (JS) 2 | 3 | A simple CLI app to introduce data storage with Layr. 4 | 5 | See the [corresponding guide](https://layrjs.com/docs/v1/introduction/data-storage?language=js). 6 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-js/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", {"targets": {"node": "10"}}]], 3 | "plugins": [ 4 | ["@babel/plugin-proposal-decorators", {"legacy": true}], 5 | ["@babel/plugin-proposal-class-properties"] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-js/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "CommonJS", 5 | "checkJs": false, 6 | "experimentalDecorators": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guestbook-cli", 3 | "version": "1.0.0", 4 | "description": "A simple CLI app to introduce data storage with Layr", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0", 10 | "@layr/memory-store": "^1.0.0", 11 | "@layr/storable": "^1.0.0" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.11.1", 15 | "@babel/node": "^7.10.5", 16 | "@babel/plugin-proposal-class-properties": "^7.10.4", 17 | "@babel/plugin-proposal-decorators": "^7.10.5", 18 | "@babel/preset-env": "^7.11.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-js/src/backend.js: -------------------------------------------------------------------------------- 1 | import {Component, expose, validators} from '@layr/component'; 2 | import {Storable, primaryIdentifier, attribute} from '@layr/storable'; 3 | import {MemoryStore} from '@layr/memory-store'; 4 | import {ComponentHTTPServer} from '@layr/component-http-server'; 5 | 6 | const {notEmpty, maxLength} = validators; 7 | 8 | @expose({ 9 | find: {call: true}, 10 | prototype: { 11 | load: {call: true}, 12 | save: {call: true} 13 | } 14 | }) 15 | export class Message extends Storable(Component) { 16 | @expose({get: true, set: true}) @primaryIdentifier() id; 17 | 18 | @expose({get: true, set: true}) 19 | @attribute('string', {validators: [notEmpty(), maxLength(300)]}) 20 | text = ''; 21 | 22 | @expose({get: true}) @attribute('Date') createdAt = new Date(); 23 | } 24 | 25 | const store = new MemoryStore(); 26 | 27 | store.registerStorable(Message); 28 | 29 | const server = new ComponentHTTPServer(Message, {port: 3210}); 30 | 31 | server.start(); 32 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-js/src/frontend.js: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | import {Storable} from '@layr/storable'; 3 | 4 | (async () => { 5 | const client = new ComponentHTTPClient('http://localhost:3210', { 6 | mixins: [Storable] 7 | }); 8 | 9 | const Message = await client.getComponent(); 10 | 11 | const text = process.argv[2]; 12 | 13 | if (text) { 14 | addMessage(text); 15 | } else { 16 | showMessages(); 17 | } 18 | 19 | async function addMessage(text) { 20 | const message = new Message({text}); 21 | await message.save(); 22 | console.log(`Message successfully added`); 23 | } 24 | 25 | async function showMessages() { 26 | const messages = await Message.find( 27 | {}, 28 | {text: true, createdAt: true}, 29 | {sort: {createdAt: 'desc'}, limit: 30} 30 | ); 31 | 32 | for (const message of messages) { 33 | console.log(`[${message.createdAt.toISOString()}] ${message.text}`); 34 | } 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-ts/README.md: -------------------------------------------------------------------------------- 1 | # Guestbook CLI (TS) 2 | 3 | A simple CLI app to introduce data storage with Layr. 4 | 5 | See the [corresponding guide](https://layrjs.com/docs/v1/introduction/data-storage?language=ts). 6 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guestbook-cli", 3 | "version": "1.0.0", 4 | "description": "A simple CLI app to introduce data storage with Layr", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0", 10 | "@layr/memory-store": "^1.0.0", 11 | "@layr/storable": "^1.0.0" 12 | }, 13 | "devDependencies": { 14 | "ts-node": "^8.10.2", 15 | "typescript": "^3.9.7" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-ts/src/backend.ts: -------------------------------------------------------------------------------- 1 | import {Component, expose, validators} from '@layr/component'; 2 | import {Storable, primaryIdentifier, attribute} from '@layr/storable'; 3 | import {MemoryStore} from '@layr/memory-store'; 4 | import {ComponentHTTPServer} from '@layr/component-http-server'; 5 | 6 | const {notEmpty, maxLength} = validators; 7 | 8 | @expose({ 9 | find: {call: true}, 10 | prototype: { 11 | load: {call: true}, 12 | save: {call: true} 13 | } 14 | }) 15 | export class Message extends Storable(Component) { 16 | @expose({get: true, set: true}) @primaryIdentifier() id!: string; 17 | 18 | @expose({get: true, set: true}) 19 | @attribute('string', {validators: [notEmpty(), maxLength(300)]}) 20 | text = ''; 21 | 22 | @expose({get: true}) @attribute('Date') createdAt = new Date(); 23 | } 24 | 25 | const store = new MemoryStore(); 26 | 27 | store.registerStorable(Message); 28 | 29 | const server = new ComponentHTTPServer(Message, {port: 3210}); 30 | 31 | server.start(); 32 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-ts/src/frontend.ts: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | import {Storable} from '@layr/storable'; 3 | 4 | import type {Message as MessageType} from './backend'; 5 | 6 | (async () => { 7 | const client = new ComponentHTTPClient('http://localhost:3210', { 8 | mixins: [Storable] 9 | }); 10 | 11 | const Message = (await client.getComponent()) as typeof MessageType; 12 | 13 | const text = process.argv[2]; 14 | 15 | if (text) { 16 | addMessage(text); 17 | } else { 18 | showMessages(); 19 | } 20 | 21 | async function addMessage(text: string) { 22 | const message = new Message({text}); 23 | await message.save(); 24 | console.log(`Message successfully added`); 25 | } 26 | 27 | async function showMessages() { 28 | const messages = await Message.find( 29 | {}, 30 | {text: true, createdAt: true}, 31 | {sort: {createdAt: 'desc'}, limit: 30} 32 | ); 33 | 34 | for (const message of messages) { 35 | console.log(`[${message.createdAt.toISOString()}] ${message.text}`); 36 | } 37 | } 38 | })(); 39 | -------------------------------------------------------------------------------- /examples/v1/guestbook-cli-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "sourceMap": true, 8 | "strict": true, 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "experimentalDecorators": true, 16 | "skipLibCheck": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-js/README.md: -------------------------------------------------------------------------------- 1 | # Guestbook Web (JS) 2 | 3 | A simple web app showing how to build a web frontend with Layr. 4 | 5 | See the [corresponding guide](https://layrjs.com/docs/v1/introduction/web-app?language=js). 6 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-js/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", {"targets": {"node": "10"}}], "@babel/preset-react"], 3 | "plugins": [ 4 | ["@babel/plugin-proposal-decorators", {"legacy": true}], 5 | ["@babel/plugin-proposal-class-properties"] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-js/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "CommonJS", 5 | "checkJs": false, 6 | "experimentalDecorators": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guestbook-web", 3 | "version": "1.0.0", 4 | "description": "A simple web app showing how to build a web frontend with Layr", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0", 10 | "@layr/memory-store": "^1.0.0", 11 | "@layr/react-integration": "^1.0.0", 12 | "@layr/storable": "^1.0.0", 13 | "react": "^16.13.1", 14 | "react-dom": "^16.13.1" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.11.5", 18 | "@babel/node": "^7.10.5", 19 | "@babel/plugin-proposal-class-properties": "^7.10.4", 20 | "@babel/plugin-proposal-decorators": "^7.10.5", 21 | "@babel/preset-env": "^7.11.5", 22 | "@babel/preset-react": "^7.10.4", 23 | "babel-loader": "^8.1.0", 24 | "html-webpack-plugin": "^4.4.1", 25 | "webpack": "^4.44.1", 26 | "webpack-cli": "^3.3.12", 27 | "webpack-dev-server": "^3.11.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-js/src/backend.js: -------------------------------------------------------------------------------- 1 | import {Component, expose, validators} from '@layr/component'; 2 | import {Storable, primaryIdentifier, attribute} from '@layr/storable'; 3 | import {MemoryStore} from '@layr/memory-store'; 4 | import {ComponentHTTPServer} from '@layr/component-http-server'; 5 | 6 | const {notEmpty, maxLength} = validators; 7 | 8 | @expose({ 9 | find: {call: true}, 10 | prototype: { 11 | load: {call: true}, 12 | save: {call: true} 13 | } 14 | }) 15 | export class Message extends Storable(Component) { 16 | @expose({get: true, set: true}) @primaryIdentifier() id; 17 | 18 | @expose({get: true, set: true}) 19 | @attribute('string', {validators: [notEmpty(), maxLength(300)]}) 20 | text = ''; 21 | 22 | @expose({get: true}) @attribute('Date') createdAt = new Date(); 23 | } 24 | 25 | const store = new MemoryStore(); 26 | 27 | store.registerStorable(Message); 28 | 29 | const server = new ComponentHTTPServer(Message, {port: 3210}); 30 | 31 | server.start(); 32 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-js/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Guestbook 6 | 7 | 8 | <%= htmlWebpackPlugin.tags.headTags %> 9 | 10 | 11 | 12 |
13 | <%= htmlWebpackPlugin.tags.bodyTags %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-js/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | module.exports = (env, argv) => { 6 | return { 7 | // The entry point of the app is './src/frontend.js' 8 | entry: './src/frontend.js', 9 | output: { 10 | // Specify '/' as the base path for all the assets 11 | // This is required for a single-page application 12 | publicPath: '/' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | // Use 'babel-loader' to compile the JS files 18 | test: /\.js$/, 19 | include: path.join(__dirname, 'src'), 20 | loader: 'babel-loader' 21 | } 22 | ] 23 | }, 24 | plugins: [ 25 | // Use 'html-webpack-plugin' to generate the 'index.html' file 26 | // from the './src/index.html' template 27 | new HtmlWebPackPlugin({ 28 | template: './src/index.html', 29 | inject: false 30 | }) 31 | ], 32 | // Generate source maps to make debugging easier 33 | devtool: 'eval-cheap-module-source-map', 34 | devServer: { 35 | // Fallback to 'index.html' in case of 404 responses 36 | // This is required for a single-page application 37 | historyApiFallback: true 38 | } 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-ts/README.md: -------------------------------------------------------------------------------- 1 | # Guestbook Web (TS) 2 | 3 | A simple web app showing how to build a web frontend with Layr. 4 | 5 | See the [corresponding guide](https://layrjs.com/docs/v1/introduction/web-app?language=ts). 6 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guestbook-web", 3 | "version": "1.0.0", 4 | "description": "A simple web app showing how to build a web frontend with Layr", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0", 10 | "@layr/memory-store": "^1.0.0", 11 | "@layr/react-integration": "^1.0.0", 12 | "@layr/storable": "^1.0.0", 13 | "react": "^16.13.1", 14 | "react-dom": "^16.13.1" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^16.9.49", 18 | "@types/react-dom": "^16.9.8", 19 | "html-webpack-plugin": "^4.3.0", 20 | "ts-loader": "^7.0.5", 21 | "ts-node": "^8.10.2", 22 | "typescript": "^3.9.7", 23 | "webpack": "^4.44.1", 24 | "webpack-cli": "^3.3.12", 25 | "webpack-dev-server": "^3.11.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-ts/src/backend.ts: -------------------------------------------------------------------------------- 1 | import {Component, expose, validators} from '@layr/component'; 2 | import {Storable, primaryIdentifier, attribute} from '@layr/storable'; 3 | import {MemoryStore} from '@layr/memory-store'; 4 | import {ComponentHTTPServer} from '@layr/component-http-server'; 5 | 6 | const {notEmpty, maxLength} = validators; 7 | 8 | @expose({ 9 | find: {call: true}, 10 | prototype: { 11 | load: {call: true}, 12 | save: {call: true} 13 | } 14 | }) 15 | export class Message extends Storable(Component) { 16 | @expose({get: true, set: true}) @primaryIdentifier() id!: string; 17 | 18 | @expose({get: true, set: true}) 19 | @attribute('string', {validators: [notEmpty(), maxLength(300)]}) 20 | text = ''; 21 | 22 | @expose({get: true}) @attribute('Date') createdAt = new Date(); 23 | } 24 | 25 | const store = new MemoryStore(); 26 | 27 | store.registerStorable(Message); 28 | 29 | const server = new ComponentHTTPServer(Message, {port: 3210}); 30 | 31 | server.start(); 32 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-ts/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Guestbook 6 | 7 | 8 | <%= htmlWebpackPlugin.tags.headTags %> 9 | 10 | 11 | 12 |
13 | <%= htmlWebpackPlugin.tags.bodyTags %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "strict": true, 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "experimentalDecorators": true, 17 | "skipLibCheck": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-ts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | module.exports = (env, argv) => { 6 | return { 7 | // The entry point of the app is './src/frontend.tsx' 8 | entry: './src/frontend.tsx', 9 | output: { 10 | // Specify '/' as the base path for all the assets 11 | // This is required for a single-page application 12 | publicPath: '/' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | // Use 'ts-loader' to compile the TS files 18 | test: /\.tsx?$/, 19 | include: path.join(__dirname, 'src'), 20 | loader: 'ts-loader' 21 | } 22 | ] 23 | }, 24 | plugins: [ 25 | // Use 'html-webpack-plugin' to generate the 'index.html' file 26 | // from the './src/index.html' template 27 | new HtmlWebPackPlugin({ 28 | template: './src/index.html', 29 | inject: false 30 | }) 31 | ], 32 | // Generate source maps to make debugging easier 33 | devtool: 'eval-cheap-module-source-map', 34 | devServer: { 35 | // Fallback to 'index.html' in case of 404 responses 36 | // This is required for a single-page application 37 | historyApiFallback: true 38 | } 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-js/README.md: -------------------------------------------------------------------------------- 1 | # Guestbook Web With Authorization (JS) 2 | 3 | A simple web app showing how to build a web frontend with authorization with Layr. 4 | 5 | See the [corresponding guide](https://layrjs.com/docs/v1/introduction/authorization?language=js). 6 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-js/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", {"targets": {"node": "10"}}], "@babel/preset-react"], 3 | "plugins": [ 4 | ["@babel/plugin-proposal-decorators", {"legacy": true}], 5 | ["@babel/plugin-proposal-class-properties"] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-js/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "CommonJS", 5 | "checkJs": false, 6 | "experimentalDecorators": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guestbook-web-with-authorization", 3 | "version": "1.0.0", 4 | "description": "A simple web app showing how to build a web frontend with authorization with Layr", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0", 10 | "@layr/memory-store": "^1.0.0", 11 | "@layr/react-integration": "^1.0.0", 12 | "@layr/routable": "^1.0.0", 13 | "@layr/storable": "^1.0.0", 14 | "@layr/with-roles": "^1.0.0", 15 | "react": "^16.13.1", 16 | "react-dom": "^16.13.1" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.11.1", 20 | "@babel/node": "^7.10.5", 21 | "@babel/plugin-proposal-class-properties": "^7.10.4", 22 | "@babel/plugin-proposal-decorators": "^7.10.5", 23 | "@babel/preset-env": "^7.11.0", 24 | "@babel/preset-react": "^7.10.4", 25 | "babel-loader": "^8.1.0", 26 | "html-webpack-plugin": "^4.3.0", 27 | "webpack": "^4.44.1", 28 | "webpack-cli": "^3.3.12", 29 | "webpack-dev-server": "^3.11.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-js/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Guestbook 6 | 7 | 8 | <%= htmlWebpackPlugin.tags.headTags %> 9 | 10 | 11 | 12 |
13 | <%= htmlWebpackPlugin.tags.bodyTags %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-js/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | module.exports = (env, argv) => { 6 | return { 7 | // The entry point of the app is './src/frontend.js' 8 | entry: './src/frontend.js', 9 | output: { 10 | // Specify '/' as the base path for all the assets 11 | // This is required for a single-page application 12 | publicPath: '/' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | // Use 'babel-loader' to compile the JS files 18 | test: /\.js$/, 19 | include: path.join(__dirname, 'src'), 20 | loader: 'babel-loader' 21 | } 22 | ] 23 | }, 24 | plugins: [ 25 | // Use 'html-webpack-plugin' to generate the 'index.html' file 26 | // from the './src/index.html' template 27 | new HtmlWebPackPlugin({ 28 | template: './src/index.html', 29 | inject: false 30 | }) 31 | ], 32 | // Generate source maps to make debugging easier 33 | devtool: 'eval-cheap-module-source-map', 34 | devServer: { 35 | // Fallback to 'index.html' in case of 404 responses 36 | // This is required for a single-page application 37 | historyApiFallback: true 38 | } 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-ts/README.md: -------------------------------------------------------------------------------- 1 | # Guestbook Web With Authorization (TS) 2 | 3 | A simple web app showing how to build a web frontend with authorization with Layr. 4 | 5 | See the [corresponding guide](https://layrjs.com/docs/v1/introduction/authorization?language=ts). 6 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guestbook-web-with-authorization", 3 | "version": "1.0.0", 4 | "description": "A simple web app showing how to build a web frontend with authorization with Layr", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0", 10 | "@layr/memory-store": "^1.0.0", 11 | "@layr/react-integration": "^1.0.0", 12 | "@layr/routable": "^1.0.0", 13 | "@layr/storable": "^1.0.0", 14 | "@layr/with-roles": "^1.0.0", 15 | "react": "^16.13.1", 16 | "react-dom": "^16.13.1" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^16.9.36", 20 | "@types/react-dom": "^16.9.8", 21 | "html-webpack-plugin": "^4.3.0", 22 | "ts-loader": "^7.0.5", 23 | "ts-node": "^8.10.2", 24 | "typescript": "^3.9.7", 25 | "webpack": "^4.44.1", 26 | "webpack-cli": "^3.3.12", 27 | "webpack-dev-server": "^3.11.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-ts/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Guestbook 6 | 7 | 8 | <%= htmlWebpackPlugin.tags.headTags %> 9 | 10 | 11 | 12 |
13 | <%= htmlWebpackPlugin.tags.bodyTags %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "strict": true, 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "experimentalDecorators": true, 17 | "skipLibCheck": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-authorization-ts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | module.exports = (env, argv) => { 6 | return { 7 | // The entry point of the app is './src/frontend.tsx' 8 | entry: './src/frontend.tsx', 9 | output: { 10 | // Specify '/' as the base path for all the assets 11 | // This is required for a single-page application 12 | publicPath: '/' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | // Use 'ts-loader' to compile the TS files 18 | test: /\.tsx?$/, 19 | include: path.join(__dirname, 'src'), 20 | loader: 'ts-loader' 21 | } 22 | ] 23 | }, 24 | plugins: [ 25 | // Use 'html-webpack-plugin' to generate the 'index.html' file 26 | // from the './src/index.html' template 27 | new HtmlWebPackPlugin({ 28 | template: './src/index.html', 29 | inject: false 30 | }) 31 | ], 32 | // Generate source maps to make debugging easier 33 | devtool: 'eval-cheap-module-source-map', 34 | devServer: { 35 | // Fallback to 'index.html' in case of 404 responses 36 | // This is required for a single-page application 37 | historyApiFallback: true 38 | } 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-js/README.md: -------------------------------------------------------------------------------- 1 | # Guestbook Web With Routes (JS) 2 | 3 | A simple web app showing how to build a web frontend using routes with Layr. 4 | 5 | See the [corresponding guide](https://layrjs.com/docs/v1/introduction/routing?language=js). 6 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-js/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", {"targets": {"node": "10"}}], "@babel/preset-react"], 3 | "plugins": [ 4 | ["@babel/plugin-proposal-decorators", {"legacy": true}], 5 | ["@babel/plugin-proposal-class-properties"] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-js/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "CommonJS", 5 | "checkJs": false, 6 | "experimentalDecorators": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guestbook-web-with-routes", 3 | "version": "1.0.0", 4 | "description": "A simple web app showing how to build a web frontend using routes with Layr", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0", 10 | "@layr/memory-store": "^1.0.0", 11 | "@layr/react-integration": "^1.0.0", 12 | "@layr/routable": "^1.0.0", 13 | "@layr/storable": "^1.0.0", 14 | "react": "^16.13.1", 15 | "react-dom": "^16.13.1" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.11.1", 19 | "@babel/node": "^7.10.5", 20 | "@babel/plugin-proposal-class-properties": "^7.10.4", 21 | "@babel/plugin-proposal-decorators": "^7.10.5", 22 | "@babel/preset-env": "^7.11.0", 23 | "@babel/preset-react": "^7.10.4", 24 | "babel-loader": "^8.1.0", 25 | "html-webpack-plugin": "^4.3.0", 26 | "webpack": "^4.44.1", 27 | "webpack-cli": "^3.3.12", 28 | "webpack-dev-server": "^3.11.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-js/src/backend.js: -------------------------------------------------------------------------------- 1 | import {Component, expose, validators} from '@layr/component'; 2 | import {Storable, primaryIdentifier, attribute} from '@layr/storable'; 3 | import {MemoryStore} from '@layr/memory-store'; 4 | import {ComponentHTTPServer} from '@layr/component-http-server'; 5 | 6 | const {notEmpty, maxLength} = validators; 7 | 8 | @expose({ 9 | find: {call: true}, 10 | prototype: { 11 | load: {call: true}, 12 | save: {call: true} 13 | } 14 | }) 15 | export class Message extends Storable(Component) { 16 | @expose({get: true, set: true}) @primaryIdentifier() id; 17 | 18 | @expose({get: true, set: true}) 19 | @attribute('string', {validators: [notEmpty(), maxLength(300)]}) 20 | text = ''; 21 | 22 | @expose({get: true}) @attribute('Date') createdAt = new Date(); 23 | } 24 | 25 | const store = new MemoryStore(); 26 | 27 | store.registerStorable(Message); 28 | 29 | const server = new ComponentHTTPServer(Message, {port: 3210}); 30 | 31 | server.start(); 32 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-js/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Guestbook 6 | 7 | 8 | <%= htmlWebpackPlugin.tags.headTags %> 9 | 10 | 11 | 12 |
13 | <%= htmlWebpackPlugin.tags.bodyTags %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-js/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | module.exports = (env, argv) => { 6 | return { 7 | // The entry point of the app is './src/frontend.js' 8 | entry: './src/frontend.js', 9 | output: { 10 | // Specify '/' as the base path for all the assets 11 | // This is required for a single-page application 12 | publicPath: '/' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | // Use 'babel-loader' to compile the JS files 18 | test: /\.js$/, 19 | include: path.join(__dirname, 'src'), 20 | loader: 'babel-loader' 21 | } 22 | ] 23 | }, 24 | plugins: [ 25 | // Use 'html-webpack-plugin' to generate the 'index.html' file 26 | // from the './src/index.html' template 27 | new HtmlWebPackPlugin({ 28 | template: './src/index.html', 29 | inject: false 30 | }) 31 | ], 32 | // Generate source maps to make debugging easier 33 | devtool: 'eval-cheap-module-source-map', 34 | devServer: { 35 | // Fallback to 'index.html' in case of 404 responses 36 | // This is required for a single-page application 37 | historyApiFallback: true 38 | } 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-ts/README.md: -------------------------------------------------------------------------------- 1 | # Guestbook Web With Routes (TS) 2 | 3 | A simple web app showing how to build a web frontend using routes with Layr. 4 | 5 | See the [corresponding guide](https://layrjs.com/docs/v1/introduction/routing?language=ts). 6 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guestbook-web-with-routes", 3 | "version": "1.0.0", 4 | "description": "A simple web app showing how to build a web frontend using routes with Layr", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0", 10 | "@layr/memory-store": "^1.0.0", 11 | "@layr/react-integration": "^1.0.0", 12 | "@layr/routable": "^1.0.0", 13 | "@layr/storable": "^1.0.0", 14 | "react": "^16.13.1", 15 | "react-dom": "^16.13.1" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^16.9.36", 19 | "@types/react-dom": "^16.9.8", 20 | "html-webpack-plugin": "^4.3.0", 21 | "ts-loader": "^7.0.5", 22 | "ts-node": "^8.10.2", 23 | "typescript": "^3.9.7", 24 | "webpack": "^4.44.1", 25 | "webpack-cli": "^3.3.12", 26 | "webpack-dev-server": "^3.11.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-ts/src/backend.ts: -------------------------------------------------------------------------------- 1 | import {Component, expose, validators} from '@layr/component'; 2 | import {Storable, primaryIdentifier, attribute} from '@layr/storable'; 3 | import {MemoryStore} from '@layr/memory-store'; 4 | import {ComponentHTTPServer} from '@layr/component-http-server'; 5 | 6 | const {notEmpty, maxLength} = validators; 7 | 8 | @expose({ 9 | find: {call: true}, 10 | prototype: { 11 | load: {call: true}, 12 | save: {call: true} 13 | } 14 | }) 15 | export class Message extends Storable(Component) { 16 | @expose({get: true, set: true}) @primaryIdentifier() id!: string; 17 | 18 | @expose({get: true, set: true}) 19 | @attribute('string', {validators: [notEmpty(), maxLength(300)]}) 20 | text = ''; 21 | 22 | @expose({get: true}) @attribute('Date') createdAt = new Date(); 23 | } 24 | 25 | const store = new MemoryStore(); 26 | 27 | store.registerStorable(Message); 28 | 29 | const server = new ComponentHTTPServer(Message, {port: 3210}); 30 | 31 | server.start(); 32 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-ts/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Guestbook 6 | 7 | 8 | <%= htmlWebpackPlugin.tags.headTags %> 9 | 10 | 11 | 12 |
13 | <%= htmlWebpackPlugin.tags.bodyTags %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext", "DOM"], 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "strict": true, 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "experimentalDecorators": true, 17 | "skipLibCheck": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/v1/guestbook-web-with-routes-ts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | module.exports = (env, argv) => { 6 | return { 7 | // The entry point of the app is './src/frontend.tsx' 8 | entry: './src/frontend.tsx', 9 | output: { 10 | // Specify '/' as the base path for all the assets 11 | // This is required for a single-page application 12 | publicPath: '/' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | // Use 'ts-loader' to compile the TS files 18 | test: /\.tsx?$/, 19 | include: path.join(__dirname, 'src'), 20 | loader: 'ts-loader' 21 | } 22 | ] 23 | }, 24 | plugins: [ 25 | // Use 'html-webpack-plugin' to generate the 'index.html' file 26 | // from the './src/index.html' template 27 | new HtmlWebPackPlugin({ 28 | template: './src/index.html', 29 | inject: false 30 | }) 31 | ], 32 | // Generate source maps to make debugging easier 33 | devtool: 'eval-cheap-module-source-map', 34 | devServer: { 35 | // Fallback to 'index.html' in case of 404 responses 36 | // This is required for a single-page application 37 | historyApiFallback: true 38 | } 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /examples/v1/hello-world-js/README.md: -------------------------------------------------------------------------------- 1 | # Hello, World! (JS) 2 | 3 | An "Hello, World!" program to introduce the core concepts of Layr. 4 | 5 | See the [corresponding guide](https://layrjs.com/docs/v1/introduction/hello-world?language=js). 6 | -------------------------------------------------------------------------------- /examples/v1/hello-world-js/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", {"targets": {"node": "10"}}]], 3 | "plugins": [ 4 | ["@babel/plugin-proposal-decorators", {"legacy": true}], 5 | ["@babel/plugin-proposal-class-properties"] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /examples/v1/hello-world-js/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "CommonJS", 5 | "checkJs": false, 6 | "experimentalDecorators": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/v1/hello-world-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-js", 3 | "version": "1.0.0", 4 | "description": "An \"Hello, World!\" program to introduce the core concepts of Layr.", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.11.1", 13 | "@babel/node": "^7.10.5", 14 | "@babel/plugin-proposal-class-properties": "^7.10.4", 15 | "@babel/plugin-proposal-decorators": "^7.10.5", 16 | "@babel/preset-env": "^7.11.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/v1/hello-world-js/src/backend.js: -------------------------------------------------------------------------------- 1 | import {Component, attribute, method, expose} from '@layr/component'; 2 | import {ComponentHTTPServer} from '@layr/component-http-server'; 3 | 4 | export class Greeter extends Component { 5 | @expose({set: true}) @attribute('string') name = 'World'; 6 | 7 | @expose({call: true}) @method() async hello() { 8 | return `Hello, ${this.name}!`; 9 | } 10 | } 11 | 12 | const server = new ComponentHTTPServer(Greeter, {port: 3210}); 13 | 14 | server.start(); 15 | -------------------------------------------------------------------------------- /examples/v1/hello-world-js/src/frontend.js: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | 3 | (async () => { 4 | const client = new ComponentHTTPClient('http://localhost:3210'); 5 | 6 | const BackendGreeter = await client.getComponent(); 7 | 8 | class Greeter extends BackendGreeter { 9 | async hello() { 10 | return (await super.hello()).toUpperCase(); 11 | } 12 | } 13 | 14 | const greeter = new Greeter({name: 'Steve'}); 15 | 16 | console.log(await greeter.hello()); 17 | })(); 18 | -------------------------------------------------------------------------------- /examples/v1/hello-world-ts/README.md: -------------------------------------------------------------------------------- 1 | # Hello, World! (TS) 2 | 3 | An "Hello, World!" program to introduce the core concepts of Layr. 4 | 5 | See the [corresponding guide](https://layrjs.com/docs/v1/introduction/hello-world?language=ts). 6 | -------------------------------------------------------------------------------- /examples/v1/hello-world-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "1.0.0", 4 | "description": "An \"Hello, World!\" program to introduce the core concepts of Layr.", 5 | "private": true, 6 | "dependencies": { 7 | "@layr/component": "^1.0.0", 8 | "@layr/component-http-client": "^1.0.0", 9 | "@layr/component-http-server": "^1.0.0" 10 | }, 11 | "devDependencies": { 12 | "ts-node": "^8.10.2", 13 | "typescript": "^3.9.7" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/v1/hello-world-ts/src/backend.ts: -------------------------------------------------------------------------------- 1 | import {Component, attribute, method, expose} from '@layr/component'; 2 | import {ComponentHTTPServer} from '@layr/component-http-server'; 3 | 4 | export class Greeter extends Component { 5 | @expose({set: true}) @attribute('string') name = 'World'; 6 | 7 | @expose({call: true}) @method() async hello() { 8 | return `Hello, ${this.name}!`; 9 | } 10 | } 11 | 12 | const server = new ComponentHTTPServer(Greeter, {port: 3210}); 13 | 14 | server.start(); 15 | -------------------------------------------------------------------------------- /examples/v1/hello-world-ts/src/frontend.ts: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | 3 | import type {Greeter as GreeterType} from './backend'; 4 | 5 | (async () => { 6 | const client = new ComponentHTTPClient('http://localhost:3210'); 7 | 8 | const BackendGreeter = (await client.getComponent()) as typeof GreeterType; 9 | 10 | class Greeter extends BackendGreeter { 11 | async hello() { 12 | return (await super.hello()).toUpperCase(); 13 | } 14 | } 15 | 16 | const greeter = new Greeter({name: 'Steve'}); 17 | 18 | console.log(await greeter.hello()); 19 | })(); 20 | -------------------------------------------------------------------------------- /examples/v1/hello-world-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "CommonJS", 6 | "lib": ["ESNext"], 7 | "sourceMap": true, 8 | "strict": true, 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "experimentalDecorators": true, 16 | "skipLibCheck": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | dist 5 | build 6 | _private 7 | *.private.* 8 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | build 4 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "editorconfig.editorconfig" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.tabSize": 2, 4 | "editor.wordWrap": "bounded", 5 | "editor.wordWrapColumn": 100, 6 | "[javascript]": { 7 | "editor.defaultFormatter": "esbenp.prettier-vscode" 8 | }, 9 | "[javascriptreact]": { 10 | "editor.defaultFormatter": "esbenp.prettier-vscode" 11 | }, 12 | "[json]": { 13 | "editor.defaultFormatter": "esbenp.prettier-vscode" 14 | }, 15 | "[typescript]": { 16 | "editor.defaultFormatter": "esbenp.prettier-vscode" 17 | }, 18 | "[typescriptreact]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | }, 21 | "prettier.requireConfig": true, 22 | "prettier.useEditorConfig": true, 23 | "eslint.enable": false 24 | } 25 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/backend/boostr.config.mjs: -------------------------------------------------------------------------------- 1 | export default ({services}) => ({ 2 | type: 'backend', 3 | 4 | dependsOn: 'database', 5 | 6 | environment: { 7 | FRONTEND_URL: services.frontend.url, 8 | BACKEND_URL: services.backend.url, 9 | DATABASE_URL: services.database.url 10 | }, 11 | 12 | rootComponent: './src/index.js', 13 | 14 | stages: { 15 | development: { 16 | url: 'http://localhost:16190/', 17 | platform: 'local' 18 | }, 19 | staging: { 20 | url: 'https://staging.backend.example.com/', 21 | platform: 'aws', 22 | aws: { 23 | region: 'us-east-1', 24 | lambda: { 25 | memorySize: 1024 26 | } 27 | } 28 | }, 29 | production: { 30 | url: 'https://backend.example.com/', 31 | platform: 'aws', 32 | aws: { 33 | region: 'us-east-1', 34 | lambda: { 35 | memorySize: 1024 36 | } 37 | } 38 | } 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/backend/boostr.config.private-example.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example of a private Boostr configuration file allowing you to 3 | * override the `boostr.config.mjs` public configuration. 4 | * 5 | * Duplicate this file and name it `boostr.config.private.mjs` to activate it 6 | * in your local development environment. 7 | */ 8 | 9 | export default () => ({ 10 | stages: { 11 | development: { 12 | environment: { 13 | JWT_SECRET: '********' 14 | } 15 | }, 16 | staging: { 17 | environment: { 18 | JWT_SECRET: '********' 19 | } 20 | }, 21 | production: { 22 | environment: { 23 | JWT_SECRET: '********' 24 | } 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/backend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-js-backend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@layr/component": "^2.0.0", 7 | "@layr/mongodb-store": "^2.0.0", 8 | "@layr/storable": "^2.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/backend/src/components/application.js: -------------------------------------------------------------------------------- 1 | import {Component, method, expose} from '@layr/component'; 2 | 3 | export class Application extends Component { 4 | @expose({call: true}) @method() static async getHelloWorld() { 5 | const translations = ['Hello, World!', 'Bonjour le monde !', 'こんにちは世界!']; 6 | 7 | const translation = translations[Math.round(Math.random() * (translations.length - 1))]; 8 | 9 | return translation; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/backend/src/index.js: -------------------------------------------------------------------------------- 1 | import {MongoDBStore} from '@layr/mongodb-store'; 2 | 3 | import {Application} from './components/application'; 4 | 5 | export default () => { 6 | const store = new MongoDBStore(process.env.DATABASE_URL); 7 | 8 | store.registerRootComponent(Application); 9 | 10 | return Application; 11 | }; 12 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/boostr.config.mjs: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | type: 'application', 3 | 4 | services: { 5 | frontend: './frontend', 6 | backend: './backend', 7 | database: './database' 8 | }, 9 | 10 | environment: { 11 | APPLICATION_NAME: 'Layr App', 12 | APPLICATION_DESCRIPTION: 'A Layr app managed by Boostr' 13 | }, 14 | 15 | stages: { 16 | staging: { 17 | environment: { 18 | NODE_ENV: 'production' 19 | } 20 | }, 21 | production: { 22 | environment: { 23 | NODE_ENV: 'production' 24 | } 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/database/.gitignore: -------------------------------------------------------------------------------- 1 | /data 2 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/database/boostr.config.mjs: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | type: 'database', 3 | 4 | stages: { 5 | development: { 6 | url: 'mongodb://localhost:16191/dev', 7 | platform: 'local' 8 | } 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/database/boostr.config.private-example.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example of a private Boostr configuration file allowing you to 3 | * override the `boostr.config.mjs` public configuration. 4 | * 5 | * Duplicate this file and name it `boostr.config.private.mjs` to activate it 6 | * in your local development environment. 7 | */ 8 | 9 | export default () => ({ 10 | stages: { 11 | staging: { 12 | url: 'mongodb+srv://user:pass@clusterNane.mongodb.net/exampleStaging?retryWrites=true&w=majority' 13 | }, 14 | production: { 15 | url: 'mongodb+srv://user:pass@clusterNane.mongodb.net/exampleProduction?retryWrites=true&w=majority' 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-js-frontend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@layr/component": "^2.0.0", 7 | "@layr/component-http-client": "^2.0.0", 8 | "@layr/react-integration": "^2.0.0", 9 | "@layr/routable": "^2.0.0", 10 | "@layr/storable": "^2.0.0", 11 | "react": "^17.0.0", 12 | "react-dom": "^17.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/frontend/public/boostr-favicon-3NjLR7w1Mu8UAIqq05vVG3.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/examples/v2/hello-world-js/frontend/public/boostr-favicon-3NjLR7w1Mu8UAIqq05vVG3.immutable.png -------------------------------------------------------------------------------- /examples/v2/hello-world-js/frontend/src/components/application.jsx: -------------------------------------------------------------------------------- 1 | import {Routable} from '@layr/routable'; 2 | import React from 'react'; 3 | import {layout, page, useData} from '@layr/react-integration'; 4 | 5 | export const extendApplication = (Base) => { 6 | class Application extends Routable(Base) { 7 | @layout('/') static MainLayout({children}) { 8 | return ( 9 | <> 10 | 11 |

{process.env.APPLICATION_NAME}

12 |
13 | {children()} 14 | 15 | ); 16 | } 17 | 18 | @page('[/]') static MainPage() { 19 | return ( 20 |

21 | See the "Hello, World!" page 22 |

23 | ); 24 | } 25 | 26 | @page('[/]hello-world') static HelloWorldPage() { 27 | return useData( 28 | async () => await this.getHelloWorld(), 29 | 30 | (helloWorld) =>

{helloWorld}

31 | ); 32 | } 33 | 34 | @page('[/]*') static NotFoundPage() { 35 | return ( 36 | <> 37 |

Page not found

38 |

Sorry, there is nothing here.

39 | 40 | ); 41 | } 42 | } 43 | 44 | return Application; 45 | }; 46 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | import {Storable} from '@layr/storable'; 3 | 4 | import {extendApplication} from './components/application'; 5 | 6 | export default async () => { 7 | const client = new ComponentHTTPClient(process.env.BACKEND_URL, { 8 | mixins: [Storable], 9 | async retryFailedRequests() { 10 | return confirm('Sorry, a network error occurred. Would you like to retry?'); 11 | } 12 | }); 13 | 14 | const BackendApplicationProxy = await client.getComponent(); 15 | 16 | const Application = extendApplication(BackendApplicationProxy); 17 | 18 | return Application; 19 | }; 20 | -------------------------------------------------------------------------------- /examples/v2/hello-world-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-js", 3 | "version": "1.0.0", 4 | "private": true, 5 | "prettier": "@boostr/prettierrc", 6 | "devDependencies": { 7 | "@boostr/prettierrc": "^1.0.0", 8 | "boostr": "^2.0.0", 9 | "prettier": "^2.0.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | dist 5 | build 6 | _private 7 | *.private.* 8 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | build 4 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "editorconfig.editorconfig" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.tabSize": 2, 4 | "editor.wordWrap": "bounded", 5 | "editor.wordWrapColumn": 100, 6 | "[javascript]": { 7 | "editor.defaultFormatter": "esbenp.prettier-vscode" 8 | }, 9 | "[javascriptreact]": { 10 | "editor.defaultFormatter": "esbenp.prettier-vscode" 11 | }, 12 | "[json]": { 13 | "editor.defaultFormatter": "esbenp.prettier-vscode" 14 | }, 15 | "[typescript]": { 16 | "editor.defaultFormatter": "esbenp.prettier-vscode" 17 | }, 18 | "[typescriptreact]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | }, 21 | "prettier.requireConfig": true, 22 | "prettier.useEditorConfig": true, 23 | "eslint.enable": false 24 | } 25 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/backend/boostr.config.mjs: -------------------------------------------------------------------------------- 1 | export default ({services}) => ({ 2 | type: 'backend', 3 | 4 | dependsOn: 'database', 5 | 6 | environment: { 7 | FRONTEND_URL: services.frontend.url, 8 | BACKEND_URL: services.backend.url, 9 | DATABASE_URL: services.database.url 10 | }, 11 | 12 | rootComponent: './src/index.ts', 13 | 14 | stages: { 15 | development: { 16 | url: 'http://localhost:14952/', 17 | platform: 'local' 18 | }, 19 | staging: { 20 | url: 'https://staging.backend.example.com/', 21 | platform: 'aws', 22 | aws: { 23 | region: 'us-east-1', 24 | lambda: { 25 | memorySize: 1024 26 | } 27 | } 28 | }, 29 | production: { 30 | url: 'https://backend.example.com/', 31 | platform: 'aws', 32 | aws: { 33 | region: 'us-east-1', 34 | lambda: { 35 | memorySize: 1024 36 | } 37 | } 38 | } 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/backend/boostr.config.private-example.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example of a private Boostr configuration file allowing you to 3 | * override the `boostr.config.mjs` public configuration. 4 | * 5 | * Duplicate this file and name it `boostr.config.private.mjs` to activate it 6 | * in your local development environment. 7 | */ 8 | 9 | export default () => ({ 10 | stages: { 11 | development: { 12 | environment: { 13 | JWT_SECRET: '********' 14 | } 15 | }, 16 | staging: { 17 | environment: { 18 | JWT_SECRET: '********' 19 | } 20 | }, 21 | production: { 22 | environment: { 23 | JWT_SECRET: '********' 24 | } 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-ts-backend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@layr/component": "^2.0.0", 7 | "@layr/mongodb-store": "^2.0.0", 8 | "@layr/storable": "^2.0.0" 9 | }, 10 | "devDependencies": { 11 | "@boostr/tsconfig": "^1.0.0", 12 | "@types/node": "^18.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/backend/src/components/application.ts: -------------------------------------------------------------------------------- 1 | import {Component, method, expose} from '@layr/component'; 2 | 3 | export class Application extends Component { 4 | @expose({call: true}) @method() static async getHelloWorld() { 5 | const translations = ['Hello, World!', 'Bonjour le monde !', 'こんにちは世界!']; 6 | 7 | const translation = translations[Math.round(Math.random() * (translations.length - 1))]; 8 | 9 | return translation; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import {MongoDBStore} from '@layr/mongodb-store'; 2 | 3 | import {Application} from './components/application'; 4 | 5 | export default () => { 6 | const store = new MongoDBStore(process.env.DATABASE_URL!); 7 | 8 | store.registerRootComponent(Application); 9 | 10 | return Application; 11 | }; 12 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@boostr/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/boostr.config.mjs: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | type: 'application', 3 | 4 | services: { 5 | frontend: './frontend', 6 | backend: './backend', 7 | database: './database' 8 | }, 9 | 10 | environment: { 11 | APPLICATION_NAME: 'Layr App', 12 | APPLICATION_DESCRIPTION: 'A Layr app managed by Boostr' 13 | }, 14 | 15 | stages: { 16 | staging: { 17 | environment: { 18 | NODE_ENV: 'production' 19 | } 20 | }, 21 | production: { 22 | environment: { 23 | NODE_ENV: 'production' 24 | } 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/database/.gitignore: -------------------------------------------------------------------------------- 1 | /data 2 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/database/boostr.config.mjs: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | type: 'database', 3 | 4 | stages: { 5 | development: { 6 | url: 'mongodb://localhost:14953/dev', 7 | platform: 'local' 8 | } 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/database/boostr.config.private-example.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is an example of a private Boostr configuration file allowing you to 3 | * override the `boostr.config.mjs` public configuration. 4 | * 5 | * Duplicate this file and name it `boostr.config.private.mjs` to activate it 6 | * in your local development environment. 7 | */ 8 | 9 | export default () => ({ 10 | stages: { 11 | staging: { 12 | url: 'mongodb+srv://user:pass@clusterNane.mongodb.net/exampleStaging?retryWrites=true&w=majority' 13 | }, 14 | production: { 15 | url: 'mongodb+srv://user:pass@clusterNane.mongodb.net/exampleProduction?retryWrites=true&w=majority' 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-ts-frontend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@layr/component": "^2.0.0", 7 | "@layr/component-http-client": "^2.0.0", 8 | "@layr/react-integration": "^2.0.0", 9 | "@layr/routable": "^2.0.0", 10 | "@layr/storable": "^2.0.0", 11 | "react": "^17.0.0", 12 | "react-dom": "^17.0.0" 13 | }, 14 | "devDependencies": { 15 | "@boostr/tsconfig": "^1.0.0", 16 | "@types/node": "^18.0.0", 17 | "@types/react": "^17.0.0", 18 | "@types/react-dom": "^17.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/frontend/public/boostr-favicon-3NjLR7w1Mu8UAIqq05vVG3.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/examples/v2/hello-world-ts/frontend/public/boostr-favicon-3NjLR7w1Mu8UAIqq05vVG3.immutable.png -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/frontend/src/index.ts: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | import {Storable} from '@layr/storable'; 3 | 4 | import type {Application as BackendApplication} from '../../backend/src/components/application'; 5 | import {extendApplication} from './components/application'; 6 | 7 | export default async () => { 8 | const client = new ComponentHTTPClient(process.env.BACKEND_URL!, { 9 | mixins: [Storable], 10 | async retryFailedRequests() { 11 | return confirm('Sorry, a network error occurred. Would you like to retry?'); 12 | } 13 | }); 14 | 15 | const BackendApplicationProxy = (await client.getComponent()) as typeof BackendApplication; 16 | 17 | const Application = extendApplication(BackendApplicationProxy); 18 | 19 | return Application; 20 | }; 21 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@boostr/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/v2/hello-world-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world-ts", 3 | "version": "1.0.0", 4 | "private": true, 5 | "prettier": "@boostr/prettierrc", 6 | "devDependencies": { 7 | "@boostr/prettierrc": "^1.0.0", 8 | "boostr": "^2.0.0", 9 | "prettier": "^2.0.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent", 3 | "npmClient": "npm", 4 | "packages": ["packages/*"] 5 | } 6 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasksRunnerOptions": { 3 | "default": { 4 | "runner": "nx/tasks-runners/default", 5 | "options": { 6 | "cacheableOperations": [ 7 | "build" 8 | ] 9 | } 10 | } 11 | }, 12 | "targetDefaults": { 13 | "build": { 14 | "dependsOn": [ 15 | "^build" 16 | ], 17 | "outputs": [ 18 | "{projectRoot}/dist" 19 | ] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layr", 3 | "version": "2.0.1", 4 | "private": true, 5 | "description": "Dramatically simplify full‑stack development", 6 | "keywords": [], 7 | "author": "Manuel Vila ", 8 | "license": "MIT", 9 | "repository": "https://github.com/layrjs/layr", 10 | "scripts": { 11 | "build:docs": "(cd ./docs && npm run build)" 12 | }, 13 | "prettier": "@mvila/prettierrc", 14 | "devDependencies": { 15 | "@mvila/prettierrc": "^1.1.2", 16 | "lerna": "^6.4.0", 17 | "prettier": "^2.8.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/aws-integration/README.md: -------------------------------------------------------------------------------- 1 | # @layr/aws-integration 2 | 3 | AWS integration for Layr. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/aws-integration 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/aws-integration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/aws-integration", 3 | "version": "2.0.101", 4 | "description": "AWS integration for Layr", 5 | "keywords": [ 6 | "layr", 7 | "aws", 8 | "integration", 9 | "lambda", 10 | "handler" 11 | ], 12 | "author": "Manuel Vila ", 13 | "license": "MIT", 14 | "repository": "https://github.com/layrjs/layr/tree/master/packages/aws-integration", 15 | "files": [ 16 | "dist" 17 | ], 18 | "main": "dist/node-cjs/index.js", 19 | "module": "dist/node-esm/index.js", 20 | "engines": { 21 | "node": ">=16.0.0" 22 | }, 23 | "scripts": { 24 | "build": "dev-tools build:ts-library", 25 | "prepare": "npm run build", 26 | "publish:package": "dev-tools publish:package", 27 | "update": "dev-tools update:dependencies" 28 | }, 29 | "dependencies": { 30 | "@layr/component": "^2.0.51", 31 | "@layr/component-server": "^2.0.70", 32 | "@layr/routable": "^2.0.113", 33 | "core-helpers": "^1.0.8", 34 | "lodash": "^4.17.21", 35 | "tslib": "^2.4.1" 36 | }, 37 | "devDependencies": { 38 | "@mvila/dev-tools": "^1.3.1", 39 | "@mvila/tsconfig": "^1.0.6", 40 | "@types/aws-lambda": "^8.10.109", 41 | "@types/lodash": "^4.14.191", 42 | "aws-sdk": "^2.1290.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/aws-integration/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module aws-integration 3 | * 4 | * Allows you to host a [component server](https://layrjs.com/docs/v2/reference/component-server) in [AWS Lambda](https://aws.amazon.com/lambda/). 5 | */ 6 | 7 | export * from './lambda-execution-queue-sender'; 8 | export * from './lambda-handler'; 9 | -------------------------------------------------------------------------------- /packages/aws-integration/src/lambda-execution-queue-sender.ts: -------------------------------------------------------------------------------- 1 | import type AWS from 'aws-sdk'; 2 | import type {PlainObject} from 'core-helpers'; 3 | 4 | export function createAWSLambdaExecutionQueueSender({ 5 | lambdaClient, 6 | functionName 7 | }: { 8 | lambdaClient: AWS.Lambda; 9 | functionName: string; 10 | }) { 11 | return async (query: PlainObject) => { 12 | await lambdaClient 13 | .invoke({ 14 | FunctionName: functionName, 15 | InvocationType: 'Event', 16 | Payload: JSON.stringify({query}) 17 | }) 18 | .promise(); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /packages/aws-integration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/browser-navigator/README.md: -------------------------------------------------------------------------------- 1 | # @layr/browser-navigator 2 | 3 | Provides a navigation system for a Layr app running in a browser. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/browser-navigator 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/browser-navigator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/browser-navigator", 3 | "version": "2.0.67", 4 | "description": "Provides a navigation system for a Layr app running in a browser", 5 | "keywords": [ 6 | "layr", 7 | "navigator", 8 | "navigation", 9 | "browser" 10 | ], 11 | "author": "Manuel Vila ", 12 | "license": "MIT", 13 | "repository": "https://github.com/layrjs/layr/tree/master/packages/browser-navigator", 14 | "files": [ 15 | "dist" 16 | ], 17 | "main": "dist/node-cjs/index.js", 18 | "module": "dist/node-esm/index.js", 19 | "engines": { 20 | "node": ">=16.0.0" 21 | }, 22 | "scripts": { 23 | "build": "dev-tools build:ts-library", 24 | "prepare": "npm run build", 25 | "publish:package": "dev-tools publish:package", 26 | "update": "dev-tools update:dependencies" 27 | }, 28 | "dependencies": { 29 | "@layr/navigator": "^2.0.55", 30 | "core-helpers": "^1.0.8", 31 | "lodash": "^4.17.21", 32 | "tslib": "^2.4.1" 33 | }, 34 | "devDependencies": { 35 | "@mvila/dev-tools": "^1.3.1", 36 | "@mvila/tsconfig": "^1.0.6", 37 | "@types/lodash": "^4.14.191", 38 | "@types/react": "^16.14.34" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/browser-navigator/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './browser-navigator'; 2 | export * from './utilities'; 3 | -------------------------------------------------------------------------------- /packages/browser-navigator/src/utilities.ts: -------------------------------------------------------------------------------- 1 | export function isInternalURL(url: URL | string) { 2 | if (!(url instanceof URL)) { 3 | url = new URL(url, 'internal:/'); 4 | } 5 | 6 | if (url.protocol === 'internal:') { 7 | return true; 8 | } 9 | 10 | const currentURL = new URL(window.location.href); 11 | 12 | if ( 13 | url.protocol === currentURL.protocol && 14 | url.username === currentURL.username && 15 | url.password === currentURL.password && 16 | url.host === currentURL.host 17 | ) { 18 | return true; 19 | } 20 | 21 | return false; 22 | } 23 | -------------------------------------------------------------------------------- /packages/browser-navigator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/component-client/README.md: -------------------------------------------------------------------------------- 1 | # @layr/component-client 2 | 3 | Consumes Layr components served by @layr/component-server. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/component-client 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/component-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/component-client", 3 | "version": "2.0.86", 4 | "description": "Consumes Layr components served by @layr/component-server", 5 | "keywords": [ 6 | "layr", 7 | "component", 8 | "client" 9 | ], 10 | "author": "Manuel Vila ", 11 | "license": "MIT", 12 | "repository": "https://github.com/layrjs/layr/tree/master/packages/component-client", 13 | "files": [ 14 | "dist" 15 | ], 16 | "main": "dist/node-cjs/index.js", 17 | "module": "dist/node-esm/index.js", 18 | "engines": { 19 | "node": ">=16.0.0" 20 | }, 21 | "scripts": { 22 | "build": "dev-tools build:ts-library", 23 | "prepare": "npm run build", 24 | "test": "dev-tools test:ts-library", 25 | "publish:package": "dev-tools publish:package", 26 | "update": "dev-tools update:dependencies" 27 | }, 28 | "dependencies": { 29 | "@layr/component": "^2.0.51", 30 | "@layr/component-server": "^2.0.70", 31 | "core-helpers": "^1.0.8", 32 | "debug": "^4.3.4", 33 | "lodash": "^4.17.21", 34 | "microbatcher": "^2.0.8", 35 | "possibly-async": "^1.0.7", 36 | "tslib": "^2.4.1" 37 | }, 38 | "devDependencies": { 39 | "@mvila/dev-tools": "^1.3.1", 40 | "@mvila/tsconfig": "^1.0.6", 41 | "@types/debug": "^4.1.7", 42 | "@types/jest": "^29.2.5", 43 | "@types/lodash": "^4.14.191" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/component-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component-client'; 2 | export * from './utilities'; 3 | -------------------------------------------------------------------------------- /packages/component-client/src/utilities.ts: -------------------------------------------------------------------------------- 1 | import type {ComponentClient} from './component-client'; 2 | 3 | export function isComponentClientClass(value: any): value is typeof ComponentClient { 4 | return typeof value?.isComponentClient === 'function'; 5 | } 6 | 7 | export function isComponentClientInstance(value: any): value is ComponentClient { 8 | return typeof value?.constructor?.isComponentClient === 'function'; 9 | } 10 | -------------------------------------------------------------------------------- /packages/component-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/component-express-middleware/README.md: -------------------------------------------------------------------------------- 1 | # @layr/component-koa-middleware 2 | 3 | A Koa middleware for your Layr components. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/component-koa-middleware 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/component-express-middleware/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component-express-middleware'; 2 | -------------------------------------------------------------------------------- /packages/component-express-middleware/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/component-http-client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules 3 | *.log 4 | /dist 5 | -------------------------------------------------------------------------------- /packages/component-http-client/README.md: -------------------------------------------------------------------------------- 1 | # @layr/component-http-client 2 | 3 | An HTTP client for your Layr components. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/component-http-client 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/component-http-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component-http-client'; 2 | -------------------------------------------------------------------------------- /packages/component-http-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/component-http-server/README.md: -------------------------------------------------------------------------------- 1 | # @layr/component-http-server 2 | 3 | A basic HTTP server for your Layr components. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/component-http-server 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/component-http-server/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component-http-server'; 2 | -------------------------------------------------------------------------------- /packages/component-http-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/component-koa-middleware/README.md: -------------------------------------------------------------------------------- 1 | # @layr/component-koa-middleware 2 | 3 | A Koa middleware for your Layr components. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/component-koa-middleware 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/component-koa-middleware/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/component-koa-middleware", 3 | "version": "2.0.94", 4 | "description": "A Koa middleware for your Layr components", 5 | "keywords": [ 6 | "layr", 7 | "component", 8 | "koa", 9 | "middleware" 10 | ], 11 | "author": "Manuel Vila ", 12 | "license": "MIT", 13 | "repository": "https://github.com/layrjs/layr/tree/master/packages/component-koa-middleware", 14 | "files": [ 15 | "dist" 16 | ], 17 | "main": "dist/node-cjs/index.js", 18 | "module": "dist/node-esm/index.js", 19 | "engines": { 20 | "node": ">=16.0.0" 21 | }, 22 | "scripts": { 23 | "build": "dev-tools build:ts-library", 24 | "prepare": "npm run build", 25 | "publish:package": "dev-tools publish:package", 26 | "update": "dev-tools update:dependencies" 27 | }, 28 | "dependencies": { 29 | "@layr/component": "^2.0.51", 30 | "@layr/component-server": "^2.0.70", 31 | "@layr/routable": "^2.0.113", 32 | "core-helpers": "^1.0.8", 33 | "mime-types": "^2.1.35", 34 | "raw-body": "^2.5.1", 35 | "sleep-promise": "^9.1.0", 36 | "tslib": "^2.4.1" 37 | }, 38 | "devDependencies": { 39 | "@mvila/dev-tools": "^1.3.1", 40 | "@mvila/tsconfig": "^1.0.6", 41 | "@types/koa": "^2.13.5", 42 | "@types/mime-types": "^2.1.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/component-koa-middleware/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component-koa-middleware'; 2 | -------------------------------------------------------------------------------- /packages/component-koa-middleware/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/component-server/README.md: -------------------------------------------------------------------------------- 1 | # @layr/component-server 2 | 3 | Serves your Layr components. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/component-server 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/component-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/component-server", 3 | "version": "2.0.70", 4 | "description": "Serves your Layr components", 5 | "keywords": [ 6 | "layr", 7 | "component", 8 | "server" 9 | ], 10 | "author": "Manuel Vila ", 11 | "license": "MIT", 12 | "repository": "https://github.com/layrjs/layr/tree/master/packages/component-server", 13 | "files": [ 14 | "dist" 15 | ], 16 | "main": "dist/node-cjs/index.js", 17 | "module": "dist/node-esm/index.js", 18 | "engines": { 19 | "node": ">=16.0.0" 20 | }, 21 | "scripts": { 22 | "build": "dev-tools build:ts-library", 23 | "prepare": "npm run build", 24 | "test": "dev-tools test:ts-library", 25 | "publish:package": "dev-tools publish:package", 26 | "update": "dev-tools update:dependencies" 27 | }, 28 | "dependencies": { 29 | "@deepr/runtime": "^1.1.0", 30 | "@layr/component": "^2.0.51", 31 | "core-helpers": "^1.0.8", 32 | "debug": "^4.3.4", 33 | "lodash": "^4.17.21", 34 | "possibly-async": "^1.0.7", 35 | "tslib": "^2.4.1" 36 | }, 37 | "devDependencies": { 38 | "@mvila/dev-tools": "^1.3.1", 39 | "@mvila/tsconfig": "^1.0.6", 40 | "@types/debug": "^4.1.7", 41 | "@types/jest": "^29.2.5", 42 | "@types/lodash": "^4.14.191" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/component-server/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component-server'; 2 | export * from './utilities'; 3 | -------------------------------------------------------------------------------- /packages/component-server/src/utilities.ts: -------------------------------------------------------------------------------- 1 | import {getTypeOf} from 'core-helpers'; 2 | 3 | import type {ComponentServer} from './component-server'; 4 | 5 | export function isComponentServerClass(value: any): value is typeof ComponentServer { 6 | return typeof value?.isComponentServer === 'function'; 7 | } 8 | 9 | export function isComponentServerInstance(value: any): value is ComponentServer { 10 | return typeof value?.constructor?.isComponentServer === 'function'; 11 | } 12 | 13 | export function assertIsComponentServerInstance(value: any): asserts value is ComponentServer { 14 | if (!isComponentServerInstance(value)) { 15 | throw new Error( 16 | `Expected a component server instance, but received a value of type '${getTypeOf(value)}'` 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/component-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/component/README.md: -------------------------------------------------------------------------------- 1 | # @layr/component 2 | 3 | The base class of all your components. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/component 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/component/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cloning'; 2 | export * from './component'; 3 | export * from './decorators'; 4 | export * from './deserialization'; 5 | export * from './embedded-component'; 6 | export * from './forking'; 7 | export * from './identity-map'; 8 | export * from './merging'; 9 | export * from './properties'; 10 | export * from './sanitization'; 11 | export * from './serialization'; 12 | export * from './utilities'; 13 | export * from './validation'; 14 | -------------------------------------------------------------------------------- /packages/component/src/js-tests/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "CommonJS", 5 | "checkJs": true, 6 | "experimentalDecorators": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/component/src/properties/index.ts: -------------------------------------------------------------------------------- 1 | export * from './attribute'; 2 | export * from './attribute-selector'; 3 | export * from './identifier-attribute'; 4 | export * from './method'; 5 | export * from './primary-identifier-attribute'; 6 | export * from './property'; 7 | export * from './secondary-identifier-attribute'; 8 | export * from './value-types'; 9 | -------------------------------------------------------------------------------- /packages/component/src/properties/value-types/any-value-type.ts: -------------------------------------------------------------------------------- 1 | import {ValueType} from './value-type'; 2 | import type {Attribute} from '../attribute'; 3 | 4 | export class AnyValueType extends ValueType { 5 | isOptional() { 6 | return true; 7 | } 8 | 9 | toString() { 10 | return 'any'; 11 | } 12 | 13 | _checkValue(_value: any, _attribute: Attribute) { 14 | return true; 15 | } 16 | 17 | static isAnyValueType(value: any): value is AnyValueType { 18 | return isAnyValueTypeInstance(value); 19 | } 20 | } 21 | 22 | export function isAnyValueTypeInstance(value: any): value is AnyValueType { 23 | return typeof value?.constructor?.isAnyValueType === 'function'; 24 | } 25 | -------------------------------------------------------------------------------- /packages/component/src/properties/value-types/boolean-value-type.ts: -------------------------------------------------------------------------------- 1 | import {ValueType} from './value-type'; 2 | import type {Attribute} from '../attribute'; 3 | 4 | export class BooleanValueType extends ValueType { 5 | toString() { 6 | return `boolean${super.toString()}`; 7 | } 8 | 9 | _checkValue(value: unknown, attribute: Attribute) { 10 | return super._checkValue(value, attribute) ?? typeof value === 'boolean'; 11 | } 12 | 13 | static isBooleanValueType(value: any): value is BooleanValueType { 14 | return isBooleanValueTypeInstance(value); 15 | } 16 | } 17 | 18 | export function isBooleanValueTypeInstance(value: any): value is BooleanValueType { 19 | return typeof value?.constructor?.isBooleanValueType === 'function'; 20 | } 21 | -------------------------------------------------------------------------------- /packages/component/src/properties/value-types/date-value-type.ts: -------------------------------------------------------------------------------- 1 | import {ValueType} from './value-type'; 2 | import type {Attribute} from '../attribute'; 3 | 4 | export class DateValueType extends ValueType { 5 | toString() { 6 | return `Date${super.toString()}`; 7 | } 8 | 9 | _checkValue(value: unknown, attribute: Attribute) { 10 | return super._checkValue(value, attribute) ?? value instanceof Date; 11 | } 12 | 13 | static isDateValueType(value: any): value is DateValueType { 14 | return isDateValueTypeInstance(value); 15 | } 16 | } 17 | 18 | export function isDateValueTypeInstance(value: any): value is DateValueType { 19 | return typeof value?.constructor?.isDateValueType === 'function'; 20 | } 21 | -------------------------------------------------------------------------------- /packages/component/src/properties/value-types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './any-value-type'; 2 | export * from './array-value-type'; 3 | export * from './boolean-value-type'; 4 | export * from './component-value-type'; 5 | export * from './date-value-type'; 6 | export * from './factory'; 7 | export * from './number-value-type'; 8 | export * from './object-value-type'; 9 | export * from './regexp-value-type'; 10 | export * from './string-value-type'; 11 | export * from './value-type'; 12 | -------------------------------------------------------------------------------- /packages/component/src/properties/value-types/number-value-type.ts: -------------------------------------------------------------------------------- 1 | import {ValueType} from './value-type'; 2 | import type {Attribute} from '../attribute'; 3 | 4 | export class NumberValueType extends ValueType { 5 | toString() { 6 | return `number${super.toString()}`; 7 | } 8 | 9 | _checkValue(value: unknown, attribute: Attribute) { 10 | return super._checkValue(value, attribute) ?? typeof value === 'number'; 11 | } 12 | 13 | static isNumberValueType(value: any): value is NumberValueType { 14 | return isNumberValueTypeInstance(value); 15 | } 16 | } 17 | 18 | export function isNumberValueTypeInstance(value: any): value is NumberValueType { 19 | return typeof value?.constructor?.isNumberValueType === 'function'; 20 | } 21 | -------------------------------------------------------------------------------- /packages/component/src/properties/value-types/object-value-type.ts: -------------------------------------------------------------------------------- 1 | import isPlainObject from 'lodash/isPlainObject'; 2 | 3 | import {ValueType} from './value-type'; 4 | import type {Attribute} from '../attribute'; 5 | 6 | export class ObjectValueType extends ValueType { 7 | toString() { 8 | return `object${super.toString()}`; 9 | } 10 | 11 | _checkValue(value: unknown, attribute: Attribute) { 12 | return super._checkValue(value, attribute) ?? isPlainObject(value); 13 | } 14 | 15 | static isObjectValueType(value: any): value is ObjectValueType { 16 | return isObjectValueTypeInstance(value); 17 | } 18 | } 19 | 20 | export function isObjectValueTypeInstance(value: any): value is ObjectValueType { 21 | return typeof value?.constructor?.isObjectValueType === 'function'; 22 | } 23 | -------------------------------------------------------------------------------- /packages/component/src/properties/value-types/regexp-value-type.ts: -------------------------------------------------------------------------------- 1 | import {ValueType} from './value-type'; 2 | import type {Attribute} from '../attribute'; 3 | 4 | export class RegExpValueType extends ValueType { 5 | toString() { 6 | return `RegExp${super.toString()}`; 7 | } 8 | 9 | _checkValue(value: unknown, attribute: Attribute) { 10 | return super._checkValue(value, attribute) ?? value instanceof RegExp; 11 | } 12 | 13 | static isRegExpValueType(value: any): value is RegExpValueType { 14 | return isRegExpValueTypeInstance(value); 15 | } 16 | } 17 | 18 | export function isRegExpValueTypeInstance(value: any): value is RegExpValueType { 19 | return typeof value?.constructor?.isRegExpValueType === 'function'; 20 | } 21 | -------------------------------------------------------------------------------- /packages/component/src/properties/value-types/string-value-type.ts: -------------------------------------------------------------------------------- 1 | import {ValueType} from './value-type'; 2 | import type {Attribute} from '../attribute'; 3 | 4 | export class StringValueType extends ValueType { 5 | toString() { 6 | return `string${super.toString()}`; 7 | } 8 | 9 | _checkValue(value: unknown, attribute: Attribute) { 10 | return super._checkValue(value, attribute) ?? typeof value === 'string'; 11 | } 12 | 13 | static isStringValueType(value: any): value is StringValueType { 14 | return isStringValueTypeInstance(value); 15 | } 16 | } 17 | 18 | export function isStringValueTypeInstance(value: any): value is StringValueType { 19 | return typeof value?.constructor?.isStringValueType === 'function'; 20 | } 21 | -------------------------------------------------------------------------------- /packages/component/src/sanitization/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utilities'; 2 | export * from './sanitizer-builders'; 3 | export * from './sanitizer'; 4 | -------------------------------------------------------------------------------- /packages/component/src/sanitization/sanitizer-builders.test.ts: -------------------------------------------------------------------------------- 1 | import {isSanitizerInstance} from './sanitizer'; 2 | import {sanitizers} from './sanitizer-builders'; 3 | 4 | describe('Sanitizer builders', () => { 5 | test('Building sanitizers', async () => { 6 | let sanitizer = sanitizers.trim(); 7 | 8 | expect(isSanitizerInstance(sanitizer)); 9 | expect(sanitizer.getName()).toBe('trim'); 10 | expect(typeof sanitizer.getFunction()).toBe('function'); 11 | expect(sanitizer.getArguments()).toEqual([]); 12 | 13 | sanitizer = sanitizers.compact(); 14 | 15 | expect(isSanitizerInstance(sanitizer)); 16 | expect(sanitizer.getName()).toBe('compact'); 17 | expect(typeof sanitizer.getFunction()).toBe('function'); 18 | expect(sanitizer.getArguments()).toEqual([]); 19 | }); 20 | 21 | test('Running built-in sanitizers', async () => { 22 | expect(sanitizers.trim().run('hello')).toBe('hello'); 23 | expect(sanitizers.trim().run(' hello ')).toBe('hello'); 24 | expect(sanitizers.trim().run(undefined)).toBe(undefined); 25 | 26 | expect(sanitizers.compact().run(['hello'])).toStrictEqual(['hello']); 27 | expect(sanitizers.compact().run(['hello', ''])).toStrictEqual(['hello']); 28 | expect(sanitizers.compact().run([''])).toStrictEqual([]); 29 | expect(sanitizers.compact().run([])).toStrictEqual([]); 30 | expect(sanitizers.compact().run(undefined)).toBe(undefined); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/component/src/sanitization/sanitizer-builders.ts: -------------------------------------------------------------------------------- 1 | import trim from 'lodash/trim'; 2 | import compact from 'lodash/compact'; 3 | 4 | import {Sanitizer, SanitizerFunction} from './sanitizer'; 5 | 6 | const sanitizerFunctions: {[name: string]: SanitizerFunction} = { 7 | // Strings 8 | 9 | trim: (value) => (value !== undefined ? trim(value) : undefined), 10 | 11 | // Arrays 12 | 13 | compact: (value) => (value !== undefined ? compact(value) : undefined) 14 | }; 15 | 16 | export type SanitizerBuilder = (...args: any[]) => Sanitizer; 17 | 18 | export const sanitizers: {[name: string]: SanitizerBuilder} = {}; 19 | 20 | for (const [name, func] of Object.entries(sanitizerFunctions)) { 21 | sanitizers[name] = (...args) => createSanitizer(name, func, args); 22 | } 23 | 24 | function createSanitizer(name: string, func: SanitizerFunction, args: any[]) { 25 | const numberOfRequiredArguments = func.length - 1; 26 | const sanitizerArguments = args.slice(0, numberOfRequiredArguments); 27 | 28 | if (sanitizerArguments.length < numberOfRequiredArguments) { 29 | throw new Error(`A required parameter is missing to build the sanitizer '${name}'`); 30 | } 31 | 32 | return new Sanitizer(func, {name, arguments: sanitizerArguments}); 33 | } 34 | -------------------------------------------------------------------------------- /packages/component/src/sanitization/utilities.ts: -------------------------------------------------------------------------------- 1 | import {sanitizers, SanitizerBuilder} from './sanitizer-builders'; 2 | import {Sanitizer, SanitizerFunction, isSanitizerInstance} from './sanitizer'; 3 | import type {Attribute} from '../properties'; 4 | 5 | export function normalizeSanitizer(sanitizer: Sanitizer | SanitizerFunction, attribute: Attribute) { 6 | if (isSanitizerInstance(sanitizer)) { 7 | return sanitizer; 8 | } 9 | 10 | if (typeof sanitizer !== 'function') { 11 | throw new Error(`The specified sanitizer is not a function (${attribute.describe()})`); 12 | } 13 | 14 | if (Object.values(sanitizers).includes((sanitizer as unknown) as SanitizerBuilder)) { 15 | throw new Error( 16 | `The specified sanitizer is a sanitizer builder that has not been called (${attribute.describe()})` 17 | ); 18 | } 19 | 20 | return new Sanitizer(sanitizer); 21 | } 22 | -------------------------------------------------------------------------------- /packages/component/src/validation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utilities'; 2 | export * from './validator-builders'; 3 | export * from './validator'; 4 | -------------------------------------------------------------------------------- /packages/component/src/validation/utilities.ts: -------------------------------------------------------------------------------- 1 | import {validators, ValidatorBuilder} from './validator-builders'; 2 | import {Validator, ValidatorFunction, isValidatorInstance} from './validator'; 3 | import type {Attribute} from '../properties'; 4 | 5 | export function normalizeValidator(validator: Validator | ValidatorFunction, attribute: Attribute) { 6 | if (isValidatorInstance(validator)) { 7 | return validator; 8 | } 9 | 10 | if (typeof validator !== 'function') { 11 | throw new Error(`The specified validator is not a function (${attribute.describe()})`); 12 | } 13 | 14 | if (Object.values(validators).includes((validator as unknown) as ValidatorBuilder)) { 15 | throw new Error( 16 | `The specified validator is a validator builder that has not been called (${attribute.describe()})` 17 | ); 18 | } 19 | 20 | return new Validator(validator); 21 | } 22 | -------------------------------------------------------------------------------- /packages/component/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/execution-queue/README.md: -------------------------------------------------------------------------------- 1 | # @layr/store 2 | 3 | A base class for implementing Layr stores. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/store 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/execution-queue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/execution-queue", 3 | "version": "2.0.37", 4 | "description": "A class for queueing method executions", 5 | "keywords": [ 6 | "layr", 7 | "method", 8 | "queue", 9 | "execution", 10 | "queueing" 11 | ], 12 | "author": "Manuel Vila ", 13 | "license": "MIT", 14 | "repository": "https://github.com/layrjs/layr/tree/master/packages/execution-queue", 15 | "files": [ 16 | "dist" 17 | ], 18 | "main": "dist/node-cjs/index.js", 19 | "module": "dist/node-esm/index.js", 20 | "engines": { 21 | "node": ">=16.0.0" 22 | }, 23 | "scripts": { 24 | "build": "dev-tools build:ts-library", 25 | "prepare": "npm run build", 26 | "test": "dev-tools test:ts-library", 27 | "publish:package": "dev-tools publish:package", 28 | "update": "dev-tools update:dependencies" 29 | }, 30 | "dependencies": { 31 | "@layr/component": "^2.0.51", 32 | "core-helpers": "^1.0.8", 33 | "lodash": "^4.17.21", 34 | "tslib": "^2.4.1" 35 | }, 36 | "devDependencies": { 37 | "@layr/component-server": "^2.0.70", 38 | "@layr/utilities": "^1.0.9", 39 | "@mvila/dev-tools": "^1.3.1", 40 | "@mvila/tsconfig": "^1.0.6", 41 | "@types/jest": "^29.2.5", 42 | "@types/lodash": "^4.14.191" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/execution-queue/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './execution-queue'; 2 | -------------------------------------------------------------------------------- /packages/execution-queue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/integration-testing/README.md: -------------------------------------------------------------------------------- 1 | # @layr/integration-testing 2 | 3 | Tests involving a combination of Layr packages. 4 | 5 | ## License 6 | 7 | MIT 8 | -------------------------------------------------------------------------------- /packages/integration-testing/src/client-server.test.ts: -------------------------------------------------------------------------------- 1 | import {ComponentClient} from '@layr/component-client'; 2 | import {ComponentServer} from '@layr/component-server'; 3 | 4 | import {Counter as BackendCounter} from './counter.fixture'; 5 | 6 | describe('Client/server', () => { 7 | test('Simple component', async () => { 8 | const server = new ComponentServer(BackendCounter); 9 | const client = new ComponentClient(server); 10 | 11 | const Counter = client.getComponent() as typeof BackendCounter; 12 | 13 | let counter = new Counter(); 14 | 15 | expect(counter.value).toBe(0); 16 | 17 | counter.increment(); 18 | 19 | expect(counter.value).toBe(1); 20 | 21 | counter.increment(); 22 | 23 | expect(counter.value).toBe(2); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/integration-testing/src/counter.fixture.ts: -------------------------------------------------------------------------------- 1 | import {Component, primaryIdentifier, attribute, method, expose} from '@layr/component'; 2 | import {Routable, httpRoute} from '@layr/routable'; 3 | import {MemoryNavigator} from '@layr/memory-navigator'; 4 | 5 | export class Counter extends Routable(Component) { 6 | @expose({get: true, set: true}) @primaryIdentifier() id!: string; 7 | 8 | @expose({get: true, set: true}) @attribute('number') value = 0; 9 | 10 | @expose({call: true}) @method() increment() { 11 | this.value++; 12 | } 13 | 14 | @httpRoute('GET', '/ping') static ping() { 15 | return 'pong'; 16 | } 17 | 18 | @httpRoute('POST', '/echo', { 19 | transformers: { 20 | input(_, {headers, body}) { 21 | let data; 22 | 23 | if (Buffer.isBuffer(body)) { 24 | data = Array.from(body); 25 | } else if (headers['content-type'] === 'application/json') { 26 | data = JSON.parse(body); 27 | } else { 28 | data = body; 29 | } 30 | 31 | return {data}; 32 | } 33 | } 34 | }) 35 | static echo({data}: {data: any}) { 36 | return data; 37 | } 38 | } 39 | 40 | const navigator = new MemoryNavigator(); 41 | 42 | Counter.registerNavigator(navigator); 43 | -------------------------------------------------------------------------------- /packages/integration-testing/src/http-client-server.test.ts: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | import {ComponentHTTPServer} from '@layr/component-http-server'; 3 | 4 | import {Counter as BackendCounter} from './counter.fixture'; 5 | 6 | const SERVER_PORT = Math.floor(Math.random() * (60000 - 50000 + 1) + 50000); 7 | 8 | describe('HTTP client/server', () => { 9 | let server: ComponentHTTPServer; 10 | 11 | beforeAll(async () => { 12 | server = new ComponentHTTPServer(BackendCounter, {port: SERVER_PORT}); 13 | await server.start(); 14 | }); 15 | 16 | afterAll(async () => { 17 | await server?.stop(); 18 | }); 19 | 20 | test('Simple component', async () => { 21 | const client = new ComponentHTTPClient(`http://localhost:${SERVER_PORT}`); 22 | 23 | const Counter = (await client.getComponent()) as typeof BackendCounter; 24 | 25 | let counter = new Counter(); 26 | 27 | expect(counter.value).toBe(0); 28 | 29 | await counter.increment(); 30 | 31 | expect(counter.value).toBe(1); 32 | 33 | await counter.increment(); 34 | 35 | expect(counter.value).toBe(2); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/integration-testing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/memory-navigator/README.md: -------------------------------------------------------------------------------- 1 | # @layr/memory-navigator 2 | 3 | Provides a navigation system for a Layr app that is not running in a browser (e.g., an Electron app or a React Native app). 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/memory-navigator 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/memory-navigator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/memory-navigator", 3 | "version": "2.0.59", 4 | "description": "Provides a navigation system for a Layr app that is not running in a browser (e.g., an Electron app or a React Native app)", 5 | "keywords": [ 6 | "layr", 7 | "navigator", 8 | "navigation", 9 | "memory" 10 | ], 11 | "author": "Manuel Vila ", 12 | "license": "MIT", 13 | "repository": "https://github.com/layrjs/layr/tree/master/packages/memory-navigator", 14 | "files": [ 15 | "dist" 16 | ], 17 | "main": "dist/node-cjs/index.js", 18 | "module": "dist/node-esm/index.js", 19 | "engines": { 20 | "node": ">=16.0.0" 21 | }, 22 | "scripts": { 23 | "build": "dev-tools build:ts-library", 24 | "prepare": "npm run build", 25 | "publish:package": "dev-tools publish:package", 26 | "update": "dev-tools update:dependencies" 27 | }, 28 | "dependencies": { 29 | "@layr/navigator": "^2.0.55", 30 | "tslib": "^2.4.1" 31 | }, 32 | "devDependencies": { 33 | "@mvila/dev-tools": "^1.3.1", 34 | "@mvila/tsconfig": "^1.0.6" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/memory-navigator/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './memory-navigator'; 2 | -------------------------------------------------------------------------------- /packages/memory-navigator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/memory-store/README.md: -------------------------------------------------------------------------------- 1 | # @layr/memory-store 2 | 3 | A Layr store using memory (useful for testing). 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/memory-store 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/memory-store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/memory-store", 3 | "version": "2.0.82", 4 | "description": "A Layr store using memory (useful for testing)", 5 | "keywords": [ 6 | "layr", 7 | "store", 8 | "memory" 9 | ], 10 | "author": "Manuel Vila ", 11 | "license": "MIT", 12 | "repository": "https://github.com/layrjs/layr/tree/master/packages/memory-store", 13 | "files": [ 14 | "dist" 15 | ], 16 | "main": "dist/node-cjs/index.js", 17 | "module": "dist/node-esm/index.js", 18 | "engines": { 19 | "node": ">=16.0.0" 20 | }, 21 | "scripts": { 22 | "build": "dev-tools build:ts-library", 23 | "prepare": "npm run build", 24 | "publish:package": "dev-tools publish:package", 25 | "update": "dev-tools update:dependencies" 26 | }, 27 | "dependencies": { 28 | "@layr/component": "^2.0.51", 29 | "@layr/storable": "^2.0.76", 30 | "@layr/store": "^2.0.81", 31 | "core-helpers": "^1.0.8", 32 | "lodash": "^4.17.21", 33 | "sort-on": "^4.1.1", 34 | "tslib": "^2.4.1" 35 | }, 36 | "devDependencies": { 37 | "@mvila/dev-tools": "^1.3.1", 38 | "@mvila/tsconfig": "^1.0.6", 39 | "@types/lodash": "^4.14.191" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/memory-store/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './memory-store'; 2 | -------------------------------------------------------------------------------- /packages/memory-store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/mongodb-store/README.md: -------------------------------------------------------------------------------- 1 | # @layr/mongodb-store 2 | 3 | A Layr store for MongoDB. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/mongodb-store 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/mongodb-store/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mongodb-store'; 2 | -------------------------------------------------------------------------------- /packages/mongodb-store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/navigator/README.md: -------------------------------------------------------------------------------- 1 | # @layr/navigator 2 | 3 | A base class for implementing Layr navigators. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/navigator 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/navigator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/navigator", 3 | "version": "2.0.55", 4 | "description": "A base class for implementing Layr navigators", 5 | "keywords": [ 6 | "layr", 7 | "navigator", 8 | "navigation", 9 | "base", 10 | "class" 11 | ], 12 | "author": "Manuel Vila ", 13 | "license": "MIT", 14 | "repository": "https://github.com/layrjs/layr/tree/master/packages/navigator", 15 | "files": [ 16 | "dist" 17 | ], 18 | "main": "dist/node-cjs/index.js", 19 | "module": "dist/node-esm/index.js", 20 | "engines": { 21 | "node": ">=16.0.0" 22 | }, 23 | "scripts": { 24 | "build": "dev-tools build:ts-library", 25 | "prepare": "npm run build", 26 | "test": "dev-tools test:ts-library", 27 | "publish:package": "dev-tools publish:package", 28 | "update": "dev-tools update:dependencies" 29 | }, 30 | "dependencies": { 31 | "@layr/component": "^2.0.51", 32 | "@layr/observable": "^1.0.16", 33 | "core-helpers": "^1.0.8", 34 | "possibly-async": "^1.0.7", 35 | "qs": "^6.11.0", 36 | "tslib": "^2.4.1" 37 | }, 38 | "devDependencies": { 39 | "@mvila/dev-tools": "^1.3.1", 40 | "@mvila/tsconfig": "^1.0.6", 41 | "@types/jest": "^29.2.5", 42 | "@types/qs": "^6.9.7" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/navigator/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './navigator'; 2 | export * from './utilities'; 3 | -------------------------------------------------------------------------------- /packages/navigator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/observable/README.md: -------------------------------------------------------------------------------- 1 | # @layr/observable 2 | 3 | Observe JavaScript objects, arrays, or your own classes. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/observable 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/observable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/observable", 3 | "version": "1.0.16", 4 | "description": "Observe JavaScript objects, arrays, or your own classes", 5 | "keywords": [ 6 | "observe", 7 | "observer", 8 | "notify", 9 | "changes", 10 | "object", 11 | "array", 12 | "class" 13 | ], 14 | "author": "Manuel Vila ", 15 | "license": "MIT", 16 | "repository": "https://github.com/layrjs/layr/tree/master/packages/observable", 17 | "files": [ 18 | "dist" 19 | ], 20 | "main": "dist/node-cjs/index.js", 21 | "module": "dist/node-esm/index.js", 22 | "engines": { 23 | "node": ">=16.0.0" 24 | }, 25 | "scripts": { 26 | "build": "dev-tools build:ts-library", 27 | "prepare": "npm run build", 28 | "test": "dev-tools test:ts-library", 29 | "publish:package": "dev-tools publish:package", 30 | "update": "dev-tools update:dependencies" 31 | }, 32 | "dependencies": { 33 | "core-helpers": "^1.0.8", 34 | "tslib": "^2.4.1" 35 | }, 36 | "devDependencies": { 37 | "@mvila/dev-tools": "^1.3.1", 38 | "@mvila/tsconfig": "^1.0.6", 39 | "@types/jest": "^29.2.5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/observable/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './observable'; 2 | -------------------------------------------------------------------------------- /packages/observable/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/react-integration/README.md: -------------------------------------------------------------------------------- 1 | # @layr/react-integration 2 | 3 | React integration for Layr. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/react-integration 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/react-integration/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module react-integration 3 | * 4 | * Provides some React components, hooks, and decorators to simplify the use of [React](https://reactjs.org/) inside a Layr app. 5 | */ 6 | 7 | export * from './components'; 8 | export * from './decorators'; 9 | export * from './hooks'; 10 | export * from './plugins'; 11 | -------------------------------------------------------------------------------- /packages/react-integration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/routable/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules 3 | *.log 4 | /dist 5 | -------------------------------------------------------------------------------- /packages/routable/README.md: -------------------------------------------------------------------------------- 1 | # @layr/routable 2 | 3 | Makes the methods of your Layr components callable by URL. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/routable 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/routable/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './addressable'; 2 | export * from './decorators'; 3 | export * from './param'; 4 | export * from './pattern'; 5 | export * from './routable'; 6 | export * from './route'; 7 | export * from './utilities'; 8 | export * from './wrapper'; 9 | -------------------------------------------------------------------------------- /packages/routable/src/js-tests/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "CommonJS", 5 | "checkJs": true, 6 | "experimentalDecorators": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/routable/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/storable/README.md: -------------------------------------------------------------------------------- 1 | # @layr/storable 2 | 3 | A mixin providing persistence capabilities to Layr components. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/storable 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/storable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/storable", 3 | "version": "2.0.76", 4 | "description": "A mixin providing persistence capabilities to Layr components", 5 | "keywords": [ 6 | "layr", 7 | "component", 8 | "mixin", 9 | "storage", 10 | "persistence", 11 | "database", 12 | "orm", 13 | "odm" 14 | ], 15 | "author": "Manuel Vila ", 16 | "license": "MIT", 17 | "repository": "https://github.com/layrjs/layr/tree/master/packages/storable", 18 | "files": [ 19 | "dist" 20 | ], 21 | "main": "dist/node-cjs/index.js", 22 | "module": "dist/node-esm/index.js", 23 | "engines": { 24 | "node": ">=16.0.0" 25 | }, 26 | "scripts": { 27 | "build": "dev-tools build:ts-library", 28 | "prepare": "npm run build", 29 | "test": "dev-tools test:ts-library", 30 | "publish:package": "dev-tools publish:package", 31 | "update": "dev-tools update:dependencies" 32 | }, 33 | "dependencies": { 34 | "@layr/component": "^2.0.51", 35 | "core-helpers": "^1.0.8", 36 | "lodash": "^4.17.21", 37 | "tslib": "^2.4.1" 38 | }, 39 | "devDependencies": { 40 | "@mvila/dev-tools": "^1.3.1", 41 | "@mvila/tsconfig": "^1.0.6", 42 | "@types/jest": "^29.2.5", 43 | "@types/lodash": "^4.14.191" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/storable/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decorators'; 2 | export * from './index-class'; 3 | export * from './properties'; 4 | export * from './operator'; 5 | export * from './query'; 6 | export * from './storable'; 7 | export * from './store-like'; 8 | export * from './utilities'; 9 | -------------------------------------------------------------------------------- /packages/storable/src/js-tests/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "module": "CommonJS", 5 | "checkJs": true, 6 | "experimentalDecorators": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/storable/src/properties/index.ts: -------------------------------------------------------------------------------- 1 | export * from './storable-attribute'; 2 | export * from './storable-method'; 3 | export * from './storable-primary-identifier-attribute'; 4 | export * from './storable-property'; 5 | export * from './storable-secondary-identifier-attribute'; 6 | -------------------------------------------------------------------------------- /packages/storable/src/store-like.ts: -------------------------------------------------------------------------------- 1 | import {AttributeSelector} from '@layr/component'; 2 | 3 | import type {StorableComponent} from './storable'; 4 | import type {Query} from './query'; 5 | import type {SortDescriptor} from './storable'; 6 | 7 | export declare class StoreLike { 8 | getURL: () => string | undefined; 9 | 10 | load: ( 11 | storable: StorableComponent, 12 | options?: {attributeSelector?: AttributeSelector; throwIfMissing?: boolean} 13 | ) => Promise; 14 | 15 | save: ( 16 | storable: StorableComponent, 17 | options?: { 18 | attributeSelector?: AttributeSelector; 19 | throwIfMissing?: boolean; 20 | throwIfExists?: boolean; 21 | } 22 | ) => Promise; 23 | 24 | delete: ( 25 | storable: StorableComponent, 26 | options?: {throwIfMissing?: boolean} 27 | ) => Promise; 28 | 29 | find: ( 30 | storable: typeof StorableComponent, 31 | query?: Query, 32 | options?: {sort?: SortDescriptor; skip?: number; limit?: number} 33 | ) => Promise; 34 | 35 | count: (storable: typeof StorableComponent, query?: Query) => Promise; 36 | } 37 | -------------------------------------------------------------------------------- /packages/storable/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/store/README.md: -------------------------------------------------------------------------------- 1 | # @layr/store 2 | 3 | A base class for implementing Layr stores. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/store 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/store", 3 | "version": "2.0.81", 4 | "description": "A base class for implementing Layr stores", 5 | "keywords": [ 6 | "layr", 7 | "store", 8 | "persistence", 9 | "storage", 10 | "database" 11 | ], 12 | "author": "Manuel Vila ", 13 | "license": "MIT", 14 | "repository": "https://github.com/layrjs/layr/tree/master/packages/store", 15 | "files": [ 16 | "dist" 17 | ], 18 | "main": "dist/node-cjs/index.js", 19 | "module": "dist/node-esm/index.js", 20 | "engines": { 21 | "node": ">=16.0.0" 22 | }, 23 | "scripts": { 24 | "build": "dev-tools build:ts-library", 25 | "prepare": "npm run build", 26 | "test": "dev-tools test:ts-library", 27 | "publish:package": "dev-tools publish:package", 28 | "update": "dev-tools update:dependencies" 29 | }, 30 | "dependencies": { 31 | "@layr/component": "^2.0.51", 32 | "@layr/storable": "^2.0.76", 33 | "core-helpers": "^1.0.8", 34 | "lodash": "^4.17.21", 35 | "simple-serialization": "^1.0.12", 36 | "tslib": "^2.4.1" 37 | }, 38 | "devDependencies": { 39 | "@mvila/dev-tools": "^1.3.1", 40 | "@mvila/tsconfig": "^1.0.6", 41 | "@types/jest": "^29.2.5", 42 | "@types/lodash": "^4.14.191" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/store/src/expression.ts: -------------------------------------------------------------------------------- 1 | import type {Operator} from '@layr/storable'; 2 | 3 | import type {AttributeValue} from './document'; 4 | import type {Path} from './path'; 5 | 6 | export type Expression = [Path, Operator, Operand]; 7 | 8 | export type Operand = AttributeValue | Expression[] | Expression[][]; 9 | -------------------------------------------------------------------------------- /packages/store/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './document'; 2 | export * from './expression'; 3 | export * from './path'; 4 | export * from './store'; 5 | export * from './utilities'; 6 | -------------------------------------------------------------------------------- /packages/store/src/path.ts: -------------------------------------------------------------------------------- 1 | export type Path = string; 2 | -------------------------------------------------------------------------------- /packages/store/src/utilities.ts: -------------------------------------------------------------------------------- 1 | import type {Store} from './store'; 2 | 3 | export function isStoreClass(value: any): value is typeof Store { 4 | return typeof value?.isStore === 'function'; 5 | } 6 | 7 | export function isStoreInstance(value: any): value is Store { 8 | return typeof value?.constructor?.isStore === 'function'; 9 | } 10 | -------------------------------------------------------------------------------- /packages/store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/utilities/README.md: -------------------------------------------------------------------------------- 1 | # @layr/utilities 2 | 3 | A set of basic utilities used by Layr. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/utilities 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/utilities/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/utilities", 3 | "version": "1.0.9", 4 | "description": "A set of basic utilities used by Layr", 5 | "keywords": [ 6 | "layr", 7 | "utilities", 8 | "utility", 9 | "tool", 10 | "function" 11 | ], 12 | "author": "Manuel Vila ", 13 | "license": "MIT", 14 | "repository": "https://github.com/layrjs/layr/tree/master/packages/utilities", 15 | "files": [ 16 | "dist" 17 | ], 18 | "main": "dist/node-cjs/index.js", 19 | "module": "dist/node-esm/index.js", 20 | "engines": { 21 | "node": ">=16.0.0" 22 | }, 23 | "scripts": { 24 | "build": "dev-tools build:ts-library", 25 | "prepare": "npm run build", 26 | "test": "dev-tools test:ts-library", 27 | "publish:package": "dev-tools publish:package", 28 | "update": "dev-tools update:dependencies" 29 | }, 30 | "dependencies": { 31 | "tslib": "^2.4.1" 32 | }, 33 | "devDependencies": { 34 | "@mvila/dev-tools": "^1.3.1", 35 | "@mvila/tsconfig": "^1.0.6", 36 | "@types/jest": "^29.2.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/utilities/src/error.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_DISPLAY_MESSAGE = 'Sorry, something went wrong.'; 2 | 3 | export type ExtendedError = Error & ExtendedErrorAttributes; 4 | 5 | type ExtendedErrorAttributes = Record & { 6 | displayMessage?: string; 7 | code?: number | string; 8 | }; 9 | 10 | export function createError(message?: string, attributes: ExtendedErrorAttributes = {}) { 11 | const error = new Error(message) as ExtendedError; 12 | 13 | for (const [name, value] of Object.entries(attributes)) { 14 | if (value !== undefined) { 15 | error[name] = value; 16 | } 17 | } 18 | 19 | return error; 20 | } 21 | 22 | export function throwError(message?: string, attributes: ExtendedErrorAttributes = {}): never { 23 | throw createError(message, attributes); 24 | } 25 | 26 | export function formatError( 27 | error?: ExtendedError, 28 | {defaultDisplayMessage = DEFAULT_DISPLAY_MESSAGE} = {} 29 | ) { 30 | return error?.displayMessage ?? defaultDisplayMessage; 31 | } 32 | -------------------------------------------------------------------------------- /packages/utilities/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error'; 2 | export * from './time'; 3 | -------------------------------------------------------------------------------- /packages/utilities/src/time.ts: -------------------------------------------------------------------------------- 1 | export const MILLISECOND = 1; 2 | export const SECOND = 1000 * MILLISECOND; 3 | export const MINUTE = 60 * SECOND; 4 | export const HOUR = 60 * MINUTE; 5 | export const DAY = 24 * HOUR; 6 | export const WEEK = 7 * DAY; 7 | export const MONTH = 30.4375 * DAY; 8 | export const YEAR = 12 * MONTH; 9 | 10 | export function sleep(milliseconds: number) { 11 | return new Promise((resolve) => { 12 | setTimeout(resolve, milliseconds); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /packages/utilities/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/with-roles/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules 3 | *.log 4 | /dist 5 | -------------------------------------------------------------------------------- /packages/with-roles/README.md: -------------------------------------------------------------------------------- 1 | # @layr/with-roles 2 | 3 | Simple role system for Layr. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install @layr/with-roles 9 | ``` 10 | 11 | ## License 12 | 13 | MIT 14 | -------------------------------------------------------------------------------- /packages/with-roles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layr/with-roles", 3 | "version": "2.0.59", 4 | "description": "Simple role system for Layr", 5 | "keywords": [ 6 | "layr", 7 | "component", 8 | "attribute", 9 | "method", 10 | "role", 11 | "authorization", 12 | "security" 13 | ], 14 | "author": "Manuel Vila ", 15 | "license": "MIT", 16 | "repository": "https://github.com/layrjs/layr/tree/master/packages/with-roles", 17 | "files": [ 18 | "dist" 19 | ], 20 | "main": "dist/node-cjs/index.js", 21 | "module": "dist/node-esm/index.js", 22 | "engines": { 23 | "node": ">=16.0.0" 24 | }, 25 | "scripts": { 26 | "build": "dev-tools build:ts-library", 27 | "prepare": "npm run build", 28 | "test": "dev-tools test:ts-library", 29 | "publish:package": "dev-tools publish:package", 30 | "update": "dev-tools update:dependencies" 31 | }, 32 | "dependencies": { 33 | "@layr/component": "^2.0.51", 34 | "core-helpers": "^1.0.8", 35 | "possibly-async": "^1.0.7", 36 | "tslib": "^2.4.1" 37 | }, 38 | "devDependencies": { 39 | "@mvila/dev-tools": "^1.3.1", 40 | "@mvila/tsconfig": "^1.0.6", 41 | "@types/jest": "^29.2.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/with-roles/src/decorators.test.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@layr/component'; 2 | 3 | import {WithRoles} from './with-roles'; 4 | import {role} from './decorators'; 5 | import {isRoleInstance} from './role'; 6 | 7 | describe('Decorators', () => { 8 | test('@role()', async () => { 9 | class Movie extends WithRoles(Component) { 10 | @role('anyone') static anyoneRoleResolver() { 11 | return true; 12 | } 13 | 14 | @role('author') authorRoleResolver() { 15 | return false; 16 | } 17 | } 18 | 19 | const anyoneRole = Movie.getRole('anyone'); 20 | 21 | expect(isRoleInstance(anyoneRole)).toBe(true); 22 | expect(anyoneRole.getName()).toBe('anyone'); 23 | expect(anyoneRole.getParent()).toBe(Movie); 24 | expect(anyoneRole.getResolver()).toBe(Movie.anyoneRoleResolver); 25 | 26 | const authorRole = Movie.prototype.getRole('author'); 27 | 28 | expect(isRoleInstance(authorRole)).toBe(true); 29 | expect(authorRole.getName()).toBe('author'); 30 | expect(authorRole.getParent()).toBe(Movie.prototype); 31 | expect(authorRole.getResolver()).toBe(Movie.prototype.authorRoleResolver); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/with-roles/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decorators'; 2 | export * from './role'; 3 | export * from './utilities'; 4 | export * from './with-roles'; 5 | -------------------------------------------------------------------------------- /packages/with-roles/src/utilities.ts: -------------------------------------------------------------------------------- 1 | import {getTypeOf} from 'core-helpers'; 2 | 3 | import type {ComponentWithRoles} from './with-roles'; 4 | 5 | /** 6 | * Returns whether the specified value is a [`ComponentWithRoles`](https://layrjs.com/docs/v2/reference/with-roles#component-with-roles-class) class or instance. 7 | * 8 | * @param value A value of any type. 9 | * 10 | * @returns A boolean. 11 | * 12 | * @category Utilities 13 | */ 14 | export function isComponentWithRolesClassOrInstance( 15 | value: any 16 | ): value is typeof ComponentWithRoles | ComponentWithRoles { 17 | return typeof value?.getRole === 'function'; 18 | } 19 | 20 | /** 21 | * Throws an error if the specified value is not a [`ComponentWithRoles`](https://layrjs.com/docs/v2/reference/with-roles#component-with-roles-class) class or instance. 22 | * 23 | * @param value A value of any type. 24 | * 25 | * @category Utilities 26 | */ 27 | export function assertIsComponentWithRolesClassOrInstance( 28 | value: any 29 | ): asserts value is typeof ComponentWithRoles | ComponentWithRoles { 30 | if (!isComponentWithRolesClassOrInstance(value)) { 31 | throw new Error( 32 | `Expected a component with roles class or instance, but received a value of type '${getTypeOf( 33 | value 34 | )}'` 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/with-roles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mvila/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /website/backend/boostr.config.mjs: -------------------------------------------------------------------------------- 1 | export default ({services}) => ({ 2 | type: 'backend', 3 | 4 | dependsOn: 'database', 5 | 6 | environment: { 7 | FRONTEND_URL: services.frontend.url, 8 | BACKEND_URL: services.backend.url, 9 | DATABASE_URL: services.database.url 10 | }, 11 | 12 | rootComponent: './src/index.ts', 13 | 14 | stages: { 15 | development: { 16 | url: 'http://localhost:18888/', 17 | platform: 'local' 18 | }, 19 | production: { 20 | url: 'https://backend.layrjs.com/', 21 | platform: 'aws', 22 | aws: { 23 | region: 'us-west-2', 24 | lambda: { 25 | memorySize: 1024, 26 | timeout: 15 27 | } 28 | } 29 | } 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /website/backend/boostr.config.private-template.mjs: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | stages: { 3 | development: { 4 | environment: { 5 | JWT_SECRET: '********' 6 | } 7 | }, 8 | production: { 9 | environment: { 10 | MAILER_LITE_API_KEY: '********', 11 | MAILER_LITE_NEWSLETTER_SUBSCRIPTIONS_GROUP_ID: '********', 12 | JWT_SECRET: '********' 13 | } 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /website/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layr-website-backend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@layr/component": "^2.0.31", 9 | "@layr/mongodb-store": "^2.0.45", 10 | "@layr/routable": "^2.0.84", 11 | "@layr/storable": "^2.0.49", 12 | "@layr/utilities": "^1.0.5", 13 | "@layr/with-roles": "^2.0.36", 14 | "bcryptjs": "^2.4.3", 15 | "cross-fetch": "^3.1.4", 16 | "jsonwebtoken": "^9.0.0", 17 | "lodash": "^4.17.21", 18 | "rss": "^1.2.2", 19 | "slugify": "^1.6.1" 20 | }, 21 | "devDependencies": { 22 | "@boostr/tsconfig": "^1.0.0", 23 | "@types/bcryptjs": "^2.4.2", 24 | "@types/jsonwebtoken": "^8.5.9", 25 | "@types/lodash": "^4.14.175", 26 | "@types/node": "^14.17.20", 27 | "@types/rss": "0.0.28" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /website/backend/src/components/application.ts: -------------------------------------------------------------------------------- 1 | import {Component, provide} from '@layr/component'; 2 | import {Routable} from '@layr/routable'; 3 | 4 | import {User} from './user'; 5 | import {Article} from './article'; 6 | import {Newsletter} from './newsletter'; 7 | 8 | export class Application extends Routable(Component) { 9 | @provide() static User = User; 10 | @provide() static Article = Article; 11 | @provide() static Newsletter = Newsletter; 12 | } 13 | -------------------------------------------------------------------------------- /website/backend/src/components/entity.ts: -------------------------------------------------------------------------------- 1 | import {Component, consume, expose, AttributeSelector} from '@layr/component'; 2 | import {Storable, primaryIdentifier, attribute, index} from '@layr/storable'; 3 | import {WithRoles, role} from '@layr/with-roles'; 4 | 5 | import type {User} from './user'; 6 | 7 | export class Entity extends WithRoles(Storable(Component)) { 8 | @consume() static User: typeof User; 9 | 10 | @expose({get: true, set: true}) @primaryIdentifier() id!: string; 11 | 12 | @expose({get: true}) @index() @attribute('Date') createdAt = new Date(); 13 | 14 | @attribute('Date?') updatedAt?: Date; 15 | 16 | @role('user') static async userRoleResolver() { 17 | return (await this.User.getAuthenticatedUser()) !== undefined; 18 | } 19 | 20 | @role('guest') static async guestRoleResolver() { 21 | return !(await this.resolveRole('user')); 22 | } 23 | 24 | async beforeSave(attributeSelector: AttributeSelector) { 25 | await super.beforeSave(attributeSelector); 26 | 27 | this.updatedAt = new Date(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /website/backend/src/components/with-author.ts: -------------------------------------------------------------------------------- 1 | import {expose} from '@layr/component'; 2 | import {attribute} from '@layr/storable'; 3 | import {role} from '@layr/with-roles'; 4 | 5 | import type {Entity} from './entity'; 6 | import type {User} from './user'; 7 | 8 | export const WithAuthor = (Base: typeof Entity) => { 9 | class WithAuthor extends Base { 10 | declare ['constructor']: typeof WithAuthor; 11 | 12 | @expose({get: true, set: 'author'}) @attribute('User') author!: User; 13 | 14 | @role('author') async authorRoleResolver() { 15 | if (await this.resolveRole('guest')) { 16 | return undefined; 17 | } 18 | 19 | if (this.isNew()) { 20 | return true; 21 | } 22 | 23 | await this.getGhost().load({author: {}}); 24 | 25 | return ( 26 | this.getGhost().author === (await this.constructor.User.getAuthenticatedUser())!.getGhost() 27 | ); 28 | } 29 | } 30 | 31 | return WithAuthor; 32 | }; 33 | -------------------------------------------------------------------------------- /website/backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import {MongoDBStore} from '@layr/mongodb-store'; 2 | 3 | import {Application} from './components/application'; 4 | 5 | export default () => { 6 | const store = new MongoDBStore(process.env.DATABASE_URL!); 7 | 8 | store.registerRootComponent(Application); 9 | 10 | return Application; 11 | }; 12 | -------------------------------------------------------------------------------- /website/backend/src/jwt.ts: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | 3 | // Tip: Use `openssl rand -hex 64` to generate a JWT secret 4 | 5 | const secretBuffer = Buffer.from(process.env.JWT_SECRET!, 'hex'); 6 | const algorithm = 'HS256'; 7 | 8 | export function generateJWT(payload: object) { 9 | return jwt.sign(payload, secretBuffer, {algorithm}); 10 | } 11 | 12 | export function verifyJWT(token: string) { 13 | try { 14 | return jwt.verify(token, secretBuffer, {algorithms: [algorithm]}); 15 | } catch (err) { 16 | return undefined; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /website/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@boostr/tsconfig", 3 | "include": ["src/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /website/boostr.config.mjs: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | type: 'application', 3 | 4 | services: { 5 | frontend: './frontend', 6 | backend: './backend', 7 | database: './database' 8 | }, 9 | 10 | environment: { 11 | APPLICATION_NAME: 'Layr', 12 | APPLICATION_DESCRIPTION: 'Dramatically simplify full‑stack development.' 13 | }, 14 | 15 | stages: { 16 | production: { 17 | environment: { 18 | NODE_ENV: 'production' 19 | } 20 | } 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /website/database/.gitignore: -------------------------------------------------------------------------------- 1 | /data 2 | -------------------------------------------------------------------------------- /website/database/boostr.config.mjs: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | type: 'database', 3 | 4 | stages: { 5 | development: { 6 | url: 'mongodb://localhost:18889/dev', 7 | platform: 'local' 8 | } 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /website/database/boostr.config.private-template.mjs: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | stages: { 3 | production: { 4 | url: '********' 5 | } 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /website/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layr-website-frontend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@emotion-kit/react": "^1.0.59", 9 | "@emotion-starter/react": "^1.0.70", 10 | "@emotion/react": "^11.4.1", 11 | "@layr/component": "^2.0.31", 12 | "@layr/component-http-client": "^2.0.44", 13 | "@layr/react-integration": "^2.0.109", 14 | "@layr/routable": "^2.0.84", 15 | "@layr/storable": "^2.0.49", 16 | "@layr/utilities": "^1.0.5", 17 | "@react-hook/window-size": "^3.0.7", 18 | "cross-fetch": "^3.1.4", 19 | "date-fns": "^2.24.0", 20 | "dompurify": "^2.3.3", 21 | "highlight.js": "^10.7.3", 22 | "lodash": "^4.17.21", 23 | "marked": "^4.0.12", 24 | "react": "^17.0.1", 25 | "react-dom": "^17.0.1", 26 | "react-helmet": "^6.1.0" 27 | }, 28 | "devDependencies": { 29 | "@boostr/tsconfig": "^1.0.0", 30 | "@types/dompurify": "^2.3.1", 31 | "@types/lodash": "^4.14.175", 32 | "@types/marked": "^4.0.1", 33 | "@types/react": "^17.0.27", 34 | "@types/react-dom": "^17.0.9", 35 | "@types/react-helmet": "^6.1.3" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /website/frontend/public/docs/v1/reference/component-server-e5RUuQXVcCIVLxHiNzCDO.immutable.md: -------------------------------------------------------------------------------- 1 | ### ComponentServer class {#component-server-class} 2 | 3 | A base class allowing to serve a root [`Component`](https://layrjs.com/docs/v1/reference/component) so it can be accessed by a [`ComponentClient`](https://layrjs.com/docs/v1/reference/component-client). 4 | 5 | Typically, instead of using this class, you would use a class such as [`ComponentHTTPServer`](https://layrjs.com/docs/v1/reference/component-http-server), or a middleware such as [`component-express-middleware`](https://layrjs.com/docs/v1/reference/component-express-middleware). 6 | 7 | #### Creation 8 | 9 | ##### `new ComponentServer(component, [options])` constructor {#constructor} 10 | 11 | Creates a component server. 12 | 13 | **Parameters:** 14 | 15 | * `component`: The root [`Component`](https://layrjs.com/docs/v1/reference/component) class to serve. 16 | * `options`: 17 | * `version`: A number specifying the version of the returned component server (default: `undefined`). 18 | 19 | **Returns:** 20 | 21 | A `ComponentServer` instance. 22 | 23 | **Example:** 24 | 25 | See [`ComponentClient`'s example](https://layrjs.com/docs/v1/reference/component-client#constructor). -------------------------------------------------------------------------------- /website/frontend/public/docs/v1/reference/identifier-attribute-6Jgrzmrlv4QJoBZVTYYDb9.immutable.md: -------------------------------------------------------------------------------- 1 | ### IdentifierAttribute class {#identifier-attribute-class} 2 | 3 | *Inherits from [`Attribute`](https://layrjs.com/docs/v1/reference/attribute).* 4 | 5 | A base class from which [`PrimaryIdentifierAttribute`](https://layrjs.com/docs/v1/reference/primary-identifier-attribute) and [`SecondaryIdentifierAttribute`](https://layrjs.com/docs/v1/reference/secondary-identifier-attribute) are constructed. Unless you build a custom identifier attribute class, you probably won't have to use this class directly. 6 | 7 | #### Utilities 8 | 9 | ##### `isIdentifierAttributeClass(value)` function {#is-identifier-attribute-class-function} 10 | 11 | Returns whether the specified value is an `IdentifierAttribute` class. 12 | 13 | **Parameters:** 14 | 15 | * `value`: A value of any type. 16 | 17 | **Returns:** 18 | 19 | A boolean. 20 | 21 | ##### `isIdentifierAttributeInstance(value)` function {#is-identifier-attribute-instance-function} 22 | 23 | Returns whether the specified value is an `IdentifierAttribute` instance. 24 | 25 | **Parameters:** 26 | 27 | * `value`: A value of any type. 28 | 29 | **Returns:** 30 | 31 | A boolean. 32 | -------------------------------------------------------------------------------- /website/frontend/public/docs/v2/concepts/coming-soon-38q4cuyCWekHuz36oHyMKG.immutable.md: -------------------------------------------------------------------------------- 1 | ### Concepts 2 | 3 | #### Coming Soon 4 | 5 | This section will introduce the basic concepts of Layr, such as: 6 | 7 | - Components 8 | - Controlled attributes and methods 9 | - Cross-layer inheritance 10 | - Storage 11 | - Routes and wrappers 12 | 13 | Unfortunately, writing documentation takes a lot of time, and the Layr project is currently handled by a [single person](https://mvila.me) who has to work for customers to make a living and is also starting a new [ambitious project](https://1place.app), which will be based on Layr. 14 | 15 | More documentation will come for sure, but please be patient. 16 | 17 | If you cannot wait and feel adventurous, you can figure out how to build an app with Layr by checking out: 18 | 19 | - The ["Hello, World!"](https://layrjs.com/docs/v2/introduction/hello-world) introductory app. 20 | - The pretty exhaustive ["Reference"](https://layrjs.com/docs/v2/reference) section. 21 | - Some [examples](https://layrjs.com/docs/v2/introduction/introduction#examples) of simple full-stack apps built with Layr. 22 | -------------------------------------------------------------------------------- /website/frontend/public/docs/v2/guides/coming-soon-6TWZbDxh3EVysir57k1tga.immutable.md: -------------------------------------------------------------------------------- 1 | ### Guides 2 | 3 | #### Coming Soon 4 | 5 | This section will contain some guides explaining how to achieve the most common tasks and features with Layr, such as: 6 | 7 | - Local development 8 | - Layouts and pages 9 | - Data fetching and storage 10 | - Authentication 11 | - Testing 12 | - Deployment 13 | 14 | Unfortunately, writing documentation takes a lot of time, and the Layr project is currently handled by a [single person](https://mvila.me) who has to work for customers to make a living and is also starting a new [ambitious project](https://1place.app), which will be based on Layr. 15 | 16 | More documentation will come for sure, but please be patient. 17 | 18 | If you cannot wait and feel adventurous, you can figure out how to build an app with Layr by checking out: 19 | 20 | - The ["Hello, World!"](https://layrjs.com/docs/v2/introduction/hello-world) introductory app. 21 | - The pretty exhaustive ["Reference"](https://layrjs.com/docs/v2/reference) section. 22 | - Some [examples](https://layrjs.com/docs/v2/introduction/introduction#examples) of simple full-stack apps built with Layr. 23 | -------------------------------------------------------------------------------- /website/frontend/public/docs/v2/introduction/handling-authorization-1sLAebuD81FLlSvJbAuJmZ.immutable.md: -------------------------------------------------------------------------------- 1 | ### Handling Authorization 2 | 3 | #### Coming Soon 4 | 5 | This tutorial will expand the ["Hello, World!"](https://layrjs.com/docs/v2/introduction/hello-world) app to show you how Layr handles user authorization. 6 | 7 | Unfortunately, writing tutorials takes a lot of time, and the Layr project is currently handled by a [single person](https://mvila.me) who has to work for customers to make a living and is also starting a new [ambitious project](https://1place.app), which will be based on Layr. 8 | 9 | This tutorial will come for sure, but please be patient. 10 | 11 | If you cannot wait and feel adventurous, you can figure out how to handle authorization with Layr by checking out: 12 | 13 | - The [`WithRoles()`](https://layrjs.com/docs/v2/reference/with-roles) mixin in the "Reference" section. 14 | - Some examples of simple full-stack apps handling authorization with Layr: 15 | - [Layr Website (TS)](https://github.com/layrjs/layr/tree/master/website) 16 | - [CodebaseShow (TS)](https://github.com/codebaseshow/codebaseshow) 17 | - [RealWorld Example App (JS)](https://github.com/layrjs/react-layr-realworld-example-app) 18 | -------------------------------------------------------------------------------- /website/frontend/public/docs/v2/introduction/hello-world/assets/screenshot-001.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/website/frontend/public/docs/v2/introduction/hello-world/assets/screenshot-001.immutable.png -------------------------------------------------------------------------------- /website/frontend/public/docs/v2/introduction/hello-world/assets/screenshot-002.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/website/frontend/public/docs/v2/introduction/hello-world/assets/screenshot-002.immutable.png -------------------------------------------------------------------------------- /website/frontend/public/docs/v2/introduction/hello-world/assets/screenshot-003.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/website/frontend/public/docs/v2/introduction/hello-world/assets/screenshot-003.immutable.png -------------------------------------------------------------------------------- /website/frontend/public/docs/v2/introduction/hello-world/assets/screenshot-004.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/website/frontend/public/docs/v2/introduction/hello-world/assets/screenshot-004.immutable.png -------------------------------------------------------------------------------- /website/frontend/public/docs/v2/introduction/storing-data-40jzWiVleR8ZE0fqfGTbO2.immutable.md: -------------------------------------------------------------------------------- 1 | ### Storing Data 2 | 3 | #### Coming Soon 4 | 5 | This tutorial will expand the ["Hello, World!"](https://layrjs.com/docs/v2/introduction/hello-world) app to show you how Layr handles database storage. 6 | 7 | Unfortunately, writing tutorials takes a lot of time, and the Layr project is currently handled by a [single person](https://mvila.me) who has to work for customers to make a living and is also starting a new [ambitious project](https://1place.app), which will be based on Layr. 8 | 9 | This tutorial will come for sure, but please be patient. 10 | 11 | If you cannot wait and feel adventurous, you can figure out how to store data with Layr by checking out: 12 | 13 | - The [`Storable()`](https://layrjs.com/docs/v2/reference/storable) mixin in the "Reference" section. 14 | - Some [examples](https://layrjs.com/docs/v2/introduction/introduction#examples) of simple full-stack apps built with Layr. 15 | -------------------------------------------------------------------------------- /website/frontend/public/docs/v2/reference/component-server-i0AsOVFbGCJUrJ9mJyjPh.immutable.md: -------------------------------------------------------------------------------- 1 | ### ComponentServer class {#component-server-class} 2 | 3 | A base class allowing to serve a root [`Component`](https://layrjs.com/docs/v2/reference/component) so it can be accessed by a [`ComponentClient`](https://layrjs.com/docs/v2/reference/component-client). 4 | 5 | Typically, instead of using this class, you would use a class such as [`ComponentHTTPServer`](https://layrjs.com/docs/v2/reference/component-http-server), or a middleware such as [`component-express-middleware`](https://layrjs.com/docs/v2/reference/component-express-middleware). 6 | 7 | #### Creation 8 | 9 | ##### `new ComponentServer(component, [options])` constructor {#constructor} 10 | 11 | Creates a component server. 12 | 13 | **Parameters:** 14 | 15 | * `component`: The root [`Component`](https://layrjs.com/docs/v2/reference/component) class to serve. 16 | * `options`: 17 | * `version`: A number specifying the version of the returned component server (default: `undefined`). 18 | 19 | **Returns:** 20 | 21 | A `ComponentServer` instance. 22 | 23 | **Example:** 24 | 25 | See [`ComponentClient`'s example](https://layrjs.com/docs/v2/reference/component-client#constructor). -------------------------------------------------------------------------------- /website/frontend/public/docs/v2/reference/identifier-attribute-2w8hckqCKGHPurgIGY5vhT.immutable.md: -------------------------------------------------------------------------------- 1 | ### IdentifierAttribute class {#identifier-attribute-class} 2 | 3 | *Inherits from [`Attribute`](https://layrjs.com/docs/v2/reference/attribute).* 4 | 5 | A base class from which [`PrimaryIdentifierAttribute`](https://layrjs.com/docs/v2/reference/primary-identifier-attribute) and [`SecondaryIdentifierAttribute`](https://layrjs.com/docs/v2/reference/secondary-identifier-attribute) are constructed. Unless you build a custom identifier attribute class, you probably won't have to use this class directly. 6 | 7 | #### Utilities 8 | 9 | ##### `isIdentifierAttributeClass(value)` function {#is-identifier-attribute-class-function} 10 | 11 | Returns whether the specified value is an `IdentifierAttribute` class. 12 | 13 | **Parameters:** 14 | 15 | * `value`: A value of any type. 16 | 17 | **Returns:** 18 | 19 | A boolean. 20 | 21 | ##### `isIdentifierAttributeInstance(value)` function {#is-identifier-attribute-instance-function} 22 | 23 | Returns whether the specified value is an `IdentifierAttribute` instance. 24 | 25 | **Parameters:** 26 | 27 | * `value`: A value of any type. 28 | 29 | **Returns:** 30 | 31 | A boolean. 32 | -------------------------------------------------------------------------------- /website/frontend/public/layr-favicon-3vtu1VGUfUfDawVC0zL4Oz.immutable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/website/frontend/public/layr-favicon-3vtu1VGUfUfDawVC0zL4Oz.immutable.png -------------------------------------------------------------------------------- /website/frontend/src/assets/page-not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/website/frontend/src/assets/page-not-found.png -------------------------------------------------------------------------------- /website/frontend/src/assets/something-wrong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/website/frontend/src/assets/something-wrong.png -------------------------------------------------------------------------------- /website/frontend/src/assets/typical-stack-vs-layr-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layrjs/layr/10a7e9f521a66620038edbbd684f4d5d3d8ec9ed/website/frontend/src/assets/typical-stack-vs-layr-stack.png -------------------------------------------------------------------------------- /website/frontend/src/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png'; 2 | declare module '*.jpg'; 3 | declare module '*.jpeg'; 4 | declare module '*.webp'; 5 | declare module '*.gif'; 6 | declare module '*.svg'; 7 | -------------------------------------------------------------------------------- /website/frontend/src/index.ts: -------------------------------------------------------------------------------- 1 | import {ComponentHTTPClient} from '@layr/component-http-client'; 2 | import {Storable} from '@layr/storable'; 3 | 4 | import type {Application as BackendApplication} from '../../backend/src/components/application'; 5 | import {extendApplication} from './components/application'; 6 | 7 | export default async () => { 8 | const client = new ComponentHTTPClient(process.env.BACKEND_URL!, { 9 | mixins: [Storable], 10 | async retryFailedRequests() { 11 | return confirm('Sorry, a network error occurred. Would you like to retry?'); 12 | } 13 | }); 14 | 15 | const BackendApplicationProxy = (await client.getComponent()) as typeof BackendApplication; 16 | 17 | const Application = extendApplication(BackendApplicationProxy); 18 | 19 | return Application; 20 | }; 21 | -------------------------------------------------------------------------------- /website/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@boostr/tsconfig", 3 | "include": ["src/**/*"], 4 | "compilerOptions": { 5 | "jsxFactory": "jsx" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layr-website", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "boostr": "^2.0.167" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /website/redirection/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "layr-website-redirection", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Manuel Vila ", 6 | "license": "MIT", 7 | "scripts": { 8 | "deploy": "FRONTEND_URL=https://liaison.dev simple-deployment" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "simple-deployment": "^0.1.41" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /website/redirection/simple-deployment.config.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | const frontendURL = process.env.FRONTEND_URL; 3 | 4 | if (!frontendURL) { 5 | throw new Error(`'FRONTEND_URL' environment variable is missing`); 6 | } 7 | 8 | const domainName = new URL(frontendURL).hostname; 9 | 10 | return { 11 | type: 'website', 12 | provider: 'aws', 13 | domainName, 14 | files: ['./public'], 15 | customErrors: [{errorCode: 404, responseCode: 200, responsePage: 'index.html'}], 16 | aws: { 17 | region: 'us-west-2', 18 | cloudFront: { 19 | priceClass: 'PriceClass_100' 20 | } 21 | } 22 | }; 23 | }; 24 | --------------------------------------------------------------------------------