├── .github ├── FUNDING.yml ├── commit-convention.md └── workflows │ ├── nightly.yml │ ├── pr-title.yml │ ├── release-notes.yml │ └── test.yml ├── .gitignore ├── .npmrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── database-branch-create.png ├── database-branches.png ├── database-bulk-edit.png ├── database-reference-edit.png ├── database-reference.png ├── database-select-many.png ├── database.png ├── factory-create-instances.png ├── factory-faker.png ├── factory-toggle-location.png ├── factory.png ├── graphql-playground.png ├── history.png ├── home.png ├── pubsub.png ├── resolvers.png ├── rest-playground.png ├── script.png └── snapshot.png ├── eslint.config.js ├── logo.svg ├── og.png ├── package.json ├── packages ├── app │ ├── .env.playground │ ├── .env.playground.rest │ ├── .gitignore │ ├── README.md │ ├── app.config.ts │ ├── app.vue │ ├── assets │ │ ├── css │ │ │ └── floating-vue.css │ │ └── moquerie.svg │ ├── components │ │ ├── AppHeader.vue │ │ ├── AppNav.vue │ │ ├── AppNavItem.vue │ │ ├── ConfirmModal.vue │ │ ├── ErrorBoundary.vue │ │ ├── ErrorMessage.vue │ │ ├── ErrorMessageSimple.vue │ │ ├── ExplanationView.vue │ │ ├── IframeKeepAlive.vue │ │ ├── InfoTooltip.vue │ │ ├── KbShortcut.vue │ │ ├── LinkList.vue │ │ ├── LinkListItem.vue │ │ ├── MonacoDiffEditor.vue │ │ ├── MonacoEditor.vue │ │ ├── RadioButtonGroup.vue │ │ ├── ServerInfo.vue │ │ ├── SimpleModal.vue │ │ ├── SplitPane.vue │ │ ├── ValuePreview.vue │ │ ├── VerticalButton.vue │ │ ├── branch │ │ │ ├── BranchCreateForm.vue │ │ │ ├── BranchSelector.vue │ │ │ └── branchOverlays.ts │ │ ├── command-palette │ │ │ ├── CommandPaletteContent.vue │ │ │ └── CommandPaletteModal.vue │ │ ├── factory │ │ │ ├── FactoryFakerInput.vue │ │ │ ├── FactoryField.vue │ │ │ ├── FactoryFieldInput.vue │ │ │ ├── FactoryFieldSummary.vue │ │ │ ├── FactoryFields.vue │ │ │ ├── FactoryForm.vue │ │ │ ├── FactoryInfo.vue │ │ │ ├── FactoryList.vue │ │ │ ├── FactoryListItem.vue │ │ │ ├── FactoryPreview.vue │ │ │ └── formTypes.ts │ │ ├── form │ │ │ ├── FormActions.vue │ │ │ └── FormGroupLocationInput.vue │ │ ├── history │ │ │ ├── HistoryRecordList.vue │ │ │ ├── HistoryRecordListItem.vue │ │ │ ├── HistoryRecordView.vue │ │ │ └── icons.ts │ │ ├── monaco.ts │ │ ├── resolver │ │ │ ├── ResolverList.vue │ │ │ ├── ResolverListItem.vue │ │ │ ├── ResolverResourceList.vue │ │ │ └── ResolverResourceListItem.vue │ │ ├── resource │ │ │ ├── ResourceBulkEditForm.vue │ │ │ ├── ResourceEnumSelect.vue │ │ │ ├── ResourceFieldInfoIcons.vue │ │ │ ├── ResourceInfo.vue │ │ │ ├── ResourceInstanceCreateForm.vue │ │ │ ├── ResourceInstanceInfo.vue │ │ │ ├── ResourceInstanceValueFaker.vue │ │ │ ├── ResourceInstanceValueForm.vue │ │ │ ├── ResourceInstanceValueFormInput.vue │ │ │ ├── ResourceInstanceValueFormSetAll.vue │ │ │ ├── ResourceInstanceValueNestedForm.vue │ │ │ ├── ResourceInstanceValueReferences.vue │ │ │ ├── ResourceList.vue │ │ │ ├── ResourceListItem.vue │ │ │ ├── ResourceMainTable.vue │ │ │ ├── ResourceReferencesPreview.vue │ │ │ ├── ResourceReferencesSummary.vue │ │ │ ├── ResourceResolverPreview.vue │ │ │ ├── ResourceSelect.vue │ │ │ ├── ResourceSelectMultiple.vue │ │ │ ├── ResourceTable.vue │ │ │ ├── ResourceTableColumnHeader.vue │ │ │ ├── ResourceTableRow.vue │ │ │ ├── resourceInstanceValueOverlays.ts │ │ │ └── tableTypes.ts │ │ ├── rest │ │ │ └── RestPlayground.vue │ │ ├── script │ │ │ ├── ScriptList.vue │ │ │ ├── ScriptListItem.vue │ │ │ ├── ScriptLogItem.vue │ │ │ ├── ScriptRunReport.vue │ │ │ ├── ScriptView.vue │ │ │ └── runScript.ts │ │ ├── snapshot │ │ │ ├── SnapshotForm.vue │ │ │ ├── SnapshotImport.vue │ │ │ ├── SnapshotInfo.vue │ │ │ ├── SnapshotInfoLine.vue │ │ │ ├── SnapshotList.vue │ │ │ ├── SnapshotListItem.vue │ │ │ └── SnapshotResources.vue │ │ └── user │ │ │ ├── UserAvatar.vue │ │ │ └── UserCurrent.vue │ ├── index.d.mts │ ├── index.mjs │ ├── nuxt.config.ts │ ├── package.json │ ├── pages │ │ ├── config │ │ │ └── inspect.vue │ │ ├── db │ │ │ ├── branches.vue │ │ │ ├── factories.vue │ │ │ ├── factories │ │ │ │ ├── [resourceName].vue │ │ │ │ ├── [resourceName] │ │ │ │ │ ├── create.vue │ │ │ │ │ └── view │ │ │ │ │ │ └── [factoryId] │ │ │ │ │ │ └── index.vue │ │ │ │ ├── create.vue │ │ │ │ └── index.vue │ │ │ ├── history.vue │ │ │ ├── history │ │ │ │ ├── [recordId].vue │ │ │ │ └── index.vue │ │ │ ├── resolvers.vue │ │ │ ├── resolvers │ │ │ │ ├── [resourceName].vue │ │ │ │ └── index.vue │ │ │ ├── resources.vue │ │ │ ├── resources │ │ │ │ ├── [resourceName].vue │ │ │ │ ├── [resourceName] │ │ │ │ │ ├── create.vue │ │ │ │ │ ├── instances.vue │ │ │ │ │ └── instances │ │ │ │ │ │ ├── [instanceId] │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ └── index.vue │ │ │ │ ├── create.vue │ │ │ │ └── index.vue │ │ │ ├── scripts.vue │ │ │ ├── scripts │ │ │ │ ├── [scriptId].vue │ │ │ │ └── index.vue │ │ │ ├── snapshots.vue │ │ │ └── snapshots │ │ │ │ ├── [snapshotId] │ │ │ │ └── index.vue │ │ │ │ ├── create.vue │ │ │ │ └── index.vue │ │ ├── debug-schema.vue │ │ ├── debug.vue │ │ ├── graphql.vue │ │ ├── graphql │ │ │ ├── playground.vue │ │ │ └── schema.vue │ │ ├── index.vue │ │ ├── pubsub │ │ │ └── index.vue │ │ └── rest.vue │ ├── public │ │ └── favicon.ico │ ├── server │ │ ├── api │ │ │ ├── branches │ │ │ │ ├── current.get.ts │ │ │ │ ├── current.post.ts │ │ │ │ ├── index.delete.ts │ │ │ │ ├── index.get.ts │ │ │ │ ├── index.post.ts │ │ │ │ └── rename.post.ts │ │ │ ├── config │ │ │ │ ├── file │ │ │ │ │ ├── read.get.ts │ │ │ │ │ └── write.post.ts │ │ │ │ └── inspect.ts │ │ │ ├── debug.ts │ │ │ ├── factories │ │ │ │ ├── [factoryId] │ │ │ │ │ ├── index.delete.ts │ │ │ │ │ ├── index.get.ts │ │ │ │ │ └── index.patch.ts │ │ │ │ ├── count.get.ts │ │ │ │ ├── create.post.ts │ │ │ │ ├── defaultFields.get.ts │ │ │ │ ├── defaultValues.get.ts │ │ │ │ ├── index.get.ts │ │ │ │ └── preview.post.ts │ │ │ ├── faker │ │ │ │ ├── factories.get.ts │ │ │ │ ├── locales.get.ts │ │ │ │ └── preview.post.ts │ │ │ ├── graphql │ │ │ │ ├── hasGraphql.ts │ │ │ │ └── schema │ │ │ │ │ └── inspect.ts │ │ │ ├── history │ │ │ │ ├── [recordId].get.ts │ │ │ │ └── index.get.ts │ │ │ ├── openInEditor.ts │ │ │ ├── project.get.ts │ │ │ ├── pubsub │ │ │ │ ├── publish.post.ts │ │ │ │ └── suggestChannels.get.ts │ │ │ ├── resolvers │ │ │ │ ├── counts.get.ts │ │ │ │ ├── index.get.ts │ │ │ │ └── preview.post.ts │ │ │ ├── resources │ │ │ │ ├── [resourceName] │ │ │ │ │ ├── ignored.get.ts │ │ │ │ │ └── index.get.ts │ │ │ │ ├── create.post.ts │ │ │ │ ├── generate.post.ts │ │ │ │ ├── ids.get.ts │ │ │ │ ├── index.get.ts │ │ │ │ └── instances │ │ │ │ │ └── [resourceName] │ │ │ │ │ ├── [instanceId] │ │ │ │ │ ├── index.get.ts │ │ │ │ │ └── index.patch.ts │ │ │ │ │ ├── bulk.delete.ts │ │ │ │ │ ├── bulk.patch.ts │ │ │ │ │ ├── duplicate.post.ts │ │ │ │ │ ├── getByIds.post.ts │ │ │ │ │ └── index.get.ts │ │ │ ├── rest │ │ │ │ ├── fetch.post.ts │ │ │ │ └── hasRest.get.ts │ │ │ ├── scripts │ │ │ │ ├── [scriptId] │ │ │ │ │ ├── index.get.ts │ │ │ │ │ └── run.post.ts │ │ │ │ └── index.get.ts │ │ │ ├── server │ │ │ │ └── index.get.ts │ │ │ ├── settings │ │ │ │ ├── index.get.ts │ │ │ │ └── index.patch.ts │ │ │ ├── snapshots │ │ │ │ ├── [snapshotId] │ │ │ │ │ ├── createBranch.post.ts │ │ │ │ │ ├── index.delete.ts │ │ │ │ │ ├── index.get.ts │ │ │ │ │ ├── index.patch.ts │ │ │ │ │ ├── overwriteBranch.post.ts │ │ │ │ │ └── resources │ │ │ │ │ │ ├── [resourceName] │ │ │ │ │ │ └── index.get.ts │ │ │ │ │ │ ├── add.post.ts │ │ │ │ │ │ ├── count.get.ts │ │ │ │ │ │ └── remove.post.ts │ │ │ │ ├── count.get.ts │ │ │ │ ├── index.get.ts │ │ │ │ └── index.post.ts │ │ │ └── user │ │ │ │ └── current │ │ │ │ └── index.get.ts │ │ ├── plugins │ │ │ └── init.ts │ │ ├── tsconfig.json │ │ └── utils │ │ │ ├── configFile.ts │ │ │ ├── instance.ts │ │ │ └── search.ts │ ├── stores │ │ ├── commandPalette.ts │ │ ├── factory.ts │ │ ├── resourceInstance.ts │ │ ├── resourceType.ts │ │ └── snapshot.ts │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── utils │ │ ├── config.ts │ │ ├── form.ts │ │ ├── graphql.ts │ │ ├── object.ts │ │ ├── resources.ts │ │ ├── rest.ts │ │ ├── route.ts │ │ ├── settings.ts │ │ ├── url.ts │ │ └── window.ts ├── core │ ├── config.d.ts │ ├── config.mjs │ ├── package.json │ ├── src │ │ ├── ast │ │ │ ├── exportVariable.ts │ │ │ ├── fn.ts │ │ │ ├── index.ts │ │ │ ├── objectToAst.ts │ │ │ ├── parse.ts │ │ │ └── print.ts │ │ ├── config.ts │ │ ├── config │ │ │ ├── index.ts │ │ │ └── resolve.ts │ │ ├── context.ts │ │ ├── factory │ │ │ ├── createDefaultFactoryFields.ts │ │ │ ├── createInstanceFromFactory.ts │ │ │ ├── deserialize.ts │ │ │ ├── execute.ts │ │ │ ├── fakerAutoSelect.ts │ │ │ ├── fakerGenerate.ts │ │ │ ├── fakerGet.ts │ │ │ ├── fakerLocales.ts │ │ │ ├── index.ts │ │ │ ├── serialize.ts │ │ │ └── storage.ts │ │ ├── graphql │ │ │ ├── index.ts │ │ │ ├── json.ts │ │ │ ├── resolvers.ts │ │ │ ├── resource.ts │ │ │ ├── schema.ts │ │ │ └── server.ts │ │ ├── hooks.ts │ │ ├── index.ts │ │ ├── instance.ts │ │ ├── mock │ │ │ ├── index.ts │ │ │ ├── mockFileHandler.ts │ │ │ └── mockFileWatcher.ts │ │ ├── pubsub │ │ │ └── createPubSub.ts │ │ ├── resolvers │ │ │ ├── index.ts │ │ │ └── resolverStore.ts │ │ ├── resource.ts │ │ ├── resource │ │ │ ├── branch.ts │ │ │ ├── branchCreate.ts │ │ │ ├── branchDelete.ts │ │ │ ├── createInstance.ts │ │ │ ├── deactivateOthers.ts │ │ │ ├── find.ts │ │ │ ├── findAll.ts │ │ │ ├── fromTypes.ts │ │ │ ├── generateInstances.ts │ │ │ ├── history.ts │ │ │ ├── index.ts │ │ │ ├── queryManager.ts │ │ │ ├── queryManagerProxy.ts │ │ │ ├── remove.ts │ │ │ ├── resourceReference.ts │ │ │ ├── schemaTransformStore.ts │ │ │ ├── storage.ts │ │ │ └── update.ts │ │ ├── rest │ │ │ ├── apiRouteStore.ts │ │ │ ├── index.ts │ │ │ ├── schema.ts │ │ │ └── server.ts │ │ ├── script │ │ │ ├── index.ts │ │ │ ├── runScript.ts │ │ │ └── scriptStore.ts │ │ ├── server.ts │ │ ├── settings │ │ │ ├── onChange.ts │ │ │ └── settingsManager.ts │ │ ├── snapshot │ │ │ ├── addResourcesToSnapshot.ts │ │ │ ├── createSnapshot.ts │ │ │ ├── deleteSnapshot.ts │ │ │ ├── folder.ts │ │ │ ├── importSnapshotToDatabase.ts │ │ │ ├── index.ts │ │ │ ├── migrate.ts │ │ │ ├── readResources.ts │ │ │ ├── removeResourcesFromSnapshot.ts │ │ │ └── storage.ts │ │ ├── storage │ │ │ ├── index.ts │ │ │ ├── mergedStorage.ts │ │ │ ├── path.ts │ │ │ └── storage.ts │ │ ├── tsCodegen │ │ │ ├── genInterface.ts │ │ │ └── genTypes.ts │ │ ├── types │ │ │ ├── apiRoute.ts │ │ │ ├── config.ts │ │ │ ├── db.ts │ │ │ ├── factory.ts │ │ │ ├── history.ts │ │ │ ├── index.ts │ │ │ ├── plugin.ts │ │ │ ├── resolver.ts │ │ │ ├── resource.ts │ │ │ ├── script.ts │ │ │ ├── server.ts │ │ │ ├── settings.ts │ │ │ ├── snapshot.ts │ │ │ └── user.ts │ │ ├── user │ │ │ ├── findOnGithub.ts │ │ │ ├── getCurrentUser.ts │ │ │ └── index.ts │ │ └── util │ │ │ ├── crypto.ts │ │ │ ├── env.ts │ │ │ ├── find-up.ts │ │ │ ├── fs.ts │ │ │ ├── hookable.ts │ │ │ ├── index.ts │ │ │ ├── object.ts │ │ │ ├── queue.ts │ │ │ ├── random.ts │ │ │ ├── types.ts │ │ │ └── vm.ts │ ├── tsconfig.json │ ├── util.d.ts │ └── util.mjs └── moquerie │ ├── README.md │ ├── bin.mjs │ ├── config.d.ts │ ├── config.mjs │ ├── mocks.d.ts │ ├── mocks.mjs │ ├── package.json │ ├── src │ ├── appServer.ts │ ├── cli.ts │ ├── config.ts │ ├── index.ts │ └── mocks.ts │ └── tsconfig.json ├── playgrounds ├── playground │ ├── .moquerie │ │ ├── factories │ │ │ └── Message │ │ │ │ └── SimpleMessageFactory.ts │ │ ├── snapshots │ │ │ └── vitest │ │ │ │ ├── Query.res.json │ │ │ │ └── snapshot.json │ │ └── types │ │ │ ├── queryManager.ts │ │ │ └── resources.ts │ ├── codegen.ts │ ├── eslint.config.js │ ├── graphql.schema.json │ ├── moquerie.config.ts │ ├── mq-api.ts │ ├── package.json │ ├── src │ │ ├── auth.ts │ │ ├── context.ts │ │ ├── extend │ │ │ ├── message.ts │ │ │ └── types.ts │ │ ├── fetch.test.ts │ │ ├── generated │ │ │ └── graphql.ts │ │ ├── index.ts │ │ ├── rest │ │ │ ├── other.ts │ │ │ ├── rest.moq.ts │ │ │ └── types.ts │ │ ├── schema.moq.ts │ │ └── schema.ts │ ├── tsconfig.json │ └── vitest.config.ts └── quick-rest │ ├── .moquerie │ └── types │ │ ├── queryManager.ts │ │ └── resources.ts │ ├── moquerie.rest.ts │ └── package.json ├── pnpm-lock.yaml └── pnpm-workspace.yaml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Akryum 2 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Publish Nightlies 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - '**' 7 | tags: 8 | - '!**' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@master 16 | 17 | - name: Install node 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 22 21 | 22 | - name: Install pnpm 23 | uses: pnpm/action-setup@v4.0.0 24 | 25 | - name: Get pnpm store directory 26 | id: pnpm-cache 27 | run: | 28 | echo "pnpm_cache_dir=$(pnpm store path)" >> "$GITHUB_OUTPUT" 29 | 30 | - name: Cache pnpm modules 31 | uses: actions/cache@v4 32 | with: 33 | path: | 34 | ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} 35 | ~/.cache/Cypress 36 | key: pnpm-v1-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 37 | restore-keys: | 38 | pnpm-v1-${{ runner.os }}- 39 | 40 | - name: Install dependencies 41 | run: pnpm install 42 | 43 | - name: Build 44 | run: pnpm build 45 | 46 | - run: pnpx pkg-pr-new publish './packages/*' 47 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yml: -------------------------------------------------------------------------------- 1 | name: Check PR title 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | jobs: 11 | check-title: 12 | runs-on: ubuntu-latest 13 | steps: 14 | # Please look up the latest version from 15 | # https://github.com/amannn/action-semantic-pull-request/releases 16 | - uses: amannn/action-semantic-pull-request@v3.4.2 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/release-notes.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | name: Create Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@master 16 | with: 17 | fetch-depth: 0 # Fetch all tags 18 | 19 | - name: Create Release for Tag 20 | id: release_tag 21 | uses: Akryum/release-tag@v4.0.7 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | with: 25 | tag_name: ${{ github.ref }} 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Continuous tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - feat/* 8 | - fix/* 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | build-and-test: 18 | runs-on: ubuntu-latest 19 | name: Build and test 20 | steps: 21 | - uses: actions/checkout@master 22 | 23 | - name: Install node 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 22 27 | 28 | - name: Install pnpm 29 | uses: pnpm/action-setup@v4.0.0 30 | 31 | - name: Get pnpm store directory 32 | id: pnpm-cache 33 | run: | 34 | echo "::set-output name=pnpm_cache_dir::$(pnpm store path)" 35 | 36 | - name: Cache pnpm modules 37 | uses: actions/cache@v2 38 | with: 39 | path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} 40 | key: pnpm-v1-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 41 | restore-keys: | 42 | pnpm-v1-${{ runner.os }}- 43 | 44 | - name: Install dependencies 45 | run: pnpm install 46 | 47 | - name: Lint 48 | run: pnpm run lint 49 | 50 | - name: Build 51 | run: pnpm run build 52 | 53 | - name: Playground tests 54 | run: pnpm run test 55 | working-directory: ./playgrounds/playground 56 | 57 | # - name: Run tests 58 | # run: pnpm run test 59 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "tailwindCSS.classAttributes": [ 3 | "class", 4 | "className", 5 | "ngClass", 6 | "popper-class" 7 | ], 8 | "editor.formatOnSave": false, 9 | "editor.codeActionsOnSave": { 10 | "source.fixAll.eslint": "explicit", 11 | "source.organizeImports": "never" 12 | }, 13 | "eslint.validate": [ 14 | "javascript", 15 | "javascriptreact", 16 | "typescript", 17 | "typescriptreact", 18 | "vue", 19 | "html", 20 | "markdown", 21 | "json", 22 | "jsonc", 23 | "yaml", 24 | "toml", 25 | "gql", 26 | "graphql" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Guillaume Chau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/database-branch-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/database-branch-create.png -------------------------------------------------------------------------------- /docs/database-branches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/database-branches.png -------------------------------------------------------------------------------- /docs/database-bulk-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/database-bulk-edit.png -------------------------------------------------------------------------------- /docs/database-reference-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/database-reference-edit.png -------------------------------------------------------------------------------- /docs/database-reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/database-reference.png -------------------------------------------------------------------------------- /docs/database-select-many.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/database-select-many.png -------------------------------------------------------------------------------- /docs/database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/database.png -------------------------------------------------------------------------------- /docs/factory-create-instances.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/factory-create-instances.png -------------------------------------------------------------------------------- /docs/factory-faker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/factory-faker.png -------------------------------------------------------------------------------- /docs/factory-toggle-location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/factory-toggle-location.png -------------------------------------------------------------------------------- /docs/factory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/factory.png -------------------------------------------------------------------------------- /docs/graphql-playground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/graphql-playground.png -------------------------------------------------------------------------------- /docs/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/history.png -------------------------------------------------------------------------------- /docs/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/home.png -------------------------------------------------------------------------------- /docs/pubsub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/pubsub.png -------------------------------------------------------------------------------- /docs/resolvers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/resolvers.png -------------------------------------------------------------------------------- /docs/rest-playground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/rest-playground.png -------------------------------------------------------------------------------- /docs/script.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/script.png -------------------------------------------------------------------------------- /docs/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/docs/snapshot.png -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | const config = await antfu({ 4 | ignores: [ 5 | '**/generated', 6 | '**/*.schema.json', 7 | ], 8 | }, { 9 | rules: { 10 | curly: ['error', 'all'], 11 | }, 12 | }) 13 | 14 | export default config 15 | -------------------------------------------------------------------------------- /og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/og.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moquerie-mono-repo", 3 | "type": "module", 4 | "version": "0.6.2", 5 | "private": true, 6 | "packageManager": "pnpm@9.7.1", 7 | "description": "", 8 | "engines": { 9 | "node": ">22.0.0" 10 | }, 11 | "scripts": { 12 | "lint": "eslint .", 13 | "dev": "pnpm run -r --filter \"./packages/**\" --filter=!@moquerie/app --parallel --stream dev", 14 | "build": "pnpm run -r --filter \"./packages/**\" build", 15 | "release": "pnpm run lint && pnpm run build && sheep release -b main --force" 16 | }, 17 | "devDependencies": { 18 | "@akryum/sheep": "^0.5.2", 19 | "@antfu/eslint-config": "^3.11.2", 20 | "eslint": "^9.16.0" 21 | }, 22 | "pnpm": { 23 | "overrides": { 24 | "graphql": "^16.8.1" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/app/.env.playground: -------------------------------------------------------------------------------- 1 | MOQUERIE_OVERRIDE_CWD=../../playgrounds/playground 2 | -------------------------------------------------------------------------------- /packages/app/.env.playground.rest: -------------------------------------------------------------------------------- 1 | MOQUERIE_OVERRIDE_CWD=../../playgrounds/quick-rest 2 | -------------------------------------------------------------------------------- /packages/app/.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.playground* 25 | -------------------------------------------------------------------------------- /packages/app/README.md: -------------------------------------------------------------------------------- 1 | # Nuxt 3 Minimal Starter 2 | 3 | Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. 4 | 5 | ## Setup 6 | 7 | Make sure to install the dependencies: 8 | 9 | ```bash 10 | # npm 11 | npm install 12 | 13 | # pnpm 14 | pnpm install 15 | 16 | # yarn 17 | yarn install 18 | 19 | # bun 20 | bun install 21 | ``` 22 | 23 | ## Development Server 24 | 25 | Start the development server on `http://localhost:3000`: 26 | 27 | ```bash 28 | # npm 29 | npm run dev 30 | 31 | # pnpm 32 | pnpm run dev 33 | 34 | # yarn 35 | yarn dev 36 | 37 | # bun 38 | bun run dev 39 | ``` 40 | 41 | ## Production 42 | 43 | Build the application for production: 44 | 45 | ```bash 46 | # npm 47 | npm run build 48 | 49 | # pnpm 50 | pnpm run build 51 | 52 | # yarn 53 | yarn build 54 | 55 | # bun 56 | bun run build 57 | ``` 58 | 59 | Locally preview production build: 60 | 61 | ```bash 62 | # npm 63 | npm run preview 64 | 65 | # pnpm 66 | pnpm run preview 67 | 68 | # yarn 69 | yarn preview 70 | 71 | # bun 72 | bun run preview 73 | ``` 74 | 75 | Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. 76 | -------------------------------------------------------------------------------- /packages/app/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | ui: { 3 | primary: 'violet', 4 | 5 | notifications: { 6 | position: 'bottom-0 left-0 right-0 mx-auto', 7 | }, 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /packages/app/app.vue: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /packages/app/assets/css/floating-vue.css: -------------------------------------------------------------------------------- 1 | .v-popper__popper.no-overflow { 2 | .v-popper__wrapper, 3 | .v-popper__inner { 4 | overflow: visible; 5 | } 6 | } 7 | 8 | /* Tooltip */ 9 | 10 | .v-popper__popper.v-popper--theme-tooltip { 11 | z-index: 99999999999; 12 | 13 | .v-popper__inner { 14 | @apply bg-gray-100 text-black border border-gray-200 shadow-lg; 15 | } 16 | 17 | .v-popper__arrow-inner { 18 | @apply border-gray-100 visible; 19 | } 20 | 21 | .v-popper__arrow-outer { 22 | @apply border-gray-200; 23 | } 24 | 25 | .dark & { 26 | .v-popper__inner { 27 | @apply bg-gray-800 text-white border-gray-700; 28 | } 29 | 30 | .v-popper__arrow-inner { 31 | @apply border-gray-800; 32 | } 33 | 34 | .v-popper__arrow-outer { 35 | @apply border-gray-700; 36 | } 37 | } 38 | } 39 | 40 | /* Dropdown */ 41 | 42 | .dark .v-popper__popper.v-popper--theme-dropdown { 43 | .v-popper__inner { 44 | @apply bg-gray-900 text-white border-gray-800; 45 | } 46 | 47 | .v-popper__arrow-inner { 48 | @apply border-gray-900; 49 | } 50 | 51 | .v-popper__arrow-outer { 52 | @apply border-gray-800; 53 | } 54 | } 55 | 56 | 57 | /* Prevent scrollbar on body */ 58 | 59 | body { 60 | overflow: hidden; 61 | } 62 | -------------------------------------------------------------------------------- /packages/app/components/AppHeader.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 38 | -------------------------------------------------------------------------------- /packages/app/components/AppNavItem.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 65 | -------------------------------------------------------------------------------- /packages/app/components/ConfirmModal.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 70 | -------------------------------------------------------------------------------- /packages/app/components/ErrorBoundary.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 41 | 42 | 54 | -------------------------------------------------------------------------------- /packages/app/components/ErrorMessage.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 42 | -------------------------------------------------------------------------------- /packages/app/components/ErrorMessageSimple.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /packages/app/components/ExplanationView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /packages/app/components/InfoTooltip.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | -------------------------------------------------------------------------------- /packages/app/components/KbShortcut.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 57 | -------------------------------------------------------------------------------- /packages/app/components/RadioButtonGroup.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 52 | -------------------------------------------------------------------------------- /packages/app/components/ServerInfo.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 52 | -------------------------------------------------------------------------------- /packages/app/components/SimpleModal.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | -------------------------------------------------------------------------------- /packages/app/components/ValuePreview.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 48 | -------------------------------------------------------------------------------- /packages/app/components/VerticalButton.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 42 | -------------------------------------------------------------------------------- /packages/app/components/branch/branchOverlays.ts: -------------------------------------------------------------------------------- 1 | // Branches selector 2 | export const isBranchesOpen = ref(false) 3 | -------------------------------------------------------------------------------- /packages/app/components/command-palette/CommandPaletteModal.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 30 | -------------------------------------------------------------------------------- /packages/app/components/factory/FactoryInfo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | -------------------------------------------------------------------------------- /packages/app/components/factory/FactoryListItem.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 43 | -------------------------------------------------------------------------------- /packages/app/components/factory/formTypes.ts: -------------------------------------------------------------------------------- 1 | import type { DBLocation, ResourceFactoryField, ResourceFactoryFieldsMap, ResourceFactoryInfo, ResourceSchemaField, ResourceSchemaType } from '@moquerie/core' 2 | 3 | export interface FactoryData { 4 | name: string 5 | location: DBLocation 6 | resourceName: string 7 | info: Omit 8 | fields: ResourceFactoryFieldsMap 9 | } 10 | 11 | export interface FlatField { 12 | fullKey: string 13 | key: string 14 | depth: number 15 | factoryField: ResourceFactoryField 16 | resourceType: ResourceSchemaType 17 | childResourceType?: ResourceSchemaType 18 | field: ResourceSchemaField 19 | parent: FlatField | null 20 | } 21 | -------------------------------------------------------------------------------- /packages/app/components/form/FormActions.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/app/components/form/FormGroupLocationInput.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 56 | -------------------------------------------------------------------------------- /packages/app/components/history/HistoryRecordListItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 41 | -------------------------------------------------------------------------------- /packages/app/components/history/icons.ts: -------------------------------------------------------------------------------- 1 | export const HistoryIcons = { 2 | create: 'i-ph-plus', 3 | update: 'i-ph-pencil', 4 | delete: 'i-ph-trash', 5 | } as const 6 | -------------------------------------------------------------------------------- /packages/app/components/resolver/ResolverList.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 50 | -------------------------------------------------------------------------------- /packages/app/components/resolver/ResolverListItem.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 41 | -------------------------------------------------------------------------------- /packages/app/components/resolver/ResolverResourceListItem.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 49 | -------------------------------------------------------------------------------- /packages/app/components/resource/ResourceEnumSelect.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 43 | -------------------------------------------------------------------------------- /packages/app/components/resource/ResourceFieldInfoIcons.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /packages/app/components/resource/ResourceInfo.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 39 | -------------------------------------------------------------------------------- /packages/app/components/resource/ResourceInstanceValueNestedForm.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 32 | -------------------------------------------------------------------------------- /packages/app/components/resource/ResourceListItem.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 54 | -------------------------------------------------------------------------------- /packages/app/components/resource/ResourceReferencesSummary.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 36 | -------------------------------------------------------------------------------- /packages/app/components/resource/ResourceResolverPreview.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 34 | -------------------------------------------------------------------------------- /packages/app/components/resource/ResourceTableColumnHeader.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /packages/app/components/resource/resourceInstanceValueOverlays.ts: -------------------------------------------------------------------------------- 1 | /* Nested overlays inside instance value form */ 2 | 3 | import { isBranchesOpen } from '@/components/branch/branchOverlays.js' 4 | 5 | export const isFakerOpen = ref(false) 6 | 7 | export const isReferencesOpen = ref(false) 8 | 9 | /** 10 | * Shortcuts on escape and meta_enter should be disabled if it's open. 11 | */ 12 | export const isAnyOpen = computed(() => isFakerOpen.value || isBranchesOpen.value || isReferencesOpen.value) 13 | -------------------------------------------------------------------------------- /packages/app/components/resource/tableTypes.ts: -------------------------------------------------------------------------------- 1 | import type { ResourceSchemaField, ResourceSchemaType } from '@moquerie/core' 2 | 3 | export interface Col { 4 | label: string 5 | field: string 6 | size: number 7 | resolver?: { file: string } 8 | fieldData?: ResourceSchemaField 9 | childResourceType?: ResourceSchemaType 10 | } 11 | 12 | export interface ColData { 13 | size?: number 14 | } 15 | -------------------------------------------------------------------------------- /packages/app/components/script/ScriptList.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 51 | -------------------------------------------------------------------------------- /packages/app/components/script/ScriptListItem.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 32 | -------------------------------------------------------------------------------- /packages/app/components/script/ScriptRunReport.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 51 | -------------------------------------------------------------------------------- /packages/app/components/script/runScript.ts: -------------------------------------------------------------------------------- 1 | import type { ScriptRunReport } from '@moquerie/core' 2 | 3 | export function useRunScript() { 4 | const toast = useToast() 5 | 6 | const reports = useSessionStorage('moquerie.scripts.reports', {} as Record) 7 | 8 | async function run(id: string) { 9 | try { 10 | const report = await $fetch(`/api/scripts/${id}/run`, { 11 | method: 'POST', 12 | }) 13 | 14 | if (report.error) { 15 | toast.add({ 16 | id: 'snapshot-error', 17 | title: 'Error', 18 | description: report.error.message, 19 | icon: 'i-ph-x-circle', 20 | color: 'red', 21 | }) 22 | } 23 | else { 24 | toast.add({ 25 | title: `Script ${id} ran successfully`, 26 | icon: 'i-ph-check-circle', 27 | color: 'green', 28 | }) 29 | } 30 | addReport(id, report) 31 | return report 32 | } 33 | catch (e: any) { 34 | toast.add({ 35 | id: 'snapshot-error', 36 | title: 'Error', 37 | description: e.data?.message ?? e.message, 38 | icon: 'i-ph-x-circle', 39 | color: 'red', 40 | }) 41 | } 42 | } 43 | 44 | function addReport(scriptId: string, report: ScriptRunReport) { 45 | reports.value = { 46 | ...reports.value, 47 | [scriptId]: [ 48 | report, 49 | ...(reports.value[scriptId] || []), 50 | ], 51 | } 52 | } 53 | 54 | return { 55 | run, 56 | reports, 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/app/components/snapshot/SnapshotInfo.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 50 | -------------------------------------------------------------------------------- /packages/app/components/snapshot/SnapshotInfoLine.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 47 | -------------------------------------------------------------------------------- /packages/app/components/snapshot/SnapshotListItem.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 42 | -------------------------------------------------------------------------------- /packages/app/components/user/UserAvatar.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 63 | -------------------------------------------------------------------------------- /packages/app/components/user/UserCurrent.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /packages/app/index.d.mts: -------------------------------------------------------------------------------- 1 | import type { RequestListener } from 'node:http' 2 | 3 | export const handler: RequestListener 4 | -------------------------------------------------------------------------------- /packages/app/index.mjs: -------------------------------------------------------------------------------- 1 | export * from './.output/server/index.mjs' 2 | -------------------------------------------------------------------------------- /packages/app/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | export default defineNuxtConfig({ 3 | devtools: { 4 | enabled: true, 5 | }, 6 | 7 | ssr: false, 8 | 9 | modules: [ 10 | '@nuxt/ui', 11 | '@vueuse/nuxt', 12 | '@pinia/nuxt', 13 | '@nuxtjs/google-fonts', 14 | ], 15 | 16 | ui: { 17 | icons: ['ph', 'mdi', 'carbon'], 18 | }, 19 | 20 | css: [ 21 | 'floating-vue/dist/style.css', 22 | '~/assets/css/floating-vue.css', 23 | ], 24 | 25 | googleFonts: { 26 | families: { 27 | 'Noto+Sans': [400, 700], 28 | 'Noto+Sans+Mono': [400, 700], 29 | }, 30 | }, 31 | 32 | typescript: { 33 | tsConfig: { 34 | compilerOptions: { 35 | moduleResolution: 'bundler', 36 | }, 37 | }, 38 | }, 39 | 40 | vite: { 41 | worker: { 42 | format: 'es', 43 | }, 44 | }, 45 | 46 | nitro: { 47 | preset: 'node', 48 | serveStatic: true, 49 | devServer: { 50 | watch: [ 51 | './node_modules/@moquerie/core/**', 52 | ], 53 | }, 54 | rollupConfig: { 55 | external: [ 56 | /@moquerie/, 57 | 'graphql', 58 | ], 59 | }, 60 | }, 61 | 62 | compatibilityDate: '2024-08-22', 63 | }) 64 | -------------------------------------------------------------------------------- /packages/app/pages/db/factories.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 33 | -------------------------------------------------------------------------------- /packages/app/pages/db/factories/[resourceName]/create.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /packages/app/pages/db/factories/[resourceName]/view/[factoryId]/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 53 | -------------------------------------------------------------------------------- /packages/app/pages/db/factories/create.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 33 | -------------------------------------------------------------------------------- /packages/app/pages/db/factories/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /packages/app/pages/db/history.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | -------------------------------------------------------------------------------- /packages/app/pages/db/history/[recordId].vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/app/pages/db/history/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /packages/app/pages/db/resolvers.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | -------------------------------------------------------------------------------- /packages/app/pages/db/resolvers/[resourceName].vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /packages/app/pages/db/resolvers/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /packages/app/pages/db/resources.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 33 | -------------------------------------------------------------------------------- /packages/app/pages/db/resources/[resourceName]/create.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/app/pages/db/resources/[resourceName]/instances.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 27 | -------------------------------------------------------------------------------- /packages/app/pages/db/resources/[resourceName]/instances/index.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /packages/app/pages/db/resources/create.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /packages/app/pages/db/resources/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /packages/app/pages/db/scripts.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | -------------------------------------------------------------------------------- /packages/app/pages/db/scripts/[scriptId].vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /packages/app/pages/db/scripts/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /packages/app/pages/db/snapshots.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | -------------------------------------------------------------------------------- /packages/app/pages/db/snapshots/create.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 32 | -------------------------------------------------------------------------------- /packages/app/pages/db/snapshots/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /packages/app/pages/debug-schema.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 54 | -------------------------------------------------------------------------------- /packages/app/pages/debug.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 50 | -------------------------------------------------------------------------------- /packages/app/pages/graphql.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 35 | -------------------------------------------------------------------------------- /packages/app/pages/graphql/playground.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | -------------------------------------------------------------------------------- /packages/app/pages/graphql/schema.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 63 | -------------------------------------------------------------------------------- /packages/app/pages/index.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /packages/app/pages/rest.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /packages/app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akryum/moquerie/44c70037d22a77dcc17b1c7ab4996d1d7831fe5a/packages/app/public/favicon.ico -------------------------------------------------------------------------------- /packages/app/server/api/branches/current.get.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentBranch } from '@moquerie/core' 2 | 3 | export default defineEventHandler(() => getCurrentBranch(getMq())) 4 | -------------------------------------------------------------------------------- /packages/app/server/api/branches/current.post.ts: -------------------------------------------------------------------------------- 1 | import { switchToBranch } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const { branch } = await readBody(event) 5 | await switchToBranch(getMq(), branch) 6 | return branch 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/server/api/branches/index.delete.ts: -------------------------------------------------------------------------------- 1 | import { deleteBranch } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const { branch } = await readBody(event) 5 | const mq = getMq() 6 | await deleteBranch(mq, branch) 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/server/api/branches/index.get.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { getLocalDbFolder, resourceInstancesFolders } from '@moquerie/core' 3 | import path from 'pathe' 4 | 5 | export default defineEventHandler(async () => { 6 | const folder = path.join(getLocalDbFolder(getMq()), ...resourceInstancesFolders) 7 | if (!fs.existsSync(folder)) { 8 | return [] 9 | } 10 | return fs.promises.readdir(folder) 11 | }) 12 | -------------------------------------------------------------------------------- /packages/app/server/api/branches/index.post.ts: -------------------------------------------------------------------------------- 1 | import { createBranch } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const { name, empty } = await readBody(event) 5 | 6 | await createBranch(getMq(), { name, empty }) 7 | 8 | return name 9 | }) 10 | -------------------------------------------------------------------------------- /packages/app/server/api/branches/rename.post.ts: -------------------------------------------------------------------------------- 1 | import { renameBranch } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const { branch, newName } = await readBody(event) 5 | const mq = getMq() 6 | await renameBranch(mq, branch, newName) 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/server/api/config/file/read.get.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises' 2 | 3 | export default defineEventHandler(async () => { 4 | const configFile = await findConfigFile() 5 | if (configFile) { 6 | return { 7 | file: configFile, 8 | content: await fs.readFile(configFile, 'utf-8'), 9 | } 10 | } 11 | throw createError({ 12 | status: 404, 13 | statusMessage: 'config not found', 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /packages/app/server/api/config/file/write.post.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const configFile = await findConfigFile() 5 | 6 | if (configFile) { 7 | const { source } = await readBody(event) 8 | await fs.writeFile(configFile, source, 'utf-8') 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /packages/app/server/api/config/inspect.ts: -------------------------------------------------------------------------------- 1 | import { resolveConfig } from '@moquerie/core' 2 | 3 | export default defineEventHandler(() => resolveConfig(getMq().data.cwd)) 4 | -------------------------------------------------------------------------------- /packages/app/server/api/debug.ts: -------------------------------------------------------------------------------- 1 | import { getLocalDbFolder, getRepositoryDbFolder, resolveConfig } from '@moquerie/core' 2 | import { getProjectName } from '@moquerie/core/util' 3 | import envinfo from 'envinfo' 4 | 5 | export default defineEventHandler(async () => { 6 | const mq = getMq() 7 | let config: any 8 | try { 9 | config = await resolveConfig(mq.data.cwd) 10 | } 11 | catch (e) { 12 | config = e 13 | } 14 | 15 | return { 16 | cwd: mq.data.cwd, 17 | projectName: getProjectName(mq), 18 | envinfo: JSON.parse(await envinfo.run({ 19 | System: ['OS', 'CPU', 'Memory', 'Shell'], 20 | Binaries: ['Node', 'Yarn', 'npm', 'pnpm'], 21 | Browsers: ['Chrome', 'Firefox', 'Safari'], 22 | npmPackages: ['moquerie'], 23 | }, { 24 | json: true, 25 | showNotFound: true, 26 | })), 27 | config, 28 | db: { 29 | localPath: getLocalDbFolder(mq), 30 | repositoryPath: getRepositoryDbFolder(mq), 31 | }, 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /packages/app/server/api/factories/[factoryId]/index.delete.ts: -------------------------------------------------------------------------------- 1 | import { getFactoryStorage } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const { factoryId } = getRouterParams(event, { 5 | decode: true, 6 | }) 7 | const mq = getMq() 8 | const storage = await getFactoryStorage(mq) 9 | await storage.remove(factoryId) 10 | return { id: factoryId } 11 | }) 12 | -------------------------------------------------------------------------------- /packages/app/server/api/factories/[factoryId]/index.get.ts: -------------------------------------------------------------------------------- 1 | import type { ResourceFactory } from '@moquerie/core' 2 | import { getFactoryStorage } from '@moquerie/core' 3 | import SuperJSON from 'superjson' 4 | 5 | export default defineEventHandler(async (event) => { 6 | const { factoryId } = getRouterParams(event, { 7 | decode: true, 8 | }) 9 | const mq = getMq() 10 | const storage = await getFactoryStorage(mq) 11 | const data = await storage.findById(factoryId) 12 | if (!data) { 13 | throw createError({ 14 | status: 404, 15 | statusMessage: `Factory ${factoryId} not found`, 16 | }) 17 | } 18 | return SuperJSON.stringify({ 19 | ...data, 20 | ast: undefined, 21 | }) as unknown as ResourceFactory 22 | }) 23 | -------------------------------------------------------------------------------- /packages/app/server/api/factories/[factoryId]/index.patch.ts: -------------------------------------------------------------------------------- 1 | import type { FactoryData } from '@/components/factory/formTypes.js' 2 | import { getFactoryStorage } from '@moquerie/core' 3 | import { nanoid } from 'nanoid' 4 | import SuperJSON from 'superjson' 5 | 6 | export default defineEventHandler<{ body: FactoryData }>(async (event) => { 7 | const { factoryId } = getRouterParams(event, { 8 | decode: true, 9 | }) 10 | const body = await readBody(event) 11 | const mq = getMq() 12 | const storage = await getFactoryStorage(mq) 13 | 14 | const factory = await storage.findById(factoryId) 15 | if (!factory) { 16 | throw new Error(`Factory ${factoryId} not found`) 17 | } 18 | 19 | if (body.location === 'local') { 20 | if (factory.location !== body.location) { 21 | await storage.remove(factory.id) 22 | // Override id with random one 23 | factory.id = `${body.resourceName ?? factory.resourceName}-${nanoid()}` 24 | } 25 | } 26 | else if (body.location === 'repository') { 27 | // Use name as id 28 | const name = body.name ?? factory.name 29 | if (await storage.findById(name, 'repository')) { 30 | throw new Error(`Factory with name "${name}" already exists in repository`) 31 | } 32 | await storage.remove(factory.id) 33 | factory.id = `${body.resourceName ?? factory.resourceName}-${name}` 34 | } 35 | Object.assign(factory, body) 36 | await storage.save(factory, factory.location) 37 | return SuperJSON.stringify({ 38 | ...factory, 39 | ast: undefined, 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /packages/app/server/api/factories/count.get.ts: -------------------------------------------------------------------------------- 1 | import type { DBLocation } from '@moquerie/core' 2 | import { getFactoryStorage } from '@moquerie/core' 3 | 4 | export default defineEventHandler<{ query: { resourceName: string, location?: DBLocation } }, Promise>>(async (event) => { 5 | const query = getQuery(event) 6 | const storage = await getFactoryStorage(getMq()) 7 | let factories = await storage.findAll() 8 | if (query.resourceName) { 9 | factories = factories.filter(factory => factory.resourceName === query.resourceName) 10 | } 11 | const result: Record = { 12 | local: 0, 13 | repository: 0, 14 | } 15 | for (const factory of factories) { 16 | result[factory.location]++ 17 | } 18 | return result 19 | }) 20 | -------------------------------------------------------------------------------- /packages/app/server/api/factories/create.post.ts: -------------------------------------------------------------------------------- 1 | import type { ResourceFactory } from '@moquerie/core' 2 | import type { FactoryData } from '~/components/factory/formTypes.js' 3 | import { getCurrentUser, getFactoryFilename, getFactoryStorage } from '@moquerie/core' 4 | import { nanoid } from 'nanoid' 5 | import path from 'pathe' 6 | import SuperJSON from 'superjson' 7 | 8 | export default defineEventHandler(async (event) => { 9 | const body: FactoryData = await readBody(event) 10 | const mq = getMq() 11 | const storage = await getFactoryStorage(mq) 12 | const id = `${body.resourceName}-${body.name}${body.location === 'local' ? `@@${nanoid()}` : ''}` 13 | 14 | if (await storage.findById(id)) { 15 | throw new Error(`Factory with id "${id}" already exists`) 16 | } 17 | 18 | const file = path.join(storage[body.location].folder, getFactoryFilename(mq, body.resourceName, id, body.name, body.location)) 19 | 20 | const factory: ResourceFactory = { 21 | ...body, 22 | id, 23 | file, 24 | lastUsedAt: null, 25 | info: { 26 | ...body.info, 27 | createdAt: new Date(), 28 | author: await getCurrentUser(), 29 | }, 30 | } 31 | await storage.save(factory, factory.location) 32 | return SuperJSON.stringify({ 33 | ...factory, 34 | ast: undefined, 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/app/server/api/factories/defaultFields.get.ts: -------------------------------------------------------------------------------- 1 | import { createDefaultFactoryFields } from '@moquerie/core' 2 | 3 | export default defineEventHandler<{ query: { resourceName: string } }>(async (event) => { 4 | const { resourceName } = getQuery(event) 5 | const mq = getMq() 6 | const ctx = await mq.getResolvedContext() 7 | const resourceType = ctx.schema.types[resourceName] 8 | if (!resourceType) { 9 | throw new Error(`Resource ${resourceName} not found`) 10 | } 11 | const fields = await createDefaultFactoryFields(mq, { 12 | resourceType, 13 | }) 14 | return { 15 | fields, 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /packages/app/server/api/factories/defaultValues.get.ts: -------------------------------------------------------------------------------- 1 | import type { ResourceFactory } from '@moquerie/core' 2 | import { createDefaultFactoryFields, createInstanceFromFactory, getCurrentUser, serializeFactory } from '@moquerie/core' 3 | import { printCode } from '@moquerie/core/util' 4 | 5 | export default defineEventHandler<{ query: { resourceName: string } }>(async (event) => { 6 | const { resourceName } = getQuery(event) 7 | const mq = getMq() 8 | const ctx = await mq.getResolvedContext() 9 | const resourceType = ctx.schema.types[resourceName] 10 | if (!resourceType) { 11 | throw new Error(`Resource ${resourceName} not found`) 12 | } 13 | const defaultFields = await createDefaultFactoryFields(mq, { 14 | resourceType, 15 | randomRefs: false, 16 | }) 17 | const factory: ResourceFactory = { 18 | id: 'default-preview', 19 | name: 'default preview', 20 | file: '', 21 | resourceName, 22 | location: 'local', 23 | lastUsedAt: null, 24 | info: { 25 | createdAt: new Date(), 26 | applyTags: [], 27 | createPrompts: [], 28 | tags: [], 29 | author: await getCurrentUser(), 30 | }, 31 | fields: defaultFields, 32 | } 33 | 34 | const ast = await serializeFactory(factory) 35 | ast.program.body = ast.program.body.filter(node => node.type !== 'ImportDeclaration') 36 | const inlineCode = printCode(ast) 37 | 38 | const instance = await createInstanceFromFactory(mq, { 39 | factory, 40 | inlineCode, 41 | save: false, 42 | }) 43 | return instance.value 44 | }) 45 | -------------------------------------------------------------------------------- /packages/app/server/api/factories/index.get.ts: -------------------------------------------------------------------------------- 1 | import type { DBLocation, ResourceFactory } from '@moquerie/core' 2 | import { getFactoryStorage } from '@moquerie/core' 3 | import SuperJSON from 'superjson' 4 | 5 | export default defineEventHandler<{ query: { resourceName: string, location?: DBLocation } }, Promise>(async (event) => { 6 | const query = getQuery(event) 7 | const mq = getMq() 8 | const storage = await getFactoryStorage(mq) 9 | let factories: ResourceFactory[] 10 | if (query.location) { 11 | factories = await storage.findAllByLocation(query.location) 12 | } 13 | else { 14 | factories = await storage.findAll() 15 | } 16 | if (query.resourceName) { 17 | factories = factories.filter(factory => factory.resourceName === query.resourceName) 18 | } 19 | factories = factories.sort((a, b) => (b.lastUsedAt?.getTime() ?? 0) - (a.lastUsedAt?.getTime() ?? 0)) 20 | factories = factories.map(factory => ({ 21 | ...factory, 22 | ast: undefined, 23 | })) 24 | return SuperJSON.stringify(factories) as unknown as ResourceFactory[] 25 | }) 26 | -------------------------------------------------------------------------------- /packages/app/server/api/factories/preview.post.ts: -------------------------------------------------------------------------------- 1 | import type { FactoryData } from '@/components/factory/formTypes.js' 2 | import { createInstanceFromFactory, getCurrentUser, serializeFactory } from '@moquerie/core' 3 | import { printCode } from '@moquerie/core/util' 4 | 5 | export default defineEventHandler<{ body: FactoryData }>(async (event) => { 6 | const mq = getMq() 7 | const ctx = await mq.getResolvedContext() 8 | const factoryData = await readBody(event) 9 | const resourceType = ctx.schema.types[factoryData.resourceName] 10 | if (!resourceType) { 11 | throw new Error(`Resource type ${factoryData.resourceName} not found`) 12 | } 13 | 14 | const factory = { 15 | ...factoryData, 16 | id: 'preview-factory', 17 | file: 'default', 18 | info: { 19 | ...factoryData.info, 20 | createdAt: new Date(), 21 | author: await getCurrentUser(), 22 | fakerSeed: undefined, 23 | }, 24 | } 25 | 26 | const ast = await serializeFactory(factory) 27 | ast.program.body = ast.program.body.filter(node => node.type !== 'ImportDeclaration') 28 | const inlineCode = printCode(ast) 29 | 30 | const instance = await createInstanceFromFactory(mq, { 31 | factory, 32 | inlineCode, 33 | save: false, 34 | }) 35 | return instance.value 36 | }) 37 | -------------------------------------------------------------------------------- /packages/app/server/api/faker/factories.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async () => { 2 | const { faker } = await import('@faker-js/faker') 3 | const deprecated = [ 4 | 'address', 5 | 'name', 6 | 'random', 7 | ] 8 | const result: string[] = [] 9 | 10 | for (const key of Object.keys(faker)) { 11 | if (deprecated.includes(key) || key.startsWith('_')) { 12 | continue 13 | } 14 | 15 | // @ts-expect-error no index defined 16 | const value = faker[key] 17 | 18 | if (typeof value === 'object') { 19 | for (const subKey of Object.keys(value)) { 20 | if (subKey === 'faker') { 21 | continue 22 | } 23 | const subValue = value[subKey] 24 | if (typeof subValue === 'function') { 25 | result.push(`${key}.${subKey}`) 26 | } 27 | } 28 | } 29 | } 30 | 31 | return result.sort((a, b) => a.localeCompare(b)) 32 | }) 33 | -------------------------------------------------------------------------------- /packages/app/server/api/faker/locales.get.ts: -------------------------------------------------------------------------------- 1 | import { fakerLocales } from '@moquerie/core' 2 | 3 | export default defineEventHandler(() => fakerLocales) 4 | -------------------------------------------------------------------------------- /packages/app/server/api/faker/preview.post.ts: -------------------------------------------------------------------------------- 1 | import { generateValueFromFaker, getFaker } from '@moquerie/core' 2 | import SuperJSON from 'superjson' 3 | 4 | interface Body { 5 | factory: string 6 | locale?: string 7 | paramsCode?: string 8 | paramsContext?: string 9 | } 10 | 11 | export default defineEventHandler<{ body: Body }>(async (event) => { 12 | const mq = getMq() 13 | const body = await readBody(event) 14 | let paramsContext = body.paramsContext ? SuperJSON.parse(body.paramsContext) ?? JSON.parse(body.paramsContext) : undefined 15 | if (!paramsContext) { 16 | paramsContext = {} 17 | } 18 | if (!paramsContext.item) { 19 | paramsContext.item = {} 20 | } 21 | const faker = await getFaker(mq, { 22 | locale: body.locale, 23 | seed: undefined, 24 | }) 25 | try { 26 | return generateValueFromFaker({ 27 | factory: body.factory, 28 | faker, 29 | paramsCode: body.paramsCode, 30 | paramsContext, 31 | }) 32 | } 33 | // eslint-disable-next-line unused-imports/no-unused-vars 34 | catch (error) { 35 | return generateValueFromFaker({ 36 | factory: body.factory, 37 | faker, 38 | }) 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /packages/app/server/api/graphql/hasGraphql.ts: -------------------------------------------------------------------------------- 1 | import { resolveConfig } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async () => { 4 | const mq = getMq() 5 | const { config } = await resolveConfig(mq.data.cwd) 6 | return !!config?.graphql 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/server/api/graphql/schema/inspect.ts: -------------------------------------------------------------------------------- 1 | import { printSchema } from 'graphql' 2 | 3 | export default defineEventHandler(async () => { 4 | const mq = getMq() 5 | const ctx = await mq.getResolvedContext() 6 | if (!ctx.graphqlSchema) { 7 | throw new Error('GraphQL schema not found') 8 | } 9 | return { 10 | schema: printSchema(ctx.graphqlSchema.schema), 11 | introspection: ctx.graphqlSchema.introspection, 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /packages/app/server/api/history/[recordId].get.ts: -------------------------------------------------------------------------------- 1 | import SuperJSON from 'superjson' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const mq = getMq() 5 | const { recordId } = getRouterParams(event) 6 | const ctx = await mq.getResolvedContext() 7 | const { historyRecords } = ctx 8 | const record = historyRecords.find(r => r.id === recordId) 9 | 10 | if (!record) { 11 | throw new Error('Record not found') 12 | } 13 | 14 | return SuperJSON.stringify(record) 15 | }) 16 | -------------------------------------------------------------------------------- /packages/app/server/api/history/index.get.ts: -------------------------------------------------------------------------------- 1 | import type { HistoryRecordForList } from '@moquerie/core' 2 | import SuperJSON from 'superjson' 3 | 4 | export default defineEventHandler(async (event) => { 5 | const mq = getMq() 6 | const query = getQuery(event) 7 | const ctx = await mq.getResolvedContext() 8 | let historyRecords = ctx.historyRecords 9 | 10 | if (query.branch) { 11 | historyRecords = historyRecords.filter(r => r.branch === query.branch) 12 | } 13 | 14 | let page = Number.parseInt(String(query.page)) 15 | if (Number.isNaN(page)) { 16 | page = 1 17 | } 18 | const pageSize = 100 19 | 20 | const start = (page - 1) * pageSize 21 | const end = start + pageSize 22 | 23 | const records = historyRecords.slice(start, end) 24 | 25 | return SuperJSON.stringify({ 26 | records: records.map(r => ({ 27 | id: r.id, 28 | type: r.type, 29 | date: r.date, 30 | branch: r.branch, 31 | resourceName: r.resourceName, 32 | } satisfies HistoryRecordForList)), 33 | page, 34 | pageSize, 35 | total: historyRecords.length, 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /packages/app/server/api/openInEditor.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error missing types 2 | import launch from 'launch-editor' 3 | import path from 'pathe' 4 | 5 | export default defineEventHandler(async (event) => { 6 | const mq = getMq() 7 | const { file } = getQuery(event) 8 | launch(path.resolve(mq.data.cwd, String(file))) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/app/server/api/project.get.ts: -------------------------------------------------------------------------------- 1 | import { getProjectName } from '@moquerie/core/util' 2 | 3 | export default defineEventHandler(async () => { 4 | const mq = getMq() 5 | return { 6 | projectName: getProjectName(mq), 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /packages/app/server/api/pubsub/publish.post.ts: -------------------------------------------------------------------------------- 1 | import type { PubSubs } from '@moquerie/core/dist/pubsub/createPubSub.js' 2 | 3 | export default defineEventHandler<{ body: { type: keyof PubSubs, channel: string, payload: any } }>(async (event) => { 4 | const { type, channel, payload } = await readBody(event) 5 | const mq = getMq() 6 | const ctx = await mq.getResolvedContext() 7 | ctx.pubSubs[type].publish(channel, payload) 8 | }) 9 | -------------------------------------------------------------------------------- /packages/app/server/api/pubsub/suggestChannels.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async () => { 2 | const mq = getMq() 3 | const ctx = await mq.getResolvedContext() 4 | const result: string[] = [] 5 | if (ctx.schema.types.Subscription) { 6 | result.push(...Object.keys(ctx.schema.types.Subscription.fields)) 7 | } 8 | return result 9 | }) 10 | -------------------------------------------------------------------------------- /packages/app/server/api/resolvers/counts.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async () => { 2 | const mq = getMq() 3 | const ctx = await mq.getResolvedContext() 4 | const actions = [...ctx.fieldActions.items, ...ctx.resolvers.items].map(fa => ({ 5 | resourceName: fa.resourceName, 6 | fieldName: fa.fieldName, 7 | file: fa.file, 8 | })) 9 | 10 | const result: Record = {} 11 | 12 | for (const key in ctx.schema.types) { 13 | result[key] = 0 14 | } 15 | 16 | for (const action of actions) { 17 | result[action.resourceName]++ 18 | } 19 | 20 | return result 21 | }) 22 | -------------------------------------------------------------------------------- /packages/app/server/api/resolvers/index.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async (event) => { 2 | const { resourceName, getCode } = getQuery(event) 3 | 4 | const mq = getMq() 5 | const ctx = await mq.getResolvedContext() 6 | let result = [...ctx.fieldActions.items, ...ctx.resolvers.items].map(fa => ({ 7 | resourceName: fa.resourceName, 8 | fieldName: fa.fieldName, 9 | file: fa.file, 10 | code: getCode ? fa.action.toString() : undefined, 11 | })) 12 | 13 | if (resourceName) { 14 | result = result.filter(r => Array.isArray(resourceName) ? resourceName.includes(r.resourceName) : r.resourceName === resourceName) 15 | } 16 | 17 | return result 18 | }) 19 | -------------------------------------------------------------------------------- /packages/app/server/api/resolvers/preview.post.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from 'nanoid' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const { 5 | resourceName, 6 | instanceId, 7 | fieldName, 8 | } = await readBody(event) 9 | 10 | const mq = getMq() 11 | const ctx = await mq.getResolvedContext() 12 | 13 | const fieldAction = ctx.resolvers.items.find(fa => fa.resourceName === resourceName && fa.fieldName === fieldName) 14 | ?? ctx.fieldActions.items.find(fa => fa.resourceName === resourceName && fa.fieldName === fieldName) 15 | if (!fieldAction) { 16 | throw new Error(`Field action not found for resource ${resourceName} and field ${fieldName}`) 17 | } 18 | 19 | const instance = await ctx.db[String(resourceName)].findByInstanceIdOrThrow(String(instanceId)) 20 | 21 | return fieldAction.action({ 22 | parent: instance, 23 | input: {}, 24 | db: ctx.db, 25 | pubsub: ctx.pubSubs, 26 | generateId: nanoid, 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/[resourceName]/ignored.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async (event) => { 2 | const mq = getMq() 3 | const ctx = await mq.getResolvedContext() 4 | const { resourceName } = getRouterParams(event) 5 | return ctx.schema.ignoredInExplorer?.some((filter) => { 6 | if (typeof filter === 'string') { 7 | return filter === resourceName 8 | } 9 | else if (filter instanceof RegExp) { 10 | return filter.test(resourceName) 11 | } 12 | return false 13 | }) ?? false 14 | }) 15 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/[resourceName]/index.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async (event) => { 2 | const mq = getMq() 3 | const ctx = await mq.getResolvedContext() 4 | const { resourceName } = getRouterParams(event) 5 | return ctx.schema.types[resourceName] 6 | }) 7 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/create.post.ts: -------------------------------------------------------------------------------- 1 | import { createResourceInstance } from '@moquerie/core' 2 | import SuperJSON from 'superjson' 3 | 4 | export default defineEventHandler(async (event) => { 5 | const mq = getMq() 6 | const ctx = await mq.getResolvedContext() 7 | const { resourceName, value, comment, tags } = await readBody(event) 8 | const resourceType = ctx.schema.types[resourceName] 9 | if (!resourceType) { 10 | throw new Error(`Resource type not found: ${resourceName}`) 11 | } 12 | 13 | const instance = await createResourceInstance(mq, { 14 | resourceName, 15 | value, 16 | comment, 17 | tags, 18 | save: true, 19 | }) 20 | 21 | return SuperJSON.stringify(instance) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/generate.post.ts: -------------------------------------------------------------------------------- 1 | import { generateResourceInstances, getFactoryStorage } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const mq = getMq() 5 | const ctx = await mq.getResolvedContext() 6 | const { resourceName, factoryId, count } = await readBody(event) 7 | const resourceType = ctx.schema.types[resourceName] 8 | if (!resourceType) { 9 | throw new Error(`Resource type not found: ${resourceName}`) 10 | } 11 | const factory = await (await getFactoryStorage(mq)).findById(factoryId) 12 | if (!factory) { 13 | throw new Error(`Factory not found: ${factoryId}`) 14 | } 15 | if (factory.resourceName !== resourceName) { 16 | throw new Error(`Factory resource type mismatch: ${factory.resourceName} !== ${resourceName}`) 17 | } 18 | const instances = await generateResourceInstances(mq, { 19 | resourceType, 20 | factory, 21 | count, 22 | }) 23 | return instances 24 | }) 25 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/ids.get.ts: -------------------------------------------------------------------------------- 1 | import { getResourceInstanceStorage } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async () => { 4 | const mq = getMq() 5 | const ctx = await mq.getResolvedContext() 6 | const ids: Record = {} 7 | for (const resourceName in ctx.schema.types) { 8 | const storage = await getResourceInstanceStorage(mq, resourceName) 9 | ids[resourceName] = Object.keys(storage.manifest.files) 10 | } 11 | return ids 12 | }) 13 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/index.get.ts: -------------------------------------------------------------------------------- 1 | import SuperJSON from 'superjson' 2 | 3 | export default defineEventHandler(async () => { 4 | const mq = getMq() 5 | const ctx = await mq.getResolvedContext() 6 | return SuperJSON.stringify(ctx.schema) 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/instances/[resourceName]/[instanceId]/index.get.ts: -------------------------------------------------------------------------------- 1 | import { findResourceInstanceById } from '@moquerie/core' 2 | import SuperJSON from 'superjson' 3 | 4 | export default defineEventHandler(async (event) => { 5 | const mq = getMq() 6 | const { resourceName, instanceId } = getRouterParams(event) 7 | const instance = await findResourceInstanceById(mq, resourceName, instanceId) 8 | return SuperJSON.stringify(instance) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/instances/[resourceName]/[instanceId]/index.patch.ts: -------------------------------------------------------------------------------- 1 | import { updateResourceInstanceById } from '@moquerie/core' 2 | import SuperJSON from 'superjson' 3 | 4 | export default defineEventHandler(async (event) => { 5 | const mq = getMq() 6 | const { resourceName, instanceId } = getRouterParams(event) 7 | const body = await readBody(event) 8 | const data = await updateResourceInstanceById(mq, resourceName, instanceId, body) 9 | return SuperJSON.stringify(data) 10 | }) 11 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/instances/[resourceName]/bulk.delete.ts: -------------------------------------------------------------------------------- 1 | import { removeResourceInstanceById } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const mq = getMq() 5 | const { resourceName } = getRouterParams(event) 6 | const { ids } = await readBody(event) 7 | 8 | await Promise.all(ids.map((id: string) => removeResourceInstanceById(mq, resourceName, id))) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/instances/[resourceName]/bulk.patch.ts: -------------------------------------------------------------------------------- 1 | import { updateResourceInstanceById } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const mq = getMq() 5 | const { resourceName } = getRouterParams(event) 6 | const body = await readBody(event) 7 | const { ids, data } = body 8 | await Promise.all(ids.map((id: string) => updateResourceInstanceById(mq, resourceName, id, data))) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/instances/[resourceName]/duplicate.post.ts: -------------------------------------------------------------------------------- 1 | import type { ResourceInstance } from '@moquerie/core' 2 | import { deactiveOtherSingletonResourceInstances, findResourceInstanceById, getResourceInstanceStorage } from '@moquerie/core' 3 | import { nanoid } from 'nanoid' 4 | import SuperJSON from 'superjson' 5 | 6 | export default defineEventHandler(async (event) => { 7 | const mq = getMq() 8 | const { resourceName } = getRouterParams(event) 9 | const { ids } = await readBody(event) 10 | 11 | const ctx = await mq.getResolvedContext() 12 | const resourceType = ctx.schema.types[resourceName] 13 | if (!resourceType) { 14 | throw new Error(`Resource not found: ${resourceName}`) 15 | } 16 | 17 | const copies = await Promise.all((ids as string[]).map(async (instanceId, index) => { 18 | const instance = await findResourceInstanceById(mq, resourceName, instanceId) 19 | if (!instance) { 20 | throw new Error(`Resource instance not found: ${resourceName}/${instanceId}`) 21 | } 22 | const copy: ResourceInstance = { 23 | ...structuredClone(instance), 24 | id: nanoid(), 25 | createdAt: new Date(), 26 | updatedAt: null, 27 | active: resourceType.array || index === ids.length - 1, 28 | } 29 | const storage = await getResourceInstanceStorage(mq, resourceName) 30 | await storage.save(copy) 31 | 32 | if (copy.active) { 33 | await deactiveOtherSingletonResourceInstances(mq, resourceName, copy.id) 34 | } 35 | 36 | return copy 37 | })) 38 | 39 | return SuperJSON.stringify(copies) 40 | }) 41 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/instances/[resourceName]/getByIds.post.ts: -------------------------------------------------------------------------------- 1 | import { findResourceInstanceById } from '@moquerie/core' 2 | import SuperJSON from 'superjson' 3 | 4 | export default defineEventHandler(async (event) => { 5 | const mq = getMq() 6 | const { resourceName } = getRouterParams(event) 7 | const { ids } = await readBody(event) 8 | 9 | const instances = await Promise.all(ids.map((id: string) => findResourceInstanceById(mq, resourceName, id))) 10 | return SuperJSON.stringify(instances.filter(Boolean)) 11 | }) 12 | -------------------------------------------------------------------------------- /packages/app/server/api/resources/instances/[resourceName]/index.get.ts: -------------------------------------------------------------------------------- 1 | import { findAllResourceInstances } from '@moquerie/core' 2 | import SuperJSON from 'superjson' 3 | 4 | export default defineEventHandler(async (event) => { 5 | const mq = getMq() 6 | const { resourceName } = getRouterParams(event) 7 | const { filterActive, searchText } = getQuery(event) as any 8 | 9 | let list = await findAllResourceInstances(mq, resourceName, { 10 | filterActive, 11 | }) 12 | 13 | if (searchText) { 14 | const reg = new RegExp(searchText, 'i') 15 | list = list.filter(i => i.comment?.match(reg) || i.tags.some(tag => tag.match(reg)) || searchInValue(reg, i.value)) 16 | } 17 | 18 | // Max 100 items 19 | // @TODO pagination 20 | if (list.length > 100) { 21 | list = list.slice(0, 100) 22 | } 23 | 24 | list = list.sort((a, b) => { 25 | const timeA = a.createdAt.getTime() 26 | const timeB = b.createdAt.getTime() 27 | if (timeA === timeB) { 28 | return a.id.localeCompare(b.id) 29 | } 30 | return timeB - timeA 31 | }) 32 | return SuperJSON.stringify(list) 33 | }) 34 | -------------------------------------------------------------------------------- /packages/app/server/api/rest/fetch.post.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async (event) => { 2 | const mq = await getMq() 3 | const ctx = await mq.getResolvedContext() 4 | 5 | const body = await readBody(event) as { 6 | path: string 7 | method: string 8 | query?: Record 9 | body?: any 10 | } 11 | 12 | let url: string 13 | 14 | if (body.path.startsWith('http')) { 15 | url = body.path 16 | } 17 | else { 18 | if (!body.path.startsWith('/')) { 19 | body.path = `/${body.path}` 20 | } 21 | 22 | const routeInfo = ctx.server.routeInfos.find(r => r.type === 'rest') 23 | if (!routeInfo) { 24 | throw new Error('RouteInfo not found') 25 | } 26 | url = `${routeInfo.url}${body.path}` 27 | } 28 | 29 | const result = await $fetch(url, { 30 | method: body.method as any, 31 | query: body.query, 32 | body: (body.method.toLowerCase() === 'get') ? undefined : body.body, 33 | }) 34 | return result 35 | }) 36 | -------------------------------------------------------------------------------- /packages/app/server/api/rest/hasRest.get.ts: -------------------------------------------------------------------------------- 1 | import { resolveConfig } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async () => { 4 | const mq = getMq() 5 | const { config } = await resolveConfig(mq.data.cwd) 6 | return !!config?.rest 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/server/api/scripts/[scriptId]/index.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async (event) => { 2 | const { scriptId } = getRouterParams(event) 3 | 4 | const mq = getMq() 5 | const ctx = await mq.getResolvedContext() 6 | return ctx.scripts.items.find(item => item.id === scriptId) 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/server/api/scripts/[scriptId]/run.post.ts: -------------------------------------------------------------------------------- 1 | import { runScript } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const { scriptId } = getRouterParams(event) 5 | 6 | const mq = getMq() 7 | const report = await runScript(mq, scriptId) 8 | return report 9 | }) 10 | -------------------------------------------------------------------------------- /packages/app/server/api/scripts/index.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async () => { 2 | const mq = getMq() 3 | const ctx = await mq.getResolvedContext() 4 | return ctx.scripts.items 5 | }) 6 | -------------------------------------------------------------------------------- /packages/app/server/api/server/index.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async () => { 2 | const mq = getMq() 3 | const ctx = await mq.getResolvedContext() 4 | 5 | return { 6 | port: ctx.context.port, 7 | routeInfos: ctx.server.routeInfos, 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /packages/app/server/api/settings/index.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async () => { 2 | const mq = getMq() 3 | const ctx = await mq.getContext() 4 | return ctx.settings.getSettings() 5 | }) 6 | -------------------------------------------------------------------------------- /packages/app/server/api/settings/index.patch.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler(async (event) => { 2 | const mq = getMq() 3 | const { settings } = await readBody(event) 4 | const ctx = await mq.getContext() 5 | await ctx.settings.updateSettings(settings) 6 | return ctx.settings.getSettings() 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/server/api/snapshots/[snapshotId]/createBranch.post.ts: -------------------------------------------------------------------------------- 1 | import { createBranchFromSnapshot, getSnapshotStorage } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const mq = getMq() 5 | const { snapshotId } = getRouterParams(event) 6 | const { branchName } = await readBody(event) 7 | 8 | const storage = await getSnapshotStorage(mq) 9 | const snapshot = await storage.findById(snapshotId) 10 | 11 | if (!snapshot) { 12 | throw new Error(`Snapshot ${snapshotId} not found`) 13 | } 14 | 15 | await createBranchFromSnapshot(mq, { 16 | snapshot, 17 | branchName, 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/app/server/api/snapshots/[snapshotId]/index.delete.ts: -------------------------------------------------------------------------------- 1 | import { deleteSnapshot } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const mq = getMq() 5 | const { snapshotId } = getRouterParams(event) 6 | await deleteSnapshot(mq, snapshotId) 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/server/api/snapshots/[snapshotId]/index.get.ts: -------------------------------------------------------------------------------- 1 | import { getSnapshotStorage } from '@moquerie/core' 2 | import SuperJSON from 'superjson' 3 | 4 | export default defineEventHandler(async (event) => { 5 | const mq = getMq() 6 | const { snapshotId } = getRouterParams(event) 7 | const storage = await getSnapshotStorage(mq) 8 | const snapshot = await storage.findById(snapshotId) 9 | if (!snapshot) { 10 | throw new Error(`Snapshot ${snapshotId} not found`) 11 | } 12 | return SuperJSON.stringify(snapshot) 13 | }) 14 | -------------------------------------------------------------------------------- /packages/app/server/api/snapshots/[snapshotId]/overwriteBranch.post.ts: -------------------------------------------------------------------------------- 1 | import { getSnapshotStorage, overwriteCurrentBranchWithSnapshot } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const mq = getMq() 5 | const { snapshotId } = getRouterParams(event) 6 | 7 | const storage = await getSnapshotStorage(mq) 8 | const snapshot = await storage.findById(snapshotId) 9 | 10 | if (!snapshot) { 11 | throw new Error(`Snapshot ${snapshotId} not found`) 12 | } 13 | 14 | await overwriteCurrentBranchWithSnapshot(mq, { 15 | snapshot, 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /packages/app/server/api/snapshots/[snapshotId]/resources/add.post.ts: -------------------------------------------------------------------------------- 1 | import { addResourcesToSnapshot, getSnapshotStorage } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const mq = getMq() 5 | const { snapshotId } = getRouterParams(event) 6 | const { resourceIds } = await readBody(event) 7 | const storage = await getSnapshotStorage(mq) 8 | const snapshot = await storage.findById(snapshotId) 9 | if (!snapshot) { 10 | throw new Error(`Snapshot ${snapshotId} not found`) 11 | } 12 | await addResourcesToSnapshot(mq, { 13 | snapshot, 14 | resourceIds, 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/app/server/api/snapshots/[snapshotId]/resources/count.get.ts: -------------------------------------------------------------------------------- 1 | import { getSnapshotStorage, readSnapshotResourceIds } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const mq = getMq() 5 | const { snapshotId } = getRouterParams(event) 6 | const storage = await getSnapshotStorage(mq) 7 | const snapshot = await storage.findById(snapshotId) 8 | if (!snapshot) { 9 | throw new Error(`Snapshot ${snapshotId} not found`) 10 | } 11 | const ids = await readSnapshotResourceIds(mq, snapshot) 12 | const result: Record = {} 13 | for (const resourceName in ids) { 14 | result[resourceName] = ids[resourceName].length 15 | } 16 | return result 17 | }) 18 | -------------------------------------------------------------------------------- /packages/app/server/api/snapshots/[snapshotId]/resources/remove.post.ts: -------------------------------------------------------------------------------- 1 | import { getSnapshotStorage, removeResourcesFromSnapshot } from '@moquerie/core' 2 | 3 | export default defineEventHandler(async (event) => { 4 | const mq = getMq() 5 | const { snapshotId } = getRouterParams(event) 6 | const { resourceIds } = await readBody(event) 7 | const storage = await getSnapshotStorage(mq) 8 | const snapshot = await storage.findById(snapshotId) 9 | if (!snapshot) { 10 | throw new Error(`Snapshot ${snapshotId} not found`) 11 | } 12 | await removeResourcesFromSnapshot(mq, { 13 | snapshot, 14 | resourceIds, 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /packages/app/server/api/snapshots/count.get.ts: -------------------------------------------------------------------------------- 1 | import type { DBLocation } from '@moquerie/core' 2 | import { getSnapshotStorage } from '@moquerie/core' 3 | 4 | export default defineEventHandler(async () => { 5 | const mq = getMq() 6 | const storage = await getSnapshotStorage(mq) 7 | const snapshots = await storage.findAll() 8 | const result: Record = { 9 | local: 0, 10 | repository: 0, 11 | } 12 | for (const snapshot of snapshots) { 13 | result[snapshot.location]++ 14 | } 15 | return result 16 | }) 17 | -------------------------------------------------------------------------------- /packages/app/server/api/snapshots/index.get.ts: -------------------------------------------------------------------------------- 1 | import type { DatabaseSnapshot, DBLocation } from '@moquerie/core' 2 | import { getSnapshotStorage } from '@moquerie/core' 3 | import SuperJSON from 'superjson' 4 | 5 | export default defineEventHandler<{ query: { location?: DBLocation }, body: DatabaseSnapshot[] }>(async (event) => { 6 | const mq = getMq() 7 | const { location } = await getQuery(event) 8 | const storage = await getSnapshotStorage(mq) 9 | let snapshots = await storage.findAll() 10 | if (location) { 11 | snapshots = snapshots.filter(s => s.location === location) 12 | } 13 | snapshots.sort((a, b) => b.date.getTime() - a.date.getTime()) 14 | return SuperJSON.stringify(snapshots) 15 | }) 16 | -------------------------------------------------------------------------------- /packages/app/server/api/snapshots/index.post.ts: -------------------------------------------------------------------------------- 1 | import { createSnapshot, getSnapshotStorage } from '@moquerie/core' 2 | import SuperJSON from 'superjson' 3 | 4 | export default defineEventHandler(async (event) => { 5 | const mq = getMq() 6 | const { id, location, description, tags, resources } = await readBody(event) 7 | 8 | const storage = await getSnapshotStorage(mq) 9 | if (await storage.findById(id)) { 10 | throw new Error(`Snapshot with id "${id}" already exists`) 11 | } 12 | 13 | const snapshot = await createSnapshot(mq, { 14 | id, 15 | location, 16 | description, 17 | tags, 18 | resources, 19 | }) 20 | return SuperJSON.stringify(snapshot) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/app/server/api/user/current/index.get.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentUser } from '@moquerie/core' 2 | 3 | export default defineEventHandler(() => getCurrentUser()) 4 | -------------------------------------------------------------------------------- /packages/app/server/plugins/init.ts: -------------------------------------------------------------------------------- 1 | import { createMoquerieInstance, startServer } from '@moquerie/core' 2 | import { getDefaultCwd } from '@moquerie/core/util' 3 | import { setMq } from '../utils/instance.js' 4 | 5 | export default defineNitroPlugin(async () => { 6 | const mq = await createMoquerieInstance({ 7 | cwd: getDefaultCwd(), 8 | }) 9 | setMq(mq) 10 | // Start server 11 | await startServer(mq) 12 | }) 13 | -------------------------------------------------------------------------------- /packages/app/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/app/server/utils/configFile.ts: -------------------------------------------------------------------------------- 1 | import { resolveConfig } from '@moquerie/core' 2 | import createJITI from 'jiti' 3 | import path from 'pathe' 4 | 5 | async function resolveConfigFile() { 6 | const mq = getMq() 7 | const cwd = mq.data.cwd 8 | const jiti = createJITI(cwd) 9 | return await jiti.resolve(path.resolve(cwd, 'moquerie.config'), { 10 | paths: [cwd], 11 | }) 12 | } 13 | 14 | export async function findConfigFile() { 15 | const mq = getMq() 16 | let configFile: string | undefined 17 | try { 18 | const result = await resolveConfig(mq.data.cwd) 19 | configFile = result.configFile 20 | } 21 | // eslint-disable-next-line unused-imports/no-unused-vars 22 | catch (e) { 23 | configFile = await resolveConfigFile() 24 | } 25 | return configFile 26 | } 27 | -------------------------------------------------------------------------------- /packages/app/server/utils/instance.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '@moquerie/core' 2 | 3 | let mq: MoquerieInstance 4 | 5 | export function getMq() { 6 | return mq 7 | } 8 | 9 | export function setMq(newMq: MoquerieInstance) { 10 | mq = newMq 11 | } 12 | -------------------------------------------------------------------------------- /packages/app/server/utils/search.ts: -------------------------------------------------------------------------------- 1 | import { isResourceInstanceReference } from '@moquerie/core' 2 | 3 | export function searchInValue(reg: RegExp, value: any): boolean { 4 | if (Array.isArray(value)) { 5 | return value.some(item => searchInValue(reg, item)) 6 | } 7 | else if (typeof value === 'string') { 8 | return !!value.match(reg) 9 | } 10 | else if (value && typeof value === 'object') { 11 | if (isResourceInstanceReference(value)) { 12 | return false 13 | } 14 | for (const key in value) { 15 | if (searchInValue(reg, value[key])) { 16 | return true 17 | } 18 | } 19 | } 20 | return false 21 | } 22 | -------------------------------------------------------------------------------- /packages/app/stores/commandPalette.ts: -------------------------------------------------------------------------------- 1 | export const useCommandPaletteStore = defineStore('commandPalette', () => { 2 | const isOpen = ref(false) 3 | 4 | return { 5 | isOpen, 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /packages/app/stores/factory.ts: -------------------------------------------------------------------------------- 1 | import type { DBLocation, ResourceFactory } from '@moquerie/core' 2 | import SuperJSON from 'superjson' 3 | 4 | export const useFactoryStore = defineStore('factories', () => { 5 | const factories = ref([]) 6 | 7 | // Fetch many 8 | 9 | let lastFetchOptions: { resourceName?: string, location?: DBLocation } = {} 10 | 11 | async function fetchFactories(options: { resourceName?: string, location?: DBLocation } = {}) { 12 | lastFetchOptions = options 13 | return factories.value = SuperJSON.parse(await $fetch('/api/factories', { 14 | query: options, 15 | })) 16 | } 17 | 18 | async function refreshFactories() { 19 | await fetchFactories(lastFetchOptions) 20 | } 21 | 22 | onWindowFocus(refreshFactories) 23 | 24 | // Fetch one 25 | 26 | async function fetchFactory(id: string) { 27 | const factory = SuperJSON.parse(await $fetch(`/api/factories/${id}`)) 28 | 29 | // Update in query cache 30 | const item = factories.value.find(i => i.id === id) 31 | if (item) { 32 | Object.assign(item, factory) 33 | } 34 | 35 | return factory 36 | } 37 | 38 | return { 39 | factories, 40 | fetchFactories, 41 | refreshFactories, 42 | fetchFactory, 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /packages/app/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | import colors from 'tailwindcss/colors.js' 3 | import defaultTheme from 'tailwindcss/defaultTheme.js' 4 | 5 | export default >{ 6 | theme: { 7 | extend: { 8 | fontFamily: { 9 | sans: ['Noto Sans', ...defaultTheme.fontFamily.sans], 10 | mono: ['Noto Sans Mono', ...defaultTheme.fontFamily.mono], 11 | }, 12 | colors: { 13 | primary: colors.violet, 14 | }, 15 | }, 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /packages/app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /packages/app/utils/config.ts: -------------------------------------------------------------------------------- 1 | const configChangeHook = createEventHook() 2 | 3 | export const onConfigChange = configChangeHook.on 4 | 5 | export const triggerConfigChange = configChangeHook.trigger 6 | -------------------------------------------------------------------------------- /packages/app/utils/form.ts: -------------------------------------------------------------------------------- 1 | import type { KeysMatching } from '@moquerie/core/util' 2 | 3 | export function useTagModel< 4 | TTarget extends Record, 5 | TKey extends KeysMatching, 6 | >(target: TTarget, key: TKey) { 7 | return computed({ 8 | get: () => target[key]?.join(', ') ?? '', 9 | set: (value) => { 10 | try { 11 | target[key] = value.split(',').map(tag => tag.trim()) as TTarget[TKey] 12 | } 13 | catch (e) { 14 | console.warn(e) 15 | } 16 | }, 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /packages/app/utils/graphql.ts: -------------------------------------------------------------------------------- 1 | export async function useHasGraphql() { 2 | const { data, refresh } = await useFetch('/api/graphql/hasGraphql') 3 | onWindowFocus(refresh) 4 | onConfigChange(refresh) 5 | 6 | return data 7 | } 8 | -------------------------------------------------------------------------------- /packages/app/utils/object.ts: -------------------------------------------------------------------------------- 1 | export function countItemsInRecordOfArrays>(obj: T) { 2 | return Object.keys(obj).reduce((acc, key) => acc + obj[key].length, 0) 3 | } 4 | -------------------------------------------------------------------------------- /packages/app/utils/resources.ts: -------------------------------------------------------------------------------- 1 | export async function useFavoriteResources() { 2 | const { settings } = await useSettings() 3 | 4 | function isFavorite(resourceName: string) { 5 | return settings.favoriteResources?.includes(resourceName) ?? false 6 | } 7 | 8 | function setFavorite(resourceName: string, favorite: boolean) { 9 | const index = settings.favoriteResources?.indexOf(resourceName) ?? -1 10 | if (favorite) { 11 | if (index === -1) { 12 | settings.favoriteResources = [ 13 | ...(settings.favoriteResources ?? []), 14 | resourceName, 15 | ] 16 | } 17 | } 18 | else { 19 | if (index !== -1) { 20 | settings.favoriteResources = [ 21 | ...(settings.favoriteResources ?? []).slice(0, index), 22 | ...(settings.favoriteResources ?? []).slice(index + 1), 23 | ] 24 | } 25 | } 26 | } 27 | 28 | function toggleFavorite(resourceName: string) { 29 | setFavorite(resourceName, !isFavorite(resourceName)) 30 | } 31 | 32 | const count = computed(() => settings.favoriteResources?.length ?? 0) 33 | 34 | return { 35 | isFavorite, 36 | setFavorite, 37 | toggleFavorite, 38 | count, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/app/utils/rest.ts: -------------------------------------------------------------------------------- 1 | export async function useHasRest() { 2 | const { data, refresh } = await useFetch('/api/rest/hasRest') 3 | onWindowFocus(refresh) 4 | onConfigChange(refresh) 5 | 6 | return data 7 | } 8 | -------------------------------------------------------------------------------- /packages/app/utils/settings.ts: -------------------------------------------------------------------------------- 1 | import type { Settings } from '@moquerie/core' 2 | 3 | export async function useSettings() { 4 | const { data, refresh } = await useFetch('/api/settings', { 5 | key: 'settings', 6 | }) 7 | onWindowFocus(refresh) 8 | 9 | const settings = reactive(data.value ?? {}) 10 | 11 | let autoUpdate = true 12 | 13 | async function applyWithoutAutoUpdate(value: Partial) { 14 | autoUpdate = false 15 | Object.assign(settings, value) 16 | await nextTick() 17 | autoUpdate = true 18 | } 19 | 20 | watch(data, () => { 21 | if (data.value) { 22 | const value = data.value 23 | applyWithoutAutoUpdate(value) 24 | } 25 | }) 26 | 27 | async function updateSettings() { 28 | const result = await $fetch('/api/settings', { 29 | method: 'PATCH', 30 | body: { 31 | settings, 32 | }, 33 | }) 34 | if (result) { 35 | data.value = result 36 | } 37 | } 38 | 39 | watch(settings, () => { 40 | if (autoUpdate) { 41 | updateSettings() 42 | } 43 | }, { 44 | deep: true, 45 | }) 46 | 47 | return { 48 | settings, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/app/utils/url.ts: -------------------------------------------------------------------------------- 1 | export function isProbablyAnImage(url: string) { 2 | return url.match(/\.(png|jpe?g|gif|svg|webp)$/) || url.includes('avatar') 3 | } 4 | -------------------------------------------------------------------------------- /packages/app/utils/window.ts: -------------------------------------------------------------------------------- 1 | export function onWindowFocus(cb: () => unknown) { 2 | useEventListener('focus', cb) 3 | } 4 | -------------------------------------------------------------------------------- /packages/core/config.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/config.js' 2 | -------------------------------------------------------------------------------- /packages/core/config.mjs: -------------------------------------------------------------------------------- 1 | export * from './dist/config.js' 2 | -------------------------------------------------------------------------------- /packages/core/src/ast/exportVariable.ts: -------------------------------------------------------------------------------- 1 | export function createExportedVariable(name: string, raw: string) { 2 | return { 3 | type: 'ExportNamedDeclaration', 4 | declarations: [ 5 | { 6 | type: 'VariableDeclarator', 7 | id: { 8 | type: 'Identifier', 9 | name, 10 | }, 11 | init: { 12 | type: 'Literal', 13 | raw, 14 | }, 15 | }, 16 | ], 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/ast/fn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the AST node which is the value of the return statement of a function or arrow function. 3 | */ 4 | export function getReturnNode(node: any) { 5 | if (node.type === 'ArrowFunctionExpression') { 6 | const body = node.body 7 | if (body.type === 'ObjectExpression' || body.type === 'Identifier' || body.type === 'CallExpression') { 8 | return body 9 | } 10 | else if (body.type === 'BlockStatement') { 11 | const returnStatement = body.body.find((node: any) => node.type === 'ReturnStatement') 12 | if (!returnStatement) { 13 | throw new Error('No return statement found') 14 | } 15 | return returnStatement.argument 16 | } 17 | else { 18 | throw new Error('Unexpected arrow function body type') 19 | } 20 | } 21 | else if (node.type === 'FunctionExpression') { 22 | const body = node.body 23 | if (body.type === 'BlockStatement') { 24 | const returnStatement = body.body.find((node: any) => node.type === 'ReturnStatement') 25 | if (!returnStatement) { 26 | throw new Error('No return statement found') 27 | } 28 | return returnStatement.argument 29 | } 30 | else { 31 | throw new Error('Unexpected function body type') 32 | } 33 | } 34 | else { 35 | throw new Error('Unexpected node type, is not a function') 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/src/ast/index.ts: -------------------------------------------------------------------------------- 1 | export * from './exportVariable.js' 2 | export * from './fn.js' 3 | export * from './objectToAst.js' 4 | export * from './parse.js' 5 | export * from './print.js' 6 | -------------------------------------------------------------------------------- /packages/core/src/ast/parse.ts: -------------------------------------------------------------------------------- 1 | import type { namedTypes } from 'ast-types' 2 | import babelParser from '@babel/parser' 3 | import { parse } from 'recast' 4 | 5 | export function parseCode(code: string): namedTypes.File { 6 | return parse(code, { 7 | parser: babelParser, 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/ast/print.ts: -------------------------------------------------------------------------------- 1 | import { print } from 'recast' 2 | 3 | export function printCode(ast: any): string { 4 | return print(ast).code 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/src/config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from './types/config.js' 2 | 3 | export function defineConfig(config: Config): Config { 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/src/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './resolve.js' 2 | -------------------------------------------------------------------------------- /packages/core/src/config/resolve.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '../types/config.js' 2 | import fs from 'node:fs' 3 | import { loadConfig } from 'c12' 4 | import path from 'pathe' 5 | 6 | export async function resolveConfig(cwd: string) { 7 | // Auto Rest API Types 8 | const autoRestTypesFile = path.resolve(cwd, 'moquerie.rest.ts') 9 | const autoRestTypesEnabled = fs.existsSync(autoRestTypesFile) 10 | 11 | return loadConfig({ 12 | name: 'moquerie', 13 | cwd, 14 | defaults: { 15 | ignoredResourcesInExplorer: [ 16 | 'Mutation', 17 | 'Subscription', 18 | ], 19 | mockFiles: [ 20 | '**/*.moq.js', 21 | '**/*.moq.ts', 22 | '**/*.mock.js', 23 | '**/*.mock.ts', 24 | ], 25 | ...autoRestTypesEnabled 26 | ? { 27 | rest: { 28 | typeFiles: [autoRestTypesFile], 29 | }, 30 | } 31 | : {}, 32 | }, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/factory/fakerGenerate.ts: -------------------------------------------------------------------------------- 1 | import type { Faker } from '@faker-js/faker' 2 | import { get } from '../util/object.js' 3 | import { runValueCode } from '../util/vm.js' 4 | 5 | export interface GenerateValueFromFakerOptions { 6 | factory: string 7 | faker: Faker 8 | paramsCode?: string 9 | paramsContext?: Record 10 | } 11 | 12 | export async function generateValueFromFaker(options: GenerateValueFromFakerOptions): Promise { 13 | const factory = get(options.faker, options.factory) 14 | 15 | if (!factory) { 16 | throw new Error(`Faker factory ${options.factory} not found`) 17 | } 18 | 19 | try { 20 | // Evaluate params as JS code using vm 21 | const params = options.paramsCode ? runValueCode(options.paramsCode, options.paramsContext) : undefined 22 | 23 | const result = factory(params) 24 | 25 | return result 26 | } 27 | catch (error: any) { 28 | // eslint-disable-next-line no-console 29 | console.log(options) 30 | throw new Error(`Faker factory ${options.factory} failed: ${error.message}`) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/src/factory/fakerGet.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | 3 | export interface GetFakerOptions { 4 | locale?: string 5 | seed?: any 6 | } 7 | 8 | export async function getFaker(mq: MoquerieInstance, options: GetFakerOptions = {}) { 9 | const ctx = await mq.getContext() 10 | 11 | const allFakers = await import('@faker-js/faker') 12 | 13 | // @ts-expect-error no index defined 14 | const locale = allFakers[options.locale ?? ctx.config.defaultFakerLocale ?? 'en'] 15 | 16 | const faker = new allFakers.Faker({ 17 | locale, 18 | }) 19 | 20 | if (options.seed) { 21 | faker.seed(options.seed) 22 | } 23 | 24 | return faker 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/factory/index.ts: -------------------------------------------------------------------------------- 1 | export * from './createDefaultFactoryFields.js' 2 | export * from './createInstanceFromFactory.js' 3 | export * from './deserialize.js' 4 | export * from './fakerAutoSelect.js' 5 | export * from './fakerGenerate.js' 6 | export * from './fakerGet.js' 7 | export * from './fakerLocales.js' 8 | export * from './serialize.js' 9 | export * from './storage.js' 10 | -------------------------------------------------------------------------------- /packages/core/src/graphql/index.ts: -------------------------------------------------------------------------------- 1 | export * from './resource.js' 2 | export * from './schema.js' 3 | export * from './server.js' 4 | -------------------------------------------------------------------------------- /packages/core/src/graphql/server.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import { mergeSchemas } from '@graphql-tools/schema' 3 | import { createYoga } from 'graphql-yoga' 4 | import { hooks } from '../hooks.js' 5 | import { createGraphQLResolvers } from './resolvers.js' 6 | 7 | export async function createYogaServer(mq: MoquerieInstance) { 8 | const yoga = createYoga({ 9 | schema: async () => { 10 | const ctx = await mq.getResolvedContext() 11 | const typeDefsOnlySchema = ctx.graphqlSchema!.schema 12 | const resolvers = await createGraphQLResolvers(mq) 13 | const schema = mergeSchemas({ 14 | schemas: [ 15 | typeDefsOnlySchema, 16 | ], 17 | resolvers, 18 | }) 19 | return schema 20 | }, 21 | graphqlEndpoint: mq.data.context?.config.graphql?.basePath ?? '/graphql', 22 | plugins: [ 23 | { 24 | onResultProcess: async ({ request, result, setResult }) => { 25 | const hookResult = await hooks.callHook('beforeSendResponse', { 26 | response: result, 27 | type: 'graphql', 28 | request, 29 | }) 30 | if (hookResult !== undefined) { 31 | setResult(hookResult) 32 | } 33 | }, 34 | }, 35 | ], 36 | }) 37 | 38 | return { 39 | yoga, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/core/src/hooks.ts: -------------------------------------------------------------------------------- 1 | import type { ResourceSchema } from './types/resource.js' 2 | import type { Awaitable } from './util/types.js' 3 | import { createHooks } from './util/hookable.js' 4 | 5 | export interface HookWriteCodeContext { 6 | path: string 7 | code: string 8 | type: 'factory' | 'types' 9 | } 10 | 11 | export interface HookTransformSchemaContext { 12 | schema: ResourceSchema 13 | } 14 | 15 | export interface HookBeforeSendResponseContext { 16 | response: any 17 | type: 'rest' | 'graphql' 18 | request?: Request 19 | params?: Record 20 | generatedResolver?: boolean 21 | resourceName?: string 22 | } 23 | 24 | export interface HookResolveResourceFromRequestContext { 25 | path: string 26 | request: Request 27 | schema: ResourceSchema 28 | } 29 | 30 | export interface Hooks { 31 | writeCode: (context: HookWriteCodeContext) => Awaitable 32 | transformSchema: (context: HookTransformSchemaContext) => Awaitable 33 | beforeSendResponse: (context: HookBeforeSendResponseContext) => Awaitable 34 | resolveResourceFromRequest: (context: HookResolveResourceFromRequestContext) => Awaitable 35 | } 36 | 37 | export const hooks = createHooks() 38 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config/index.js' 2 | export * from './context.js' 3 | export * from './factory/index.js' 4 | export * from './graphql/index.js' 5 | export * from './hooks.js' 6 | export * from './instance.js' 7 | export { addResolvers } from './resolvers/index.js' 8 | export * from './resource.js' 9 | export * from './resource/index.js' 10 | export * from './script/index.js' 11 | export * from './snapshot/index.js' 12 | export * from './storage/index.js' 13 | export type * from './types/index.js' 14 | export * from './user/index.js' 15 | -------------------------------------------------------------------------------- /packages/core/src/mock/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mockFileHandler.js' 2 | export * from './mockFileWatcher.js' 3 | -------------------------------------------------------------------------------- /packages/core/src/mock/mockFileHandler.ts: -------------------------------------------------------------------------------- 1 | export interface MockFileItem { 2 | file: string 3 | } 4 | 5 | export class MockFileHandler { 6 | key: string 7 | items: TItem[] = [] 8 | changeHandlers: Array<() => unknown> = [] 9 | 10 | constructor(key: string) { 11 | this.key = key 12 | } 13 | 14 | handleMockFile(file: string, data: any) { 15 | // Cleanup 16 | this.removeMany(file) 17 | 18 | if (data[this.key]) { 19 | this.add(file, data[this.key]) 20 | this.notifyChange() 21 | } 22 | } 23 | 24 | // eslint-disable-next-line unused-imports/no-unused-vars 25 | add(file: string, data: any) { 26 | // @TODO: Implement 27 | } 28 | 29 | handleMockFileRemoved(file: string) { 30 | this.removeMany(file) 31 | this.notifyChange() 32 | } 33 | 34 | removeMany(file: string) { 35 | this.items = this.items.filter(item => item.file !== file) 36 | } 37 | 38 | onChange(handler: () => unknown) { 39 | this.changeHandlers.push(handler) 40 | } 41 | 42 | destroy() { 43 | this.items.length = 0 44 | } 45 | 46 | private notifyChange() { 47 | for (const handler of this.changeHandlers) { 48 | handler() 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/core/src/pubsub/createPubSub.ts: -------------------------------------------------------------------------------- 1 | import { createPubSub as _createPubSub } from 'graphql-yoga' 2 | 3 | export async function createPubSubs() { 4 | return { 5 | rest: _createPubSub(), 6 | graphql: _createPubSub(), 7 | } 8 | } 9 | 10 | export type PubSubs = Awaited> 11 | -------------------------------------------------------------------------------- /packages/core/src/resolvers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './resolverStore.js' 2 | -------------------------------------------------------------------------------- /packages/core/src/resource/branch.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import fs from 'node:fs' 3 | import path from 'pathe' 4 | import { getLocalDbFolder } from '../storage/path.js' 5 | import { getCurrentBranch, resourceInstancesFolders } from './storage.js' 6 | 7 | export function getCurrentBranchFolder(mq: MoquerieInstance) { 8 | return path.join(getLocalDbFolder(mq), ...resourceInstancesFolders, getCurrentBranch(mq)) 9 | } 10 | 11 | export async function renameBranch(mq: MoquerieInstance, branch: string, newName: string) { 12 | if (branch === 'default') { 13 | throw new Error('Cannot rename default branch') 14 | } 15 | 16 | if (branch === getCurrentBranch(mq)) { 17 | throw new Error(`Cannot rename current branch (${getCurrentBranch(mq)})`) 18 | } 19 | 20 | const branchFolder = path.join(getLocalDbFolder(mq), ...resourceInstancesFolders, branch) 21 | if (!fs.existsSync(branchFolder)) { 22 | throw new Error(`Branch ${branch} does not exist`) 23 | } 24 | 25 | const newBranchFolder = path.join(getLocalDbFolder(mq), ...resourceInstancesFolders, newName) 26 | if (fs.existsSync(newBranchFolder)) { 27 | throw new Error(`Branch ${newName} already exists`) 28 | } 29 | 30 | await fs.promises.rename(branchFolder, newBranchFolder) 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/resource/branchDelete.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import fs from 'node:fs' 3 | import path from 'pathe' 4 | import { getLocalDbFolder } from '../storage/path.js' 5 | import { getCurrentBranch, resourceInstancesFolders } from './storage.js' 6 | 7 | /** 8 | * Delete a branch. Will throw if attempting to delete the current branch or the 'default' branch. 9 | */ 10 | export async function deleteBranch(mq: MoquerieInstance, name: string) { 11 | if (name === 'default') { 12 | throw new Error('Cannot delete default branch') 13 | } 14 | 15 | if (name === getCurrentBranch(mq)) { 16 | throw new Error(`Cannot delete current branch (${getCurrentBranch(mq)})`) 17 | } 18 | 19 | const branchFolder = path.join(getLocalDbFolder(mq), ...resourceInstancesFolders, name) 20 | if (!fs.existsSync(branchFolder)) { 21 | throw new Error(`Branch ${name} does not exist`) 22 | } 23 | 24 | await fs.promises.rm(branchFolder, { recursive: true }) 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/resource/createInstance.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import type { ResourceInstance } from '../types/resource.js' 3 | import { nanoid } from 'nanoid' 4 | import { deactiveOtherSingletonResourceInstances } from './deactivateOthers.js' 5 | import { createHistoryRecord } from './history.js' 6 | import { getResourceInstanceStorage } from './storage.js' 7 | 8 | export interface CreateInstanceOptions { 9 | resourceName: string 10 | value: any 11 | tags?: string[] 12 | comment?: string 13 | id?: string 14 | factoryId?: string 15 | save: boolean 16 | } 17 | 18 | export async function createResourceInstance(mq: MoquerieInstance, options: CreateInstanceOptions) { 19 | const { resourceName, value, tags, comment } = options 20 | 21 | const id = options.id ?? nanoid() 22 | 23 | const instance: ResourceInstance = { 24 | id, 25 | resourceName, 26 | createdAt: new Date(), 27 | updatedAt: null, 28 | active: true, 29 | value, 30 | tags: tags ?? [], 31 | comment: comment ?? null, 32 | factoryId: options.factoryId ?? null, 33 | } 34 | 35 | if (options.save) { 36 | const storage = await getResourceInstanceStorage(mq, resourceName) 37 | await storage.save(instance) 38 | await deactiveOtherSingletonResourceInstances(mq, resourceName, id) 39 | await createHistoryRecord(mq, { 40 | type: 'create', 41 | resourceName, 42 | instanceId: id, 43 | value, 44 | }) 45 | } 46 | 47 | return instance 48 | } 49 | -------------------------------------------------------------------------------- /packages/core/src/resource/deactivateOthers.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import { findAllResourceInstances } from './findAll.js' 3 | import { updateResourceInstanceById } from './update.js' 4 | 5 | export async function deactivateOtherResourceInstances(mq: MoquerieInstance, resourceName: string, id: string) { 6 | const instances = await findAllResourceInstances(mq, resourceName, { 7 | filterActive: 'active', 8 | }) 9 | const promises: Promise[] = [] 10 | for (const otherInstance of instances) { 11 | if (otherInstance.id !== id) { 12 | promises.push(updateResourceInstanceById(mq, resourceName, otherInstance.id, { 13 | active: false, 14 | })) 15 | } 16 | } 17 | await Promise.all(promises) 18 | } 19 | 20 | export async function deactiveOtherSingletonResourceInstances(mq: MoquerieInstance, resourceName: string, id: string) { 21 | const ctx = await mq.getResolvedContext() 22 | const resourceType = ctx.schema.types[resourceName] 23 | if (resourceType && !resourceType.array) { 24 | await deactivateOtherResourceInstances(mq, resourceName, id) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/resource/find.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import { getResourceInstanceStorage } from './storage.js' 3 | 4 | export async function findResourceInstanceById(mq: MoquerieInstance, resourceTypeName: string, id: string) { 5 | const storage = await getResourceInstanceStorage(mq, resourceTypeName) 6 | return storage.findById(id) 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/resource/findAll.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import type { FilterActive } from '../types/resource.js' 3 | import { getResourceInstanceStorage } from './storage.js' 4 | 5 | export interface FindAllResourceInstancesOptions { 6 | /** 7 | * By default only active instances are returned. 8 | */ 9 | filterActive?: FilterActive 10 | } 11 | 12 | export async function findAllResourceInstances(mq: MoquerieInstance, resourceTypeName: string, options: FindAllResourceInstancesOptions = {}) { 13 | const storage = await getResourceInstanceStorage(mq, resourceTypeName) 14 | const instances = await storage.findAll() 15 | 16 | if (!options.filterActive || options.filterActive === 'active') { 17 | return instances.filter(instance => instance.active) 18 | } 19 | else if (options.filterActive === 'inactive') { 20 | return instances.filter(instance => !instance.active) 21 | } 22 | else { 23 | return instances 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/resource/generateInstances.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import type { ResourceFactory } from '../types/factory.js' 3 | import type { ResourceSchemaType } from '../types/resource.js' 4 | import { createInstanceFromFactory } from '../factory/createInstanceFromFactory.js' 5 | import { getFactoryStorage } from '../factory/storage.js' 6 | import { getResourceInstanceStorage } from './storage.js' 7 | 8 | export interface GenerateResourceInstancesOptions { 9 | resourceType: ResourceSchemaType 10 | factory: ResourceFactory 11 | count: number 12 | } 13 | 14 | export async function generateResourceInstances(mq: MoquerieInstance, options: GenerateResourceInstancesOptions) { 15 | const { resourceType, factory, count } = options 16 | const storage = await getResourceInstanceStorage(mq, resourceType.name) 17 | const instances = [] 18 | for (let i = 0; i < count; i++) { 19 | const instance = await createInstanceFromFactory(mq, { 20 | factory, 21 | save: true, 22 | }) 23 | if (!resourceType.array) { 24 | instance.active = false 25 | } 26 | instances.push(instance) 27 | await storage?.save(instance) 28 | } 29 | 30 | const factoryStorage = await getFactoryStorage(mq) 31 | await factoryStorage.save({ 32 | ...factory, 33 | lastUsedAt: new Date(), 34 | }) 35 | 36 | return instances 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/src/resource/history.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import type { HistoryRecord } from '../types/history.js' 3 | import { nanoid } from 'nanoid' 4 | import { getCurrentBranch } from './storage.js' 5 | 6 | export type CreateHistoryRecordOptions = Omit 7 | 8 | export async function createHistoryRecord(mq: MoquerieInstance, options: CreateHistoryRecordOptions) { 9 | const record: HistoryRecord = { 10 | ...options, 11 | id: nanoid(), 12 | date: new Date(), 13 | branch: getCurrentBranch(mq), 14 | } 15 | const ctx = await mq.getResolvedContext() 16 | ctx.historyRecords.unshift(record) 17 | return record 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/resource/index.ts: -------------------------------------------------------------------------------- 1 | export * from './branch.js' 2 | export * from './branchCreate.js' 3 | export * from './branchDelete.js' 4 | export * from './createInstance.js' 5 | export * from './deactivateOthers.js' 6 | export * from './find.js' 7 | export * from './findAll.js' 8 | export * from './generateInstances.js' 9 | export * from './queryManager.js' 10 | export * from './queryManagerProxy.js' 11 | export * from './remove.js' 12 | export * from './resourceReference.js' 13 | export * from './storage.js' 14 | export * from './update.js' 15 | -------------------------------------------------------------------------------- /packages/core/src/resource/queryManagerProxy.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import { createQueryManager, type QueryManager } from './queryManager.js' 3 | 4 | export interface QueryManagerProxy {} 5 | 6 | export type UntypedQueryManagerProxy = Record> 7 | 8 | export function createQueryManagerProxy(mq: MoquerieInstance) { 9 | const cache = new Map>() 10 | 11 | const proxy = new Proxy({}, { 12 | get(_, key: string) { 13 | if (cache.has(key)) { 14 | return cache.get(key) 15 | } 16 | 17 | const queryManager = createQueryManager(mq, { 18 | resourceName: key, 19 | }) 20 | cache.set(key, queryManager) 21 | return queryManager 22 | }, 23 | }) 24 | 25 | return proxy as QueryManagerProxy 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/src/resource/remove.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import { createHistoryRecord } from './history.js' 3 | import { getResourceInstanceStorage } from './storage.js' 4 | 5 | export async function removeResourceInstanceById(mq: MoquerieInstance, resourceTypeName: string, id: string): Promise { 6 | const storage = await getResourceInstanceStorage(mq, resourceTypeName) 7 | await storage.remove(id) 8 | await createHistoryRecord(mq, { 9 | type: 'delete', 10 | resourceName: resourceTypeName, 11 | instanceId: id, 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/resource/schemaTransformStore.ts: -------------------------------------------------------------------------------- 1 | import type { SchemaTransform } from '../types/resource.js' 2 | import { MockFileHandler } from '../mock/mockFileHandler.js' 3 | 4 | export class SchemaTransformStore extends MockFileHandler { 5 | constructor() { 6 | super('__schemaTransforms') 7 | } 8 | 9 | add(file: string, data: any): void { 10 | if (Array.isArray(data)) { 11 | for (const transform of data) { 12 | this.items.push({ 13 | file, 14 | action: transform, 15 | }) 16 | } 17 | } 18 | else { 19 | this.items.push({ 20 | file, 21 | action: data, 22 | }) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/rest/apiRouteStore.ts: -------------------------------------------------------------------------------- 1 | import type { ApiRoute, ApiRouteHandlerFn, ApiRouter } from '../types/index.js' 2 | import { type Key, pathToRegexp } from 'path-to-regexp' 3 | import { MockFileHandler } from '../mock/mockFileHandler.js' 4 | 5 | export class ApiRouteStore extends MockFileHandler { 6 | constructor() { 7 | super('__apiRouteFn') 8 | } 9 | 10 | add(file: string, data: any): void { 11 | const router: ApiRouter = { 12 | get: (path, setup) => this.addRoute(path, 'GET', setup, file), 13 | post: (path, setup) => this.addRoute(path, 'POST', setup, file), 14 | put: (path, setup) => this.addRoute(path, 'PUT', setup, file), 15 | patch: (path, setup) => this.addRoute(path, 'PATCH', setup, file), 16 | delete: (path, setup) => this.addRoute(path, 'DELETE', setup, file), 17 | use: (path, setup) => this.addRoute(path, undefined, setup, file), 18 | middleware: setup => this.addRoute(/./, undefined, setup, file), 19 | } 20 | data(router) 21 | } 22 | 23 | addRoute(path: string | RegExp, method: string | undefined, handler: ApiRouteHandlerFn, file: string) { 24 | const keys: Array = [] 25 | const finalPath = typeof path === 'string' ? pathToRegexp(path, keys) : path 26 | 27 | this.items.push({ 28 | path: finalPath, 29 | method, 30 | file, 31 | handler, 32 | keys, 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/src/rest/index.ts: -------------------------------------------------------------------------------- 1 | export * from './apiRouteStore.js' 2 | export * from './schema.js' 3 | export * from './server.js' 4 | -------------------------------------------------------------------------------- /packages/core/src/rest/schema.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import type { ResourceSchemaType } from '../types/resource.js' 3 | import { getContext } from '../context.js' 4 | import { getTypesFromFile } from '../resource/fromTypes.js' 5 | 6 | export async function getRestResourceSchema(mq: MoquerieInstance) { 7 | const ctx = await getContext(mq) 8 | 9 | if (ctx.config.rest?.typeFiles) { 10 | const { types } = await getTypesFromFile(mq, ctx.config.rest.typeFiles, ['rest']) 11 | 12 | const finalTypes: Array = [] 13 | 14 | for (const key in types) { 15 | const type = types[key] 16 | finalTypes.push({ 17 | ...type, 18 | inline: !Object.keys(type.fields).some(field => ['id', '_id'].includes(field)), 19 | nonNull: false, 20 | array: true, 21 | }) 22 | } 23 | 24 | return { 25 | types, 26 | } 27 | } 28 | return { 29 | types: [], 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/script/index.ts: -------------------------------------------------------------------------------- 1 | export * from './runScript.js' 2 | export * from './scriptStore.js' 3 | -------------------------------------------------------------------------------- /packages/core/src/script/scriptStore.ts: -------------------------------------------------------------------------------- 1 | import type { ScriptItem, ScriptRunReport } from '../types/script.js' 2 | import { MockFileHandler } from '../mock/mockFileHandler.js' 3 | 4 | export class ScriptStore extends MockFileHandler { 5 | reports: Map = new Map() 6 | 7 | constructor() { 8 | super('__scripts') 9 | } 10 | 11 | add(file: string, data: any): void { 12 | if (Array.isArray(data)) { 13 | for (const item of data) { 14 | this.items.push({ 15 | file, 16 | ...item, 17 | }) 18 | } 19 | } 20 | else { 21 | this.items.push({ 22 | file, 23 | ...data, 24 | }) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/settings/onChange.ts: -------------------------------------------------------------------------------- 1 | import type { Settings } from '../types/settings.js' 2 | 3 | const listeners: Array<(settings: Settings) => void> = [] 4 | 5 | export function onSettingsChange(listener: (settings: Settings) => void) { 6 | listeners.push(listener) 7 | return function off() { 8 | const index = listeners.indexOf(listener) 9 | if (index !== -1) { 10 | listeners.splice(index, 1) 11 | } 12 | } 13 | } 14 | 15 | export function notifySettingsChange(settings: Settings) { 16 | for (const listener of listeners) { 17 | listener(settings) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/src/snapshot/deleteSnapshot.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import fs from 'node:fs' 3 | import { getSnapshotFolder } from './folder.js' 4 | import { getSnapshotStorage } from './storage.js' 5 | 6 | export async function deleteSnapshot(mq: MoquerieInstance, id: string) { 7 | const storage = await getSnapshotStorage(mq) 8 | const snapshot = await storage.findById(id) 9 | if (!snapshot) { 10 | throw new Error(`Snapshot ${id} not found`) 11 | } 12 | 13 | await storage.remove(snapshot.id) 14 | 15 | const snapshotFolder = await getSnapshotFolder(mq, snapshot) 16 | await fs.promises.rm(snapshotFolder, { 17 | recursive: true, 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/src/snapshot/folder.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import type { DatabaseSnapshot } from '../types/snapshot.js' 3 | import path from 'pathe' 4 | import { getSnapshotStorage } from './storage.js' 5 | 6 | export async function getSnapshotFolder(mq: MoquerieInstance, snapshot: Pick) { 7 | const storage = await getSnapshotStorage(mq) 8 | return path.join(storage[snapshot.location].folder, snapshot.id) 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/snapshot/index.ts: -------------------------------------------------------------------------------- 1 | export * from './addResourcesToSnapshot.js' 2 | export * from './createSnapshot.js' 3 | export * from './deleteSnapshot.js' 4 | export * from './folder.js' 5 | export * from './importSnapshotToDatabase.js' 6 | export * from './readResources.js' 7 | export * from './removeResourcesFromSnapshot.js' 8 | export * from './storage.js' 9 | -------------------------------------------------------------------------------- /packages/core/src/snapshot/removeResourcesFromSnapshot.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import type { DatabaseSnapshot } from '../types/snapshot.js' 3 | import fs from 'node:fs' 4 | import path from 'pathe' 5 | import SuperJSON from 'superjson' 6 | import { getSnapshotFolder } from './folder.js' 7 | import { migrateSnapshotFolder } from './migrate.js' 8 | 9 | export interface RemoveResourcesToSnapshotOptions { 10 | snapshot: DatabaseSnapshot 11 | resourceIds: { [resourceName: string]: string[] } 12 | } 13 | 14 | export async function removeResourcesFromSnapshot(mq: MoquerieInstance, options: RemoveResourcesToSnapshotOptions) { 15 | const { snapshot, resourceIds } = options 16 | 17 | if (mq.data.skipWrites) { 18 | throw new Error('Cannot remove resources from a snapshot in read-only mode') 19 | } 20 | 21 | // Delete resources 22 | const snapshotFolder = await getSnapshotFolder(mq, snapshot) 23 | await migrateSnapshotFolder(mq, snapshotFolder) 24 | 25 | for (const resourceName in resourceIds) { 26 | const ids = resourceIds[resourceName] 27 | const targetFile = path.join(snapshotFolder, `${resourceName}.res.json`) 28 | if (fs.existsSync(targetFile)) { 29 | // New format with each resource type in a single file 30 | const content = await fs.promises.readFile(targetFile, 'utf-8') 31 | const data = SuperJSON.parse(content) 32 | for (const id of ids) { 33 | delete data[id] 34 | } 35 | if (Object.keys(data).length === 0) { 36 | await fs.promises.rm(targetFile) 37 | } 38 | else { 39 | await fs.promises.writeFile(targetFile, JSON.stringify(SuperJSON.serialize(data), null, 2), 'utf-8') 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/core/src/storage/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mergedStorage.js' 2 | export * from './path.js' 3 | export * from './storage.js' 4 | -------------------------------------------------------------------------------- /packages/core/src/storage/path.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import envPaths from 'env-paths' 3 | import path from 'pathe' 4 | import { getProjectName } from '../util/env.js' 5 | 6 | export function getLocalFolder(mq: MoquerieInstance) { 7 | const projectName = getProjectName(mq) 8 | const paths = envPaths('moquerie') 9 | return path.join(paths.data, 'projects', projectName) 10 | } 11 | 12 | export function getLocalDbFolder(mq: MoquerieInstance) { 13 | return path.join(getLocalFolder(mq), 'db') 14 | } 15 | 16 | export function getRepositoryDbFolder(mq: MoquerieInstance) { 17 | return path.join(mq.data.cwd, '.moquerie') 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/types/db.ts: -------------------------------------------------------------------------------- 1 | export type DBLocation = 'local' | 'repository' 2 | -------------------------------------------------------------------------------- /packages/core/src/types/history.ts: -------------------------------------------------------------------------------- 1 | import type { Operation } from 'just-diff' 2 | 3 | export interface HistoryRecord { 4 | id: string 5 | branch: string 6 | resourceName: string 7 | instanceId: string 8 | date: Date 9 | type: 'create' | 'update' | 'delete' 10 | diff?: Array<{ 11 | op: Operation 12 | path: Array 13 | value: any 14 | }> 15 | value?: any 16 | } 17 | 18 | export type HistoryRecordForList = Omit 19 | -------------------------------------------------------------------------------- /packages/core/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type * from './apiRoute.js' 2 | export type * from './config.js' 3 | export type * from './db.js' 4 | export type * from './factory.js' 5 | export type * from './history.js' 6 | export type * from './plugin.js' 7 | export type * from './resolver.js' 8 | export type * from './resource.js' 9 | export type * from './script.js' 10 | export type * from './settings.js' 11 | export type * from './snapshot.js' 12 | export type * from './user.js' 13 | -------------------------------------------------------------------------------- /packages/core/src/types/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { Hooks } from '../hooks.js' 2 | import type { Awaitable } from '../util/types.js' 3 | 4 | export interface Plugin extends Partial { 5 | name: string 6 | } 7 | 8 | export interface PluginInstance { 9 | plugin: Plugin 10 | destroyHandlers: Array<() => Awaitable> 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/types/resolver.ts: -------------------------------------------------------------------------------- 1 | import type { PubSubs } from '../pubsub/createPubSub.js' 2 | import type { QueryManagerProxy } from '../resource/queryManagerProxy.js' 3 | 4 | export interface Resolver { 5 | resourceName: string 6 | fieldName: string 7 | action: (ctx: ResolverContext) => any 8 | file: string 9 | } 10 | 11 | export interface ResolverContext { 12 | /** 13 | * The parent object. For example, if the resolver is for a field `user` on a `Post` type, then `parent` will be the `Post` object. 14 | */ 15 | parent: any 16 | /** 17 | * The input object. This is the object that was passed to the resolver as part of the field parameters. 18 | * 19 | * For example, if the resolver is for a field `user` on a `Post` type, then `input` will be the parameters object that was passed to the `user` field. 20 | */ 21 | input: any 22 | /** 23 | * The database query manager. 24 | */ 25 | db: QueryManagerProxy 26 | /** 27 | * The pubsub instance to send real-time updates. 28 | */ 29 | pubsub: PubSubs 30 | /** 31 | * Generate a random id. 32 | */ 33 | generateId: () => string 34 | } 35 | 36 | export type ResolverBaseDefinitions = Record> 37 | 38 | /** 39 | * @deprecated use `Resolver` instead 40 | */ 41 | export type FieldAction = Resolver 42 | 43 | /** 44 | * @deprecated use `ResolverContext` instead 45 | */ 46 | export type FieldActionContext = ResolverContext 47 | 48 | /** 49 | * @deprecated use `ResolverBaseDefinitions` instead 50 | */ 51 | export type FieldActionBaseDefinitions = ResolverBaseDefinitions 52 | -------------------------------------------------------------------------------- /packages/core/src/types/script.ts: -------------------------------------------------------------------------------- 1 | import type { Faker } from '@faker-js/faker' 2 | import type { PubSubs } from '../pubsub/createPubSub.js' 3 | import type { QueryManagerProxy } from '../resource/queryManagerProxy.js' 4 | import type { Awaitable } from '../util/types.js' 5 | import type { ResourceInstanceReference } from './resource.js' 6 | 7 | export interface ScriptContext { 8 | /** 9 | * The database query manager. 10 | */ 11 | db: QueryManagerProxy 12 | /** 13 | * The pubsub instance to send real-time updates. 14 | */ 15 | pubsub: PubSubs 16 | /** 17 | * Generate a random id. 18 | */ 19 | generateId: () => string 20 | /** 21 | * Generate one or more resource instances using a factory. 22 | */ 23 | generateResource: (resourceName: string, factoryId: string, count?: number) => Promise 24 | /** 25 | * The faker instance. 26 | */ 27 | faker: Faker 28 | /** 29 | * Repeat a function multiple times. 30 | */ 31 | repeat: (fn: () => T, min: number, max: number) => Promise> 32 | /** 33 | * Pick a random item from a list. 34 | */ 35 | pickRandom: (list: T[]) => T | null 36 | } 37 | 38 | export type ScriptFn = (context: ScriptContext) => Awaitable 39 | 40 | export interface ScriptItem { 41 | id: string 42 | description?: string 43 | file: string 44 | fn: ScriptFn 45 | } 46 | 47 | export interface ScriptLogItem { 48 | type: keyof ScriptContext 49 | label: string 50 | time: number 51 | data?: any 52 | } 53 | 54 | export interface ScriptRunReport { 55 | id: string 56 | logs: ScriptLogItem[] 57 | error?: Error 58 | startTime: number 59 | endTime: number 60 | } 61 | -------------------------------------------------------------------------------- /packages/core/src/types/server.ts: -------------------------------------------------------------------------------- 1 | export interface ServerRouteInfo { 2 | url: string 3 | label: string 4 | type: string 5 | icon?: string 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/types/settings.ts: -------------------------------------------------------------------------------- 1 | export interface Settings { 2 | currentBranch?: string 3 | favoriteResources?: string[] 4 | } 5 | -------------------------------------------------------------------------------- /packages/core/src/types/snapshot.ts: -------------------------------------------------------------------------------- 1 | import type { DBLocation } from './db.js' 2 | import type { User } from './user.js' 3 | 4 | export interface DatabaseSnapshot { 5 | id: string 6 | location: DBLocation 7 | description?: string 8 | date: Date 9 | author: User 10 | tags: string[] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/types/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | name: string 3 | email?: string 4 | avatar?: string 5 | github?: { 6 | login: string 7 | profilePageUrl?: string 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/user/findOnGithub.ts: -------------------------------------------------------------------------------- 1 | export interface GithubUser { 2 | login: string 3 | avatar?: string 4 | profilePageUrl?: string 5 | } 6 | 7 | export async function findUserOnGithub(email: string): Promise { 8 | const r = await fetch(`https://api.github.com/search/users?q=${email}+in:email`) 9 | const json = await r.json() 10 | if (!json.items.length) { 11 | return null 12 | } 13 | const item = json.items[0] 14 | return { 15 | login: item.login, 16 | avatar: item.avatar_url, 17 | profilePageUrl: item.html_url, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/src/user/getCurrentUser.ts: -------------------------------------------------------------------------------- 1 | import type { User } from '../types/user.js' 2 | import os from 'node:os' 3 | import { getGitUserInfo } from 'git-user-info' 4 | import { LRUCache } from 'lru-cache' 5 | import { findUserOnGithub } from './findOnGithub.js' 6 | 7 | const userCache = new LRUCache({ 8 | max: 20, 9 | ttl: 1000 * 60 * 60, // 1 hour 10 | }) 11 | 12 | export async function getCurrentUser() { 13 | const cachedUser = userCache.get('current') 14 | if (cachedUser) { 15 | return cachedUser 16 | } 17 | 18 | const user: User = { 19 | name: os.userInfo().username, 20 | } 21 | 22 | try { 23 | const gitUser = await getGitUserInfo() 24 | if (gitUser) { 25 | user.name = gitUser.name 26 | user.email = gitUser.email 27 | } 28 | } 29 | // eslint-disable-next-line unused-imports/no-unused-vars 30 | catch (e) { 31 | // ignore 32 | } 33 | 34 | if (user.email) { 35 | try { 36 | const githubUser = await findUserOnGithub(user.email) 37 | if (githubUser) { 38 | user.avatar = githubUser.avatar 39 | user.github = { 40 | login: githubUser.login, 41 | profilePageUrl: githubUser.profilePageUrl, 42 | } 43 | } 44 | } 45 | // eslint-disable-next-line unused-imports/no-unused-vars 46 | catch (e) { 47 | // ignore 48 | } 49 | } 50 | 51 | userCache.set('current', user) 52 | return user 53 | } 54 | -------------------------------------------------------------------------------- /packages/core/src/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './findOnGithub.js' 2 | export * from './getCurrentUser.js' 3 | -------------------------------------------------------------------------------- /packages/core/src/util/crypto.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from 'node:crypto' 2 | 3 | export function hashContent(content: string): string { 4 | return createHash('sha256').update(content).digest('hex') 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/src/util/env.ts: -------------------------------------------------------------------------------- 1 | import type { MoquerieInstance } from '../instance.js' 2 | import fs from 'node:fs' 3 | import process from 'node:process' 4 | import { LRUCache } from 'lru-cache' 5 | import path from 'pathe' 6 | import { findUp } from './find-up.js' 7 | 8 | export function getDefaultCwd() { 9 | return path.resolve(process.env.MOQUERIE_OVERRIDE_CWD ?? process.cwd()) 10 | } 11 | 12 | const projectNameCache = new LRUCache({ 13 | max: 100, 14 | ttl: 5000, 15 | }) 16 | 17 | export function getProjectName(mq: MoquerieInstance): string { 18 | const cwd = mq.data.cwd 19 | const cached = projectNameCache.get(cwd) 20 | if (cached) { 21 | return cached 22 | } 23 | const pkgFile = findUp(cwd, ['package.json']) 24 | if (pkgFile) { 25 | try { 26 | const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8')) 27 | projectNameCache.set(cwd, pkg.name) 28 | return pkg.name 29 | } 30 | catch (e: any) { 31 | throw new Error(`[moquerie] Error parsing ${pkgFile}: ${e.message ?? e}`) 32 | } 33 | } 34 | throw new Error(`[moquerie] package.json not found looking from ${cwd}`) 35 | } 36 | 37 | const projectHasTypescriptCache = new LRUCache({ 38 | max: 100, 39 | ttl: 1000 * 60 * 5, 40 | }) 41 | 42 | export function projectHasTypescript(mq: MoquerieInstance): boolean { 43 | const cwd = mq.data.cwd 44 | const cached = projectHasTypescriptCache.get(cwd) 45 | if (cached !== undefined) { 46 | return cached 47 | } 48 | const tsconfigFile = findUp(cwd, ['tsconfig.json']) 49 | projectHasTypescriptCache.set(cwd, !!tsconfigFile) 50 | return !!tsconfigFile 51 | } 52 | -------------------------------------------------------------------------------- /packages/core/src/util/find-up.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'pathe' 3 | 4 | export function findUp(cwd: string, fileNames: string[]): string | null { 5 | let { root } = path.parse(cwd) 6 | let dir = cwd 7 | 8 | // On Windows, it will for example return `C:`, we need to add the trailing `/` 9 | if (root[1] === ':' && root[2] === undefined) { 10 | root += '/' 11 | } 12 | 13 | while (dir !== root) { 14 | for (const fileName of fileNames) { 15 | const searchPath = path.join(dir, fileName) 16 | if (fs.existsSync(searchPath)) { 17 | return searchPath 18 | } 19 | } 20 | dir = path.dirname(dir) 21 | } 22 | 23 | return null 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/util/fs.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'pathe' 3 | 4 | export async function ensureDir(folder: string) { 5 | if (!fs.existsSync(folder)) { 6 | await fs.promises.mkdir(folder, { recursive: true }) 7 | } 8 | } 9 | 10 | export async function copyDir(src: string, dest: string) { 11 | await ensureDir(dest) 12 | const entries = await fs.promises.readdir(src) 13 | 14 | for (const entry of entries) { 15 | const srcPath = path.join(src, entry) 16 | const destPath = path.join(dest, entry) 17 | 18 | if (fs.statSync(srcPath).isDirectory()) { 19 | await copyDir(srcPath, destPath) 20 | } 21 | else { 22 | await fs.promises.copyFile(srcPath, destPath) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../ast/index.js' 2 | export * from './env.js' 3 | export * from './find-up.js' 4 | export * from './fs.js' 5 | export * from './hookable.js' 6 | export * from './object.js' 7 | export * from './queue.js' 8 | export * from './random.js' 9 | export * from './types.js' 10 | 11 | export * from './vm.js' 12 | -------------------------------------------------------------------------------- /packages/core/src/util/queue.ts: -------------------------------------------------------------------------------- 1 | export interface UseQueueOptions { 2 | /** 3 | * Delay between each queue calls (in ms) 4 | */ 5 | delay?: number 6 | } 7 | 8 | export function useQueue(options: UseQueueOptions) { 9 | const queued = new Map Promise) | null>() 10 | const processing = new Set() 11 | 12 | function queue(key: string, callback: () => Promise) { 13 | const alreadyQueued = queued.has(key) 14 | queued.set(key, callback) 15 | if (alreadyQueued) { 16 | return 17 | } 18 | next(key) 19 | } 20 | 21 | function next(key: string) { 22 | if (options.delay) { 23 | setTimeout(() => { 24 | run(key) 25 | }, options.delay) 26 | } 27 | else { 28 | run(key) 29 | } 30 | } 31 | 32 | function run(key: string) { 33 | const callback = queued.get(key) 34 | if (callback) { 35 | processing.add(key) 36 | queued.delete(key) 37 | callback().finally(() => { 38 | processing.delete(key) 39 | if (queued.has(key)) { 40 | next(key) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | return { 47 | queue, 48 | get size() { 49 | return queued.size 50 | }, 51 | get busy() { 52 | return queued.size > 0 || processing.size > 0 53 | }, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/core/src/util/random.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker' 2 | import { isResourceInstanceReference } from '../resource/resourceReference.js' 3 | 4 | export async function repeat(item: any, fn: (item: any) => T, min: number, max: number) { 5 | const list: T[] = [] 6 | const count = faker.number.int({ 7 | min, 8 | max, 9 | }) 10 | const deduped = new Set() 11 | for (let i = 0; i < count; i++) { 12 | const val = await fn(item) 13 | if (val != null) { 14 | // Returned an array 15 | if (Array.isArray(val)) { 16 | const selected: any[] = [] 17 | while (selected.length < count && val.length > 0) { 18 | const index = Math.floor(Math.random() * val.length) 19 | selected.push(val[index]) 20 | val.splice(index, 1) 21 | } 22 | return selected 23 | } 24 | else { 25 | if (isResourceInstanceReference(val)) { 26 | const id = `${val.__resourceName}:${val.__id}` 27 | if (deduped.has(id)) { 28 | continue 29 | } 30 | deduped.add(id) 31 | } 32 | 33 | list.push(val) 34 | } 35 | } 36 | } 37 | return list 38 | } 39 | 40 | export function pickRandom(list: any[]) { 41 | if (list.length === 0) { 42 | return null 43 | } 44 | const index = Math.floor(Math.random() * list.length) 45 | return list[index] 46 | } 47 | -------------------------------------------------------------------------------- /packages/core/src/util/types.ts: -------------------------------------------------------------------------------- 1 | export type Awaitable = T | Promise 2 | 3 | export type KeysMatching = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T] 4 | -------------------------------------------------------------------------------- /packages/core/src/util/vm.ts: -------------------------------------------------------------------------------- 1 | import vm from 'node:vm' 2 | 3 | export function runValueCode(code: string, context?: Record) { 4 | const lines = code.split('\n').map(line => line.trim()).filter(Boolean) 5 | // Add a return statement on last line if not present 6 | const lastLine = lines.at(-1) 7 | if (!lastLine?.startsWith('return ')) { 8 | lines[lines.length - 1] = `return ${lastLine}` 9 | } 10 | code = lines.join('\n') 11 | return vm.runInNewContext(`(() => {${code}})()`, context ?? {}) 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "jsx": "preserve", 5 | "jsxImportSource": "vue", 6 | "lib": [ 7 | "ESNext", 8 | "DOM" 9 | ], 10 | "useDefineForClassFields": true, 11 | "module": "ESNext", 12 | "moduleResolution": "Node", 13 | "resolveJsonModule": true, 14 | "types": [ 15 | "node" 16 | ], 17 | "allowJs": true, 18 | "strict": true, 19 | "noImplicitThis": true, 20 | "declaration": true, 21 | "outDir": "./dist", 22 | "allowSyntheticDefaultImports": true, 23 | "esModuleInterop": true, 24 | "forceConsistentCasingInFileNames": true, 25 | "isolatedModules": true, 26 | "verbatimModuleSyntax": true, 27 | "skipLibCheck": true 28 | }, 29 | "include": [ 30 | "src", 31 | "src/types" 32 | ], 33 | "exclude": [ 34 | "node_modules", 35 | "generated/**/*", 36 | "dist/**/*", 37 | "src/**/*.spec.ts" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/util.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/util/index.js' 2 | -------------------------------------------------------------------------------- /packages/core/util.mjs: -------------------------------------------------------------------------------- 1 | export * from './dist/util/index.js' 2 | -------------------------------------------------------------------------------- /packages/moquerie/README.md: -------------------------------------------------------------------------------- 1 | # moquerie 2 | 3 | > Effortlessly mock your entire API with simple configuration and a beautiful UI. 4 | 5 | [Documentation](https://github.com/Akryum/moquerie) 6 | -------------------------------------------------------------------------------- /packages/moquerie/bin.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | import './dist/cli.js' 5 | -------------------------------------------------------------------------------- /packages/moquerie/config.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/config.js' 2 | -------------------------------------------------------------------------------- /packages/moquerie/config.mjs: -------------------------------------------------------------------------------- 1 | export * from './dist/config.js' 2 | -------------------------------------------------------------------------------- /packages/moquerie/mocks.d.ts: -------------------------------------------------------------------------------- 1 | export * from './dist/mocks.js' 2 | -------------------------------------------------------------------------------- /packages/moquerie/mocks.mjs: -------------------------------------------------------------------------------- 1 | export * from './dist/mocks.js' 2 | -------------------------------------------------------------------------------- /packages/moquerie/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moquerie", 3 | "type": "module", 4 | "version": "0.6.2", 5 | "description": "Create API & backend mocks with ease", 6 | "author": { 7 | "name": "Guillaume Chau" 8 | }, 9 | "repository": { 10 | "url": "https://github.com/Akryum/moquerie.git", 11 | "type": "git", 12 | "directory": "packages/moquerie" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "exports": { 18 | ".": { 19 | "types": "./dist/index.d.ts", 20 | "default": "./dist/index.js" 21 | }, 22 | "./config": { 23 | "types": "./dist/config.d.ts", 24 | "default": "./dist/config.js" 25 | }, 26 | "./config.js": { 27 | "types": "./dist/config.d.ts", 28 | "default": "./dist/config.js" 29 | }, 30 | "./mocks": { 31 | "types": "./dist/mocks.d.ts", 32 | "default": "./dist/mocks.js" 33 | }, 34 | "./mocks.js": { 35 | "types": "./dist/mocks.d.ts", 36 | "default": "./dist/mocks.js" 37 | }, 38 | "./*": "./*" 39 | }, 40 | "types": "./dist/index.d.ts", 41 | "bin": { 42 | "moquerie": "./bin.mjs" 43 | }, 44 | "scripts": { 45 | "build": "rm -rf ./dist && tsc", 46 | "dev": "tsc --watch" 47 | }, 48 | "dependencies": { 49 | "@moquerie/app": "workspace:^", 50 | "@moquerie/core": "workspace:^", 51 | "citty": "^0.1.5", 52 | "listhen": "^1.5.6", 53 | "picocolors": "^1.0.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/moquerie/src/appServer.ts: -------------------------------------------------------------------------------- 1 | import { handler } from '@moquerie/app' 2 | import { listen, type Listener } from 'listhen' 3 | 4 | export interface StartAppServerOptions { 5 | port?: number 6 | open?: boolean 7 | } 8 | 9 | export async function startAppServer(options: StartAppServerOptions): Promise { 10 | const server = await listen(handler, { 11 | port: options.port, 12 | showURL: false, 13 | open: options.open, 14 | }) 15 | 16 | return server 17 | } 18 | -------------------------------------------------------------------------------- /packages/moquerie/src/cli.ts: -------------------------------------------------------------------------------- 1 | import { defineCommand, runMain } from 'citty' 2 | 3 | const main = defineCommand({ 4 | meta: { 5 | name: 'moquerie', 6 | }, 7 | args: { 8 | uiPort: { 9 | description: 'Port to run the UI on', 10 | }, 11 | open: { 12 | type: 'boolean', 13 | description: 'Open the UI in the browser', 14 | }, 15 | }, 16 | run: async ({ args }) => { 17 | const port = args.uiPort ? Number(args.uiPort) : undefined 18 | 19 | const { startAppServer } = await import('./appServer.js') 20 | const { default: colors } = await import('picocolors') 21 | 22 | const server = await startAppServer({ 23 | port, 24 | open: args.open, 25 | }) 26 | 27 | console.log(`GUI available at ${colors.blue(server.url)}`) 28 | }, 29 | }) 30 | 31 | runMain(main) 32 | -------------------------------------------------------------------------------- /packages/moquerie/src/config.ts: -------------------------------------------------------------------------------- 1 | export * from '@moquerie/core/config' 2 | -------------------------------------------------------------------------------- /packages/moquerie/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@moquerie/core' 2 | -------------------------------------------------------------------------------- /packages/moquerie/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "jsx": "preserve", 5 | "jsxImportSource": "vue", 6 | "lib": [ 7 | "ESNext", 8 | "DOM" 9 | ], 10 | "useDefineForClassFields": true, 11 | "module": "NodeNext", 12 | "moduleResolution": "Node16", 13 | "resolveJsonModule": true, 14 | "types": [ 15 | "node" 16 | ], 17 | "allowJs": true, 18 | "strict": true, 19 | "noImplicitThis": true, 20 | "declaration": true, 21 | "outDir": "./dist", 22 | "allowSyntheticDefaultImports": true, 23 | "esModuleInterop": true, 24 | "forceConsistentCasingInFileNames": true, 25 | "isolatedModules": true, 26 | "verbatimModuleSyntax": true, 27 | "skipLibCheck": true 28 | }, 29 | "include": [ 30 | "src", 31 | "types" 32 | ], 33 | "exclude": [ 34 | "node_modules", 35 | "generated/**/*", 36 | "dist/**/*", 37 | "src/**/*.spec.ts" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /playgrounds/playground/.moquerie/factories/Message/SimpleMessageFactory.ts: -------------------------------------------------------------------------------- 1 | import { defineFactory } from 'moquerie/mocks' 2 | 3 | export const version = '0.0.1' 4 | 5 | export default defineFactory({ 6 | author: { 7 | name: 'Guillaume Chau', 8 | email: 'guillaume.b.chau@gmail.com', 9 | avatar: 'https://avatars.githubusercontent.com/u/2798204?v=4', 10 | 11 | github: { 12 | login: 'Akryum', 13 | profilePageUrl: 'https://github.com/Akryum', 14 | }, 15 | }, 16 | 17 | description: 'Used in script example', 18 | }, ( 19 | { 20 | db, 21 | faker, 22 | pickRandom, 23 | repeat, 24 | }, 25 | ) => ({ 26 | archived: faker.datatype.boolean(), 27 | content: faker.lorem.paragraph({ min: 1, max: 5 }), 28 | from: db.User.pickOneRandom(), 29 | id: faker.string.uuid(), 30 | tags: repeat(() => faker.lorem.word(), 1, 3), 31 | to: db.User.pickOneRandom(), 32 | type: pickRandom(['public', 'private']), 33 | })) 34 | -------------------------------------------------------------------------------- /playgrounds/playground/.moquerie/snapshots/vitest/Query.res.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "1nS5zGHNDOKzTEkKIsxyP": { 4 | "version": "0.0.1", 5 | "item": { 6 | "id": "1nS5zGHNDOKzTEkKIsxyP", 7 | "resourceName": "Query", 8 | "createdAt": "2024-09-18T12:32:52.301Z", 9 | "updatedAt": "2024-09-18T12:33:01.539Z", 10 | "active": true, 11 | "value": { 12 | "currentUser": null, 13 | "hello": "villa", 14 | "manyHellosCount": "3", 15 | "manyHellos": [ 16 | "succedo", 17 | "comparo", 18 | "denego" 19 | ] 20 | }, 21 | "tags": [ 22 | "test" 23 | ], 24 | "comment": "", 25 | "factoryId": null 26 | } 27 | } 28 | }, 29 | "meta": { 30 | "values": { 31 | "1nS5zGHNDOKzTEkKIsxyP.item.createdAt": [ 32 | "Date" 33 | ], 34 | "1nS5zGHNDOKzTEkKIsxyP.item.updatedAt": [ 35 | "Date" 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /playgrounds/playground/.moquerie/snapshots/vitest/snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": { 3 | "version": "0.0.1", 4 | "item": { 5 | "description": "", 6 | "tags": [], 7 | "date": "2024-09-18T13:03:14.255Z", 8 | "author": { 9 | "name": "Guillaume Chau", 10 | "email": "guillaume.b.chau@gmail.com", 11 | "avatar": "https://avatars.githubusercontent.com/u/2798204?v=4", 12 | "github": { 13 | "login": "Akryum", 14 | "profilePageUrl": "https://github.com/Akryum" 15 | } 16 | } 17 | } 18 | }, 19 | "meta": { 20 | "values": { 21 | "item.date": [ 22 | "Date" 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /playgrounds/playground/.moquerie/types/queryManager.ts: -------------------------------------------------------------------------------- 1 | import type { QueryManager } from 'moquerie' 2 | 3 | import type { 4 | Cat, 5 | Dog, 6 | Message, 7 | Mutation, 8 | MutationAPayload, 9 | MutationBPayload, 10 | MyObject, 11 | MyObjectNotExported, 12 | MyOtherObject, 13 | OldObject, 14 | Query, 15 | Simple, 16 | Subscription, 17 | User, 18 | } from './resources.js' 19 | 20 | declare module 'moquerie' { 21 | export interface QueryManagerProxy { 22 | Mutation: QueryManager 23 | Query: QueryManager 24 | Subscription: QueryManager 25 | Cat: QueryManager 26 | Dog: QueryManager 27 | Message: QueryManager 28 | MutationAPayload: QueryManager 29 | MutationBPayload: QueryManager 30 | MyObject: QueryManager 31 | MyObjectNotExported: QueryManager 32 | MyOtherObject: QueryManager 33 | Simple: QueryManager 34 | User: QueryManager 35 | OldObject: QueryManager 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /playgrounds/playground/codegen.ts: -------------------------------------------------------------------------------- 1 | import type { CodegenConfig } from '@graphql-codegen/cli' 2 | 3 | const config: CodegenConfig = { 4 | overwrite: true, 5 | schema: 'src/schema.ts', 6 | generates: { 7 | 'src/generated/graphql.ts': { 8 | plugins: ['typescript', 'typescript-resolvers'], 9 | config: { 10 | enumsAsTypes: true, 11 | }, 12 | }, 13 | './graphql.schema.json': { 14 | plugins: ['introspection'], 15 | }, 16 | }, 17 | } 18 | 19 | export default config 20 | -------------------------------------------------------------------------------- /playgrounds/playground/eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | const config = await antfu({ 4 | ignores: [ 5 | '**/generated', 6 | '**/*.schema.json', 7 | ], 8 | }, { 9 | rules: { 10 | curly: ['error', 'all'], 11 | }, 12 | }) 13 | 14 | export default config 15 | -------------------------------------------------------------------------------- /playgrounds/playground/mq-api.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable antfu/no-top-level-await */ 2 | /* eslint-disable no-console */ 3 | import process from 'node:process' 4 | import { createInstanceFromFactory, createMoquerieInstance, getFactoryByName, runScript, startServer } from 'moquerie' 5 | 6 | const mq = await createMoquerieInstance({ 7 | cwd: process.cwd(), 8 | watching: true, 9 | skipWrites: false, 10 | }) 11 | 12 | await startServer(mq) 13 | 14 | const report = await runScript(mq, 'createSimpleMessage') 15 | console.log(report) 16 | 17 | const factory = await getFactoryByName(mq, 'SimpleMessage') 18 | const instance = await createInstanceFromFactory(mq, { 19 | factory, 20 | save: true, 21 | }) 22 | console.log(instance) 23 | 24 | const ctx = await mq.getResolvedContext() 25 | // You can even check for the tags 26 | // @ts-expect-error example file 27 | const me = await ctx.db.User.findFirstReference((data, { tags }) => tags.includes('me')) 28 | console.log(me) 29 | 30 | await mq.destroy() 31 | -------------------------------------------------------------------------------- /playgrounds/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moquerie-playground", 3 | "type": "module", 4 | "version": "0.0.0", 5 | "private": true, 6 | "scripts": { 7 | "dev": "pnpm run codegen && pnpm run build && pnpm run start", 8 | "build": "tsc", 9 | "start": "node dist/index.js", 10 | "prepare": "pnpm run codegen", 11 | "codegen": "graphql-codegen --config codegen.ts", 12 | "moquerie": "moquerie", 13 | "test": "vitest" 14 | }, 15 | "dependencies": { 16 | "@graphql-tools/schema": "^10.0.0", 17 | "graphql": "^16.8.1", 18 | "graphql-yoga": "^4.0.4" 19 | }, 20 | "devDependencies": { 21 | "@antfu/eslint-config": "^3.11.2", 22 | "@graphql-codegen/cli": "5.0.0", 23 | "@graphql-codegen/introspection": "4.0.0", 24 | "@graphql-codegen/typescript": "4.0.1", 25 | "@graphql-codegen/typescript-resolvers": "4.0.1", 26 | "@types/eslint": "^8.56.2", 27 | "@types/node": "^20.7.1", 28 | "eslint": "^9.16.0", 29 | "moquerie": "workspace:*", 30 | "typescript": "^5.2.2", 31 | "vitest": "^2.1.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /playgrounds/playground/src/auth.ts: -------------------------------------------------------------------------------- 1 | export interface AuthenticatedUser { 2 | id: string 3 | } 4 | 5 | export async function getUser(authorizationHeader: string): Promise { 6 | if (authorizationHeader === 'Bearer 123') { 7 | return { id: '123' } 8 | } 9 | 10 | return null 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/playground/src/context.ts: -------------------------------------------------------------------------------- 1 | import type { YogaInitialContext } from 'graphql-yoga' 2 | import { type AuthenticatedUser, getUser } from './auth.js' 3 | 4 | export interface Context { 5 | user: AuthenticatedUser | null 6 | } 7 | 8 | export async function getContext(initialContext: YogaInitialContext): Promise { 9 | return { 10 | user: await getUser(initialContext.request.headers.get('authorization') ?? ''), 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /playgrounds/playground/src/extend/message.ts: -------------------------------------------------------------------------------- 1 | export interface Message { 2 | /** 3 | * Some example property added to the schema 4 | */ 5 | internalProp: string 6 | } 7 | -------------------------------------------------------------------------------- /playgrounds/playground/src/extend/types.ts: -------------------------------------------------------------------------------- 1 | export * from './message.js' 2 | -------------------------------------------------------------------------------- /playgrounds/playground/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from 'node:http' 2 | import { createYoga } from 'graphql-yoga' 3 | import { getContext } from './context.js' 4 | import { schema } from './schema.js' 5 | 6 | // Create a Yoga instance with a GraphQL schema. 7 | const yoga = createYoga({ 8 | schema, 9 | context: getContext, 10 | }) 11 | 12 | // Pass it into a server to hook into request handlers. 13 | const server = createServer(yoga) 14 | 15 | // Start the server and you're done! 16 | server.listen(4000, () => { 17 | // eslint-disable-next-line no-console 18 | console.info('Server is running on http://localhost:4000/graphql') 19 | }) 20 | -------------------------------------------------------------------------------- /playgrounds/playground/src/rest/other.ts: -------------------------------------------------------------------------------- 1 | export interface MyObjectNotExported { 2 | id: string 3 | title: string 4 | } 5 | -------------------------------------------------------------------------------- /playgrounds/playground/src/rest/rest.moq.ts: -------------------------------------------------------------------------------- 1 | import { defineApiRoutes } from 'moquerie/mocks' 2 | 3 | export default defineApiRoutes((router) => { 4 | router.get('/auth/token', async ({ db }) => { 5 | const Query = await db.Query.findFirst() 6 | if (Query?.currentUser) { 7 | return { 8 | token: Query.currentUser.id, 9 | } 10 | } 11 | return null 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /playgrounds/playground/src/rest/types.ts: -------------------------------------------------------------------------------- 1 | import type { MyObjectNotExported } from './other.js' 2 | 3 | export interface MyObject { 4 | id: string 5 | name: string 6 | count: number 7 | } 8 | 9 | /** 10 | * Another object 11 | * @restPath /foo 12 | */ 13 | export interface MyOtherObject { 14 | id: string 15 | /** 16 | * Some useful description 17 | */ 18 | description?: string 19 | otherDescription: string | undefined 20 | thirdDescription: null | string 21 | objects: MyObject[] 22 | notExported: MyObjectNotExported 23 | /** 24 | * @deprecated Use `otherDescription` instead 25 | */ 26 | deprecatedField: string 27 | } 28 | 29 | /** 30 | * @deprecated Use `MyOtherObject` instead 31 | */ 32 | export interface OldObject { 33 | id: string 34 | name: string 35 | count: number 36 | } 37 | -------------------------------------------------------------------------------- /playgrounds/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "types": [ 7 | "node" 8 | ], 9 | "strict": true, 10 | "outDir": "dist", 11 | "sourceMap": true, 12 | "skipLibCheck": true 13 | }, 14 | "include": [ 15 | "src/**/*.ts", 16 | ".moquerie/**/*.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /playgrounds/playground/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | server: { 6 | deps: { 7 | external: [ 8 | /\/node_modules\//, 9 | /graphql/, 10 | ], 11 | }, 12 | }, 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /playgrounds/quick-rest/.moquerie/types/queryManager.ts: -------------------------------------------------------------------------------- 1 | import type { QueryManager } from 'moquerie' 2 | import type { Message, MyObject, User } from './resources.js' 3 | 4 | declare module 'moquerie' { 5 | export interface QueryManagerProxy { 6 | Message: QueryManager 7 | MyObject: QueryManager 8 | User: QueryManager 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /playgrounds/quick-rest/.moquerie/types/resources.ts: -------------------------------------------------------------------------------- 1 | export interface Message { 2 | id: string 3 | text: string 4 | user: User 5 | } 6 | 7 | export interface MyObject { 8 | id: string 9 | count: number 10 | title: string 11 | } 12 | 13 | export interface User { 14 | id: string 15 | email: string 16 | messages: Message 17 | name: string 18 | } 19 | -------------------------------------------------------------------------------- /playgrounds/quick-rest/moquerie.rest.ts: -------------------------------------------------------------------------------- 1 | export interface MyObject { 2 | id: string 3 | title: string 4 | count: number 5 | } 6 | 7 | export interface User { 8 | id: string 9 | name: string 10 | email: string 11 | messages: Message[] 12 | } 13 | 14 | export interface Message { 15 | id: string 16 | text: string 17 | user: User 18 | } 19 | -------------------------------------------------------------------------------- /playgrounds/quick-rest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moquerie-playground-quick-rest", 3 | "type": "module", 4 | "version": "0.0.0", 5 | "private": true, 6 | "scripts": { 7 | "moquerie": "moquerie" 8 | }, 9 | "devDependencies": { 10 | "moquerie": "workspace:*", 11 | "typescript": "^5.2.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - playgrounds/* 4 | --------------------------------------------------------------------------------