├── .depcheckrc
├── .editorconfig
├── .eslintignore
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
├── actions
│ ├── build
│ │ └── action.yml
│ ├── check
│ │ └── action.yml
│ ├── job-publish
│ │ └── action.yml
│ ├── job-verify
│ │ └── action.yml
│ ├── publish-npm
│ │ └── action.yml
│ ├── setup
│ │ └── action.yml
│ └── test
│ │ └── action.yml
└── workflows
│ ├── core.yml
│ ├── create.yml
│ ├── crypto.yml
│ ├── design-mui.yml
│ ├── express.yml
│ ├── koa.yml
│ ├── node.yml
│ ├── playwright.yml
│ ├── push.yml
│ ├── react-rest.yml
│ ├── react-ui.yml
│ ├── react.yml
│ ├── release.yml
│ ├── rest-client-jsonapi.yml
│ └── rest-client.yml
├── .gitignore
├── .idea
├── .gitignore
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── hatchify.iml
├── inspectionProfiles
│ └── Project_Default.xml
├── modules.xml
└── vcs.xml
├── .nvmrc
├── .prettierignore
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── docs
├── NAMESPACE-SCHEMAS.md
├── api-documentation.md
├── attachments
│ ├── FiltersClosed.png
│ ├── FiltersOpen.png
│ ├── List.png
│ ├── ListWithPagination.png
│ ├── add-postgres.png
│ ├── create-database.png
│ ├── customNoRecords.png
│ ├── data-grid-example.png
│ ├── dateonly-column.png
│ ├── datetime-column.png
│ ├── defaultNoRecords.png
│ ├── enum-grid.png
│ ├── frontend-todo-list.png
│ ├── integer-column.png
│ ├── number-column.png
│ ├── reactTs.gif
│ ├── stringNullFilter.png
│ └── text-grid.png
├── code-examples.md
├── core
│ ├── PartialSchema.md
│ ├── README.md
│ ├── attribute-types
│ │ ├── README.md
│ │ ├── boolean.md
│ │ ├── dateonly.md
│ │ ├── datetime.md
│ │ ├── enum.md
│ │ ├── integer.md
│ │ ├── number.md
│ │ ├── string.md
│ │ ├── text.md
│ │ └── uuid.md
│ └── relationship-types
│ │ ├── README.md
│ │ ├── belongs-to.md
│ │ ├── has-many-through.md
│ │ ├── has-many.md
│ │ └── has-one.md
├── express
│ ├── README.md
│ ├── hatchedExpress.everything.md
│ ├── hatchedExpress.middleware.md
│ ├── hatchedExpress.model.md
│ ├── hatchedExpress.parse.md
│ └── hatchedExpress.serialize.md
├── guides
│ ├── adding-checkboxes-to-the-list.md
│ ├── adding-custom-endpoints.md
│ ├── adding-form-like-behavior.md
│ ├── adding-request-authorization.md
│ ├── application-data-validation.md
│ ├── customizing-your-list.md
│ ├── production-custom-usage.md
│ └── using-postgres-db.md
├── jsonapi
│ ├── README.md
│ ├── creating.md
│ ├── deleting.md
│ ├── reading
│ │ ├── README.md
│ │ ├── filtering
│ │ │ ├── $eq.md
│ │ │ ├── $gt.md
│ │ │ ├── $gte.md
│ │ │ ├── $ilike.md
│ │ │ ├── $in.md
│ │ │ ├── $like.md
│ │ │ ├── $lt.md
│ │ │ ├── $lte.md
│ │ │ ├── $ne.md
│ │ │ ├── $nin.md
│ │ │ ├── README.md
│ │ │ └── no-operator.md
│ │ ├── paginating
│ │ │ └── README.md
│ │ ├── relationships
│ │ │ └── README.md
│ │ ├── sorting
│ │ │ └── README.md
│ │ └── sparse-fields
│ │ │ └── README.md
│ └── updating.md
├── koa
│ ├── README.md
│ ├── hatchedKoa.everything.md
│ ├── hatchedKoa.middleware.md
│ ├── hatchedKoa.model.md
│ ├── hatchedKoa.parse.md
│ └── hatchedKoa.serialize.md
├── react-jsonapi
│ └── README.md
├── react
│ ├── README.md
│ ├── hatchedReact.Navigation.md
│ ├── hatchedReact.components.everything.md
│ ├── hatchedReact.components.md
│ ├── hatchedReact.components[schemaName].DataGrid.Column.md
│ ├── hatchedReact.components[schemaName].DataGrid.Empty.md
│ ├── hatchedReact.components[schemaName].DataGrid.md
│ ├── hatchedReact.components[schemaName].Filters.md
│ ├── hatchedReact.components[schemaName].List.md
│ ├── hatchedReact.components[schemaName].Pagination.md
│ ├── hatchedReact.model.md
│ ├── hatchedReact.state.md
│ └── types.md
└── subpackages.md
├── e2e
├── App.txt
├── main.txt
├── playwright.config.ts
├── schemas.txt
└── tests
│ ├── getting-started.spec.ts
│ ├── grid-demo.spec.ts
│ ├── react-rest-jsonapi.spec.ts
│ └── react-rest.spec.ts
├── example
├── getting-started
│ ├── .env
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── README.md
│ ├── backend
│ │ └── index.ts
│ ├── frontend
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ └── react.svg
│ │ ├── index.css
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── index.html
│ ├── nodemon.json
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── schemas.ts
│ ├── tsconfig.backend.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── grid-demo
│ ├── .gitignore
│ ├── Dockerfile
│ ├── README.md
│ ├── backend
│ │ ├── index.ts
│ │ └── util.ts
│ ├── docker-compose.yaml
│ ├── frontend
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ └── react.svg
│ │ ├── components
│ │ │ └── DocumentTable.tsx
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── index.html
│ ├── nodemon.json
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── schemas.ts
│ ├── tsconfig.backend.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── namespaces
│ ├── .env
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── README.md
│ ├── backend
│ │ └── index.ts
│ ├── frontend
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ └── react.svg
│ │ ├── index.css
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── index.html
│ ├── nodemon.json
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── schemas.ts
│ ├── tsconfig.backend.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── react-rest-hatchify-backend
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── backend
│ │ └── index.ts
│ ├── frontend
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── WithoutHooks.tsx
│ │ ├── assets
│ │ │ └── react.svg
│ │ ├── index.css
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── index.html
│ ├── nodemon.json
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── schemas.ts
│ ├── tsconfig.backend.json
│ ├── tsconfig.json
│ └── vite.config.ts
└── react-rest
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── mockServiceWorker.js
│ └── vite.svg
│ ├── src
│ ├── App.css
│ ├── App.tsx
│ ├── assets
│ │ └── react.svg
│ ├── index.css
│ ├── main.tsx
│ ├── mocks
│ │ └── browser.ts
│ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── nx.json
├── package-lock.json
├── package.json
├── packages
├── core
│ ├── .depcheckrc
│ ├── .prettierignore
│ ├── README.md
│ ├── jest.config.json
│ ├── package.json
│ ├── src
│ │ ├── assembler
│ │ │ ├── ensureDefaultPrimaryAttribute.spec.ts
│ │ │ ├── ensureDefaultPrimaryAttribute.ts
│ │ │ ├── ensureDefaultReadOnlyAttribute.spec.ts
│ │ │ ├── ensureDefaultReadOnlyAttribute.ts
│ │ │ ├── finalizeRelationships.spec.ts
│ │ │ ├── finalizeRelationships.ts
│ │ │ ├── finalizeSchema.spec.ts
│ │ │ ├── finalizeSchema.ts
│ │ │ ├── getDefaultPrimaryAttribute.spec.ts
│ │ │ ├── getDefaultPrimaryAttribute.ts
│ │ │ ├── index.spec.ts
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ ├── core.ts
│ │ ├── dataTypes
│ │ │ ├── boolean
│ │ │ │ ├── coerce.spec.ts
│ │ │ │ ├── coerce.ts
│ │ │ │ ├── finalizeControl.spec.ts
│ │ │ │ ├── finalizeControl.ts
│ │ │ │ ├── finalizeOrm.spec.ts
│ │ │ │ ├── finalizeOrm.ts
│ │ │ │ ├── getFinalize.spec.ts
│ │ │ │ ├── getFinalize.ts
│ │ │ │ ├── getPartialControl.spec.ts
│ │ │ │ ├── getPartialControl.ts
│ │ │ │ ├── getPartialOrm.spec.ts
│ │ │ │ ├── getPartialOrm.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── dateonly
│ │ │ │ ├── coerce.spec.ts
│ │ │ │ ├── coerce.ts
│ │ │ │ ├── finalizeControl.spec.ts
│ │ │ │ ├── finalizeControl.ts
│ │ │ │ ├── finalizeOrm.spec.ts
│ │ │ │ ├── finalizeOrm.ts
│ │ │ │ ├── getFinalize.spec.ts
│ │ │ │ ├── getFinalize.ts
│ │ │ │ ├── getPartialControl.spec.ts
│ │ │ │ ├── getPartialControl.ts
│ │ │ │ ├── getPartialOrm.spec.ts
│ │ │ │ ├── getPartialOrm.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── isISO8601DateString.spec.ts
│ │ │ │ ├── isISO8601DateString.ts
│ │ │ │ ├── types.ts
│ │ │ │ ├── validateStep.spec.ts
│ │ │ │ └── validateStep.ts
│ │ │ ├── datetime
│ │ │ │ ├── coerce.spec.ts
│ │ │ │ ├── coerce.ts
│ │ │ │ ├── finalizeControl.spec.ts
│ │ │ │ ├── finalizeControl.ts
│ │ │ │ ├── finalizeOrm.spec.ts
│ │ │ │ ├── finalizeOrm.ts
│ │ │ │ ├── getFinalize.spec.ts
│ │ │ │ ├── getFinalize.ts
│ │ │ │ ├── getPartialControl.spec.ts
│ │ │ │ ├── getPartialControl.ts
│ │ │ │ ├── getPartialOrm.spec.ts
│ │ │ │ ├── getPartialOrm.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── isISO8601DatetimeString.spec.ts
│ │ │ │ ├── isISO8601DatetimeString.ts
│ │ │ │ ├── types.ts
│ │ │ │ ├── validateStep.spec.ts
│ │ │ │ └── validateStep.ts
│ │ │ ├── enumerate
│ │ │ │ ├── coerce.spec.ts
│ │ │ │ ├── coerce.ts
│ │ │ │ ├── finalizeControl.spec.ts
│ │ │ │ ├── finalizeControl.ts
│ │ │ │ ├── finalizeOrm.spec.ts
│ │ │ │ ├── finalizeOrm.ts
│ │ │ │ ├── getFinalize.spec.ts
│ │ │ │ ├── getFinalize.ts
│ │ │ │ ├── getPartialControl.spec.ts
│ │ │ │ ├── getPartialControl.ts
│ │ │ │ ├── getPartialOrm.spec.ts
│ │ │ │ ├── getPartialOrm.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── types.ts
│ │ │ │ ├── validateValues.spec.ts
│ │ │ │ └── validateValues.ts
│ │ │ ├── index.ts
│ │ │ ├── integer
│ │ │ │ ├── getPartialControl.spec.ts
│ │ │ │ ├── getPartialControl.ts
│ │ │ │ ├── getPartialOrm.spec.ts
│ │ │ │ ├── getPartialOrm.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── number
│ │ │ │ ├── coerce.spec.ts
│ │ │ │ ├── coerce.ts
│ │ │ │ ├── finalizeControl.spec.ts
│ │ │ │ ├── finalizeControl.ts
│ │ │ │ ├── finalizeOrm.spec.ts
│ │ │ │ ├── finalizeOrm.ts
│ │ │ │ ├── getFinalize.spec.ts
│ │ │ │ ├── getFinalize.ts
│ │ │ │ ├── getPartialControl.spec.ts
│ │ │ │ ├── getPartialControl.ts
│ │ │ │ ├── getPartialOrm.spec.ts
│ │ │ │ ├── getPartialOrm.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── types.ts
│ │ │ │ ├── validateStep.spec.ts
│ │ │ │ └── validateStep.ts
│ │ │ ├── string
│ │ │ │ ├── coerce.spec.ts
│ │ │ │ ├── coerce.ts
│ │ │ │ ├── finalizeControl.spec.ts
│ │ │ │ ├── finalizeControl.ts
│ │ │ │ ├── finalizeOrm.spec.ts
│ │ │ │ ├── finalizeOrm.ts
│ │ │ │ ├── getFinalize.spec.ts
│ │ │ │ ├── getFinalize.ts
│ │ │ │ ├── getPartialControl.spec.ts
│ │ │ │ ├── getPartialControl.ts
│ │ │ │ ├── getPartialOrm.spec.ts
│ │ │ │ ├── getPartialOrm.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── text
│ │ │ │ ├── finalizeControl.spec.ts
│ │ │ │ ├── finalizeControl.ts
│ │ │ │ ├── finalizeOrm.spec.ts
│ │ │ │ ├── finalizeOrm.ts
│ │ │ │ ├── getFinalize.spec.ts
│ │ │ │ ├── getFinalize.ts
│ │ │ │ ├── getPartialControl.spec.ts
│ │ │ │ ├── getPartialControl.ts
│ │ │ │ ├── getPartialOrm.spec.ts
│ │ │ │ ├── getPartialOrm.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ └── uuid
│ │ │ │ ├── constants.ts
│ │ │ │ ├── finalizeControl.spec.ts
│ │ │ │ ├── finalizeControl.ts
│ │ │ │ ├── finalizeOrm.spec.ts
│ │ │ │ ├── finalizeOrm.ts
│ │ │ │ ├── getFinalize.spec.ts
│ │ │ │ ├── getFinalize.ts
│ │ │ │ ├── getPartialControl.spec.ts
│ │ │ │ ├── getPartialControl.ts
│ │ │ │ ├── getPartialOrm.spec.ts
│ │ │ │ ├── getPartialOrm.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ ├── relationships
│ │ │ ├── belongsTo
│ │ │ │ ├── finalize.spec.ts
│ │ │ │ ├── finalize.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── hasMany
│ │ │ │ ├── finalize.spec.ts
│ │ │ │ ├── finalize.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── hasManyThrough
│ │ │ │ ├── buildThrough.spec.ts
│ │ │ │ ├── buildThrough.ts
│ │ │ │ ├── finalize.spec.ts
│ │ │ │ ├── finalize.ts
│ │ │ │ └── types.ts
│ │ │ ├── hasOne
│ │ │ │ ├── finalize.spec.ts
│ │ │ │ ├── finalize.ts
│ │ │ │ ├── index.spec.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── utils
│ │ │ │ ├── getForeignKeyAttribute.spec.ts
│ │ │ │ └── getForeignKeyAttribute.ts
│ │ ├── types
│ │ │ ├── index.ts
│ │ │ └── schema.ts
│ │ └── util
│ │ │ ├── camelCaseToPascalCase.spec.ts
│ │ │ ├── camelCaseToPascalCase.ts
│ │ │ ├── camelCaseToTitleCase.spec.ts
│ │ │ ├── camelCaseToTitleCase.ts
│ │ │ ├── getEndpoint.spec.ts
│ │ │ ├── getEndpoint.ts
│ │ │ ├── getSchemaKey.spec.ts
│ │ │ ├── getSchemaKey.ts
│ │ │ ├── mergeSchemaUI.spec.ts
│ │ │ ├── mergeSchemaUI.ts
│ │ │ ├── pascalCaseToCamelCase.spec.ts
│ │ │ ├── pascalCaseToCamelCase.ts
│ │ │ ├── pascalCaseToKebabCase.spec.ts
│ │ │ ├── pascalCaseToKebabCase.ts
│ │ │ ├── pluralize.spec.ts
│ │ │ ├── pluralize.ts
│ │ │ ├── singularize.spec.ts
│ │ │ ├── singularize.ts
│ │ │ └── uuidv4.ts
│ └── tsconfig.json
├── create
│ ├── .depcheckrc
│ ├── README.md
│ ├── build.config.ts
│ ├── index.js
│ ├── package.json
│ ├── src
│ │ ├── constants.ts
│ │ ├── index.ts
│ │ ├── types.ts
│ │ └── util.ts
│ ├── template-express
│ │ ├── _gitignore
│ │ ├── backend
│ │ │ └── index.ts
│ │ └── schemas.ts
│ ├── template-koa
│ │ ├── _gitignore
│ │ ├── backend
│ │ │ └── index.ts
│ │ └── schemas.ts
│ ├── template-react
│ │ ├── App.tsx
│ │ ├── index.css
│ │ └── main.tsx
│ └── tsconfig.json
├── crypto
│ ├── .depcheckrc
│ ├── README.md
│ ├── index.browser.js
│ ├── index.browser.spec.js
│ ├── index.d.ts
│ ├── index.js
│ ├── index.spec.js
│ ├── jest.config.json
│ └── package.json
├── design-mui
│ ├── .depcheckrc
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── assets
│ │ │ ├── Robochicken.tsx
│ │ │ └── index.ts
│ │ ├── components
│ │ │ ├── MuiAutocomplete
│ │ │ │ ├── MuiAutocomplete.test.tsx
│ │ │ │ ├── MuiAutocomplete.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── MuiDataGrid
│ │ │ │ ├── MuiDataGrid.tsx
│ │ │ │ └── index.ts
│ │ │ ├── MuiEverything
│ │ │ │ ├── MuiEverything.test.tsx
│ │ │ │ ├── MuiEverything.tsx
│ │ │ │ └── index.ts
│ │ │ ├── MuiFilters
│ │ │ │ ├── MuiFilters.test.tsx
│ │ │ │ ├── MuiFilters.tsx
│ │ │ │ ├── components
│ │ │ │ │ ├── MuiFilterRows.test.tsx
│ │ │ │ │ ├── MuiFilterRows.tsx
│ │ │ │ │ └── inputs
│ │ │ │ │ │ ├── ColumnSelect.test.tsx
│ │ │ │ │ │ ├── ColumnSelect.tsx
│ │ │ │ │ │ ├── DateInput.test.tsx
│ │ │ │ │ │ ├── DateInput.tsx
│ │ │ │ │ │ ├── EnumInput.test.tsx
│ │ │ │ │ │ ├── EnumInput.tsx
│ │ │ │ │ │ ├── NumberInput.tsx
│ │ │ │ │ │ ├── OperatorSelect.test.tsx
│ │ │ │ │ │ ├── OperatorSelect.tsx
│ │ │ │ │ │ ├── StringInput.test.tsx
│ │ │ │ │ │ ├── StringInput.tsx
│ │ │ │ │ │ └── ValueInput.tsx
│ │ │ │ ├── constants.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── utils
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── utils.test.tsx
│ │ │ │ │ └── utils.tsx
│ │ │ ├── MuiLayout
│ │ │ │ ├── MuiLayout.test.tsx
│ │ │ │ ├── MuiLayout.tsx
│ │ │ │ └── index.ts
│ │ │ ├── MuiList
│ │ │ │ ├── MuiList.test.tsx
│ │ │ │ ├── MuiList.tsx
│ │ │ │ ├── components
│ │ │ │ │ ├── MuiBody.tsx
│ │ │ │ │ ├── MuiBodySkeleton.tsx
│ │ │ │ │ ├── MuiHeaders
│ │ │ │ │ │ ├── MuiHeaders.tsx
│ │ │ │ │ │ ├── components
│ │ │ │ │ │ │ ├── Sort.tsx
│ │ │ │ │ │ │ ├── Sortable.tsx
│ │ │ │ │ │ │ └── index.ts
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ └── index.ts
│ │ │ │ └── index.ts
│ │ │ ├── MuiNavigation
│ │ │ │ ├── MuiNavigation.tsx
│ │ │ │ └── index.ts
│ │ │ ├── MuiNoSchemas
│ │ │ │ ├── MuiNoSchemas.tsx
│ │ │ │ └── index.ts
│ │ │ ├── MuiPagination
│ │ │ │ ├── MuiPagination.test.tsx
│ │ │ │ ├── MuiPagination.tsx
│ │ │ │ └── index.ts
│ │ │ ├── MuiProvider
│ │ │ │ ├── DefaultDisplayComponents
│ │ │ │ │ ├── DefaultDisplayComponents.test.tsx
│ │ │ │ │ ├── DefaultDisplayComponents.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── DefaultFieldComponents
│ │ │ │ │ ├── DefaultFieldComponents.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── MuiProvider.test.tsx
│ │ │ │ ├── MuiProvider.tsx
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── design-mui.ts
│ │ └── services
│ │ │ └── index.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── express
│ ├── .depcheckrc
│ ├── .gitignore
│ ├── .prettierignore
│ ├── LICENSE.md
│ ├── README.md
│ ├── jest.config.json
│ ├── package.json
│ ├── src
│ │ ├── attributes.spec.ts
│ │ ├── database.spec.ts
│ │ ├── errorCodes.spec.ts
│ │ ├── exports.ts
│ │ ├── express.ts
│ │ ├── jsonapi.spec.ts
│ │ ├── jsonapiNamespace.spec.ts
│ │ ├── middleware.spec.ts
│ │ ├── middleware.ts
│ │ ├── naming.spec.ts
│ │ ├── queryStringFilters.spec.ts
│ │ ├── relationships.spec.ts
│ │ ├── relationshipsv2.spec.ts
│ │ ├── schema.spec.ts
│ │ └── testing
│ │ │ └── utils.ts
│ └── tsconfig.json
├── koa
│ ├── .depcheckrc
│ ├── .gitignore
│ ├── .prettierignore
│ ├── LICENSE.md
│ ├── README.md
│ ├── jest.config.json
│ ├── package.json
│ ├── src
│ │ ├── attributes.spec.ts
│ │ ├── custom.spec.ts
│ │ ├── database.spec.ts
│ │ ├── errorCodes.spec.ts
│ │ ├── exports.ts
│ │ ├── hatchify-gsg-seed.ts
│ │ ├── jsonapi.spec.ts
│ │ ├── jsonapiNamespace.spec.ts
│ │ ├── koa.ts
│ │ ├── middleware.ts
│ │ ├── naming.spec.ts
│ │ ├── queryStringFilters.spec.ts
│ │ ├── relationships.spec.ts
│ │ ├── relationshipsv2.spec.ts
│ │ ├── schema.spec.ts
│ │ └── testing
│ │ │ └── utils.ts
│ ├── tsconfig.json
│ └── typedoc.json
├── node
│ ├── .depcheckrc
│ ├── .gitignore
│ ├── .prettierignore
│ ├── README.md
│ ├── jest.config.json
│ ├── package.json
│ ├── src
│ │ ├── error
│ │ │ ├── constants.ts
│ │ │ ├── handlers
│ │ │ │ ├── errorResponseHandler.ts
│ │ │ │ ├── hatchifyErrorHandler.ts
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ └── types
│ │ │ │ ├── ConflictError.ts
│ │ │ │ ├── HatchifyError.ts
│ │ │ │ ├── NotFoundError.ts
│ │ │ │ ├── RelationshipPathError.ts
│ │ │ │ ├── UnexpectedValueError.ts
│ │ │ │ ├── ValueRequiredError.ts
│ │ │ │ └── index.ts
│ │ ├── everything.ts
│ │ ├── exports.ts
│ │ ├── internals.spec.ts
│ │ ├── jest.d.ts
│ │ ├── jest.setup.ts
│ │ ├── middleware.spec.ts
│ │ ├── middleware.ts
│ │ ├── node.spec.ts
│ │ ├── node.ts
│ │ ├── parse
│ │ │ ├── IsValidInclude.spec.ts
│ │ │ ├── IsValidInclude.ts
│ │ │ ├── body.ts
│ │ │ ├── builder.spec.ts
│ │ │ ├── builder.ts
│ │ │ ├── getColumnName.spec.ts
│ │ │ ├── getColumnName.ts
│ │ │ ├── handlePostgresUuid.ts
│ │ │ ├── handleSqliteDateNestedColumns.ts
│ │ │ ├── handleSqliteLike.spec.ts
│ │ │ ├── handleSqliteLike.ts
│ │ │ ├── handleWhere.ts
│ │ │ ├── index.spec.ts
│ │ │ ├── index.ts
│ │ │ ├── indexNamespace.spec.ts
│ │ │ ├── isPathIncluded.spec.ts
│ │ │ ├── isPathIncluded.ts
│ │ │ ├── isValidAttribute.spec.ts
│ │ │ ├── isValidAttribute.ts
│ │ │ ├── validator.ts
│ │ │ └── walk.ts
│ │ ├── schema.ts
│ │ ├── sequelize
│ │ │ ├── convertHatchifyModels.spec.ts
│ │ │ ├── convertHatchifyModels.ts
│ │ │ ├── createSequelizeInstance.ts
│ │ │ ├── customTypes
│ │ │ │ ├── CustomDecimal.ts
│ │ │ │ └── CustomInteger.ts
│ │ │ ├── getSequelizeSchemaName.spec.ts
│ │ │ ├── getSequelizeSchemaName.ts
│ │ │ ├── index.ts
│ │ │ ├── toSequelize.spec.ts
│ │ │ └── toSequelize.ts
│ │ ├── serialize.ts
│ │ └── types.ts
│ └── tsconfig.json
├── react-jsonapi
│ ├── .depcheckrc
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── react-jsonapi.test.ts
│ │ └── react-jsonapi.tsx
│ └── tsconfig.json
├── react-rest
│ ├── .depcheckrc
│ ├── README.md
│ ├── doc
│ │ └── attachments
│ │ │ ├── relationships.png
│ │ │ ├── todo-app-with-relationships.png
│ │ │ ├── todo-app.png
│ │ │ ├── ts.gif
│ │ │ └── ui.gif
│ ├── package.json
│ ├── src
│ │ ├── react-rest.ts
│ │ ├── services
│ │ │ ├── hatchifyReactRest
│ │ │ │ ├── hatchifyReactRest.test.ts
│ │ │ │ ├── hatchifyReactRest.ts
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── useAll
│ │ │ │ ├── index.ts
│ │ │ │ ├── useAll.test.ts
│ │ │ │ └── useAll.ts
│ │ │ ├── useCreateOne
│ │ │ │ ├── index.ts
│ │ │ │ ├── useCreateOne.test.ts
│ │ │ │ └── useCreateOne.ts
│ │ │ ├── useDeleteOne
│ │ │ │ ├── index.ts
│ │ │ │ ├── useDeleteOne.test.ts
│ │ │ │ └── useDeleteOne.ts
│ │ │ ├── useOne
│ │ │ │ ├── index.ts
│ │ │ │ ├── useOne.test.ts
│ │ │ │ └── useOne.ts
│ │ │ └── useUpdateOne
│ │ │ │ ├── index.ts
│ │ │ │ ├── useUpdateOne.test.ts
│ │ │ │ └── useUpdateOne.ts
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── react-ui
│ ├── .depcheckrc
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ ├── HatchifyCollection
│ │ │ │ └── index.ts
│ │ │ ├── HatchifyColumn
│ │ │ │ ├── HatchifyColumn.tsx
│ │ │ │ └── index.ts
│ │ │ ├── HatchifyDataGrid
│ │ │ │ ├── HatchifyDataGrid.tsx
│ │ │ │ └── index.ts
│ │ │ ├── HatchifyEmpty
│ │ │ │ ├── HatchifyEmpty.tsx
│ │ │ │ └── index.ts
│ │ │ ├── HatchifyEverything
│ │ │ │ ├── HatchifyEverything.tsx
│ │ │ │ ├── components
│ │ │ │ │ ├── NoSchemas.tsx
│ │ │ │ │ ├── WithSchemas.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── index.ts
│ │ │ ├── HatchifyFilters
│ │ │ │ ├── HatchifyFilters.tsx
│ │ │ │ └── index.ts
│ │ │ ├── HatchifyList
│ │ │ │ ├── HatchifyList.tsx
│ │ │ │ └── index.ts
│ │ │ ├── HatchifyNavigation
│ │ │ │ ├── HatchifyNavigation.tsx
│ │ │ │ └── index.ts
│ │ │ ├── HatchifyNoSchemas
│ │ │ │ ├── HatchifyNoSchemas.tsx
│ │ │ │ └── index.ts
│ │ │ ├── HatchifyPagination
│ │ │ │ ├── HatchifyPagination.tsx
│ │ │ │ └── index.ts
│ │ │ ├── HatchifyPresentationProvider
│ │ │ │ ├── DefaultDisplayComponents
│ │ │ │ │ ├── DefaultDisplayComponents.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── DefaultFieldComponents
│ │ │ │ │ ├── DefaultFieldComponents.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── HatchifyPresentationProvider.tsx
│ │ │ │ └── index.ts
│ │ │ └── index.ts
│ │ ├── hatchifyReact
│ │ │ ├── hatchifyReact.test.ts
│ │ │ ├── hatchifyReact.tsx
│ │ │ └── index.ts
│ │ ├── hooks
│ │ │ ├── index.ts
│ │ │ ├── useCompoundComponents
│ │ │ │ ├── helpers
│ │ │ │ │ ├── getColumn.test.tsx
│ │ │ │ │ ├── getColumn.tsx
│ │ │ │ │ ├── getColumns.test.ts
│ │ │ │ │ ├── getColumns.ts
│ │ │ │ │ ├── getColumnsFromSchema.test.ts
│ │ │ │ │ ├── getColumnsFromSchema.ts
│ │ │ │ │ ├── getDefaultDataRender.test.ts
│ │ │ │ │ ├── getDefaultDataRender.tsx
│ │ │ │ │ ├── getEmptyList.test.tsx
│ │ │ │ │ ├── getEmptyList.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ └── useCompoundComponents.tsx
│ │ │ ├── useDataGridState.test.ts
│ │ │ ├── useDataGridState.ts
│ │ │ ├── useFilter.test.ts
│ │ │ ├── useFilter.ts
│ │ │ ├── usePage.test.ts
│ │ │ ├── usePage.ts
│ │ │ ├── useSelected.test.ts
│ │ │ ├── useSelected.ts
│ │ │ ├── useSort.test.ts
│ │ │ └── useSort.ts
│ │ ├── presentation
│ │ │ ├── index.ts
│ │ │ └── interfaces.ts
│ │ └── react-ui.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── react
│ ├── .depcheckrc
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── react.test.ts
│ │ └── react.tsx
│ └── tsconfig.json
├── rest-client-jsonapi
│ ├── .depcheckrc
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── mocks
│ │ │ ├── handlers.ts
│ │ │ └── server.ts
│ │ ├── rest-client-jsonapi.ts
│ │ ├── services
│ │ │ ├── createOne
│ │ │ │ ├── createOne.test.ts
│ │ │ │ ├── createOne.ts
│ │ │ │ └── index.ts
│ │ │ ├── deleteOne
│ │ │ │ ├── deleteOne.test.ts
│ │ │ │ ├── deleteOne.ts
│ │ │ │ └── index.ts
│ │ │ ├── findAll
│ │ │ │ ├── findAll.test.ts
│ │ │ │ ├── findAll.ts
│ │ │ │ └── index.ts
│ │ │ ├── findOne
│ │ │ │ ├── findOne.test.ts
│ │ │ │ ├── findOne.ts
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── jsonapi
│ │ │ │ ├── index.ts
│ │ │ │ ├── jsonapi.test.ts
│ │ │ │ └── jsonapi.ts
│ │ │ ├── services.test.ts
│ │ │ ├── updateOne
│ │ │ │ ├── index.ts
│ │ │ │ ├── updateOne.test.ts
│ │ │ │ └── updateOne.ts
│ │ │ └── utils
│ │ │ │ ├── fetch
│ │ │ │ ├── fetch.test.ts
│ │ │ │ ├── fetch.ts
│ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── query
│ │ │ │ ├── index.ts
│ │ │ │ ├── query.test.ts
│ │ │ │ └── query.ts
│ │ │ │ └── resources
│ │ │ │ ├── index.ts
│ │ │ │ ├── resources.test.ts
│ │ │ │ └── resources.ts
│ │ ├── setupTests.ts
│ │ └── vite-env.d.ts
│ └── tsconfig.json
└── rest-client
│ ├── .depcheckrc
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── rest-client.ts
│ └── services
│ │ ├── index.ts
│ │ ├── mocks
│ │ └── testData.ts
│ │ ├── promise
│ │ ├── createOne
│ │ │ ├── createOne.test.ts
│ │ │ ├── createOne.ts
│ │ │ └── index.ts
│ │ ├── deleteOne
│ │ │ ├── deleteOne.test.ts
│ │ │ ├── deleteOne.ts
│ │ │ └── index.ts
│ │ ├── findAll
│ │ │ ├── findAll.test.ts
│ │ │ ├── findAll.ts
│ │ │ └── index.ts
│ │ ├── findOne
│ │ │ ├── findOne.test.ts
│ │ │ ├── findOne.ts
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ └── updateOne
│ │ │ ├── index.ts
│ │ │ ├── updateOne.test.ts
│ │ │ └── updateOne.ts
│ │ ├── store
│ │ ├── index.ts
│ │ ├── store.test.ts
│ │ └── store.ts
│ │ ├── subscribe
│ │ ├── index.ts
│ │ ├── subscribeToAll
│ │ │ ├── index.ts
│ │ │ ├── subscribeToAll.test.ts
│ │ │ └── subscribeToAll.ts
│ │ └── subscribeToOne
│ │ │ ├── index.ts
│ │ │ ├── subscribeToOne.test.ts
│ │ │ └── subscribeToOne.ts
│ │ ├── types
│ │ ├── data.ts
│ │ ├── index.ts
│ │ ├── meta.ts
│ │ ├── query.ts
│ │ ├── rest-client.ts
│ │ ├── schema.ts
│ │ ├── subscribe.ts
│ │ └── utility.ts
│ │ └── utils
│ │ ├── index.ts
│ │ ├── meta.test.ts
│ │ ├── meta.ts
│ │ ├── notify.ts
│ │ ├── records.test.ts
│ │ ├── records.ts
│ │ ├── request.test.ts
│ │ ├── request.ts
│ │ ├── response.test.ts
│ │ ├── response.ts
│ │ ├── schema.test.ts
│ │ ├── schema.ts
│ │ ├── selector.test.ts
│ │ ├── selector.ts
│ │ ├── unflatten.test.ts
│ │ └── unflatten.ts
│ └── tsconfig.json
├── scripts
├── bump-packages.ts
├── check-packages.ts
├── get-changed-packages.ts
├── lib
│ └── packages.ts
├── pree2e.sh
└── updategs.sh
└── tsconfig.json
/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignore-dirs:
2 | - /dist
3 | ignores:
4 | - "@types/*"
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*.ts]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 | indent_size = 2
15 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
3 | example
4 | playwright-report
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ""
5 | labels: fix
6 | assignees: ""
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Installed Packages**
24 | Output of the `npm ls` command in your project's root folder.
25 |
26 | **Desktop (please complete the following information):**
27 |
28 | - OS: [e.g. iOS]
29 | - Browser [e.g. chrome, safari]
30 | - Version [e.g. 22]
31 |
32 | **Screenshots**
33 | If applicable, add screenshots to help explain your problem.
34 |
35 | **Additional context**
36 | Add any other context about the problem here.
37 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/actions/build/action.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | inputs:
4 | package:
5 | description: "The package folder name."
6 | required: true
7 |
8 | runs:
9 | using: "composite"
10 |
11 | steps:
12 | - name: Build
13 | shell: bash
14 | run: npx nx build ${{ inputs.package }}
15 |
--------------------------------------------------------------------------------
/.github/actions/check/action.yml:
--------------------------------------------------------------------------------
1 | name: check
2 |
3 | inputs:
4 | package:
5 | description: "The package folder name."
6 | required: true
7 |
8 | runs:
9 | using: "composite"
10 |
11 | steps:
12 | - name: Typecheck
13 | shell: bash
14 | run: npx nx typecheck ${{ inputs.package }}
15 |
16 | - name: ESLint
17 | shell: bash
18 | run: npx nx lint ${{ inputs.package }}
19 |
20 | - name: depcheck
21 | shell: bash
22 | run: npx nx depcheck ${{ inputs.package }}
23 |
--------------------------------------------------------------------------------
/.github/actions/job-verify/action.yml:
--------------------------------------------------------------------------------
1 | name: job-verify
2 |
3 | inputs:
4 | node-version:
5 | description: "The Node version."
6 | required: true
7 |
8 | runs:
9 | using: "composite"
10 |
11 | steps:
12 | - name: Setup
13 | uses: ./.github/actions/setup
14 | with:
15 | node-version: ${{ inputs.node-version }}
16 |
17 | - name: Check
18 | uses: ./.github/actions/check
19 | with:
20 | package: ${{ github.workflow }}
21 |
22 | - name: Test
23 | uses: ./.github/actions/test
24 | with:
25 | package: ${{ github.workflow }}
26 |
27 | - name: Build
28 | uses: ./.github/actions/build
29 | with:
30 | package: ${{ github.workflow }}
31 |
--------------------------------------------------------------------------------
/.github/actions/setup/action.yml:
--------------------------------------------------------------------------------
1 | name: setup
2 |
3 | inputs:
4 | node-version:
5 | description: "The Node version."
6 | required: true
7 |
8 | runs:
9 | using: "composite"
10 |
11 | steps:
12 | - name: Setup Node.js ${{ inputs.node-version }} environment
13 | uses: actions/setup-node@v4
14 | with:
15 | node-version: ${{ inputs.node-version }}
16 | registry-url: "https://registry.npmjs.org/"
17 |
18 | - name: Install dependencies
19 | shell: bash
20 | run: npm ci
21 |
--------------------------------------------------------------------------------
/.github/actions/test/action.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | inputs:
4 | package:
5 | description: "The package folder name."
6 | required: true
7 |
8 | runs:
9 | using: "composite"
10 |
11 | steps:
12 | - name: Test
13 | shell: bash
14 | run: npx nx test ${{ inputs.package }}
15 | env:
16 | DB_URI: postgres://postgres:password@localhost:5432/postgres
17 |
--------------------------------------------------------------------------------
/.github/workflows/core.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/core"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 20.x]
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Verify
20 | uses: ./.github/actions/job-verify
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/create.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/create"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 20.x]
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Verify
20 | uses: ./.github/actions/job-verify
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/crypto.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/crypto"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 20.x]
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Verify
20 | uses: ./.github/actions/job-verify
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/design-mui.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/design-mui"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 20.x]
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Verify
20 | uses: ./.github/actions/job-verify
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/express.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/express"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | services:
10 | postgres:
11 | image: postgres:alpine
12 | env:
13 | POSTGRES_USER: postgres
14 | POSTGRES_PASSWORD: password
15 | options: >-
16 | --health-cmd pg_isready
17 | --health-interval 10s
18 | --health-timeout 5s
19 | --health-retries 5
20 | ports:
21 | - 5432:5432
22 |
23 | strategy:
24 | matrix:
25 | node-version: [18.x, 20.x]
26 |
27 | steps:
28 | - name: Checkout
29 | uses: actions/checkout@v4
30 | with:
31 | fetch-depth: 0
32 |
33 | - name: Verify
34 | uses: ./.github/actions/job-verify
35 | with:
36 | node-version: ${{ matrix.node-version }}
37 |
--------------------------------------------------------------------------------
/.github/workflows/koa.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/koa"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | services:
10 | postgres:
11 | image: postgres:alpine
12 | env:
13 | POSTGRES_USER: postgres
14 | POSTGRES_PASSWORD: password
15 | options: >-
16 | --health-cmd pg_isready
17 | --health-interval 10s
18 | --health-timeout 5s
19 | --health-retries 5
20 | ports:
21 | - 5432:5432
22 |
23 | strategy:
24 | matrix:
25 | node-version: [18.x, 20.x]
26 |
27 | steps:
28 | - name: Checkout
29 | uses: actions/checkout@v4
30 | with:
31 | fetch-depth: 0
32 |
33 | # - name: Postgres
34 | # uses: ./.github/actions/postgres
35 |
36 | - name: Verify
37 | uses: ./.github/actions/job-verify
38 | with:
39 | node-version: ${{ matrix.node-version }}
40 |
--------------------------------------------------------------------------------
/.github/workflows/node.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/node"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 20.x]
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Verify
20 | uses: ./.github/actions/job-verify
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/react-rest.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/react-rest"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 20.x]
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Verify
20 | uses: ./.github/actions/job-verify
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/react-ui.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/react-ui"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 20.x]
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Verify
20 | uses: ./.github/actions/job-verify
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/react.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/react"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 20.x]
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Verify
20 | uses: ./.github/actions/job-verify
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/release"
2 | on:
3 | push:
4 | tags:
5 | - "*"
6 |
7 | jobs:
8 | release:
9 | if: startsWith(github.ref, 'refs/tags/')
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Build Changelog
13 | id: build_changelog
14 | uses: mikepenz/release-changelog-builder-action@v3
15 | env:
16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 |
18 | - name: Create Release
19 | uses: mikepenz/action-gh-release@v0.2.0-a03 #softprops/action-gh-release
20 | with:
21 | body: ${{steps.build_changelog.outputs.changelog}}
22 |
--------------------------------------------------------------------------------
/.github/workflows/rest-client-jsonapi.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/rest-client-jsonapi"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | services:
10 | postgres:
11 | image: postgres:alpine
12 | env:
13 | POSTGRES_USER: postgres
14 | POSTGRES_PASSWORD: password
15 | options: >-
16 | --health-cmd pg_isready
17 | --health-interval 10s
18 | --health-timeout 5s
19 | --health-retries 5
20 | ports:
21 | - 5432:5432
22 |
23 | strategy:
24 | matrix:
25 | node-version: [18.x, 20.x]
26 |
27 | steps:
28 | - name: Checkout
29 | uses: actions/checkout@v4
30 | with:
31 | fetch-depth: 0
32 |
33 | - name: Verify
34 | uses: ./.github/actions/job-verify
35 | with:
36 | node-version: ${{ matrix.node-version }}
37 |
--------------------------------------------------------------------------------
/.github/workflows/rest-client.yml:
--------------------------------------------------------------------------------
1 | name: "@hatchifyjs/rest-client"
2 |
3 | on: push
4 |
5 | jobs:
6 | verify:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 20.x]
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Verify
20 | uses: ./.github/actions/job-verify
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # artifacts
2 | coverage
3 | node_modules
4 | /**/node_modules
5 | /packages/*/dist
6 |
7 | # misc
8 | .DS_Store
9 | Thumbs.db
10 | *.tsbuildinfo
11 | npm-debug.log*
12 | .obsidian
13 | .nx
14 | yalc.lock
15 | .yalc
16 |
17 | /e2e/hatchify-app
18 | /e2e/test-results/
19 | /e2e/playwright-report/
20 | /playwright/.cache/
21 | /packages/create/hatchify-app
22 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/hatchify.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "esbenp.prettier-vscode",
4 | "dbaeumer.vscode-eslint",
5 | "streetsidesoftware.code-spell-checker",
6 | "davidanson.vscode-markdownlint",
7 | "firsttris.vscode-jest-runner",
8 | "ms-playwright.playwright"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "ts-node",
9 | "type": "node",
10 | "request": "launch",
11 | "args": ["${relativeFile}"],
12 | "runtimeArgs": ["-r", "ts-node/register"],
13 | "cwd": "${workspaceRoot}",
14 | "protocol": "inspector",
15 | "internalConsoleOptions": "openOnSessionStart"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": ["Hatchify", "hatchifyjs"],
3 | "editor.defaultFormatter": "esbenp.prettier-vscode",
4 | "editor.codeActionsOnSave": {
5 | "source.fixAll.eslint": "explicit"
6 | },
7 | "editor.formatOnSave": true,
8 | "jestrunner.jestCommand": "node --experimental-vm-modules ../../node_modules/.bin/jest",
9 | "search.exclude": {
10 | "**/coverage": true,
11 | "**/dist": true,
12 | "**/node_modules": true
13 | },
14 | "typescript.tsdk": "node_modules/typescript/lib"
15 | }
16 |
--------------------------------------------------------------------------------
/docs/attachments/FiltersClosed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/FiltersClosed.png
--------------------------------------------------------------------------------
/docs/attachments/FiltersOpen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/FiltersOpen.png
--------------------------------------------------------------------------------
/docs/attachments/List.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/List.png
--------------------------------------------------------------------------------
/docs/attachments/ListWithPagination.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/ListWithPagination.png
--------------------------------------------------------------------------------
/docs/attachments/add-postgres.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/add-postgres.png
--------------------------------------------------------------------------------
/docs/attachments/create-database.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/create-database.png
--------------------------------------------------------------------------------
/docs/attachments/customNoRecords.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/customNoRecords.png
--------------------------------------------------------------------------------
/docs/attachments/data-grid-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/data-grid-example.png
--------------------------------------------------------------------------------
/docs/attachments/dateonly-column.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/dateonly-column.png
--------------------------------------------------------------------------------
/docs/attachments/datetime-column.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/datetime-column.png
--------------------------------------------------------------------------------
/docs/attachments/defaultNoRecords.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/defaultNoRecords.png
--------------------------------------------------------------------------------
/docs/attachments/enum-grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/enum-grid.png
--------------------------------------------------------------------------------
/docs/attachments/frontend-todo-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/frontend-todo-list.png
--------------------------------------------------------------------------------
/docs/attachments/integer-column.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/integer-column.png
--------------------------------------------------------------------------------
/docs/attachments/number-column.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/number-column.png
--------------------------------------------------------------------------------
/docs/attachments/reactTs.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/reactTs.gif
--------------------------------------------------------------------------------
/docs/attachments/stringNullFilter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/stringNullFilter.png
--------------------------------------------------------------------------------
/docs/attachments/text-grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/attachments/text-grid.png
--------------------------------------------------------------------------------
/docs/jsonapi/deleting.md:
--------------------------------------------------------------------------------
1 | # Deleting Resources
2 |
3 | A resource can be deleted by sending a DELETE request to the URL that represents the resource:
4 |
5 | ```bash
6 | DELETE /api/photos/8ff0beed-2585-4391-8735-cc560eaf287e
7 | Accept: application/vnd.api+json
8 | ```
9 |
10 | ## Responses
11 |
12 | `200 OK`: A server MAY return a 200 OK response with a document that contains no primary data if a deletion request is successful. Other top-level members, such as meta, could be included in the response document.
13 |
--------------------------------------------------------------------------------
/docs/react/hatchedReact.Navigation.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/docs/react/hatchedReact.Navigation.md
--------------------------------------------------------------------------------
/e2e/main.txt:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.js";
4 | import "./index.css";
5 |
6 | ReactDOM.createRoot(document.getElementById("root")!).render(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/e2e/schemas.txt:
--------------------------------------------------------------------------------
1 | import {
2 | PartialSchema,
3 | belongsTo,
4 | dateonly,
5 | datetime,
6 | enumerate,
7 | hasMany,
8 | integer,
9 | string,
10 | text,
11 | } from "@hatchifyjs/core"
12 |
13 | export const Todo = {
14 | name: "Todo",
15 | attributes: {
16 | name: string(),
17 | dueDate: datetime(),
18 | importance: integer(),
19 | status: enumerate({ values: ["Pending", "Failed", "Completed"] }),
20 | completionDate: dateonly(),
21 | notes: text(),
22 | },
23 | relationships: {
24 | user: belongsTo("User"),
25 | },
26 | } satisfies PartialSchema
27 |
28 | export const User = {
29 | name: "User",
30 | attributes: {
31 | name: string(),
32 | },
33 | relationships: {
34 | todos: hasMany(),
35 | },
36 | } satisfies PartialSchema
37 |
--------------------------------------------------------------------------------
/e2e/tests/react-rest-jsonapi.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test"
2 |
3 | test("works", async ({ page }) => {
4 | const messages: string[] = []
5 |
6 | page.on(
7 | "console",
8 | (message) => message.type() === "assert" && messages.push(message.text()),
9 | )
10 |
11 | await page.goto("http://localhost:5175", { waitUntil: "networkidle" })
12 |
13 | await expect(
14 | page.getByRole("tab", { name: "Article", exact: true }),
15 | ).toBeVisible()
16 | await expect(
17 | page.getByRole("tab", { name: "Feature Article", exact: true }),
18 | ).toBeVisible()
19 | await expect(
20 | page.getByRole("tab", { name: "Admin User", exact: true }),
21 | ).toBeVisible()
22 |
23 | expect(messages).toEqual([])
24 | })
25 |
--------------------------------------------------------------------------------
/e2e/tests/react-rest.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test"
2 |
3 | test("works", async ({ page }) => {
4 | await page.goto("http://localhost:5174")
5 |
6 | // * rows are visible
7 | await expect(page.getByText("Workout")).toBeVisible()
8 | await expect(page.getByText("Shopping")).toBeVisible()
9 | await expect(page.getByText("Cooking")).toBeVisible()
10 |
11 | // * submit a new todo
12 | await page.locator('input[type="text"]').fill("Homework")
13 | await page.locator('button[type="button"]').first().click()
14 |
15 | // * new todo is visible
16 | await expect(page.getByText("Homework")).toBeVisible()
17 |
18 | // * delete a todo
19 | await page.locator('button[type="button"]').nth(1).click()
20 | await expect(page.getByText("Workout")).not.toBeVisible()
21 | })
22 |
--------------------------------------------------------------------------------
/example/getting-started/.env:
--------------------------------------------------------------------------------
1 | DB_URI=sqlite://localhost/:memory
--------------------------------------------------------------------------------
/example/getting-started/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/example/getting-started/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/example/getting-started/frontend/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/example/getting-started/frontend/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import App from "./App.js"
4 | import "./index.css"
5 |
6 | ReactDOM.createRoot(document.getElementById("root")!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/example/getting-started/frontend/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/example/getting-started/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/getting-started/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext": "ts",
3 | "execMap": {
4 | "ts": "node --loader ts-node/esm"
5 | }
6 | }
--------------------------------------------------------------------------------
/example/getting-started/schemas.ts:
--------------------------------------------------------------------------------
1 | import {
2 | belongsTo,
3 | boolean,
4 | dateonly,
5 | integer,
6 | hasMany,
7 | string,
8 | } from "@hatchifyjs/core"
9 | import type { PartialSchema } from "@hatchifyjs/core"
10 |
11 | export const Todo = {
12 | name: "Todo",
13 | attributes: {
14 | name: string({ required: true }),
15 | dueDate: dateonly(),
16 | importance: integer(),
17 | complete: boolean({ default: false }),
18 | },
19 | relationships: {
20 | user: belongsTo("User"),
21 | },
22 | } satisfies PartialSchema
23 |
24 | export const User = {
25 | name: "User",
26 | attributes: {
27 | name: string({ required: true }),
28 | },
29 | relationships: {
30 | todos: hasMany("Todo"),
31 | },
32 | } satisfies PartialSchema
33 |
--------------------------------------------------------------------------------
/example/getting-started/tsconfig.backend.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {},
4 | "include": [
5 | "backend",
6 | "schemas.ts"
7 | ]
8 | }
--------------------------------------------------------------------------------
/example/getting-started/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | optimizeDeps: {
7 | esbuildOptions: {
8 | target: "esnext",
9 | },
10 | },
11 | plugins: [react()],
12 | })
13 |
--------------------------------------------------------------------------------
/example/grid-demo/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/example/grid-demo/Dockerfile:
--------------------------------------------------------------------------------
1 | ## this is a multi-stage build
2 | ## use --target backend/frontend to specify which stage to build
3 | ## otherwise only frontend builds
4 | ## using alpine and then installing node reduces size from 375MB to 269MB
5 | FROM alpine AS base
6 | WORKDIR /app
7 | RUN apk update && apk add npm nodejs~=20 && apk cache clean
8 | COPY package*.json ./
9 | RUN npm ci
10 | COPY schemas.ts schemas.ts
11 |
12 | FROM base AS backend
13 | COPY backend/ backend/
14 | COPY tsconfig*.json ./
15 | COPY frontend/ frontend/
16 | COPY public/ public/
17 | COPY index.html tsconfig.json vite.config.ts ./
18 | EXPOSE $APP_PORT
19 | CMD npm run "dev:$BACKEND:$DATABASE"
20 |
--------------------------------------------------------------------------------
/example/grid-demo/README.md:
--------------------------------------------------------------------------------
1 | # Hatchify Grid Demo
2 |
3 | - - React Koa SQLite
4 | - - React Koa Postgres
5 | - - React Express SQLite
6 | - - React Express Postgres
7 |
8 | The demo is redeployed on every push to `main`. To deploy a version with the latest changes:
9 |
10 | 1. Create a new branch
11 | 2. Update the dependencies in `package.json`
12 |
13 | ```bash
14 | npm install @hatchifyjs/core@latest @hatchifyjs/express@latest @hatchifyjs/koa@latest @hatchifyjs/react@latest
15 | ```
16 |
17 | 3. The demo will be deployed once the branch is merged into `main`
18 |
--------------------------------------------------------------------------------
/example/grid-demo/frontend/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/example/grid-demo/frontend/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import App from "./App.js"
4 |
5 | ReactDOM.createRoot(document.getElementById("root")!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/example/grid-demo/frontend/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/example/grid-demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/grid-demo/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext": "ts",
3 | "execMap": {
4 | "ts": "node --loader ts-node/esm"
5 | }
6 | }
--------------------------------------------------------------------------------
/example/grid-demo/tsconfig.backend.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {},
4 | "include": [
5 | "backend",
6 | "schemas.ts"
7 | ]
8 | }
--------------------------------------------------------------------------------
/example/grid-demo/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from "@vitejs/plugin-react"
2 | import { defineConfig } from "vite"
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | optimizeDeps: {
7 | esbuildOptions: {
8 | target: "esnext",
9 | },
10 | },
11 | plugins: [react()],
12 | })
13 |
--------------------------------------------------------------------------------
/example/namespaces/.env:
--------------------------------------------------------------------------------
1 | DB_URI=sqlite://localhost/:memory
--------------------------------------------------------------------------------
/example/namespaces/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/example/namespaces/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/example/namespaces/frontend/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/example/namespaces/frontend/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import App from "./App.js"
4 | import "./index.css"
5 |
6 | ReactDOM.createRoot(document.getElementById("root")!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/example/namespaces/frontend/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/example/namespaces/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/namespaces/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext": "ts",
3 | "execMap": {
4 | "ts": "node --loader ts-node/esm"
5 | }
6 | }
--------------------------------------------------------------------------------
/example/namespaces/schemas.ts:
--------------------------------------------------------------------------------
1 | import { belongsTo, hasMany, string } from "@hatchifyjs/core"
2 | import type { PartialSchema } from "@hatchifyjs/core"
3 |
4 | export const Article = {
5 | name: "Article",
6 | ui: { displayAttribute: "author" },
7 | attributes: {
8 | author: string({ required: true }),
9 | tag: string({ required: true }),
10 | },
11 | } satisfies PartialSchema
12 |
13 | export const Feature_Article = {
14 | name: "Article",
15 | namespace: "Feature",
16 | ui: { displayAttribute: "author" },
17 | attributes: {
18 | author: string({ required: true }),
19 | tag: string({ required: true }),
20 | },
21 | relationships: {
22 | adminUser: belongsTo("Admin_User"),
23 | },
24 | } satisfies PartialSchema
25 |
26 | export const Admin_User = {
27 | name: "User",
28 | namespace: "Admin",
29 | attributes: {
30 | name: string(),
31 | },
32 | relationships: {
33 | articles: hasMany("Feature_Article"),
34 | },
35 | } satisfies PartialSchema
36 |
--------------------------------------------------------------------------------
/example/namespaces/tsconfig.backend.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {},
4 | "include": [
5 | "backend",
6 | "schemas.ts"
7 | ]
8 | }
--------------------------------------------------------------------------------
/example/namespaces/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | optimizeDeps: {
7 | esbuildOptions: {
8 | target: "esnext",
9 | },
10 | },
11 | plugins: [react()],
12 | })
13 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "env": {
4 | "browser": true,
5 | "es2020": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:@typescript-eslint/recommended",
10 | "plugin:react-hooks/recommended"
11 | ],
12 | "parser": "@typescript-eslint/parser",
13 | "parserOptions": {
14 | "ecmaVersion": "latest",
15 | "sourceType": "module"
16 | },
17 | "plugins": ["react-refresh"],
18 | "rules": {
19 | "react-refresh/only-export-components": "warn"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/backend/index.ts:
--------------------------------------------------------------------------------
1 | import { dirname } from "path"
2 | import { fileURLToPath } from "url"
3 | import Koa from "koa"
4 | import c2k from "koa-connect"
5 | import { createServer as createViteServer } from "vite"
6 | import { hatchifyKoa } from "@hatchifyjs/koa"
7 | import * as schemas from "../schemas.js"
8 |
9 | const currentDir = dirname(fileURLToPath(import.meta.url))
10 |
11 | const app = new Koa()
12 | const hatchedKoa = hatchifyKoa(schemas, { prefix: "/api" })
13 |
14 | ;(async () => {
15 | await hatchedKoa.modelSync({ alter: true })
16 |
17 | const vite = await createViteServer({
18 | root: `${currentDir}/../`,
19 | server: { middlewareMode: true },
20 | })
21 |
22 | app.use(hatchedKoa.middleware.allModels.all)
23 | app.use(c2k(vite.middlewares))
24 |
25 | app.listen(3000, () => {
26 | console.log("Started on http://localhost:3000")
27 | })
28 | })()
29 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/frontend/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/frontend/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import App from "./App.js"
4 |
5 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/frontend/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext": "ts",
3 | "execMap": {
4 | "ts": "node --loader ts-node/esm"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/schemas.ts:
--------------------------------------------------------------------------------
1 | import { belongsTo, hasMany, string } from "@hatchifyjs/core"
2 | import type { PartialSchema } from "@hatchifyjs/core"
3 |
4 | export const Todo = {
5 | name: "Todo",
6 | attributes: {
7 | name: string({ required: false }),
8 | },
9 | relationships: {
10 | user: belongsTo("User"),
11 | },
12 | } satisfies PartialSchema
13 |
14 | export const User = {
15 | name: "User",
16 | attributes: {
17 | name: string(),
18 | },
19 | relationships: {
20 | todos: hasMany("Todo"),
21 | },
22 | } satisfies PartialSchema
23 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/tsconfig.backend.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {},
4 | "include": ["backend", "schemas.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "experimentalResolver": true,
3 | "compilerOptions": {
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "target": "ESNext",
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "jsx": "react-jsx",
9 |
10 | // "composite": true,
11 | // "declaration": true,
12 | "incremental": true,
13 | "strict": true,
14 |
15 | "allowJs": false,
16 | "allowSyntheticDefaultImports": true,
17 | "downlevelIteration": true,
18 | "esModuleInterop": true,
19 | "forceConsistentCasingInFileNames": true,
20 | "isolatedModules": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "noImplicitReturns": true,
23 | "noUnusedLocals": true,
24 | "resolveJsonModule": true,
25 | "skipLibCheck": true,
26 | "useDefineForClassFields": true
27 | },
28 | "include": ["frontend", "backend", "schemas.ts", "vite.config.ts"],
29 | "exclude": ["node_modules"]
30 | }
31 |
--------------------------------------------------------------------------------
/example/react-rest-hatchify-backend/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite"
2 | import react from "@vitejs/plugin-react"
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()]
7 | })
8 |
--------------------------------------------------------------------------------
/example/react-rest/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: { browser: true, es2020: true },
3 | extends: [
4 | 'eslint:recommended',
5 | 'plugin:@typescript-eslint/recommended',
6 | 'plugin:react-hooks/recommended',
7 | ],
8 | parser: '@typescript-eslint/parser',
9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
10 | plugins: ['react-refresh'],
11 | rules: {
12 | 'react-refresh/only-export-components': 'warn',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/example/react-rest/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/example/react-rest/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/react-rest/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/example/react-rest/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import App from "./App.js"
4 |
5 | if (process.env.NODE_ENV === "development") {
6 | await import("./mocks/browser").then(({ worker }) => {
7 | worker.start()
8 | })
9 | }
10 |
11 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
12 |
13 |
14 | ,
15 | )
16 |
--------------------------------------------------------------------------------
/example/react-rest/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/example/react-rest/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/example/react-rest/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/example/react-rest/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite"
2 | import react from "@vitejs/plugin-react"
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | server: {
8 | port: 5174,
9 | },
10 | })
11 |
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "nx/presets/npm.json",
3 | "$schema": "./node_modules/nx/schemas/nx-schema.json",
4 | "tasksRunnerOptions": {
5 | "default": {
6 | "runner": "nx/tasks-runners/default",
7 | "options": {
8 | "cacheableOperations": [
9 | "typecheck",
10 | "lint",
11 | "depcheck",
12 | "test",
13 | "clean",
14 | "build"
15 | ]
16 | }
17 | }
18 | },
19 | "targetDefaults": {
20 | "typecheck": {
21 | "dependsOn": ["^build"]
22 | },
23 | "test": {
24 | "dependsOn": ["^build"]
25 | },
26 | "build": {
27 | "dependsOn": ["^build", "clean"]
28 | },
29 | "dev": {
30 | "dependsOn": ["^build"]
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/core/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignore-dirs:
2 | - /dist
3 | ignores:
4 | - "@types/*"
5 | - ts-jest
6 |
--------------------------------------------------------------------------------
/packages/core/.prettierignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
3 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | `@hatchifyjs/core` provides core libraries and types for defining Schemas in Hatchify.
2 |
3 | For more information, see [`@hatchifyjs/core`'s documentation page](https://github.com/bitovi/hatchify/blob/main/docs/core/README.md)
--------------------------------------------------------------------------------
/packages/core/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectCoverageFrom": [
3 | "/src/**/*.ts",
4 | "!/src/**/types.ts",
5 | "!/src/dataTypes/index.ts",
6 | "!/src/core.ts",
7 | "!/src/relationships/index.ts",
8 | "!/src/types/**/*.ts"
9 | ],
10 | "coverageDirectory": "../../coverage/core",
11 | "coverageReporters": ["html", "text", "text-summary"],
12 | "coverageThreshold": {
13 | "global": {
14 | "branches": 100,
15 | "functions": 100,
16 | "lines": 100,
17 | "statements": 100
18 | }
19 | },
20 | "extensionsToTreatAsEsm": [".ts"],
21 | "moduleNameMapper": {
22 | "^(\\.{1,2}/.*)\\.js$": "$1"
23 | },
24 | "roots": ["/src"],
25 | "testEnvironment": "node",
26 | "transform": {
27 | ".+\\.ts$": [
28 | "ts-jest",
29 | {
30 | "useESM": true
31 | }
32 | ]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/core/src/assembler/ensureDefaultPrimaryAttribute.ts:
--------------------------------------------------------------------------------
1 | import { getDefaultPrimaryAttribute } from "./getDefaultPrimaryAttribute.js"
2 | import type {
3 | PartialAttributeOptions,
4 | PartialSchema,
5 | PartialSchemaWithPrimaryAttribute,
6 | } from "./types.js"
7 |
8 | export function ensureDefaultPrimaryAttribute(
9 | schema: PartialSchema,
10 | ): PartialSchemaWithPrimaryAttribute {
11 | return {
12 | ...schema,
13 | id: schema.id
14 | ? ({
15 | ...schema.id,
16 | control: {
17 | ...schema.id.control,
18 | primary: true,
19 | allowNull: false,
20 | },
21 | orm: {
22 | ...schema.id.orm,
23 | sequelize: {
24 | ...schema.id.orm.sequelize,
25 | primaryKey: true,
26 | allowNull: false,
27 | },
28 | },
29 | } as PartialAttributeOptions)
30 | : getDefaultPrimaryAttribute(),
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/core/src/assembler/ensureDefaultReadOnlyAttribute.spec.ts:
--------------------------------------------------------------------------------
1 | import { ensureDefaultReadOnlyAttribute } from "./ensureDefaultReadOnlyAttribute.js"
2 | import { string } from "../dataTypes/index.js"
3 |
4 | describe("ensureDefaultReadOnlyAttribute", () => {
5 | it("handles existing read-only schema", () => {
6 | expect(
7 | JSON.stringify(
8 | ensureDefaultReadOnlyAttribute({
9 | name: "Todo",
10 | attributes: {
11 | name: string({ readOnly: true }),
12 | description: string(),
13 | },
14 | readOnly: true,
15 | }),
16 | ),
17 | ).toEqual(
18 | JSON.stringify({
19 | name: "Todo",
20 | attributes: {
21 | name: string({ readOnly: true }),
22 | description: {
23 | ...string({ readOnly: true }),
24 | name: "string()",
25 | },
26 | },
27 | readOnly: true,
28 | }),
29 | )
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/packages/core/src/assembler/ensureDefaultReadOnlyAttribute.ts:
--------------------------------------------------------------------------------
1 | import type { PartialAttributeRecord, PartialSchema } from "./types.js"
2 |
3 | export function ensureDefaultReadOnlyAttribute(
4 | schema: PartialSchema,
5 | ): PartialSchema {
6 | return {
7 | ...schema,
8 | attributes: Object.entries(schema.attributes).reduce(
9 | (acc, [key, value]) => ({
10 | ...acc,
11 | [key]: {
12 | ...value,
13 | control: {
14 | ...value.control,
15 | readOnly: value.control.readOnly || schema.readOnly,
16 | },
17 | },
18 | }),
19 | {} as PartialAttributeRecord,
20 | ),
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/assembler/finalizeSchema.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PartialSchemaWithPrimaryAttribute,
3 | SemiFinalSchema,
4 | } from "./types.js"
5 |
6 | export function finalizeSchema(
7 | schema: PartialSchemaWithPrimaryAttribute,
8 | ): SemiFinalSchema {
9 | return {
10 | ...schema,
11 | id: schema.id.finalize(),
12 | ui: schema.ui ?? {},
13 | attributes: Object.entries(schema.attributes).reduce(
14 | (acc, [attributeName, attribute]) => ({
15 | ...acc,
16 | [attributeName]: attribute.finalize(),
17 | }),
18 | {},
19 | ),
20 | readOnly: !!schema.readOnly,
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/assembler/getDefaultPrimaryAttribute.ts:
--------------------------------------------------------------------------------
1 | import { uuid } from "../dataTypes/index.js"
2 | import type { PartialAttributeOptions } from "../types/index.js"
3 | import { uuidv4 } from "../util/uuidv4.js"
4 |
5 | export function getDefaultPrimaryAttribute(): PartialAttributeOptions {
6 | return uuid({
7 | primary: true,
8 | required: true,
9 | default: uuidv4,
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/src/core.ts:
--------------------------------------------------------------------------------
1 | export * from "./types/index.js"
2 | export { assembler } from "./assembler/index.js"
3 | export * from "./dataTypes/index.js"
4 | export * from "./relationships/index.js"
5 | export * from "./util/camelCaseToPascalCase.js"
6 | export * from "./util/camelCaseToTitleCase.js"
7 | export * from "./util/getEndpoint.js"
8 | export * from "./util/getSchemaKey.js"
9 | export * from "./util/mergeSchemaUI.js"
10 | export * from "./util/pascalCaseToCamelCase.js"
11 | export * from "./util/pascalCaseToKebabCase.js"
12 | export * from "./util/pluralize.js"
13 | export * from "./util/singularize.js"
14 | export * from "./util/uuidv4.js"
15 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/boolean/coerce.ts:
--------------------------------------------------------------------------------
1 | import type { PartialBooleanControlType } from "./types.js"
2 | import type { ValueInRequest } from "../../types/index.js"
3 | import { HatchifyCoerceError } from "../../types/index.js"
4 |
5 | export function coerce(
6 | value: ValueInRequest,
7 | control: Omit, "allowNullInfer">,
8 | ): boolean | null {
9 | if (value === undefined) {
10 | throw new HatchifyCoerceError("as a non-undefined value")
11 | }
12 |
13 | if (value === null) {
14 | if (control.allowNull) {
15 | return value
16 | }
17 | throw new HatchifyCoerceError("as a non-null value")
18 | }
19 |
20 | if (typeof value !== "boolean") {
21 | throw new HatchifyCoerceError("as a boolean")
22 | }
23 |
24 | return value
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/boolean/finalizeControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialBooleanControlType } from "./types.js"
2 |
3 | export function finalizeControl(
4 | props: Omit, "allowNullInfer">,
5 | ): Required, "allowNullInfer">> {
6 | // @ts-expect-error @todo HATCH-417
7 | delete props.allowNullInfer
8 | return {
9 | ...props,
10 | allowNull: props.allowNull !== false,
11 | primary: !!props.primary,
12 | default: props.default ?? null,
13 | readOnly: props.readOnly ?? false,
14 | ui: {
15 | displayName: props?.ui?.displayName ?? null,
16 | hidden: props?.ui?.hidden ?? false,
17 | },
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/boolean/finalizeOrm.ts:
--------------------------------------------------------------------------------
1 | import type { FinalBooleanORM, PartialBooleanORM } from "./types.js"
2 |
3 | export function finalizeOrm({ sequelize }: PartialBooleanORM): FinalBooleanORM {
4 | return {
5 | sequelize: {
6 | ...sequelize,
7 | allowNull: sequelize.allowNull !== false,
8 | primaryKey: !!sequelize.primaryKey,
9 | defaultValue: sequelize.defaultValue ?? null,
10 | unique: !!sequelize.unique,
11 | },
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/boolean/getFinalize.spec.ts:
--------------------------------------------------------------------------------
1 | import { boolean } from "./index.js"
2 |
3 | describe("getFinalize", () => {
4 | it("finalizes a partial attribute", () => {
5 | expect(boolean()).toEqual({
6 | name: "boolean()",
7 | control: {
8 | allowNull: undefined,
9 | type: "Boolean",
10 | ui: {},
11 | },
12 | orm: {
13 | sequelize: {
14 | allowNull: undefined,
15 | type: "BOOLEAN",
16 | },
17 | },
18 | finalize: expect.any(Function),
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/boolean/getPartialControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialBooleanControlType, PartialBooleanProps } from "./types.js"
2 |
3 | export function getPartialControl(
4 | props?: PartialBooleanProps,
5 | ): PartialBooleanControlType {
6 | return {
7 | type: "Boolean",
8 | allowNull: props?.required == null ? props?.required : !props.required,
9 | allowNullInfer: (props?.required == null
10 | ? props?.required
11 | : !props.required) as TRequired extends true ? false : true,
12 | primary: props?.primary,
13 | default: props?.default,
14 | readOnly: props?.readOnly,
15 | ui: {
16 | displayName: props?.ui?.displayName,
17 | hidden: props?.ui?.hidden,
18 | },
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/boolean/getPartialOrm.ts:
--------------------------------------------------------------------------------
1 | import type { PartialBooleanORM, PartialBooleanProps } from "./types.js"
2 |
3 | export function getPartialOrm(
4 | props?: PartialBooleanProps,
5 | ): PartialBooleanORM {
6 | return {
7 | sequelize: {
8 | type: "BOOLEAN",
9 | allowNull: props?.required == null ? props?.required : !props.required,
10 | primaryKey: props?.primary,
11 | defaultValue: props?.default,
12 | unique: props?.unique,
13 | },
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/boolean/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PartialControlType,
3 | PartialDataTypeProps,
4 | PartialSequelizeDataType,
5 | } from "../../types/index.js"
6 |
7 | export type PartialBooleanProps =
8 | PartialDataTypeProps
9 |
10 | export interface PartialBooleanControlType
11 | extends PartialControlType {
12 | type: "Boolean"
13 | }
14 |
15 | export interface PartialBooleanORM {
16 | sequelize: Omit, "typeArgs">
17 | }
18 |
19 | export interface FinalBooleanORM {
20 | sequelize: Required<
21 | Omit, "typeArgs">
22 | >
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/dateonly/finalizeControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialDateonlyControlType } from "./types.js"
2 |
3 | export function finalizeControl(
4 | props: Omit, "allowNullInfer">,
5 | ): Required, "allowNullInfer">> {
6 | // @ts-expect-error @todo HATCH-417
7 | delete props.allowNullInfer
8 | return {
9 | ...props,
10 | allowNull: props.allowNull !== false && !props.primary,
11 | min: props.min ?? -Infinity,
12 | max: props.max ?? Infinity,
13 | primary: !!props.primary,
14 | default: props.default ?? null,
15 | step: props.step || 0,
16 | readOnly: props.readOnly ?? false,
17 | ui: {
18 | displayName: props?.ui?.displayName ?? null,
19 | hidden: props?.ui?.hidden ?? false,
20 | },
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/dateonly/finalizeOrm.ts:
--------------------------------------------------------------------------------
1 | import type { FinalDateonlyORM, PartialDateonlyORM } from "./types.js"
2 |
3 | export function finalizeOrm({
4 | sequelize,
5 | }: PartialDateonlyORM): FinalDateonlyORM {
6 | return {
7 | sequelize: {
8 | ...sequelize,
9 | allowNull: sequelize.allowNull !== false && !sequelize.primaryKey,
10 | primaryKey: !!sequelize.primaryKey,
11 | defaultValue: sequelize.defaultValue ?? null,
12 | unique: !!sequelize.unique || !!sequelize.primaryKey,
13 | },
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/dateonly/getFinalize.spec.ts:
--------------------------------------------------------------------------------
1 | import { dateonly } from "./index.js"
2 |
3 | describe("getFinalize", () => {
4 | it("finalizes a partial attribute", () => {
5 | expect(dateonly()).toEqual({
6 | name: "dateonly()",
7 | control: {
8 | allowNull: undefined,
9 | max: undefined,
10 | min: undefined,
11 | type: "Dateonly",
12 | primary: undefined,
13 | ui: {},
14 | },
15 | orm: {
16 | sequelize: {
17 | allowNull: undefined,
18 | type: "DATEONLY",
19 | typeArgs: [],
20 | primaryKey: undefined,
21 | },
22 | },
23 | finalize: expect.any(Function),
24 | })
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/dateonly/getPartialControl.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PartialDateonlyControlType,
3 | PartialDateonlyProps,
4 | } from "./types.js"
5 |
6 | export function getPartialControl(
7 | props?: PartialDateonlyProps,
8 | ): PartialDateonlyControlType {
9 | return {
10 | type: "Dateonly",
11 | allowNull: props?.required == null ? props?.required : !props.required,
12 | allowNullInfer: (props?.required == null
13 | ? props?.required
14 | : !props.required) as TRequired extends true ? false : true,
15 | min: props?.min,
16 | max: props?.max,
17 | primary: props?.primary,
18 | step: props?.step,
19 | default: props?.default,
20 | readOnly: props?.readOnly,
21 | ui: {
22 | displayName: props?.ui?.displayName,
23 | hidden: props?.ui?.hidden,
24 | },
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/dateonly/getPartialOrm.ts:
--------------------------------------------------------------------------------
1 | import type { PartialDateonlyORM, PartialDateonlyProps } from "./types.js"
2 |
3 | export function getPartialOrm(
4 | props?: PartialDateonlyProps,
5 | ): PartialDateonlyORM {
6 | return {
7 | sequelize: {
8 | type: "DATEONLY",
9 | typeArgs: [],
10 | allowNull: props?.required == null ? props?.required : !props.required,
11 | primaryKey: props?.primary,
12 | defaultValue: props?.default,
13 | unique: props?.unique,
14 | },
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/dateonly/isISO8601DateString.spec.ts:
--------------------------------------------------------------------------------
1 | import { isISO8601DateString } from "./isISO8601DateString.js"
2 |
3 | describe("isISO8601DateString", () => {
4 | it("validates date strings correctly", () => {
5 | expect(isISO8601DateString("2011-10-05")).toBe(true)
6 | expect(isISO8601DateString("")).toBe(false)
7 | expect(isISO8601DateString("2011-10-05T14:48:00.000Z")).toBe(false)
8 | expect(isISO8601DateString("2018-11-10T11:22:33+00:00")).toBe(false)
9 | expect(isISO8601DateString("2011-10-05T14:99:00.000Z")).toBe(false)
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/dateonly/isISO8601DateString.ts:
--------------------------------------------------------------------------------
1 | export function isISO8601DateString(string: string): boolean {
2 | if (!/\d{4}-\d{2}-\d{2}/.test(string)) {
3 | return false
4 | }
5 |
6 | const date = new Date(string)
7 |
8 | return (
9 | date instanceof Date &&
10 | !isNaN(date.getTime()) &&
11 | date.toISOString() === `${string}T00:00:00.000Z`
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/dateonly/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PartialControlType,
3 | PartialDataTypeProps,
4 | PartialSequelizeDataType,
5 | } from "../../types/index.js"
6 |
7 | export type StringStep = "day" | "week" | "year" | "decade"
8 |
9 | export interface PartialDateonlyProps
10 | extends PartialDataTypeProps {
11 | min?: string | typeof Infinity
12 | max?: string | typeof Infinity
13 | step?: StringStep | number
14 | }
15 |
16 | export interface PartialDateonlyControlType
17 | extends PartialControlType {
18 | type: "Dateonly"
19 | min?: string | typeof Infinity
20 | max?: string | typeof Infinity
21 | step?: StringStep | number
22 | }
23 |
24 | export interface PartialDateonlyORM {
25 | sequelize: PartialSequelizeDataType
26 | }
27 |
28 | export interface FinalDateonlyORM {
29 | sequelize: Required>
30 | }
31 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/datetime/finalizeControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialDatetimeControlType } from "./types.js"
2 |
3 | export function finalizeControl(
4 | props: Omit, "allowNullInfer">,
5 | ): Required, "allowNullInfer">> {
6 | // @ts-expect-error @todo HATCH-417
7 | delete props.allowNullInfer
8 | return {
9 | ...props,
10 | allowNull: props.allowNull !== false && !props.primary,
11 | min: props.min ?? -Infinity,
12 | max: props.max ?? Infinity,
13 | primary: !!props.primary,
14 | default: props.default ?? null,
15 | step: props.step || 0,
16 | readOnly: props.readOnly ?? false,
17 | ui: {
18 | displayName: props?.ui?.displayName ?? null,
19 | hidden: props?.ui?.hidden ?? false,
20 | },
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/datetime/finalizeOrm.ts:
--------------------------------------------------------------------------------
1 | import type { FinalDatetimeORM, PartialDatetimeORM } from "./types.js"
2 |
3 | export function finalizeOrm({
4 | sequelize,
5 | }: PartialDatetimeORM): FinalDatetimeORM {
6 | return {
7 | sequelize: {
8 | ...sequelize,
9 | allowNull: sequelize.allowNull !== false && !sequelize.primaryKey,
10 | primaryKey: !!sequelize.primaryKey,
11 | defaultValue: sequelize.defaultValue ?? null,
12 | unique: !!sequelize.unique || !!sequelize.primaryKey,
13 | },
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/datetime/getFinalize.spec.ts:
--------------------------------------------------------------------------------
1 | import { datetime } from "./index.js"
2 |
3 | describe("getFinalize", () => {
4 | it("finalizes a partial attribute", () => {
5 | expect(datetime()).toEqual({
6 | name: "datetime()",
7 | control: {
8 | allowNull: undefined,
9 | max: undefined,
10 | min: undefined,
11 | primary: undefined,
12 | type: "Date",
13 | ui: {},
14 | },
15 | orm: {
16 | sequelize: {
17 | allowNull: undefined,
18 | primaryKey: undefined,
19 | type: "DATE",
20 | typeArgs: [],
21 | },
22 | },
23 | finalize: expect.any(Function),
24 | })
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/datetime/getPartialControl.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PartialDatetimeControlType,
3 | PartialDatetimeProps,
4 | } from "./types.js"
5 |
6 | export function getPartialControl(
7 | props?: PartialDatetimeProps,
8 | ): PartialDatetimeControlType {
9 | return {
10 | type: "Date",
11 | allowNull: props?.required == null ? props?.required : !props.required,
12 | allowNullInfer: (props?.required == null
13 | ? props?.required
14 | : !props.required) as TRequired extends true ? false : true,
15 | min: props?.min,
16 | max: props?.max,
17 | primary: props?.primary,
18 | step: props?.step,
19 | default: props?.default,
20 | readOnly: props?.readOnly,
21 | ui: {
22 | displayName: props?.ui?.displayName,
23 | hidden: props?.ui?.hidden,
24 | },
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/datetime/getPartialOrm.ts:
--------------------------------------------------------------------------------
1 | import type { PartialDatetimeORM, PartialDatetimeProps } from "./types.js"
2 |
3 | export function getPartialOrm(
4 | props?: PartialDatetimeProps,
5 | ): PartialDatetimeORM {
6 | return {
7 | sequelize: {
8 | type: "DATE",
9 | typeArgs: [],
10 | allowNull: props?.required == null ? props?.required : !props.required,
11 | primaryKey: props?.primary,
12 | defaultValue: props?.default,
13 | unique: props?.unique,
14 | },
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/datetime/isISO8601DatetimeString.spec.ts:
--------------------------------------------------------------------------------
1 | import { isISO8601DatetimeString } from "./isISO8601DatetimeString.js"
2 |
3 | describe("isISO8601DatetimeString", () => {
4 | it("validates date strings correctly", () => {
5 | expect(isISO8601DatetimeString("2011-10-05T14:48:00.000Z")).toBe(true)
6 | expect(isISO8601DatetimeString("2018-11-10T11:22:33+00:00")).toBe(false)
7 | expect(isISO8601DatetimeString("2011-10-05T14:99:00.000Z")).toBe(false)
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/datetime/isISO8601DatetimeString.ts:
--------------------------------------------------------------------------------
1 | export function isISO8601DatetimeString(string: string): boolean {
2 | if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(string)) {
3 | return false
4 | }
5 |
6 | const date = new Date(string)
7 |
8 | return (
9 | date instanceof Date &&
10 | !isNaN(date.getTime()) &&
11 | date.toISOString() === string
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/enumerate/finalizeControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialEnumControlType } from "./types.js"
2 |
3 | export function finalizeControl(
4 | props: Omit, "allowNullInfer">,
5 | ): Required, "allowNullInfer">> {
6 | // @ts-expect-error @todo HATCH-417
7 | delete props.allowNullInfer
8 | return {
9 | ...props,
10 | allowNull: props.allowNull !== false && !props.primary,
11 | primary: !!props.primary,
12 | default: props.default ?? null,
13 | readOnly: props.readOnly ?? false,
14 | ui: {
15 | displayName: props?.ui?.displayName ?? null,
16 | hidden: props?.ui?.hidden ?? false,
17 | },
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/enumerate/finalizeOrm.ts:
--------------------------------------------------------------------------------
1 | import type { FinalEnumORM, PartialEnumORM } from "./types.js"
2 |
3 | export function finalizeOrm({ sequelize }: PartialEnumORM): FinalEnumORM {
4 | return {
5 | sequelize: {
6 | ...sequelize,
7 | allowNull: sequelize.allowNull !== false && !sequelize.primaryKey,
8 | primaryKey: !!sequelize.primaryKey,
9 | defaultValue: sequelize.defaultValue ?? null,
10 | unique: !!sequelize.unique || !!sequelize.primaryKey,
11 | },
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/enumerate/getFinalize.spec.ts:
--------------------------------------------------------------------------------
1 | import { enumerate } from "./index.js"
2 |
3 | describe("getFinalize", () => {
4 | const values = ["foo", "bar"]
5 |
6 | it("finalizes a partial attribute", () => {
7 | expect(enumerate({ values })).toEqual({
8 | name: 'enumerate({"values":["foo","bar"]})',
9 | control: {
10 | allowNull: undefined,
11 | primary: undefined,
12 | type: "enum",
13 | values,
14 | ui: {},
15 | },
16 | orm: {
17 | sequelize: {
18 | allowNull: undefined,
19 | primaryKey: undefined,
20 | type: "ENUM",
21 | typeArgs: values,
22 | },
23 | },
24 | finalize: expect.any(Function),
25 | })
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/enumerate/getPartialOrm.ts:
--------------------------------------------------------------------------------
1 | import type { PartialEnumORM, PartialEnumProps } from "./types.js"
2 | import { validateValues } from "./validateValues.js"
3 | import { HatchifyInvalidSchemaError } from "../../types/index.js"
4 |
5 | export function getPartialOrm(
6 | // @todo HATCH-417
7 | props: PartialEnumProps,
8 | ): PartialEnumORM {
9 | if (!validateValues(props.values)) {
10 | throw new HatchifyInvalidSchemaError(
11 | "enum must be called with values as a non-empty string array",
12 | )
13 | }
14 |
15 | return {
16 | sequelize: {
17 | type: "ENUM",
18 | typeArgs: props.values,
19 | allowNull: props?.required == null ? props?.required : !props.required,
20 | primaryKey: props?.primary,
21 | defaultValue: props?.default,
22 | unique: props?.unique,
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/enumerate/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PartialControlType,
3 | PartialDataTypeProps,
4 | PartialSequelizeDataType,
5 | } from "../../types/index.js"
6 |
7 | export interface PartialEnumProps<
8 | TRequired extends boolean,
9 | TValues extends readonly string[],
10 | > extends PartialDataTypeProps {
11 | values: TValues
12 | }
13 |
14 | export interface PartialEnumControlType<
15 | TRequired extends boolean,
16 | TValues extends readonly string[],
17 | > extends PartialControlType {
18 | type: "enum"
19 | values: TValues
20 | }
21 |
22 | export interface PartialEnumORM {
23 | sequelize: PartialSequelizeDataType
24 | }
25 |
26 | export interface FinalEnumORM {
27 | sequelize: Required>
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/enumerate/validateValues.spec.ts:
--------------------------------------------------------------------------------
1 | import { validateValues } from "./validateValues.js"
2 |
3 | describe("validateValues", () => {
4 | it("validates an array of strings", () => {
5 | expect(validateValues(["foo"])).toBe(true)
6 | expect(validateValues(["foo", "bar"])).toBe(true)
7 | expect(validateValues(null as unknown as string[])).toBe(false)
8 | expect(validateValues(undefined as unknown as string[])).toBe(false)
9 | expect(validateValues(1 as unknown as string[])).toBe(false)
10 | expect(validateValues("string" as unknown as string[])).toBe(false)
11 | expect(validateValues({} as unknown as string[])).toBe(false)
12 | expect(validateValues({ foo: "bar" } as unknown as string[])).toBe(false)
13 | expect(
14 | validateValues((() => {
15 | /* noop */
16 | }) as unknown as string[]),
17 | ).toBe(false)
18 | expect(validateValues([])).toBe(false)
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/enumerate/validateValues.ts:
--------------------------------------------------------------------------------
1 | export function validateValues(values: readonly string[]): boolean {
2 | return (
3 | Array.isArray(values) &&
4 | values.length !== 0 &&
5 | values.every((value) => typeof value === "string")
6 | )
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/integer/getPartialControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialIntegerProps } from "./types.js"
2 | import { getPartialControl as getNumberPartialControl } from "../number/index.js"
3 | import type { PartialNumberControlType } from "../number/types.js"
4 |
5 | export function getPartialControl(
6 | props?: PartialIntegerProps,
7 | ): PartialNumberControlType {
8 | return getNumberPartialControl({ ...props, step: 1 })
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/integer/getPartialOrm.ts:
--------------------------------------------------------------------------------
1 | import type { PartialIntegerProps } from "./types.js"
2 | import { getPartialOrm as getNumberPartialOrm } from "../number/index.js"
3 | import type { PartialNumberORM } from "../number/index.js"
4 |
5 | export function getPartialOrm(
6 | props?: PartialIntegerProps,
7 | ): PartialNumberORM {
8 | const numberOrm = getNumberPartialOrm(props)
9 |
10 | return {
11 | ...numberOrm,
12 | sequelize: {
13 | ...numberOrm.sequelize,
14 | type: "INTEGER",
15 | },
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/integer/types.ts:
--------------------------------------------------------------------------------
1 | import type { PartialNumberProps } from "../number/index.js"
2 |
3 | export type PartialIntegerProps = Omit<
4 | PartialNumberProps,
5 | "step"
6 | >
7 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/number/finalizeControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialNumberControlType } from "./types.js"
2 |
3 | export function finalizeControl(
4 | props: Omit, "allowNullInfer">,
5 | ): Required, "allowNullInfer">> {
6 | // @ts-expect-error @todo HATCH-417
7 | delete props.allowNullInfer
8 | return {
9 | ...props,
10 | allowNull: props.allowNull !== false && !props.primary,
11 | min: props.min ?? -Infinity,
12 | max: props.max ?? Infinity,
13 | primary: !!props.primary,
14 | step: props.step || 0,
15 | default: props.default ?? null,
16 | readOnly: props.readOnly ?? false,
17 | ui: {
18 | displayName: props?.ui?.displayName ?? null,
19 | hidden: props?.ui?.hidden ?? false,
20 | },
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/number/finalizeOrm.ts:
--------------------------------------------------------------------------------
1 | import type { FinalNumberORM, PartialNumberORM } from "./types.js"
2 |
3 | export function finalizeOrm({ sequelize }: PartialNumberORM): FinalNumberORM {
4 | return {
5 | sequelize: {
6 | ...sequelize,
7 | allowNull: sequelize.allowNull !== false && !sequelize.primaryKey,
8 | autoIncrement: !!sequelize.autoIncrement,
9 | primaryKey: !!sequelize.primaryKey,
10 | defaultValue: sequelize.defaultValue ?? null,
11 | unique: !!sequelize.unique || !!sequelize.primaryKey,
12 | },
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/number/getFinalize.spec.ts:
--------------------------------------------------------------------------------
1 | import { number } from "./index.js"
2 |
3 | describe("getFinalize", () => {
4 | it("finalizes a partial attribute", () => {
5 | expect(number()).toEqual({
6 | name: "number()",
7 | control: {
8 | allowNull: undefined,
9 | max: undefined,
10 | min: undefined,
11 | primary: undefined,
12 | step: undefined,
13 | type: "Number",
14 | ui: {},
15 | },
16 | orm: {
17 | sequelize: {
18 | allowNull: undefined,
19 | autoIncrement: undefined,
20 | primaryKey: undefined,
21 | type: "DECIMAL",
22 | typeArgs: [],
23 | },
24 | },
25 | finalize: expect.any(Function),
26 | })
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/number/getPartialControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialNumberControlType, PartialNumberProps } from "./types.js"
2 |
3 | export function getPartialControl(
4 | props?: PartialNumberProps,
5 | ): PartialNumberControlType {
6 | return {
7 | type: "Number",
8 | allowNull: props?.required == null ? props?.required : !props.required,
9 | allowNullInfer: (props?.required == null
10 | ? props?.required
11 | : !props.required) as TRequired extends true ? false : true,
12 | min: props?.min,
13 | max: props?.max,
14 | primary: props?.primary,
15 | step: props?.step,
16 | default: props?.default,
17 | readOnly: props?.readOnly,
18 | ui: {
19 | displayName: props?.ui?.displayName,
20 | hidden: props?.ui?.hidden,
21 | },
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/number/getPartialOrm.ts:
--------------------------------------------------------------------------------
1 | import type { PartialNumberORM, PartialNumberProps } from "./types.js"
2 |
3 | export function getPartialOrm(
4 | props?: PartialNumberProps,
5 | ): PartialNumberORM {
6 | return {
7 | sequelize: {
8 | type: "DECIMAL",
9 | typeArgs: [],
10 | allowNull: props?.required == null ? props?.required : !props.required,
11 | autoIncrement: props?.autoIncrement,
12 | primaryKey: props?.primary,
13 | defaultValue: props?.default,
14 | unique: props?.unique,
15 | },
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/number/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PartialControlType,
3 | PartialDataTypeProps,
4 | PartialSequelizeDataType,
5 | } from "../../types/index.js"
6 |
7 | export interface PartialNumberProps
8 | extends PartialDataTypeProps {
9 | autoIncrement?: boolean
10 | min?: number
11 | max?: number
12 | step?: number
13 | }
14 |
15 | export interface PartialNumberControlType
16 | extends PartialControlType {
17 | type: "Number"
18 | min?: number
19 | max?: number
20 | step?: number
21 | }
22 |
23 | export interface PartialNumberORM {
24 | sequelize: PartialSequelizeDataType & {
25 | autoIncrement?: boolean
26 | }
27 | }
28 |
29 | export interface FinalNumberORM {
30 | sequelize: Required> & {
31 | autoIncrement: boolean
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/number/validateStep.ts:
--------------------------------------------------------------------------------
1 | export function validateStep(
2 | value: number,
3 | step?: number,
4 | min?: number,
5 | ): boolean {
6 | if (value == null) {
7 | return false
8 | }
9 |
10 | if (!step) {
11 | return true
12 | }
13 |
14 | const diff = (!min || min === -Infinity ? value : value - min) % step
15 | const epsilon = 0.00001 // a small tolerance
16 | const absDiff = diff < 0 ? -diff : diff
17 | return absDiff < epsilon || step - absDiff < epsilon
18 | }
19 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/string/finalizeControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialStringControlType } from "./types.js"
2 |
3 | export function finalizeControl(
4 | props: Omit, "allowNullInfer">,
5 | ): Required, "allowNullInfer">> {
6 | // @ts-expect-error @todo HATCH-417
7 | delete props.allowNullInfer
8 | return {
9 | ...props,
10 | allowNull: props.allowNull !== false && !props.primary,
11 | min: props.min ?? 0,
12 | max: props.max ?? 255,
13 | primary: !!props.primary,
14 | default: props.default ?? null,
15 | regex: props.regex ?? /(.*?)/,
16 | readOnly: props.readOnly ?? false,
17 | ui: {
18 | enableCaseSensitiveContains:
19 | props.ui?.enableCaseSensitiveContains ?? false,
20 | maxDisplayLength: props?.ui?.maxDisplayLength ?? null,
21 | displayName: props?.ui?.displayName ?? null,
22 | hidden: props?.ui?.hidden ?? false,
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/string/finalizeOrm.ts:
--------------------------------------------------------------------------------
1 | import type { FinalStringORM, PartialStringORM } from "./types.js"
2 |
3 | export function finalizeOrm({ sequelize }: PartialStringORM): FinalStringORM {
4 | return {
5 | sequelize: {
6 | ...sequelize,
7 | typeArgs: sequelize.typeArgs.length ? sequelize.typeArgs : [255],
8 | allowNull: sequelize.allowNull !== false && !sequelize.primaryKey,
9 | primaryKey: !!sequelize.primaryKey,
10 | defaultValue: sequelize.defaultValue ?? null,
11 | unique: !!sequelize.unique || !!sequelize.primaryKey,
12 | },
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/string/getFinalize.spec.ts:
--------------------------------------------------------------------------------
1 | import { string } from "./index.js"
2 |
3 | describe("getFinalize", () => {
4 | it("finalizes a partial attribute", () => {
5 | expect(string()).toEqual({
6 | name: "string()",
7 | control: {
8 | allowNull: undefined,
9 | max: undefined,
10 | min: undefined,
11 | primary: undefined,
12 | regex: undefined,
13 | type: "String",
14 | ui: {},
15 | },
16 | orm: {
17 | sequelize: {
18 | allowNull: undefined,
19 | primaryKey: undefined,
20 | type: "STRING",
21 | typeArgs: [],
22 | },
23 | },
24 | finalize: expect.any(Function),
25 | })
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/string/getPartialControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialStringControlType, PartialStringProps } from "./types.js"
2 |
3 | export function getPartialControl(
4 | props?: PartialStringProps,
5 | ): PartialStringControlType {
6 | return {
7 | type: "String",
8 | allowNull: props?.required == null ? props?.required : !props.required,
9 | allowNullInfer: (props?.required == null
10 | ? props?.required
11 | : !props.required) as TRequired extends true ? false : true,
12 | min: props?.min,
13 | max: props?.max,
14 | primary: props?.primary,
15 | default: props?.default,
16 | regex: props?.regex,
17 | readOnly: props?.readOnly,
18 | ui: {
19 | enableCaseSensitiveContains: props?.ui?.enableCaseSensitiveContains,
20 | maxDisplayLength: props?.ui?.maxDisplayLength,
21 | displayName: props?.ui?.displayName,
22 | hidden: props?.ui?.hidden,
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/string/getPartialOrm.ts:
--------------------------------------------------------------------------------
1 | import type { PartialStringORM, PartialStringProps } from "./types.js"
2 |
3 | export function getPartialOrm(
4 | props?: PartialStringProps,
5 | ): PartialStringORM {
6 | return {
7 | sequelize: {
8 | type: "STRING",
9 | typeArgs: props?.max == null || props.max === Infinity ? [] : [props.max],
10 | allowNull: props?.required == null ? props?.required : !props.required,
11 | primaryKey: props?.primary,
12 | defaultValue: props?.default,
13 | unique: props?.unique,
14 | },
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/text/finalizeControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialTextControlType } from "./types.js"
2 |
3 | export function finalizeControl(
4 | props: Omit, "allowNullInfer">,
5 | ): Required, "allowNullInfer">> {
6 | // @ts-expect-error @todo HATCH-417
7 | delete props.allowNullInfer
8 | return {
9 | ...props,
10 | allowNull: props.allowNull !== false && !props.primary,
11 | min: props.min ?? 0,
12 | max: props.max ?? Infinity,
13 | primary: !!props.primary,
14 | default: props.default ?? null,
15 | regex: props.regex ?? /(.*?)/,
16 | readOnly: props.readOnly ?? false,
17 | ui: {
18 | enableCaseSensitiveContains:
19 | props.ui?.enableCaseSensitiveContains ?? false,
20 | displayName: props?.ui?.displayName ?? null,
21 | maxDisplayLength: props?.ui?.maxDisplayLength ?? null,
22 | hidden: props?.ui?.hidden ?? false,
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/text/finalizeOrm.ts:
--------------------------------------------------------------------------------
1 | import type { FinalTextORM, PartialTextORM } from "./types.js"
2 |
3 | export function finalizeOrm({ sequelize }: PartialTextORM): FinalTextORM {
4 | return {
5 | sequelize: {
6 | ...sequelize,
7 | allowNull: sequelize.allowNull !== false && !sequelize.primaryKey,
8 | primaryKey: !!sequelize.primaryKey,
9 | defaultValue: sequelize.defaultValue ?? null,
10 | unique: !!sequelize.unique || !!sequelize.primaryKey,
11 | },
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/text/getFinalize.spec.ts:
--------------------------------------------------------------------------------
1 | import { text } from "./index.js"
2 |
3 | describe("getFinalize", () => {
4 | it("finalizes a partial attribute", () => {
5 | expect(text()).toEqual({
6 | name: "text()",
7 | control: {
8 | allowNull: undefined,
9 | max: Infinity,
10 | min: 0,
11 | primary: undefined,
12 | regex: undefined,
13 | type: "String",
14 | ui: {},
15 | },
16 | orm: {
17 | sequelize: {
18 | allowNull: undefined,
19 | primaryKey: undefined,
20 | type: "TEXT",
21 | },
22 | },
23 | finalize: expect.any(Function),
24 | })
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/text/getPartialControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialTextProps } from "./types.js"
2 | import { getPartialControl as getStringPartialControl } from "../string/index.js"
3 | import type { PartialStringControlType } from "../string/types.js"
4 |
5 | export function getPartialControl(
6 | props?: PartialTextProps,
7 | ): PartialStringControlType {
8 | return getStringPartialControl({
9 | ...props,
10 | min: 0,
11 | max: Infinity,
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/text/getPartialOrm.ts:
--------------------------------------------------------------------------------
1 | import type { PartialTextORM, PartialTextProps } from "./types.js"
2 |
3 | export function getPartialOrm(
4 | props?: PartialTextProps,
5 | ): PartialTextORM {
6 | return {
7 | sequelize: {
8 | type: "TEXT",
9 | allowNull: props?.required == null ? props?.required : !props.required,
10 | primaryKey: props?.primary,
11 | defaultValue: props?.default,
12 | unique: props?.unique,
13 | },
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/text/index.ts:
--------------------------------------------------------------------------------
1 | import { getFinalize } from "./getFinalize.js"
2 | import { getPartialControl } from "./getPartialControl.js"
3 | import { getPartialOrm } from "./getPartialOrm.js"
4 | import type {
5 | FinalTextORM,
6 | PartialTextControlType,
7 | PartialTextORM,
8 | PartialTextProps,
9 | } from "./types.js"
10 | import type { PartialAttribute } from "../../types/index.js"
11 |
12 | export function text(
13 | props?: PartialTextProps,
14 | ): PartialAttribute<
15 | PartialTextORM,
16 | PartialTextControlType,
17 | string,
18 | FinalTextORM
19 | > {
20 | return {
21 | name: `text(${props ? JSON.stringify(props) : ""})`,
22 | orm: getPartialOrm(props),
23 | control: getPartialControl(props),
24 | finalize: function finalizeText() {
25 | return getFinalize(this)
26 | },
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/text/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PartialStringControlType,
3 | PartialStringProps,
4 | } from "../string/index.js"
5 |
6 | export type PartialTextProps =
7 | PartialStringProps
8 |
9 | export type PartialTextControlType =
10 | PartialStringControlType
11 |
12 | export interface PartialTextORM {
13 | sequelize: {
14 | type: "TEXT"
15 | allowNull?: boolean
16 | primaryKey?: boolean
17 | defaultValue?: string | (() => string) | null
18 | unique?: boolean
19 | }
20 | }
21 |
22 | export interface FinalTextORM {
23 | sequelize: Required
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/uuid/constants.ts:
--------------------------------------------------------------------------------
1 | export const UUID_REGEX =
2 | /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
3 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/uuid/finalizeControl.ts:
--------------------------------------------------------------------------------
1 | import type { PartialUuidControlType } from "./types.js"
2 |
3 | export function finalizeControl(
4 | props: Omit, "allowNullInfer">,
5 | ): Required, "allowNullInfer">> {
6 | // @ts-expect-error @todo HATCH-417
7 | delete props.allowNullInfer
8 | return {
9 | ...props,
10 | allowNull: props.allowNull !== false && !props.primary,
11 | min: props.min ?? 0,
12 | max: props.max ?? 255,
13 | primary: !!props.primary,
14 | default: props.default ?? null,
15 | regex: props.regex ?? /(.*?)/,
16 | readOnly: props.readOnly ?? false,
17 | ui: {
18 | displayName: props?.ui?.displayName ?? null,
19 | hidden: props?.ui?.hidden ?? false,
20 | enableCaseSensitiveContains:
21 | props?.ui?.enableCaseSensitiveContains ?? false,
22 | },
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/uuid/finalizeOrm.ts:
--------------------------------------------------------------------------------
1 | import type { FinalUuidORM, PartialUuidORM } from "./types.js"
2 |
3 | export function finalizeOrm({ sequelize }: PartialUuidORM): FinalUuidORM {
4 | return {
5 | sequelize: {
6 | ...sequelize,
7 | allowNull: sequelize.allowNull !== false && !sequelize.primaryKey,
8 | primaryKey: !!sequelize.primaryKey,
9 | defaultValue: sequelize.defaultValue ?? null,
10 | unique: !!sequelize.unique || !!sequelize.primaryKey,
11 | },
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/uuid/getFinalize.spec.ts:
--------------------------------------------------------------------------------
1 | import { UUID_REGEX } from "./constants.js"
2 |
3 | import { uuid } from "./index.js"
4 |
5 | describe("getFinalize", () => {
6 | it("finalizes a partial attribute", () => {
7 | expect(uuid()).toEqual({
8 | name: "uuid()",
9 | control: {
10 | allowNull: undefined,
11 | max: 36,
12 | min: 36,
13 | primary: undefined,
14 | regex: UUID_REGEX,
15 | type: "String",
16 | ui: {
17 | displayName: undefined,
18 | enableCaseSensitiveContains: undefined,
19 | hidden: undefined,
20 | },
21 | },
22 | orm: {
23 | sequelize: {
24 | allowNull: undefined,
25 | primaryKey: undefined,
26 | type: "UUID",
27 | },
28 | },
29 | finalize: expect.any(Function),
30 | })
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/uuid/getPartialControl.ts:
--------------------------------------------------------------------------------
1 | import { UUID_REGEX } from "./constants.js"
2 | import type { PartialUuidControlType, PartialUuidProps } from "./types.js"
3 |
4 | export function getPartialControl(
5 | props?: PartialUuidProps,
6 | ): PartialUuidControlType {
7 | return {
8 | type: "String",
9 | readOnly: props?.readOnly,
10 | allowNull: props?.required == null ? props?.required : !props.required,
11 | allowNullInfer: (props?.required == null
12 | ? props?.required
13 | : !props.required) as TRequired extends true ? false : true,
14 | min: 36,
15 | max: 36,
16 | primary: props?.primary,
17 | default: props?.default,
18 | regex: UUID_REGEX,
19 | ui: {
20 | displayName: props?.ui?.displayName,
21 | hidden: props?.ui?.hidden,
22 | enableCaseSensitiveContains: props?.ui?.enableCaseSensitiveContains,
23 | },
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/uuid/getPartialOrm.ts:
--------------------------------------------------------------------------------
1 | import type { PartialUuidORM, PartialUuidProps } from "./types.js"
2 |
3 | export function getPartialOrm(
4 | props?: PartialUuidProps,
5 | ): PartialUuidORM {
6 | return {
7 | sequelize: {
8 | type: "UUID",
9 | allowNull: props?.required == null ? props?.required : !props.required,
10 | primaryKey: props?.primary,
11 | defaultValue: props?.default,
12 | unique: props?.unique,
13 | },
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/uuid/index.ts:
--------------------------------------------------------------------------------
1 | import { getFinalize } from "./getFinalize.js"
2 | import { getPartialControl } from "./getPartialControl.js"
3 | import { getPartialOrm } from "./getPartialOrm.js"
4 | import type {
5 | FinalUuidORM,
6 | PartialUuidControlType,
7 | PartialUuidORM,
8 | PartialUuidProps,
9 | } from "./types.js"
10 | import type { PartialAttribute } from "../../types/index.js"
11 |
12 | export function uuid(
13 | props?: PartialUuidProps,
14 | ): PartialAttribute<
15 | PartialUuidORM,
16 | PartialUuidControlType,
17 | string,
18 | FinalUuidORM
19 | > {
20 | return {
21 | name: `uuid(${props ? JSON.stringify(props) : ""})`,
22 | orm: getPartialOrm(props),
23 | control: getPartialControl(props),
24 | finalize: function finalizeUuid() {
25 | return getFinalize(this)
26 | },
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/src/dataTypes/uuid/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PartialDataTypeProps,
3 | PartialSequelizeDataType,
4 | } from "../../types/index.js"
5 | import type { PartialStringControlType } from "../string/index.js"
6 |
7 | export type PartialUuidProps = PartialDataTypeProps<
8 | string,
9 | TRequired
10 | >
11 |
12 | export type PartialUuidControlType =
13 | PartialStringControlType
14 |
15 | export interface PartialUuidORM {
16 | sequelize: Omit, "typeArgs">
17 | }
18 |
19 | export interface FinalUuidORM {
20 | sequelize: Required<
21 | Omit, "typeArgs">
22 | >
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/relationships/belongsTo/index.spec.ts:
--------------------------------------------------------------------------------
1 | import { belongsTo } from "./index.js"
2 |
3 | describe("belongsTo", () => {
4 | it("belongsTo()", () => {
5 | expect(belongsTo()).toEqual({
6 | type: "belongsTo",
7 | targetSchema: null,
8 | sourceAttribute: null,
9 | targetAttribute: null,
10 | })
11 | })
12 |
13 | it("belongsTo(schemaName)", () => {
14 | expect(belongsTo("SalesPerson")).toEqual({
15 | type: "belongsTo",
16 | targetSchema: "SalesPerson",
17 | sourceAttribute: null,
18 | targetAttribute: null,
19 | })
20 | })
21 |
22 | it("belongsTo(schemaName, {sourceAttribute})", () => {
23 | expect(belongsTo("SalesPerson", { sourceAttribute: "finisherId" })).toEqual(
24 | {
25 | type: "belongsTo",
26 | targetSchema: "SalesPerson",
27 | sourceAttribute: "finisherId",
28 | targetAttribute: null,
29 | },
30 | )
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/packages/core/src/relationships/belongsTo/index.ts:
--------------------------------------------------------------------------------
1 | import type { PartialBelongsToRelationship } from "./types.js"
2 |
3 | // @todo HATCH-417
4 | export function belongsTo(
5 | targetSchema?: TTargetSchema,
6 | props?: {
7 | sourceAttribute: string
8 | targetAttribute?: string
9 | },
10 | ): PartialBelongsToRelationship {
11 | return {
12 | type: "belongsTo",
13 | targetSchema: (targetSchema ?? null) as TTargetSchema,
14 | sourceAttribute: props?.sourceAttribute ?? null,
15 | targetAttribute: props?.targetAttribute ?? null,
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/src/relationships/belongsTo/types.ts:
--------------------------------------------------------------------------------
1 | // @todo HATCH-417
2 | export interface PartialBelongsToRelationship<
3 | TTargetSchema extends string | undefined | null,
4 | > {
5 | type: "belongsTo"
6 | targetSchema: TTargetSchema
7 | sourceAttribute: string | null
8 | targetAttribute: string | null
9 | }
10 |
11 | export interface FinalBelongsToRelationship {
12 | type: "belongsTo"
13 | targetSchema: string
14 | sourceAttribute: string
15 | targetAttribute: string
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/src/relationships/hasMany/index.ts:
--------------------------------------------------------------------------------
1 | import type { PartialHasManyRelationship } from "./types.js"
2 | import { buildThrough } from "../hasManyThrough/buildThrough.js"
3 |
4 | // HATCH-417
5 | export function hasMany(
6 | schemaName?: TTargetSchema,
7 | props?: {
8 | targetAttribute: string
9 | sourceAttribute?: string
10 | },
11 | ): PartialHasManyRelationship {
12 | const targetSchema = schemaName ?? null
13 |
14 | return {
15 | type: "hasMany",
16 | targetSchema: targetSchema as TTargetSchema,
17 | targetAttribute: props?.targetAttribute ?? null,
18 | sourceAttribute: props?.sourceAttribute ?? null,
19 | through: buildThrough(targetSchema as TTargetSchema),
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/src/relationships/hasMany/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PartialHasManyThroughRelationship,
3 | ThroughOptions,
4 | } from "../hasManyThrough/types.js"
5 |
6 | // @todo HATCH-417
7 | export interface PartialHasManyRelationship<
8 | TTargetSchema extends string | undefined | null,
9 | > {
10 | type: "hasMany"
11 | targetSchema: TTargetSchema
12 | targetAttribute: string | null
13 | sourceAttribute: string | null
14 | through: (
15 | through?: string | null,
16 | options?: ThroughOptions,
17 | ) => PartialHasManyThroughRelationship
18 | }
19 |
20 | export interface FinalHasManyRelationship {
21 | type: "hasMany"
22 | targetSchema: string
23 | targetAttribute: string
24 | sourceAttribute: string
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/relationships/hasOne/index.spec.ts:
--------------------------------------------------------------------------------
1 | import { hasOne } from "./index.js"
2 |
3 | describe("hasOne", () => {
4 | it("hasOne()", () => {
5 | expect(hasOne()).toEqual({
6 | type: "hasOne",
7 | targetSchema: null,
8 | targetAttribute: null,
9 | sourceAttribute: null,
10 | })
11 | })
12 |
13 | it("hasOne(schemaName)", () => {
14 | expect(hasOne("SalesPerson")).toEqual({
15 | type: "hasOne",
16 | targetSchema: "SalesPerson",
17 | targetAttribute: null,
18 | sourceAttribute: null,
19 | })
20 | })
21 |
22 | it("hasOne(schemaName, {targetAttribute})", () => {
23 | expect(hasOne("SalesPerson", { targetAttribute: "salesId" })).toEqual({
24 | type: "hasOne",
25 | targetSchema: "SalesPerson",
26 | targetAttribute: "salesId",
27 | sourceAttribute: null,
28 | })
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/packages/core/src/relationships/hasOne/index.ts:
--------------------------------------------------------------------------------
1 | import type { PartialHasOneRelationship } from "./types.js"
2 |
3 | // @todo HATCH-417
4 | export function hasOne(
5 | targetSchema?: TTargetSchema,
6 | props?: {
7 | targetAttribute: string
8 | sourceAttribute?: string
9 | },
10 | ): PartialHasOneRelationship {
11 | return {
12 | type: "hasOne",
13 | targetSchema: (targetSchema ?? null) as TTargetSchema,
14 | targetAttribute: props?.targetAttribute ?? null,
15 | sourceAttribute: props?.sourceAttribute ?? null,
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/core/src/relationships/hasOne/types.ts:
--------------------------------------------------------------------------------
1 | // @todo HATCH-417
2 | export interface PartialHasOneRelationship<
3 | TTargetSchema extends string | undefined | null,
4 | > {
5 | type: "hasOne"
6 | targetSchema: TTargetSchema
7 | targetAttribute: string | null
8 | sourceAttribute: string | null
9 | }
10 |
11 | export interface FinalHasOneRelationship {
12 | type: "hasOne"
13 | targetSchema: string
14 | targetAttribute: string
15 | sourceAttribute: string
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/src/relationships/index.ts:
--------------------------------------------------------------------------------
1 | export { belongsTo } from "./belongsTo/index.js"
2 | export { hasMany } from "./hasMany/index.js"
3 | export { hasOne } from "./hasOne/index.js"
4 |
5 | export type {
6 | PartialBelongsToRelationship,
7 | FinalBelongsToRelationship,
8 | } from "./belongsTo/types.js"
9 | export type {
10 | PartialHasManyRelationship,
11 | FinalHasManyRelationship,
12 | } from "./hasMany/types.js"
13 | export type {
14 | PartialHasManyThroughRelationship,
15 | FinalHasManyThroughRelationship,
16 | ThroughOptions,
17 | } from "./hasManyThrough/types.js"
18 | export type {
19 | PartialHasOneRelationship,
20 | FinalHasOneRelationship,
21 | } from "./hasOne/types.js"
22 |
--------------------------------------------------------------------------------
/packages/core/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./schema.js"
2 |
--------------------------------------------------------------------------------
/packages/core/src/util/camelCaseToPascalCase.spec.ts:
--------------------------------------------------------------------------------
1 | import { camelCaseToPascalCase } from "./camelCaseToPascalCase.js"
2 |
3 | describe("camelCaseToPascalCase", () => {
4 | it("works as expected", () => {
5 | expect(camelCaseToPascalCase("user")).toBe("User")
6 | expect(camelCaseToPascalCase("userName")).toBe("UserName")
7 | expect(camelCaseToPascalCase("parent1LastName")).toBe("Parent1LastName")
8 | expect(camelCaseToPascalCase("addressLine1")).toBe("AddressLine1")
9 | expect(camelCaseToPascalCase("u")).toBe("U")
10 | expect(camelCaseToPascalCase("")).toBe("")
11 | expect(camelCaseToPascalCase(null as unknown as string)).toBeNull()
12 | expect(
13 | camelCaseToPascalCase(undefined as unknown as string),
14 | ).toBeUndefined()
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/packages/core/src/util/camelCaseToPascalCase.ts:
--------------------------------------------------------------------------------
1 | export function camelCaseToPascalCase(camelCaseString: string): string {
2 | return typeof camelCaseString !== "string"
3 | ? camelCaseString
4 | : camelCaseString.charAt(0).toUpperCase() + camelCaseString.slice(1)
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/src/util/camelCaseToTitleCase.spec.ts:
--------------------------------------------------------------------------------
1 | import { camelCaseToTitleCase } from "./camelCaseToTitleCase.js"
2 |
3 | describe("camelCaseToTitleCase", () => {
4 | it("works as expected", () => {
5 | expect(camelCaseToTitleCase("user")).toBe("User")
6 | expect(camelCaseToTitleCase("userName")).toBe("User Name")
7 | expect(camelCaseToTitleCase("parent1LastName")).toBe("Parent 1 Last Name")
8 | expect(camelCaseToTitleCase("addressLine1")).toBe("Address Line 1")
9 | expect(camelCaseToTitleCase("zipCode12345")).toBe("Zip Code 12345")
10 | expect(camelCaseToTitleCase("u")).toBe("U")
11 | expect(camelCaseToTitleCase("")).toBe("")
12 | expect(camelCaseToTitleCase(null as unknown as string)).toBeNull()
13 | expect(camelCaseToTitleCase(undefined as unknown as string)).toBeUndefined()
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/packages/core/src/util/camelCaseToTitleCase.ts:
--------------------------------------------------------------------------------
1 | export function camelCaseToTitleCase(
2 | camelCaseString?: string,
3 | ): string | undefined {
4 | return typeof camelCaseString !== "string"
5 | ? camelCaseString
6 | : camelCaseString
7 | ?.replace(/([A-Z]|[0-9]+)/g, " $1")
8 | .replace(/^./, function (str) {
9 | return str.toUpperCase()
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/src/util/getEndpoint.ts:
--------------------------------------------------------------------------------
1 | import { pascalCaseToKebabCase } from "./pascalCaseToKebabCase.js"
2 | import { pluralize } from "./pluralize.js"
3 | import type { FinalSchema } from "../types/index.js"
4 |
5 | export function getEndpoint(
6 | schema: Pick,
7 | ): string | undefined {
8 | const { name, namespace } = schema
9 | const pluralName = pascalCaseToKebabCase(schema.pluralName ?? pluralize(name))
10 | return namespace
11 | ? `${pascalCaseToKebabCase(namespace)}/${pluralName}`
12 | : pluralName
13 | }
14 |
--------------------------------------------------------------------------------
/packages/core/src/util/getSchemaKey.spec.ts:
--------------------------------------------------------------------------------
1 | import { getSchemaKey } from "./getSchemaKey.js"
2 |
3 | describe("getSchemaKey", () => {
4 | it("works as expected", () => {
5 | expect(getSchemaKey({ name: "Two" })).toBe("Two")
6 | expect(getSchemaKey({ namespace: "One", name: "Two" })).toBe("One_Two")
7 | expect(getSchemaKey({ name: undefined as unknown as string })).toBe("")
8 | expect(
9 | getSchemaKey({
10 | namespace: "One",
11 | name: undefined as unknown as string,
12 | }),
13 | ).toBe("One_")
14 | expect(
15 | getSchemaKey(null as unknown as { namespace: string; name: string }),
16 | ).toBeNull()
17 | expect(
18 | getSchemaKey(undefined as unknown as { namespace: string; name: string }),
19 | ).toBeUndefined()
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/packages/core/src/util/getSchemaKey.ts:
--------------------------------------------------------------------------------
1 | import type { FinalSchema } from "../types/index.js"
2 |
3 | export function getSchemaKey(
4 | schema: Pick,
5 | ): string {
6 | if (!schema) return schema
7 |
8 | const { name, namespace } = schema
9 | return !namespace ? name ?? "" : `${namespace}_${name ?? ""}`
10 | }
11 |
--------------------------------------------------------------------------------
/packages/core/src/util/pascalCaseToCamelCase.spec.ts:
--------------------------------------------------------------------------------
1 | import { pascalCaseToCamelCase } from "./pascalCaseToCamelCase.js"
2 |
3 | describe("pascalCaseToCamelCase", () => {
4 | it("works as expected", () => {
5 | expect(pascalCaseToCamelCase("User")).toBe("user")
6 | expect(pascalCaseToCamelCase("UserName")).toBe("userName")
7 | expect(pascalCaseToCamelCase("Parent1LastName")).toBe("parent1LastName")
8 | expect(pascalCaseToCamelCase("AddressLine1")).toBe("addressLine1")
9 | expect(pascalCaseToCamelCase("U")).toBe("u")
10 | expect(pascalCaseToCamelCase("")).toBe("")
11 | expect(pascalCaseToCamelCase(null as unknown as string)).toBeNull()
12 | expect(
13 | pascalCaseToCamelCase(undefined as unknown as string),
14 | ).toBeUndefined()
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/packages/core/src/util/pascalCaseToCamelCase.ts:
--------------------------------------------------------------------------------
1 | export function pascalCaseToCamelCase(pascalCaseString: string): string {
2 | return typeof pascalCaseString !== "string"
3 | ? pascalCaseString
4 | : pascalCaseString.charAt(0).toLowerCase() + pascalCaseString.slice(1)
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/src/util/pascalCaseToKebabCase.spec.ts:
--------------------------------------------------------------------------------
1 | import { pascalCaseToKebabCase } from "./pascalCaseToKebabCase.js"
2 |
3 | describe("pascalCaseToKebabCase", () => {
4 | it("works as expected", () => {
5 | expect(pascalCaseToKebabCase("User")).toBe("user")
6 | expect(pascalCaseToKebabCase("UserName")).toBe("user-name")
7 | expect(pascalCaseToKebabCase("Parent1LastName")).toBe("parent1-last-name")
8 | expect(pascalCaseToKebabCase("AddressLine1")).toBe("address-line1")
9 | expect(pascalCaseToKebabCase("U")).toBe("u")
10 | expect(pascalCaseToKebabCase("")).toBe("")
11 | expect(pascalCaseToKebabCase(null as unknown as string)).toBeNull()
12 | expect(
13 | pascalCaseToKebabCase(undefined as unknown as string),
14 | ).toBeUndefined()
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/packages/core/src/util/pascalCaseToKebabCase.ts:
--------------------------------------------------------------------------------
1 | export function pascalCaseToKebabCase(
2 | pascalCaseString?: string,
3 | ): string | undefined {
4 | return typeof pascalCaseString !== "string"
5 | ? pascalCaseString
6 | : pascalCaseString
7 | ?.replace(
8 | /[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,
9 | (match) => "-" + match.toLowerCase(),
10 | )
11 | .substring(1)
12 | }
13 |
--------------------------------------------------------------------------------
/packages/core/src/util/pluralize.spec.ts:
--------------------------------------------------------------------------------
1 | import { pluralize } from "./pluralize.js"
2 |
3 | describe("pluralize", () => {
4 | it("should pluralize a word with an s", () => {
5 | expect(pluralize("word")).toBe("words")
6 | expect(pluralize("words")).toBe("wordss")
7 | expect(pluralize("twoWord")).toBe("twoWords")
8 | expect(pluralize(null as unknown as string)).toBeNull()
9 | expect(pluralize(undefined as unknown as string)).toBeUndefined()
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/packages/core/src/util/pluralize.ts:
--------------------------------------------------------------------------------
1 | export function pluralize(singularName: string): string {
2 | if (typeof singularName !== "string") {
3 | return singularName
4 | }
5 |
6 | return singularName + "s"
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/src/util/singularize.spec.ts:
--------------------------------------------------------------------------------
1 | import { singularize } from "./singularize.js"
2 |
3 | describe("singularize", () => {
4 | it("removes trailing 's'", () => {
5 | expect(singularize("users")).toBe("user")
6 | expect(singularize("persons")).toBe("person")
7 | expect(singularize("s")).toBe("")
8 | })
9 |
10 | it("ignores if there is no trailing 's'", () => {
11 | expect(singularize("")).toBe("")
12 | expect(singularize("user")).toBe("user")
13 | expect(singularize("u")).toBe("u")
14 | expect(singularize(null as unknown as string)).toBeNull()
15 | expect(singularize(undefined as unknown as string)).toBeUndefined()
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/packages/core/src/util/singularize.ts:
--------------------------------------------------------------------------------
1 | export function singularize(pluralName: string): string {
2 | if (typeof pluralName !== "string") {
3 | return pluralName
4 | }
5 |
6 | return pluralName.endsWith("s")
7 | ? pluralName.substring(0, pluralName.length - 1)
8 | : pluralName
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/src/util/uuidv4.ts:
--------------------------------------------------------------------------------
1 | export * from "@hatchifyjs/crypto"
2 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist",
4 | "rootDir": "./src"
5 | },
6 | "extends": "../../tsconfig.json",
7 | "include": [
8 | "src",
9 | "src/core.ts"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/create/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignore-dirs:
2 | - /dist
3 | ignores:
4 | - "@hatchifyjs/core"
5 | - "@hatchifyjs/express"
6 | - "@hatchifyjs/koa"
7 | - "@hatchifyjs/react"
8 | - "@mui/material"
9 | - "dotenv"
10 | - "express"
11 | - "koa"
12 | - "koa-connect"
13 | - "react"
14 | - "react-dom"
15 | - "vite"
16 |
--------------------------------------------------------------------------------
/packages/create/build.config.ts:
--------------------------------------------------------------------------------
1 | import { defineBuildConfig } from "unbuild"
2 |
3 | export default defineBuildConfig({
4 | entries: ["src/index"],
5 | clean: true,
6 | rollup: {
7 | inlineDependencies: true,
8 | esbuild: {
9 | target: "node18",
10 | minify: true,
11 | },
12 | },
13 | alias: {
14 | // we can always use non-transpiled code since we support node 18+
15 | prompts: "prompts/lib/index.js",
16 | },
17 | hooks: {},
18 | })
19 |
--------------------------------------------------------------------------------
/packages/create/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import "./dist/index.mjs"
4 |
--------------------------------------------------------------------------------
/packages/create/src/types.ts:
--------------------------------------------------------------------------------
1 | type ColorFunc = (str: string | number) => string
2 |
3 | export type Backend = {
4 | name: string
5 | display: string
6 | color: ColorFunc
7 | dependencies: string[]
8 | devDependencies: string[]
9 | }
10 |
11 | export type Database = {
12 | name: string
13 | display: string
14 | color: ColorFunc
15 | dependencies: string[]
16 | devDependencies: string[]
17 | }
18 |
19 | export type Frontend = {
20 | name: string
21 | display: string
22 | color: ColorFunc
23 | dependencies: string[]
24 | devDependencies: string[]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/create/template-express/_gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/packages/create/template-express/schemas.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/packages/create/template-express/schemas.ts
--------------------------------------------------------------------------------
/packages/create/template-koa/_gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/packages/create/template-koa/schemas.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/packages/create/template-koa/schemas.ts
--------------------------------------------------------------------------------
/packages/create/template-react/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom/client"
3 | import App from "./App.js"
4 | import "./index.css"
5 |
6 | ReactDOM.createRoot(document.getElementById("root")!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/packages/create/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["build.config.ts", "src"],
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "target": "ES2022",
6 | "module": "ES2020",
7 | "moduleResolution": "bundler",
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "declaration": false,
11 | "sourceMap": false,
12 | "noUnusedLocals": true,
13 | "esModuleInterop": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/crypto/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignores:
2 | - "@hatchifyjs/core"
3 |
--------------------------------------------------------------------------------
/packages/crypto/README.md:
--------------------------------------------------------------------------------
1 | Isomorphic WebCrypto package. Only Isomorphic. No Polyfills. Used by Hatchify to create UUIDs.
--------------------------------------------------------------------------------
/packages/crypto/index.browser.js:
--------------------------------------------------------------------------------
1 | export function getCrypto() {
2 | return window.crypto
3 | }
4 |
5 | export function uuidv4() {
6 | return window.crypto.randomUUID()
7 | }
8 |
--------------------------------------------------------------------------------
/packages/crypto/index.browser.spec.js:
--------------------------------------------------------------------------------
1 | describe("getCrypto", () => {
2 | it("returns window.crypto if available", async () => {
3 | Object.defineProperty(window, "crypto", {
4 | value: "window.crypto",
5 | })
6 | const { getCrypto } = await import("@hatchifyjs/core/dist/util/uuidv4.js")
7 | expect(getCrypto()).toBe("window.crypto")
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/packages/crypto/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@hatchifyjs/crypto"
2 |
3 | export declare function getCrypto(): Crypto
4 |
5 | export declare function uuidv4(): string
6 |
--------------------------------------------------------------------------------
/packages/crypto/index.js:
--------------------------------------------------------------------------------
1 | import crypto from "node:crypto"
2 |
3 | export function getCrypto() {
4 | return crypto.webcrypto
5 | }
6 |
7 | export function uuidv4() {
8 | return crypto.webcrypto.randomUUID()
9 | }
10 |
--------------------------------------------------------------------------------
/packages/crypto/index.spec.js:
--------------------------------------------------------------------------------
1 | import crypto from "node:crypto"
2 |
3 | import { getCrypto, uuidv4 } from "./index.js"
4 |
5 | describe("crypto", () => {
6 | it("generates a uuid", () => {
7 | expect(uuidv4()).toMatch(
8 | /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
9 | )
10 | })
11 | })
12 |
13 | describe("getCrypto", () => {
14 | it("returns node crypto if available", async () => {
15 | expect(getCrypto()).toBe(crypto.webcrypto)
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/packages/crypto/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectCoverageFrom": [
3 | "/**/*.js",
4 | "!/**/*.browser.js",
5 | "!/**/*.spec.js",
6 | "!/**/coverage/**/*.js"
7 | ],
8 | "coverageDirectory": "../../coverage/crypto",
9 | "coverageReporters": ["html", "text", "text-summary"],
10 | "coverageThreshold": {
11 | "global": {
12 | "branches": 100,
13 | "functions": 100,
14 | "lines": 100,
15 | "statements": 100
16 | }
17 | },
18 | "roots": [""],
19 | "testEnvironment": "node",
20 | "testPathIgnorePatterns": [".browser"]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/design-mui/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignores: ["@emotion/styled", "@testing-library/jest-dom", "@vitest/coverage-c8", "lodash"]
2 |
--------------------------------------------------------------------------------
/packages/design-mui/README.md:
--------------------------------------------------------------------------------
1 | # @hatchifyjs/design-mui
2 |
3 | Material UI implementation on of Hatchify's React components. This isn't used directly. Use [`@hatchifyjs/react`](https://github.com/bitovi/hatchify/blob/main/docs/react/README.md) instead.
4 |
--------------------------------------------------------------------------------
/packages/design-mui/src/assets/index.ts:
--------------------------------------------------------------------------------
1 | export { Robochicken } from "./Robochicken.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiAutocomplete/index.tsx:
--------------------------------------------------------------------------------
1 | export { default } from "./MuiAutocomplete.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiDataGrid/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MuiDataGrid } from "./MuiDataGrid.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiEverything/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MuiEverything } from "./MuiEverything.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiFilters/components/inputs/ColumnSelect.tsx:
--------------------------------------------------------------------------------
1 | import { InputLabel, MenuItem, Select } from "@mui/material"
2 | import { camelCaseToTitleCase } from "@hatchifyjs/core"
3 |
4 | const ColumnSelect: React.FC<{
5 | fields: string[]
6 | labelId: string
7 | value: string
8 | onChange: (value: string) => void
9 | }> = ({ value, labelId, fields, onChange }) => {
10 | return (
11 | <>
12 | Column
13 |
27 | >
28 | )
29 | }
30 |
31 | export default ColumnSelect
32 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiFilters/components/inputs/DateInput.test.tsx:
--------------------------------------------------------------------------------
1 | import { describe, it, vi } from "vitest"
2 | import { render } from "@testing-library/react"
3 | import DateInput from "./DateInput.js"
4 |
5 | describe("components/MuiFilters/inputs/DateInput", () => {
6 | it("works", async () => {
7 | render(
8 | ,
9 | )
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiFilters/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MuiFilters } from "./MuiFilters.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiFilters/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./utils.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiLayout/MuiLayout.test.tsx:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest"
2 | import { render, screen } from "@testing-library/react"
3 | import { string } from "@hatchifyjs/core"
4 | import { MuiLayout } from "./MuiLayout.js"
5 |
6 | const partialSchemas = {
7 | Todo: {
8 | name: "Todo",
9 | attributes: { name: string() },
10 | },
11 | }
12 |
13 | describe("hatchifyjs/presentation/mui/MuiLayout", () => {
14 | describe("MuiLayout", () => {
15 | it("works", async () => {
16 | render()
17 | expect(await screen.findByText("Todo")).toBeDefined()
18 | })
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiLayout/index.ts:
--------------------------------------------------------------------------------
1 | export { MuiLayout } from "./MuiLayout.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiList/components/MuiBodySkeleton.tsx:
--------------------------------------------------------------------------------
1 | import { Skeleton, TableCell, TableRow } from "@mui/material"
2 | import type { HatchifyColumn } from "@hatchifyjs/react-ui"
3 |
4 | export const MuiBodySkeleton: React.FC<{ columns: HatchifyColumn[] }> = ({
5 | columns,
6 | }) => {
7 | return (
8 | <>
9 | {[1, 2, 3].map((key) => (
10 |
11 | {columns.map((column) => (
12 |
13 |
14 |
15 | ))}
16 |
17 | ))}
18 | >
19 | )
20 | }
21 |
22 | export default MuiBodySkeleton
23 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiList/components/MuiHeaders/components/Sortable.tsx:
--------------------------------------------------------------------------------
1 | import type { Meta } from "@hatchifyjs/rest-client"
2 | import { Sort } from "./Sort.js"
3 | import type { HatchifyColumn, HeaderProps } from "@hatchifyjs/react-ui"
4 |
5 | export const Sortable: React.FC<
6 | Pick & {
7 | children: React.ReactNode
8 | isPending: Meta["isPending"]
9 | columnKey: HatchifyColumn["key"]
10 | sortable: boolean
11 | }
12 | > = ({
13 | children,
14 | columnKey,
15 | direction,
16 | isPending,
17 | setSort,
18 | sortable,
19 | sortBy,
20 | }) =>
21 | sortable ? (
22 |
29 | {children}
30 |
31 | ) : (
32 | <>{children}>
33 | )
34 |
35 | export default Sortable
36 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiList/components/MuiHeaders/components/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Sort } from "./Sort.js"
2 | export { default as Sortable } from "./Sortable.js"
3 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiList/components/MuiHeaders/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./MuiHeaders.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiList/components/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MuiBody } from "./MuiBody.js"
2 | export { default as MuiBodySkeleton } from "./MuiBodySkeleton.js"
3 | export { default as MuiHeaders } from "./MuiHeaders/index.js"
4 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiList/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./MuiList.js"
2 | export * from "./components/index.js"
3 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiNavigation/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MuiNavigation } from "./MuiNavigation.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiNoSchemas/MuiNoSchemas.tsx:
--------------------------------------------------------------------------------
1 | import { Grid, Typography } from "@mui/material"
2 | import { Robochicken } from "../../assets"
3 |
4 | function MuiNoSchemas(): JSX.Element {
5 | return (
6 |
7 |
8 |
14 | Welcome to Hatchify!
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
24 | MuiNoSchemas.displayName = "NoSchemas"
25 |
26 | export default MuiNoSchemas
27 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiNoSchemas/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MuiNoSchemas } from "./MuiNoSchemas.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiPagination/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MuiPagination } from "./MuiPagination.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiProvider/DefaultDisplayComponents/DefaultDisplayComponents.tsx:
--------------------------------------------------------------------------------
1 | /** @jsxImportSource @emotion/react */
2 | import { Chip } from "@mui/material"
3 | import { css } from "@emotion/react"
4 |
5 | import type { Relationship as RelationshipType } from "@hatchifyjs/react-ui"
6 |
7 | const styles = {
8 | chip: css`
9 | margin: 0 2.5px;
10 | &:first-of-type {
11 | margin-left: 0;
12 | }
13 | &:last-of-type {
14 | margin-right: 0;
15 | }
16 | `,
17 | }
18 |
19 | export const Relationship: React.FC<{ value: RelationshipType }> = ({
20 | value,
21 | }) =>
22 |
23 | export const RelationshipList: React.FC<{ values: RelationshipType[] }> = ({
24 | values,
25 | }) => (
26 | <>
27 | {values.map((value) => (
28 |
29 | ))}
30 | >
31 | )
32 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiProvider/DefaultDisplayComponents/index.ts:
--------------------------------------------------------------------------------
1 | export { Relationship, RelationshipList } from "./DefaultDisplayComponents.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiProvider/DefaultFieldComponents/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | String,
3 | Number,
4 | Boolean,
5 | Date,
6 | Relationship,
7 | } from "./DefaultFieldComponents.js"
8 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/MuiProvider/index.ts:
--------------------------------------------------------------------------------
1 | export { MuiProvider } from "./MuiProvider.js"
2 |
--------------------------------------------------------------------------------
/packages/design-mui/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./MuiProvider/index.js"
2 | export * from "./MuiDataGrid/index.js"
3 | export * from "./MuiEverything/index.js"
4 | export * from "./MuiFilters/index.js"
5 | export * from "./MuiLayout/index.js"
6 | export * from "./MuiList/index.js"
7 | export * from "./MuiPagination/index.js"
8 |
--------------------------------------------------------------------------------
/packages/design-mui/src/design-mui.ts:
--------------------------------------------------------------------------------
1 | import { MuiProvider } from "./components/index.js"
2 |
3 | export {
4 | MuiDataGrid as DataGrid,
5 | MuiList as List,
6 | MuiPagination as Pagination,
7 | MuiFilters as Filters,
8 | } from "./components/index.js"
9 |
10 | export default MuiProvider
11 |
--------------------------------------------------------------------------------
/packages/design-mui/src/services/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/packages/design-mui/src/services/index.ts
--------------------------------------------------------------------------------
/packages/design-mui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ESNext",
4 | "moduleResolution": "bundler", // uses "bundler" as this library is currently built with vite
5 | "jsx": "react-jsx",
6 | "outDir": "dist"
7 | },
8 | "extends": "../../tsconfig.json",
9 | "include": ["src"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/design-mui/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { defineConfig } from "vite"
3 | import dts from "vite-plugin-dts"
4 | import react from "@vitejs/plugin-react"
5 | import nodeExternals from "rollup-plugin-node-externals"
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | build: {
10 | lib: {
11 | entry: "src/design-mui.ts",
12 | formats: ["es"],
13 | },
14 | },
15 | esbuild: {
16 | supported: {
17 | "top-level-await": true,
18 | },
19 | },
20 | plugins: [{ ...nodeExternals(), enforce: "pre" }, dts(), react()],
21 | test: {
22 | globals: true,
23 | environment: "jsdom",
24 | },
25 | })
26 |
--------------------------------------------------------------------------------
/packages/express/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignore-dirs:
2 | - /dist
3 | ignores:
4 | - "@types/*"
5 | - json-api-serializer
6 | - pg
7 | - ts-jest
8 |
--------------------------------------------------------------------------------
/packages/express/.gitignore:
--------------------------------------------------------------------------------
1 | **/*.log
2 | docs
3 |
--------------------------------------------------------------------------------
/packages/express/.prettierignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
3 | docs
4 |
--------------------------------------------------------------------------------
/packages/express/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectCoverageFrom": ["/src/**/*.ts"],
3 | "coverageDirectory": "../../coverage/express",
4 | "coverageReporters": ["html", "text", "text-summary"],
5 | "coverageThreshold": {
6 | "global": {
7 | "branches": 76,
8 | "functions": 75,
9 | "lines": 87,
10 | "statements": 84
11 | }
12 | },
13 | "extensionsToTreatAsEsm": [".ts"],
14 | "moduleNameMapper": {
15 | "^(\\.{1,2}/.*)\\.js$": "$1"
16 | },
17 | "modulePathIgnorePatterns": ["/src/testing"],
18 | "roots": ["/src"],
19 | "testEnvironment": "node",
20 | "transform": {
21 | ".+\\.ts$": [
22 | "ts-jest",
23 | {
24 | "useESM": true
25 | }
26 | ]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/express/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "moduleResolution": "NodeNext",
5 | "rootDir": "src",
6 | "outDir": "dist",
7 | "sourceMap": true,
8 | "strict": true,
9 | "strictNullChecks": true
10 | },
11 | "include": ["src/**/*"]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/koa/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignore-dirs:
2 | - /dist
3 | ignores:
4 | - "@types/*"
5 | - json-api-serializer
6 | - pg
7 | - ts-jest
8 |
--------------------------------------------------------------------------------
/packages/koa/.gitignore:
--------------------------------------------------------------------------------
1 | **/*.log
2 | docs
3 |
--------------------------------------------------------------------------------
/packages/koa/.prettierignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
3 | docs
4 |
--------------------------------------------------------------------------------
/packages/koa/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectCoverageFrom": ["/src/**/*.ts"],
3 | "coverageDirectory": "../../coverage/koa",
4 | "coverageReporters": ["html", "text", "text-summary"],
5 | "coverageThreshold": {
6 | "global": {
7 | "branches": 91,
8 | "functions": 94,
9 | "lines": 91,
10 | "statements": 91
11 | }
12 | },
13 | "extensionsToTreatAsEsm": [".ts"],
14 | "moduleNameMapper": {
15 | "^(\\.{1,2}/.*)\\.js$": "$1"
16 | },
17 | "modulePathIgnorePatterns": ["/src/testing"],
18 | "roots": ["/src"],
19 | "testEnvironment": "node",
20 | "transform": {
21 | ".+\\.ts$": [
22 | "ts-jest",
23 | {
24 | "useESM": true
25 | }
26 | ]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/koa/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "moduleResolution": "NodeNext",
5 | "rootDir": "src",
6 | "outDir": "dist",
7 | "sourceMap": true,
8 | "strict": true,
9 | "strictNullChecks": true,
10 | "resolvePackageJsonImports": true
11 | },
12 | "include": ["src/**/*"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/koa/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://typedoc.org/schema.json",
3 | "entryPoints": ["./src/exports.ts"],
4 | "out": "docs",
5 | "theme": "default",
6 | "name": "@hatchifyjs/koa",
7 | "includeVersion": true,
8 | "disableSources": true,
9 | "githubPages": true,
10 | "media": "./media",
11 | "navigationLinks": {
12 | "Bitovi": "https://bitovi.com",
13 | "GitHub": "https://github.com/bitovi/hatchify"
14 | },
15 | "sidebarLinks": {
16 | "Confluence": "https://bitovi.atlassian.net/wiki/spaces/SCAFFOLD/overview",
17 | "Jira Software": "https://bitovi.atlassian.net/browse/SCAFFOLD"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/node/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignore-dirs:
2 | - /dist
3 | ignores:
4 | - "@types/*"
5 | - ts-jest
6 |
--------------------------------------------------------------------------------
/packages/node/.gitignore:
--------------------------------------------------------------------------------
1 | **/*.log
2 |
--------------------------------------------------------------------------------
/packages/node/.prettierignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
3 |
--------------------------------------------------------------------------------
/packages/node/README.md:
--------------------------------------------------------------------------------
1 | Core NodeJS functionality for Hatchify. This isn't used directly. Instead use [@hatchifyjs/express](https://github.com/bitovi/hatchify/blob/main/docs/express/README.md) or [@hatchifyjs/koa](https://github.com/bitovi/hatchify/blob/main/docs/koa/README.md).
--------------------------------------------------------------------------------
/packages/node/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "collectCoverageFrom": ["/src/**/*.ts"],
3 | "coverageDirectory": "../../coverage/node",
4 | "coverageReporters": ["html", "text", "text-summary"],
5 | "coverageThreshold": {
6 | "global": {
7 | "branches": 57,
8 | "functions": 55,
9 | "lines": 57,
10 | "statements": 57
11 | }
12 | },
13 | "extensionsToTreatAsEsm": [".ts"],
14 | "moduleNameMapper": {
15 | "^(\\.{1,2}/.*)\\.js$": "$1"
16 | },
17 | "roots": ["/src"],
18 | "setupFilesAfterEnv": ["./src/jest.setup.ts"],
19 | "testEnvironment": "node",
20 | "transform": {
21 | ".+\\.ts$": [
22 | "ts-jest",
23 | {
24 | "useESM": true
25 | }
26 | ]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/node/src/error/constants.ts:
--------------------------------------------------------------------------------
1 | export enum codes {
2 | ERR_CONFLICT = "resource-conflict-occurred",
3 | ERR_INVALID_PARAMETER = "invalid-parameter",
4 | ERR_NOT_FOUND = "not-found",
5 | ERR_DATABASE_ERROR = "database-error",
6 | ERR_SERVER_ERROR = "server-error",
7 | ERR_VALUE_REQUIRED = "value-required",
8 | ERR_UNEXPECTED_VALUE = "unexpected-value",
9 | ERR_RELATIONSHIP_PATH = "relationship-path",
10 | }
11 |
12 | export enum statusCodes {
13 | BAD_REQUEST = 400,
14 | NOT_FOUND = 404,
15 | CONFLICT = 409,
16 | UNPROCESSABLE_ENTITY = 422,
17 | INTERNAL_SERVER_ERROR = 500,
18 | }
19 |
--------------------------------------------------------------------------------
/packages/node/src/error/handlers/hatchifyErrorHandler.ts:
--------------------------------------------------------------------------------
1 | import { HatchifyError } from "../types/index.js"
2 | import type { HatchifyErrorOptions } from "../types/index.js"
3 |
4 | export function hatchifyErrorHandler(
5 | error: HatchifyErrorOptions,
6 | ): HatchifyError {
7 | console.error("Uncaught Hatchify Error:", error)
8 |
9 | if (error instanceof HatchifyError) {
10 | return error
11 | }
12 |
13 | return new HatchifyError(error)
14 | }
15 |
--------------------------------------------------------------------------------
/packages/node/src/error/handlers/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./errorResponseHandler.js"
2 |
--------------------------------------------------------------------------------
/packages/node/src/error/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./constants.js"
2 | export * from "./handlers/index.js"
3 | export * from "./types/index.js"
4 |
--------------------------------------------------------------------------------
/packages/node/src/error/types/ConflictError.ts:
--------------------------------------------------------------------------------
1 | import { HatchifyError } from "./HatchifyError.js"
2 | import { codes, statusCodes } from "../constants.js"
3 |
4 | export class ConflictError extends HatchifyError {
5 | constructor({ pointer }: { pointer: string }) {
6 | super({
7 | title: "Conflict",
8 | code: codes.ERR_CONFLICT,
9 | status: statusCodes.CONFLICT,
10 | pointer,
11 | })
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/node/src/error/types/NotFoundError.ts:
--------------------------------------------------------------------------------
1 | import { HatchifyError } from "./HatchifyError.js"
2 | import type { HatchifyErrorOptions } from "./HatchifyError.js"
3 | import { codes, statusCodes } from "../constants.js"
4 |
5 | export class NotFoundError extends HatchifyError {
6 | constructor({
7 | detail,
8 | parameter,
9 | pointer,
10 | }: Pick) {
11 | super({
12 | code: codes.ERR_NOT_FOUND,
13 | status: statusCodes.NOT_FOUND,
14 | title: "Resource not found.",
15 | detail,
16 | parameter,
17 | pointer,
18 | })
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/node/src/error/types/RelationshipPathError.ts:
--------------------------------------------------------------------------------
1 | import { HatchifyError } from "./HatchifyError.js"
2 | import { codes, statusCodes } from "../constants.js"
3 |
4 | export class RelationshipPathError extends HatchifyError {
5 | constructor({
6 | detail,
7 | parameter,
8 | pointer,
9 | }: {
10 | detail?: string
11 | parameter?: string
12 | pointer?: string
13 | } = {}) {
14 | super({
15 | status: statusCodes.BAD_REQUEST,
16 | code: codes.ERR_RELATIONSHIP_PATH,
17 | title: "Relationship path could not be identified.",
18 | detail,
19 | parameter,
20 | pointer,
21 | })
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/node/src/error/types/UnexpectedValueError.ts:
--------------------------------------------------------------------------------
1 | import { HatchifyError } from "./HatchifyError.js"
2 | import { codes, statusCodes } from "../constants.js"
3 |
4 | export class UnexpectedValueError extends HatchifyError {
5 | constructor({
6 | detail,
7 | parameter,
8 | pointer,
9 | }: {
10 | detail?: string
11 | parameter?: string
12 | pointer?: string
13 | }) {
14 | super({
15 | status: statusCodes.UNPROCESSABLE_ENTITY,
16 | code: codes.ERR_UNEXPECTED_VALUE,
17 | title: "Unexpected value.",
18 | detail,
19 | parameter,
20 | pointer,
21 | })
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/node/src/error/types/ValueRequiredError.ts:
--------------------------------------------------------------------------------
1 | import { HatchifyError } from "./HatchifyError.js"
2 | import type { HatchifyErrorOptions } from "./HatchifyError.js"
3 | import { codes, statusCodes } from "../constants.js"
4 |
5 | export class ValueRequiredError extends HatchifyError {
6 | constructor({
7 | detail,
8 | parameter,
9 | pointer,
10 | }: Pick) {
11 | super({
12 | status: statusCodes.UNPROCESSABLE_ENTITY,
13 | code: codes.ERR_VALUE_REQUIRED,
14 | title: "Payload is missing a required value.",
15 | detail,
16 | parameter,
17 | pointer,
18 | })
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/node/src/error/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./ConflictError.js"
2 | export * from "./HatchifyError.js"
3 | export * from "./NotFoundError.js"
4 | export * from "./RelationshipPathError.js"
5 | export * from "./UnexpectedValueError.js"
6 | export * from "./ValueRequiredError.js"
7 |
--------------------------------------------------------------------------------
/packages/node/src/jest.d.ts:
--------------------------------------------------------------------------------
1 | type OwnMatcher = (
2 | this: jest.MatcherContext,
3 | actual: Error[],
4 | ...params: Params
5 | ) => jest.CustomMatcherResult
6 |
7 | declare global {
8 | namespace jest {
9 | interface ExpectExtendMap {
10 | // A helper for comparing errors array. `toEqual` always returns `true`
11 | toEqualErrors: OwnMatcher<[errors: Error[]]>
12 | }
13 |
14 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
15 | interface Matchers {
16 | toEqualErrors(errors: Error[]): T
17 | }
18 | }
19 | }
20 |
21 | export {}
22 |
--------------------------------------------------------------------------------
/packages/node/src/jest.setup.ts:
--------------------------------------------------------------------------------
1 | function toPojo(error: Error) {
2 | return Object.entries(error).reduce(
3 | (acc, [key, value]) => ({ ...acc, [key]: value }),
4 | {},
5 | )
6 | }
7 |
8 | expect.extend({
9 | toEqualErrors(actual, expected) {
10 | const actualPojos = actual.map(toPojo)
11 | const expectedPojos = expected.map(toPojo)
12 | const stringifiedActual = JSON.stringify(actualPojos)
13 | const stringifiedExpected = JSON.stringify(expectedPojos)
14 | const pass = stringifiedActual === stringifiedExpected
15 |
16 | return {
17 | pass,
18 | message: pass
19 | ? () =>
20 | `expected ${stringifiedActual} not to equal ${stringifiedExpected}`
21 | : () => `expected ${stringifiedActual} to equal ${stringifiedExpected}`,
22 | }
23 | },
24 | })
25 |
26 | export {}
27 |
--------------------------------------------------------------------------------
/packages/node/src/parse/IsValidInclude.ts:
--------------------------------------------------------------------------------
1 | import type { FinalSchema } from "@hatchifyjs/core"
2 |
3 | import type { ModelFunctionsCollection } from "../types.js"
4 |
5 | export function isValidInclude(
6 | schemaName: string,
7 | include: string[],
8 | allSchemas: ModelFunctionsCollection,
9 | ): boolean {
10 | if (!allSchemas[schemaName]) {
11 | return false
12 | }
13 |
14 | if (include.length === 0) {
15 | return false
16 | }
17 |
18 | if (include.length === 1) {
19 | return !!allSchemas[schemaName].relationships?.[include[0]]
20 | }
21 |
22 | if (!allSchemas[schemaName].relationships?.[include[0]]) {
23 | return false
24 | }
25 |
26 | return isValidInclude(
27 | allSchemas[schemaName].relationships?.[include[0]].targetSchema as string,
28 | include.slice(1),
29 | allSchemas,
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/packages/node/src/parse/body.ts:
--------------------------------------------------------------------------------
1 | import coBody from "co-body"
2 |
3 | export const parseHatchifyBody = coBody
4 |
--------------------------------------------------------------------------------
/packages/node/src/parse/getColumnName.spec.ts:
--------------------------------------------------------------------------------
1 | import { getColumnName } from "./getColumnName.js"
2 |
3 | describe("getColumnName", () => {
4 | it("escapes the column name without dialect", () => {
5 | expect(getColumnName("word")).toBe("word")
6 | expect(getColumnName("twoWords")).toBe("two_words")
7 | expect(getColumnName("itemA.itemB")).toBe("itemA.item_b")
8 | })
9 |
10 | it("escapes the column name for SQLite", () => {
11 | expect(getColumnName("word", "sqlite")).toBe("word")
12 | expect(getColumnName("twoWords", "sqlite")).toBe("two_words")
13 | expect(getColumnName("itemA.itemB", "sqlite")).toBe("`itemA.item_b`")
14 | })
15 |
16 | it("escapes the column name for Postgres", () => {
17 | expect(getColumnName("word", "postgres")).toBe("word")
18 | expect(getColumnName("twoWords", "postgres")).toBe("two_words")
19 | expect(getColumnName("itemA.itemB", "postgres")).toBe(`"itemA.item_b"`)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/packages/node/src/parse/getColumnName.ts:
--------------------------------------------------------------------------------
1 | import { snakeCase } from "lodash-es"
2 | import type { Dialect } from "sequelize"
3 |
4 | export function getColumnName(
5 | attributeName: string,
6 | escapeStyle?: Dialect,
7 | ): string {
8 | if (!attributeName.includes(".")) {
9 | return snakeCase(attributeName)
10 | }
11 |
12 | const columnName = attributeName
13 | .replaceAll("$", "")
14 | .split(".")
15 | .map((part, index, { length }) =>
16 | index === length - 1 ? snakeCase(part) : part,
17 | )
18 | .join(".")
19 |
20 | if (escapeStyle === "sqlite") {
21 | return `\`${columnName}\``
22 | }
23 | if (escapeStyle === "postgres") {
24 | return `"${columnName}"`
25 | }
26 | return columnName
27 | }
28 |
--------------------------------------------------------------------------------
/packages/node/src/parse/isPathIncluded.ts:
--------------------------------------------------------------------------------
1 | export function isPathIncluded(
2 | flatIncludes: string[],
3 | relationshipPath: string[],
4 | ): boolean {
5 | if (relationshipPath.length < 2) {
6 | return true
7 | }
8 |
9 | if (
10 | !flatIncludes.includes(
11 | relationshipPath.slice(0, relationshipPath.length - 1).join("."),
12 | )
13 | ) {
14 | return false
15 | }
16 |
17 | if (relationshipPath.length === 2) {
18 | return true
19 | }
20 |
21 | return isPathIncluded(
22 | flatIncludes,
23 | relationshipPath.slice(0, relationshipPath.length - 1),
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/packages/node/src/parse/isValidAttribute.ts:
--------------------------------------------------------------------------------
1 | import type { FinalSchema } from "@hatchifyjs/core"
2 |
3 | import type { ModelFunctionsCollection } from "../types.js"
4 |
5 | export function isValidAttribute(
6 | schemaName: string,
7 | relationshipPath: string[],
8 | allSchemas: ModelFunctionsCollection,
9 | ): boolean {
10 | if (!allSchemas[schemaName]) {
11 | return false
12 | }
13 |
14 | if (relationshipPath.length === 0) {
15 | return false
16 | }
17 |
18 | if (relationshipPath.length === 1) {
19 | return (
20 | relationshipPath[0] === "id" ||
21 | !!allSchemas[schemaName].attributes[relationshipPath[0]]
22 | )
23 | }
24 |
25 | if (!allSchemas[schemaName].relationships?.[relationshipPath[0]]) {
26 | return false
27 | }
28 |
29 | return isValidAttribute(
30 | allSchemas[schemaName].relationships?.[relationshipPath[0]]
31 | .targetSchema as string,
32 | relationshipPath.slice(1),
33 | allSchemas,
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/packages/node/src/parse/walk.ts:
--------------------------------------------------------------------------------
1 | export const walk: (
2 | maybeObj: T | any[],
3 | fn: (key: string | symbol, value: unknown) => void,
4 | ) => T | any[] = (maybeObj, fn) => {
5 | if (Array.isArray(maybeObj)) {
6 | return maybeObj.map((item) => walk(item, fn))
7 | } else if (maybeObj && typeof maybeObj === "object") {
8 | const keys = [
9 | ...Object.getOwnPropertyNames(maybeObj),
10 | ...Object.getOwnPropertySymbols(maybeObj),
11 | ]
12 | const entries = keys.map((k) => [
13 | k,
14 | (maybeObj as { [key: typeof k]: any })[k],
15 | ])
16 | return entries.reduce((acc, [key, value]) => {
17 | const [newVal, newKey = key] =
18 | (fn(key, value) as unknown as [unknown, string | symbol]) ?? []
19 | return {
20 | ...acc,
21 | [newKey]: newVal ?? walk(value, fn),
22 | }
23 | }, {}) as typeof maybeObj
24 | } else {
25 | return maybeObj
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/node/src/schema.ts:
--------------------------------------------------------------------------------
1 | import type { FinalSchema } from "@hatchifyjs/core"
2 |
3 | import type { Hatchify } from "./node.js"
4 | import { HatchifySymbolModel } from "./types.js"
5 |
6 | export function buildSchemaForModel(
7 | hatchify: Hatchify,
8 | modelName: string,
9 | ): FinalSchema {
10 | return hatchify.model[modelName]?.[HatchifySymbolModel]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/node/src/sequelize/customTypes/CustomDecimal.ts:
--------------------------------------------------------------------------------
1 | import { HatchifyCoerceError } from "@hatchifyjs/core"
2 | import { DataTypes } from "sequelize"
3 |
4 | // @ts-expect-error
5 | export class CustomDecimal extends DataTypes.DECIMAL {
6 | static parse(value: string): number {
7 | const parsed = parseFloat(value)
8 |
9 | if ([Infinity, -Infinity].includes(parsed)) {
10 | throw new HatchifyCoerceError(
11 | "Retrieved number is outside of the JavaScript number range",
12 | )
13 | }
14 |
15 | return parsed
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/node/src/sequelize/customTypes/CustomInteger.ts:
--------------------------------------------------------------------------------
1 | import { HatchifyCoerceError } from "@hatchifyjs/core"
2 | import { DataTypes } from "sequelize"
3 |
4 | // @ts-expect-error
5 | export class CustomInteger extends DataTypes.INTEGER {
6 | static parse(value: string): number {
7 | const parsed = parseInt(value)
8 |
9 | if (parsed < Number.MIN_SAFE_INTEGER || parsed > Number.MAX_SAFE_INTEGER) {
10 | throw new HatchifyCoerceError(
11 | "Retrieved number is outside of the JavaScript safe integer range",
12 | )
13 | }
14 |
15 | return parsed
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/node/src/sequelize/getSequelizeSchemaName.spec.ts:
--------------------------------------------------------------------------------
1 | import { getSequelizeSchemaName } from "./getSequelizeSchemaName.js"
2 |
3 | describe("getSequelizeSchemaName", () => {
4 | it("is always undefined for sqlite", () => {
5 | expect(getSequelizeSchemaName("sqlite", "schemaName")).toBeUndefined()
6 | expect(getSequelizeSchemaName("sqlite", "")).toBeUndefined()
7 | expect(getSequelizeSchemaName("sqlite", undefined)).toBeUndefined()
8 | expect(
9 | getSequelizeSchemaName("sqlite", null as unknown as string),
10 | ).toBeUndefined()
11 | })
12 |
13 | it("is always defined for postgres", () => {
14 | expect(getSequelizeSchemaName("postgres", "schemaName")).toBe("schema_name")
15 | expect(getSequelizeSchemaName("postgres", "")).toBe("public")
16 | expect(getSequelizeSchemaName("postgres", undefined)).toBe("public")
17 | expect(getSequelizeSchemaName("postgres", null as unknown as string)).toBe(
18 | "public",
19 | )
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/packages/node/src/sequelize/getSequelizeSchemaName.ts:
--------------------------------------------------------------------------------
1 | import { snakeCase } from "lodash-es"
2 | import type { Dialect } from "sequelize"
3 |
4 | export function getSequelizeSchemaName(
5 | dialect: Dialect,
6 | namespace?: string,
7 | ): string | undefined {
8 | if (dialect !== "postgres") {
9 | return undefined
10 | }
11 |
12 | if (!namespace) {
13 | return "public"
14 | }
15 |
16 | return snakeCase(namespace)
17 | }
18 |
--------------------------------------------------------------------------------
/packages/node/src/sequelize/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./convertHatchifyModels.js"
2 | export * from "./createSequelizeInstance.js"
3 |
--------------------------------------------------------------------------------
/packages/node/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "rootDir": "src",
7 | "outDir": "dist",
8 | "sourceMap": true,
9 | "strict": true,
10 | "strictNullChecks": true
11 | },
12 | "exclude": ["dist/*"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/react-jsonapi/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignores: ["@types/react", "@vitest/coverage-c8", "@hatchifyjs/rest-client"]
2 |
--------------------------------------------------------------------------------
/packages/react-jsonapi/README.md:
--------------------------------------------------------------------------------
1 | # @hatchifyjs/react-jsonapi
2 |
3 | `@hatchifyjs/react-jsonapi` is part of the [Hatchify](https://github.com/bitovi/hatchify) toolkit and provides data models for React.
4 |
5 | For more information, see [`@hatchifyjs/react-jsonapi`'s documentation page](https://github.com/bitovi/hatchify/blob/main/docs/react-jsonapi/README.md)
6 |
--------------------------------------------------------------------------------
/packages/react-jsonapi/src/react-jsonapi.tsx:
--------------------------------------------------------------------------------
1 | export type {
2 | CreateType,
3 | FlatCreateType,
4 | FlatUpdateType,
5 | RecordType,
6 | UpdateType,
7 | } from "@hatchifyjs/rest-client"
8 | export { default as createJsonapiClient } from "@hatchifyjs/rest-client-jsonapi"
9 | export { default as hatchifyReactRest } from "@hatchifyjs/react-rest"
10 |
--------------------------------------------------------------------------------
/packages/react-jsonapi/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-jsx",
4 | "outDir": "dist",
5 | "rootDir": "src"
6 | },
7 | "extends": "../../tsconfig.json",
8 | "include": ["src"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/react-rest/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignores: ["@vitest/coverage-c8", "@types/react"]
2 |
--------------------------------------------------------------------------------
/packages/react-rest/doc/attachments/relationships.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/packages/react-rest/doc/attachments/relationships.png
--------------------------------------------------------------------------------
/packages/react-rest/doc/attachments/todo-app-with-relationships.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/packages/react-rest/doc/attachments/todo-app-with-relationships.png
--------------------------------------------------------------------------------
/packages/react-rest/doc/attachments/todo-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/packages/react-rest/doc/attachments/todo-app.png
--------------------------------------------------------------------------------
/packages/react-rest/doc/attachments/ts.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/packages/react-rest/doc/attachments/ts.gif
--------------------------------------------------------------------------------
/packages/react-rest/doc/attachments/ui.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/packages/react-rest/doc/attachments/ui.gif
--------------------------------------------------------------------------------
/packages/react-rest/src/react-rest.ts:
--------------------------------------------------------------------------------
1 | import { hatchifyReactRest } from "./services/index.js"
2 |
3 | export type { HatchifyReactRest } from "./services/index.js"
4 |
5 | export {
6 | useCreateOne,
7 | useDeleteOne,
8 | useAll,
9 | useOne,
10 | useUpdateOne,
11 | } from "./services/index.js"
12 |
13 | export default hatchifyReactRest
14 |
--------------------------------------------------------------------------------
/packages/react-rest/src/services/hatchifyReactRest/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./hatchifyReactRest.js"
2 |
--------------------------------------------------------------------------------
/packages/react-rest/src/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./hatchifyReactRest/index.js"
2 | export * from "./useCreateOne/index.js"
3 | export * from "./useDeleteOne/index.js"
4 | export * from "./useAll/index.js"
5 | export * from "./useOne/index.js"
6 | export * from "./useUpdateOne/index.js"
7 |
--------------------------------------------------------------------------------
/packages/react-rest/src/services/useAll/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useAll.js"
2 |
--------------------------------------------------------------------------------
/packages/react-rest/src/services/useCreateOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useCreateOne.js"
2 |
--------------------------------------------------------------------------------
/packages/react-rest/src/services/useDeleteOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useDeleteOne.js"
2 |
--------------------------------------------------------------------------------
/packages/react-rest/src/services/useOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useOne.js"
2 |
--------------------------------------------------------------------------------
/packages/react-rest/src/services/useUpdateOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useUpdateOne.js"
2 |
--------------------------------------------------------------------------------
/packages/react-rest/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/react-rest/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src"
6 | },
7 | "include": ["src"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/react-rest/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite"
2 | import dts from "vite-plugin-dts"
3 | import nodeExternals from "rollup-plugin-node-externals"
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | build: {
8 | lib: {
9 | entry: "src/react-rest.ts",
10 | formats: ["es", "cjs"],
11 | },
12 | },
13 | plugins: [{ ...nodeExternals(), enforce: "pre" }, dts()],
14 | })
15 |
--------------------------------------------------------------------------------
/packages/react-ui/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignores: ["@vitest/coverage-c8", "@hatchifyjs/core", "@testing-library/jest-dom"]
2 |
--------------------------------------------------------------------------------
/packages/react-ui/README.md:
--------------------------------------------------------------------------------
1 | # @hatchifyjs/react-ui
2 |
3 | React components that tie together design-\* components with React Rest data-fetching.
4 |
5 | This isn't used directly. Use [`@hatchifyjs/react`](https://github.com/bitovi/hatchify/blob/main/docs/react/README.md) instead.
6 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyCollection/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/packages/react-ui/src/components/HatchifyCollection/index.ts
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyColumn/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./HatchifyColumn.js"
2 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyDataGrid/index.ts:
--------------------------------------------------------------------------------
1 | export type { HatchifyDataGridProps } from "./HatchifyDataGrid.js"
2 | export {
3 | default as HatchifyDataGrid,
4 | getDefaultInclude,
5 | } from "./HatchifyDataGrid.js"
6 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyEmpty/HatchifyEmpty.tsx:
--------------------------------------------------------------------------------
1 | export type HatchifyEmptyProps = {
2 | children: React.ReactNode
3 | }
4 |
5 | export const HatchifyEmpty: React.FC = () => {
6 | return null
7 | }
8 |
9 | HatchifyEmpty.displayName = "Empty"
10 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyEmpty/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./HatchifyEmpty.js"
2 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyEverything/components/NoSchemas.tsx:
--------------------------------------------------------------------------------
1 | import { useHatchifyPresentation } from "../../HatchifyPresentationProvider/index.js"
2 |
3 | export function NoSchemas(): JSX.Element {
4 | const { Everything } = useHatchifyPresentation()
5 |
6 | return
7 | }
8 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyEverything/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./NoSchemas.js"
2 | export * from "./WithSchemas.js"
3 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyEverything/index.ts:
--------------------------------------------------------------------------------
1 | export { default as HatchifyEverything } from "./HatchifyEverything.js"
2 | export type { HatchifyEverythingProps } from "./HatchifyEverything.js"
3 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyFilters/HatchifyFilters.tsx:
--------------------------------------------------------------------------------
1 | import type { PartialSchema } from "@hatchifyjs/core"
2 | import type { GetSchemaNames } from "@hatchifyjs/rest-client"
3 | import type { XDataGridProps } from "../../react-ui.js"
4 | import { useHatchifyPresentation } from "../index.js"
5 |
6 | function HatchifyFilters<
7 | const TSchemas extends Record,
8 | const TSchemaName extends GetSchemaNames,
9 | >(props: XDataGridProps): JSX.Element {
10 | const { Filters } = useHatchifyPresentation()
11 | return
12 | }
13 |
14 | export default HatchifyFilters
15 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyFilters/index.ts:
--------------------------------------------------------------------------------
1 | export { default as HatchifyFilters } from "./HatchifyFilters.js"
2 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyList/HatchifyList.tsx:
--------------------------------------------------------------------------------
1 | import type { PartialSchema } from "@hatchifyjs/core"
2 | import type { GetSchemaNames } from "@hatchifyjs/rest-client"
3 | import type { XDataGridProps } from "../../react-ui.js"
4 | import { useHatchifyPresentation } from "../index.js"
5 |
6 | function HatchifyList<
7 | const TSchemas extends Record,
8 | const TSchemaName extends GetSchemaNames,
9 | >(props: XDataGridProps): JSX.Element {
10 | const { List } = useHatchifyPresentation()
11 | return
12 | }
13 |
14 | export default HatchifyList
15 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyList/index.ts:
--------------------------------------------------------------------------------
1 | export { default as HatchifyList } from "./HatchifyList.js"
2 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyNavigation/HatchifyNavigation.tsx:
--------------------------------------------------------------------------------
1 | import type { FinalSchemas } from "@hatchifyjs/rest-client"
2 | import type { PartialSchema } from "@hatchifyjs/core"
3 | import { useHatchifyPresentation } from "../index.js"
4 |
5 | export interface HatchifyNavigationProps<
6 | TSchemas extends Record,
7 | > {
8 | finalSchemas: FinalSchemas
9 | partialSchemas: TSchemas
10 | activeTab: string | undefined
11 | onTabChange: (schema: string) => void
12 | children?: React.ReactNode | null
13 | }
14 |
15 | function HatchifyNavigation<
16 | const TSchemas extends Record,
17 | >({ ...props }: HatchifyNavigationProps): JSX.Element {
18 | const { Navigation } = useHatchifyPresentation()
19 | return
20 | }
21 |
22 | HatchifyNavigation.displayName = "Navigation"
23 |
24 | export default HatchifyNavigation
25 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyNavigation/index.ts:
--------------------------------------------------------------------------------
1 | export { default as HatchifyNavigation } from "./HatchifyNavigation.js"
2 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyNoSchemas/HatchifyNoSchemas.tsx:
--------------------------------------------------------------------------------
1 | import { useHatchifyPresentation } from "../index.js"
2 |
3 | function HatchifyNoSchemas(): JSX.Element {
4 | const { NoSchemas } = useHatchifyPresentation()
5 | return
6 | }
7 |
8 | HatchifyNoSchemas.displayName = "NoSchemas"
9 |
10 | export default HatchifyNoSchemas
11 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyNoSchemas/index.ts:
--------------------------------------------------------------------------------
1 | export { default as HatchifyNoSchemas } from "./HatchifyNoSchemas.js"
2 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyPagination/HatchifyPagination.tsx:
--------------------------------------------------------------------------------
1 | import type { PartialSchema } from "@hatchifyjs/core"
2 | import type { GetSchemaNames } from "@hatchifyjs/rest-client"
3 | import type { XDataGridProps } from "../../react-ui.js"
4 | import { useHatchifyPresentation } from "../index.js"
5 |
6 | function HatchifyPagination<
7 | const TSchemas extends Record,
8 | const TSchemaName extends GetSchemaNames,
9 | >(props: XDataGridProps): JSX.Element {
10 | const { Pagination } = useHatchifyPresentation()
11 | return
12 | }
13 |
14 | export default HatchifyPagination
15 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyPagination/index.ts:
--------------------------------------------------------------------------------
1 | export { default as HatchifyPagination } from "./HatchifyPagination.js"
2 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyPresentationProvider/DefaultDisplayComponents/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | String,
3 | StringList,
4 | Number,
5 | NumberList,
6 | Boolean,
7 | BooleanList,
8 | Date,
9 | DateList,
10 | Relationship,
11 | RelationshipList,
12 | } from "./DefaultDisplayComponents.js"
13 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyPresentationProvider/DefaultFieldComponents/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | String,
3 | Number,
4 | Boolean,
5 | Date,
6 | Relationship,
7 | } from "./DefaultFieldComponents.js"
8 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/HatchifyPresentationProvider/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | useHatchifyPresentation,
3 | HatchifyPresentationDefaultDisplayComponents,
4 | HatchifyPresentationDefaultFieldComponents,
5 | HatchifyPresentationProvider,
6 | } from "./HatchifyPresentationProvider.js"
7 | export type {
8 | DefaultDisplayComponentsTypes,
9 | DefaultFieldComponentsTypes,
10 | } from "./HatchifyPresentationProvider.js"
11 |
--------------------------------------------------------------------------------
/packages/react-ui/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./HatchifyDataGrid/index.js"
2 | export * from "./HatchifyPresentationProvider/index.js"
3 | export * from "./HatchifyEmpty/index.js"
4 |
--------------------------------------------------------------------------------
/packages/react-ui/src/hatchifyReact/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./hatchifyReact.js"
2 |
--------------------------------------------------------------------------------
/packages/react-ui/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { default as useDataGridState } from "./useDataGridState.js"
2 | export { default as useCompoundComponents } from "./useCompoundComponents/useCompoundComponents.js"
3 | export { default as usePage } from "./usePage.js"
4 | export { default as useSelected } from "./useSelected.js"
5 | export { default as useSort } from "./useSort.js"
6 | export type { HatchifyColumn } from "./useCompoundComponents/useCompoundComponents.js"
7 | export type { DataGridState } from "./useDataGridState.js"
8 |
--------------------------------------------------------------------------------
/packages/react-ui/src/hooks/useCompoundComponents/helpers/getEmptyList.test.tsx:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest"
2 | import { getEmptyList } from "./index.js"
3 |
4 | describe("hooks/useCompoundComponents/helpers/getEmptyList", () => {
5 | it.todo("works when valid children are passed", () => {
6 | // When using `HatchifyEmpty` in this test, it is filtered out because in
7 | // the test environment its name property is resolving to `HatchifyEmpty`
8 | // and not `Empty`. This is not an issue when running outside of a test
9 | // environment.
10 | })
11 |
12 | it("falls back to default EmptyList", () => {
13 | const EmptyList = getEmptyList([])
14 |
15 | expect(EmptyList).toBeInstanceOf(Function)
16 | expect(EmptyList()).toMatchInlineSnapshot(`
17 |
18 | No records found
19 |
20 | `)
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/packages/react-ui/src/hooks/useCompoundComponents/helpers/getEmptyList.tsx:
--------------------------------------------------------------------------------
1 | export function getEmptyList(childArray: JSX.Element[]): () => JSX.Element {
2 | const emptyComponent = childArray.find(
3 | (c) => c.type.name === "Empty" || c.type.displayName === "Empty",
4 | )
5 | const emptyDisplay: JSX.Element = emptyComponent?.props.children || undefined
6 |
7 | const EmptyList = () => emptyDisplay || No records found
8 |
9 | return EmptyList
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-ui/src/hooks/useCompoundComponents/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./getColumn.js"
2 | export * from "./getColumns.js"
3 | export * from "./getColumnsFromSchema.js"
4 | export * from "./getDefaultDataRender.js"
5 | export * from "./getEmptyList.js"
6 |
--------------------------------------------------------------------------------
/packages/react-ui/src/hooks/useFilter.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest"
2 | import { renderHook, waitFor } from "@testing-library/react"
3 | import useFilter from "./useFilter.js"
4 |
5 | describe("usePage", () => {
6 | it("works", async () => {
7 | const { result } = renderHook(() => useFilter())
8 |
9 | expect(result.current.filter).toEqual(undefined)
10 |
11 | await waitFor(() => {
12 | result.current.setFilter([
13 | { field: "name", value: "Walk the dog", operator: "$eq" },
14 | ])
15 | })
16 |
17 | expect(result.current.filter).toEqual([
18 | { field: "name", value: "Walk the dog", operator: "$eq" },
19 | ])
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/packages/react-ui/src/hooks/useFilter.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import type { Filters } from "@hatchifyjs/rest-client"
3 | import type { HatchifyDataGridFilters } from "../presentation/index.js"
4 |
5 | export default function useFilter(): HatchifyDataGridFilters {
6 | const [filter, setFilter] = useState(undefined)
7 |
8 | return {
9 | filter,
10 | setFilter,
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/react-ui/src/hooks/usePage.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest"
2 | import { renderHook, waitFor } from "@testing-library/react"
3 | import usePage from "./usePage.js"
4 |
5 | describe("usePage", () => {
6 | it("works", async () => {
7 | const { result } = renderHook(() => usePage())
8 |
9 | expect(result.current.page).toEqual({
10 | size: 10,
11 | number: 1,
12 | })
13 |
14 | await waitFor(() => {
15 | result.current.setPage({ size: 10, number: 2 })
16 | })
17 |
18 | expect(result.current.page).toEqual({
19 | size: 10,
20 | number: 2,
21 | })
22 |
23 | await waitFor(() => {
24 | result.current.setPage({ size: 15, number: 12 })
25 | })
26 |
27 | expect(result.current.page).toEqual({
28 | size: 15,
29 | number: 12,
30 | })
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/packages/react-ui/src/hooks/usePage.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import type {
3 | HatchifyDataGridPage,
4 | PageCountObject,
5 | } from "../presentation/index.js"
6 | import type { PaginationObject } from "@hatchifyjs/rest-client"
7 |
8 | // todo (future): customizable pagination.size?
9 | // todo (future): page number can come from query string
10 | export default function usePage(
11 | defaultPage?: PaginationObject,
12 | ): HatchifyDataGridPage {
13 | const [page, setPage] = useState(
14 | defaultPage ?? {
15 | number: 1,
16 | size: 10,
17 | },
18 | )
19 |
20 | return {
21 | page,
22 | setPage,
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/react-ui/src/hooks/useSelected.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import type {
3 | HatchifyDataGridSelected,
4 | HatchifyDataGridSelectedState,
5 | } from "../presentation/index.js"
6 |
7 | export default function useSelected(
8 | defaultSelected?: HatchifyDataGridSelectedState,
9 | onSelectedChange?: (selected: HatchifyDataGridSelectedState) => void,
10 | ): HatchifyDataGridSelected {
11 | const [selected, setSelected] = useState(
12 | defaultSelected || { all: false, ids: [] },
13 | )
14 |
15 | const setSelectedWrapper: (
16 | selected: HatchifyDataGridSelectedState,
17 | ) => void = (selected) => {
18 | setSelected(selected)
19 | if (onSelectedChange) {
20 | onSelectedChange(selected)
21 | }
22 | }
23 |
24 | return {
25 | selected,
26 | setSelected: setSelectedWrapper,
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/react-ui/src/presentation/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./interfaces.js"
2 |
--------------------------------------------------------------------------------
/packages/react-ui/src/react-ui.ts:
--------------------------------------------------------------------------------
1 | import { hatchifyReact } from "./hatchifyReact/index.js"
2 |
3 | export type {
4 | CreateType,
5 | FlatCreateType,
6 | FlatUpdateType,
7 | RecordType,
8 | UpdateType,
9 | } from "@hatchifyjs/rest-client"
10 |
11 | export type { HatchifyApp } from "./hatchifyReact/index.js"
12 |
13 | export * from "./components/index.js"
14 | export * from "./presentation/index.js"
15 | export * from "./hooks/index.js"
16 |
17 | export default hatchifyReact
18 |
--------------------------------------------------------------------------------
/packages/react-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-jsx",
4 | },
5 | "extends": "../../tsconfig.json",
6 | "include": ["src"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/react-ui/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { defineConfig } from "vite"
3 | import dts from "vite-plugin-dts"
4 | import react from "@vitejs/plugin-react"
5 | import nodeExternals from "rollup-plugin-node-externals"
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | build: {
10 | lib: {
11 | entry: "src/react-ui.ts",
12 | formats: ["es"],
13 | },
14 | },
15 | plugins: [{ ...nodeExternals(), enforce: "pre" }, dts(), react()],
16 | test: {
17 | globals: true,
18 | environment: "jsdom",
19 | },
20 | })
21 |
--------------------------------------------------------------------------------
/packages/react/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignores: ["@types/react", "@vitest/coverage-c8"]
2 |
--------------------------------------------------------------------------------
/packages/react/README.md:
--------------------------------------------------------------------------------
1 | # @hatchifyjs/react
2 |
3 | `@hatchifyjs/react` is part of the [Hatchify](https://github.com/bitovi/hatchify) toolkit and provides data models and low-code like Components for React.
4 |
5 | For more information, see [`@hatchifyjs/react`'s documentation page](https://github.com/bitovi/hatchify/blob/main/docs/react/README.md)
6 |
--------------------------------------------------------------------------------
/packages/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hatchifyjs/react",
3 | "version": "0.2.3",
4 | "type": "module",
5 | "exports": "./dist/react.js",
6 | "types": "./dist/react.d.ts",
7 | "scripts": {
8 | "prepublish": "npm run clean && npm run build",
9 | "typecheck": "tsc --noEmit",
10 | "lint": "eslint src",
11 | "depcheck": "depcheck .",
12 | "test": "vitest run",
13 | "test:dev": "vitest",
14 | "test:coverage": "vitest run --coverage",
15 | "clean": "rm -rf tsconfig.tsbuildinfo dist",
16 | "build": "tsc"
17 | },
18 | "dependencies": {
19 | "@hatchifyjs/design-mui": "^0.2.2",
20 | "@hatchifyjs/react-ui": "^0.2.2",
21 | "@hatchifyjs/rest-client-jsonapi": "^0.1.41"
22 | },
23 | "devDependencies": {
24 | "@types/react": "^18.2.0",
25 | "jsdom": "^21.1.1",
26 | "vitest": "^0.30.1"
27 | },
28 | "peerDependencies": {
29 | "@hatchifyjs/core": "^0.4.x",
30 | "react": "^18.2.0",
31 | "react-dom": "^18.2.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/react/src/react.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest"
2 |
3 | describe("react", () => it("todo", () => expect(true).toBe(true)))
4 |
--------------------------------------------------------------------------------
/packages/react/src/react.tsx:
--------------------------------------------------------------------------------
1 | export type { PartialSchema } from "@hatchifyjs/core"
2 | export { default as hatchifyReact } from "@hatchifyjs/react-ui"
3 | export type {
4 | DataGridState,
5 | FlatCreateType as CreateType,
6 | FlatUpdateType as UpdateType,
7 | HatchifyApp,
8 | RecordType,
9 | } from "@hatchifyjs/react-ui"
10 | export {
11 | default as HatchifyProvider,
12 | List,
13 | Pagination,
14 | Filters,
15 | DataGrid,
16 | } from "@hatchifyjs/design-mui"
17 | export {
18 | convertToHatchifyResources,
19 | default as createJsonapiClient,
20 | } from "@hatchifyjs/rest-client-jsonapi"
21 |
--------------------------------------------------------------------------------
/packages/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-jsx",
4 | "rootDir": "src",
5 | "outDir": "dist"
6 | },
7 | "extends": "../../tsconfig.json",
8 | "include": ["src"]
9 | }
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignores: ["@vitest/coverage-c8"]
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/README.md:
--------------------------------------------------------------------------------
1 | # @hatchifyjs/rest-client-jsonapi
2 |
3 | Responsible for making network requests to a JSON:API-compliant backend.
4 |
5 | This isn't used directly. Use [`@hatchifyjs/react-jsonapi`](https://github.com/bitovi/hatchify/blob/main/docs/react-jsonapi/README.md) instead.
6 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/mocks/server.ts:
--------------------------------------------------------------------------------
1 | import { setupServer } from "msw/node"
2 | import { handlers } from "./handlers.js"
3 |
4 | export const server = setupServer(...handlers)
5 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/rest-client-jsonapi.ts:
--------------------------------------------------------------------------------
1 | import { jsonapi } from "./services/index.js"
2 |
3 | export {
4 | convertToHatchifyResources,
5 | createOne,
6 | findAll,
7 | findOne,
8 | updateOne,
9 | } from "./services/index.js"
10 | export default jsonapi
11 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/createOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./createOne.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/deleteOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./deleteOne.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/findAll/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./findAll.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/findOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./findOne.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./createOne/index.js"
2 | export * from "./deleteOne/index.js"
3 | export * from "./findAll/index.js"
4 | export * from "./findOne/index.js"
5 | export * from "./jsonapi/index.js"
6 | export * from "./updateOne/index.js"
7 | export * from "./utils/index.js"
8 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/jsonapi/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./jsonapi.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/updateOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./updateOne.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/utils/fetch/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./fetch.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./fetch/index.js"
2 | export * from "./resources/index.js"
3 | export * from "./query/index.js"
4 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/utils/query/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./query.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/services/utils/resources/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./resources.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/rest-client-jsonapi/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "rootDir": "src",
6 | "outDir": "dist"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/rest-client/.depcheckrc:
--------------------------------------------------------------------------------
1 | ignores: ["@vitest/coverage-c8"]
2 |
--------------------------------------------------------------------------------
/packages/rest-client/README.md:
--------------------------------------------------------------------------------
1 | # @hatchifyjs/rest-client
2 |
3 | Data-fetching functions that are not framework-specific.
4 |
5 | This isn't used directly. Use [`@hatchifyjs/react-jsonapi`](https://github.com/bitovi/hatchify/blob/main/docs/react-jsonapi/README.md) instead.
6 |
--------------------------------------------------------------------------------
/packages/rest-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@hatchifyjs/rest-client",
3 | "version": "0.1.29",
4 | "type": "module",
5 | "exports": "./dist/rest-client.js",
6 | "types": "./dist/rest-client.d.ts",
7 | "scripts": {
8 | "prepublish": "npm run clean && npm run build",
9 | "typecheck": "tsc --noEmit",
10 | "lint": "eslint src",
11 | "depcheck": "depcheck .",
12 | "test": "vitest run",
13 | "test:dev": "vitest",
14 | "test:coverage": "vitest run --coverage",
15 | "clean": "rm -rf tsconfig.tsbuildinfo dist",
16 | "build": "tsc"
17 | },
18 | "devDependencies": {
19 | "@types/lodash.isempty": "^4.4.9",
20 | "@vitest/coverage-c8": "^0.32.2",
21 | "vitest": "^0.30.1"
22 | },
23 | "peerDependencies": {
24 | "@hatchifyjs/core": "^0.4.x"
25 | },
26 | "dependencies": {
27 | "lodash.isempty": "^4.4.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./promise/index.js"
2 | export * from "./store/index.js"
3 | export * from "./subscribe/index.js"
4 | export * from "./types/index.js"
5 | export * from "./utils/index.js"
6 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/promise/createOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./createOne.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/promise/deleteOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./deleteOne.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/promise/findAll/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./findAll.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/promise/findOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./findOne.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/promise/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./createOne/index.js"
2 | export * from "./deleteOne/index.js"
3 | export * from "./findAll/index.js"
4 | export * from "./findOne/index.js"
5 | export * from "./updateOne/index.js"
6 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/promise/updateOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./updateOne.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/store/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./store.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/subscribe/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./subscribeToAll/index.js"
2 | export * from "./subscribeToOne/index.js"
3 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/subscribe/subscribeToAll/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./subscribeToAll.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/subscribe/subscribeToAll/subscribeToAll.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi } from "vitest"
2 | import { createStore } from "../../store/index.js"
3 | import { subscribeToAll } from "./subscribeToAll.js"
4 |
5 | describe("rest-client/services/subscribe/subscribeToAll", () => {
6 | it("callback should be called from store subscribers", () => {
7 | const store = createStore(["articles"])
8 | const spy = vi.fn()
9 |
10 | subscribeToAll("articles", undefined, spy)
11 | store.articles.subscribers.forEach((fn) => fn([]))
12 |
13 | expect(spy).toHaveBeenCalledTimes(1)
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/subscribe/subscribeToOne/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./subscribeToOne.js"
2 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/subscribe/subscribeToOne/subscribeToOne.ts:
--------------------------------------------------------------------------------
1 | import { getStore } from "../../store/index.js"
2 | import type { Record, Unsubscribe } from "../../types/index.js"
3 |
4 | /**
5 | * Adds a subscriber to the store for a given schema and id. Will only notify
6 | * the subscriber when the record with the given id is updated.
7 | */
8 | export const subscribeToOne = (
9 | resource: string,
10 | id: string,
11 | onChange: (data: Record) => void,
12 | ): Unsubscribe => {
13 | const store = getStore(resource)
14 |
15 | function notify(data: Record[]) {
16 | const record = data.find((record) => record.id === id)
17 | if (record) {
18 | onChange(record)
19 | }
20 | }
21 |
22 | store.subscribers.push(notify)
23 |
24 | return () => {
25 | store.subscribers = store.subscribers.filter((fn) => fn !== notify)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./data.js"
2 | export * from "./meta.js"
3 | export * from "./query.js"
4 | export * from "./schema.js"
5 | export * from "./rest-client.js"
6 | export * from "./subscribe.js"
7 | export * from "./utility.js"
8 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/types/subscribe.ts:
--------------------------------------------------------------------------------
1 | import type { PartialSchema } from "@hatchifyjs/core"
2 | import type { GetSchemaFromName, GetSchemaNames, RecordType } from "./index.js"
3 |
4 | export type Unsubscribe = () => void
5 |
6 | export type Subscription = <
7 | const TSchemas extends Record,
8 | const TSchemaName extends GetSchemaNames,
9 | >(
10 | data: Array>>,
11 | ) => void
12 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/types/utility.ts:
--------------------------------------------------------------------------------
1 | export type WithRequiredProperty = Type & {
2 | [Property in Key]-?: Type[Property]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./meta.js"
2 | export * from "./records.js"
3 | export * from "./request.js"
4 | export * from "./response.js"
5 | export * from "./schema.js"
6 | export * from "./selector.js"
7 | export * from "./unflatten.js"
8 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/utils/notify.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitovi/hatchify/a47ddb72bf2c0d93931eafa90d996546adf20f83/packages/rest-client/src/services/utils/notify.ts
--------------------------------------------------------------------------------
/packages/rest-client/src/services/utils/schema.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest"
2 | import { schemaNameIsString } from "./schema.js"
3 |
4 | describe("rest-client/services/utils/schema", () => {
5 | describe("scehmaNameIsString", () => {
6 | it("works", () => {
7 | expect(schemaNameIsString("Foo")).toBe(true)
8 | expect(schemaNameIsString("Bar")).toBe(true)
9 | expect(schemaNameIsString(5)).toBe(false)
10 | expect(schemaNameIsString(true)).toBe(false)
11 | })
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/packages/rest-client/src/services/utils/schema.ts:
--------------------------------------------------------------------------------
1 | export function schemaNameIsString(schemaName: unknown): schemaName is string {
2 | return typeof schemaName === "string"
3 | }
4 |
5 | export class SchemaNameNotStringError extends Error {
6 | constructor(schemaName: unknown) {
7 | super(`Expected schemaName to be a string, received ${typeof schemaName}`)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/rest-client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "allowJs": true,
6 | "rootDir": "src",
7 | "outDir": "dist"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/scripts/check-packages.ts:
--------------------------------------------------------------------------------
1 | import semver from "semver"
2 |
3 | import packages from "./lib/packages"
4 |
5 | for (const pkg of Object.values(packages)) {
6 | if (!semver.valid(pkg.version)) {
7 | console.error(`${pkg.name} - Invalid version: ${pkg.version}`)
8 | }
9 |
10 | if ("private" in pkg) {
11 | console.error(`${pkg.name} - Package should not be private.`)
12 | }
13 |
14 | if (pkg.dependencies) {
15 | for (const [name, range] of Object.entries(pkg.dependencies)) {
16 | if (packages[name]) {
17 | if (!semver.satisfies(packages[name].version, range)) {
18 | console.error(`${pkg.name} - Unsatisfied package: ${name}@${range}`)
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/scripts/lib/packages.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs"
2 | import path from "path"
3 |
4 | interface Package {
5 | name: string
6 | private?: boolean
7 | version: string
8 | dependencies?: Record
9 | devDependencies?: Record
10 | }
11 |
12 | const packagesPath = path.join(__dirname, "..", "..", "packages")
13 |
14 | const list = fs
15 | .readdirSync(packagesPath)
16 | .map((name) => path.join(packagesPath, name, "package.json"))
17 | .filter((pkg) => fs.existsSync(pkg))
18 | .map((pkg) => JSON.parse(fs.readFileSync(pkg, "utf-8")) as Package)
19 |
20 | const packages: Record = {}
21 | export default packages
22 |
23 | for (const pkg of list) {
24 | packages[pkg.name] = pkg
25 | }
26 |
--------------------------------------------------------------------------------
/scripts/pree2e.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | cd e2e;
3 | rm -rf hatchify-app;
4 | node ../packages/create/index.js hatchify-app \
5 | --frontend ${npm_config_frontend} \
6 | --backend ${npm_config_backend} \
7 | --database ${npm_config_database} \
8 | --path ../../packages;
9 | cp schemas.txt hatchify-app/schemas.ts;
10 | cp App.txt hatchify-app/frontend/App.tsx;
11 | cp main.txt hatchify-app/main.tsx;
12 |
--------------------------------------------------------------------------------
/scripts/updategs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | cd example;
3 | rm -rf getting-started;
4 | node ../packages/create/index.js getting-started \
5 | --frontend=react \
6 | --backend=koa \
7 | --database=sqlite://localhost/:memory
8 |
--------------------------------------------------------------------------------