···11+/**
22+ * This file is automatically generated.
33+ * DO NOT EDIT manually
44+ */
55+66+/// <reference path="./manifest.d.ts" />
77+import type { InferData, InferVariants } from '@adonisjs/core/types/transformers'
88+import type { InferSharedProps } from '@adonisjs/inertia/types'
99+import type UserTransformer from '#transformers/user_transformer'
1010+import type InertiaMiddleware from '#middleware/inertia_middleware'
1111+1212+export namespace Data {
1313+ export type User = InferData<UserTransformer>
1414+ export namespace User {
1515+ export type Variants = InferVariants<UserTransformer>
1616+ }
1717+ export type SharedProps = InferSharedProps<InertiaMiddleware>
1818+}
+10
.adonisjs/client/manifest.d.ts
···11+/**
22+ * This file is automatically generated.
33+ * DO NOT EDIT manually
44+ */
55+66+/// <reference path="../../adonisrc.ts" />
77+/// <reference path="../../config/atproto_oauth.ts" />
88+/// <reference path="../../config/auth.ts" />
99+/// <reference path="../../config/hash.ts" />
1010+/// <reference path="../../config/logger.ts" />
···11+# the name by which the project can be referenced within Serena
22+project_name: "ATlast"
33+44+55+# list of languages for which language servers are started; choose from:
66+# al bash clojure cpp csharp
77+# csharp_omnisharp dart elixir elm erlang
88+# fortran fsharp go groovy haskell
99+# java julia kotlin lua markdown
1010+# matlab nix pascal perl php
1111+# php_phpactor powershell python python_jedi r
1212+# rego ruby ruby_solargraph rust scala
1313+# swift terraform toml typescript typescript_vts
1414+# vue yaml zig
1515+# (This list may be outdated. For the current list, see values of Language enum here:
1616+# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
1717+# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
1818+# Note:
1919+# - For C, use cpp
2020+# - For JavaScript, use typescript
2121+# - For Free Pascal/Lazarus, use pascal
2222+# Special requirements:
2323+# Some languages require additional setup/installations.
2424+# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
2525+# When using multiple languages, the first language server that supports a given file will be used for that file.
2626+# The first language is the default language and the respective language server will be used as a fallback.
2727+# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
2828+languages:
2929+- vue
3030+3131+# the encoding used by text files in the project
3232+# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
3333+encoding: "utf-8"
3434+3535+# The language backend to use for this project.
3636+# If not set, the global setting from serena_config.yml is used.
3737+# Valid values: LSP, JetBrains
3838+# Note: the backend is fixed at startup. If a project with a different backend
3939+# is activated post-init, an error will be returned.
4040+language_backend:
4141+4242+# whether to use project's .gitignore files to ignore files
4343+ignore_all_files_in_gitignore: true
4444+4545+# list of additional paths to ignore in this project.
4646+# Same syntax as gitignore, so you can use * and **.
4747+# Note: global ignored_paths from serena_config.yml are also applied additively.
4848+ignored_paths: []
4949+5050+# whether the project is in read-only mode
5151+# If set to true, all editing tools will be disabled and attempts to use them will result in an error
5252+# Added on 2025-04-18
5353+read_only: false
5454+5555+# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
5656+# Below is the complete list of tools for convenience.
5757+# To make sure you have the latest list of tools, and to view their descriptions,
5858+# execute `uv run scripts/print_tool_overview.py`.
5959+#
6060+# * `activate_project`: Activates a project by name.
6161+# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
6262+# * `create_text_file`: Creates/overwrites a file in the project directory.
6363+# * `delete_lines`: Deletes a range of lines within a file.
6464+# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
6565+# * `execute_shell_command`: Executes a shell command.
6666+# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
6767+# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
6868+# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
6969+# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
7070+# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
7171+# * `initial_instructions`: Gets the initial instructions for the current project.
7272+# Should only be used in settings where the system prompt cannot be set,
7373+# e.g. in clients you have no control over, like Claude Desktop.
7474+# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
7575+# * `insert_at_line`: Inserts content at a given line in a file.
7676+# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
7777+# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
7878+# * `list_memories`: Lists memories in Serena's project-specific memory store.
7979+# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
8080+# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
8181+# * `read_file`: Reads a file within the project directory.
8282+# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
8383+# * `remove_project`: Removes a project from the Serena configuration.
8484+# * `replace_lines`: Replaces a range of lines within a file with new content.
8585+# * `replace_symbol_body`: Replaces the full definition of a symbol.
8686+# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
8787+# * `search_for_pattern`: Performs a search for a pattern in the project.
8888+# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
8989+# * `switch_modes`: Activates modes by providing a list of their names
9090+# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
9191+# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
9292+# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
9393+# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
9494+excluded_tools: []
9595+9696+# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
9797+included_optional_tools: []
9898+9999+# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
100100+# This cannot be combined with non-empty excluded_tools or included_optional_tools.
101101+fixed_tools: []
102102+103103+# list of mode names to that are always to be included in the set of active modes
104104+# The full set of modes to be activated is base_modes + default_modes.
105105+# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
106106+# Otherwise, this setting overrides the global configuration.
107107+# Set this to [] to disable base modes for this project.
108108+# Set this to a list of mode names to always include the respective modes for this project.
109109+base_modes:
110110+111111+# list of mode names that are to be activated by default.
112112+# The full set of modes to be activated is base_modes + default_modes.
113113+# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
114114+# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
115115+# This setting can, in turn, be overridden by CLI parameters (--mode).
116116+default_modes:
117117+118118+# initial prompt for the project. It will always be given to the LLM upon activating the project
119119+# (contrary to the memories, which are loaded on demand).
120120+initial_prompt: ""
121121+122122+# time budget (seconds) per tool call for the retrieval of additional symbol information
123123+# such as docstrings or parameter information.
124124+# This overrides the corresponding setting in the global configuration; see the documentation there.
125125+# If null or missing, use the setting from the global configuration.
126126+symbol_info_budget:
···11+/*
22+|--------------------------------------------------------------------------
33+| JavaScript entrypoint for running ace commands
44+|--------------------------------------------------------------------------
55+|
66+| DO NOT MODIFY THIS FILE AS IT WILL BE OVERRIDDEN DURING THE BUILD
77+| PROCESS.
88+|
99+| See docs.adonisjs.com/guides/typescript-build-process#creating-production-build
1010+|
1111+| Since, we cannot run TypeScript source code using "node" binary, we need
1212+| a JavaScript entrypoint to run ace commands.
1313+|
1414+| This file registers the "ts-node/esm" hook with the Node.js module system
1515+| and then imports the "bin/console.ts" file.
1616+|
1717+*/
1818+1919+/**
2020+ * Register hook to process TypeScript files using @poppinss/ts-exec
2121+ */
2222+import '@poppinss/ts-exec'
2323+2424+/**
2525+ * Import ace console entrypoint
2626+ */
2727+await import('./bin/console.js')
+139
adonisrc.ts
···11+import { indexPages } from '@adonisjs/inertia'
22+import { indexEntities } from '@adonisjs/core'
33+import { defineConfig } from '@adonisjs/core/app'
44+import { generateRegistry } from '@tuyau/core/hooks'
55+66+export default defineConfig({
77+ /*
88+ |--------------------------------------------------------------------------
99+ | Experimental flags
1010+ |--------------------------------------------------------------------------
1111+ |
1212+ | The following features will be enabled by default in the next major release
1313+ | of AdonisJS. You can opt into them today to avoid any breaking changes
1414+ | during upgrade.
1515+ |
1616+ */
1717+ experimental: {},
1818+1919+ /*
2020+ |--------------------------------------------------------------------------
2121+ | Commands
2222+ |--------------------------------------------------------------------------
2323+ |
2424+ | List of ace commands to register from packages. The application commands
2525+ | will be scanned automatically from the "./commands" directory.
2626+ |
2727+ */
2828+ commands: [
2929+ () => import('@adonisjs/core/commands'),
3030+ () => import('@adonisjs/lucid/commands'),
3131+ () => import('@adonisjs/session/commands'),
3232+ () => import('@adonisjs/inertia/commands'),
3333+ ],
3434+3535+ /*
3636+ |--------------------------------------------------------------------------
3737+ | Service providers
3838+ |--------------------------------------------------------------------------
3939+ |
4040+ | List of service providers to import and register when booting the
4141+ | application
4242+ |
4343+ */
4444+ providers: [
4545+ () => import('@adonisjs/core/providers/app_provider'),
4646+ () => import('@adonisjs/core/providers/hash_provider'),
4747+ {
4848+ file: () => import('@adonisjs/core/providers/repl_provider'),
4949+ environment: ['repl', 'test'],
5050+ },
5151+ () => import('@adonisjs/core/providers/vinejs_provider'),
5252+ () => import('@adonisjs/core/providers/edge_provider'),
5353+ () => import('@adonisjs/session/session_provider'),
5454+ () => import('@adonisjs/vite/vite_provider'),
5555+ () => import('@adonisjs/shield/shield_provider'),
5656+ () => import('@adonisjs/static/static_provider'),
5757+ () => import('@adonisjs/lucid/database_provider'),
5858+ () => import('@adonisjs/cors/cors_provider'),
5959+ () => import('@adonisjs/inertia/inertia_provider'),
6060+ () => import('@adonisjs/auth/auth_provider'),
6161+ () => import('#providers/api_provider'),
6262+ () => import('@thisismissem/adonisjs-atproto-oauth/provider')
6363+ ],
6464+6565+ /*
6666+ |--------------------------------------------------------------------------
6767+ | Preloads
6868+ |--------------------------------------------------------------------------
6969+ |
7070+ | List of modules to import before starting the application.
7171+ |
7272+ */
7373+ preloads: [
7474+ () => import('#start/routes'),
7575+ () => import('#start/kernel'),
7676+ () => import('#start/validator'),
7777+ ],
7878+7979+ /*
8080+ |--------------------------------------------------------------------------
8181+ | Tests
8282+ |--------------------------------------------------------------------------
8383+ |
8484+ | List of test suites to organize tests by their type. Feel free to remove
8585+ | and add additional suites.
8686+ |
8787+ */
8888+ tests: {
8989+ suites: [
9090+ {
9191+ files: ['tests/unit/**/*.spec.{ts,js}'],
9292+ name: 'unit',
9393+ timeout: 2000,
9494+ },
9595+ {
9696+ files: ['tests/functional/**/*.spec.{ts,js}'],
9797+ name: 'functional',
9898+ timeout: 30000,
9999+ },
100100+ {
101101+ files: ['tests/browser/**/*.spec.{ts,js}'],
102102+ name: 'browser',
103103+ timeout: 300000,
104104+ },
105105+ ],
106106+ forceExit: false,
107107+ },
108108+109109+ /*
110110+ |--------------------------------------------------------------------------
111111+ | Metafiles
112112+ |--------------------------------------------------------------------------
113113+ |
114114+ | A collection of files you want to copy to the build folder when creating
115115+ | the production build.
116116+ |
117117+ */
118118+ metaFiles: [
119119+ {
120120+ pattern: 'resources/views/**/*.edge',
121121+ reloadServer: false,
122122+ },
123123+ {
124124+ pattern: 'public/**',
125125+ reloadServer: false,
126126+ },
127127+ ],
128128+129129+ hooks: {
130130+ init: [
131131+ indexEntities({
132132+ transformers: { enabled: true, withSharedProps: true },
133133+ }),
134134+ indexPages({ framework: 'vue3' }),
135135+ generateRegistry(),
136136+ ],
137137+ buildStarting: [() => import('@adonisjs/vite/build_hook')],
138138+ },
139139+})
+17
app/controllers/new_account_controller.ts
···11+import User from '#models/user'
22+import { signupValidator } from '#validators/user'
33+import type { HttpContext } from '@adonisjs/core/http'
44+55+export default class NewAccountController {
66+ async create({ inertia }: HttpContext) {
77+ return inertia.render('auth/signup', {})
88+ }
99+1010+ async store({ request, response, auth }: HttpContext) {
1111+ const payload = await request.validateUsing(signupValidator)
1212+ const user = await User.create({ ...payload })
1313+1414+ await auth.use('web').login(user)
1515+ response.redirect().toRoute('home')
1616+ }
1717+}
+65
app/controllers/oauth_controller.ts
···11+import type { HttpContext } from '@adonisjs/core/http'
22+import { OAuthResolverError } from '@atproto/oauth-client-node'
33+import { loginRequestValidator, signupRequestValidator } from '#validators/oauth'
44+55+export default class OAuthController {
66+ async handleLogin({ request, response, oauth, logger }: HttpContext) {
77+ // input should be a handle or service URL:
88+ const { input } = await request.validateUsing(loginRequestValidator)
99+ try {
1010+ const authorizationUrl = await oauth.authorize(input)
1111+1212+ response.redirect().toPath(authorizationUrl)
1313+ } catch (err) {
1414+ logger.error(err, 'Error starting AT Protocol OAuth flow')
1515+ if (err instanceof OAuthResolverError) {
1616+ // Handle the input not being AT Protocol OAuth compatible
1717+ }
1818+1919+ response.redirect().back()
2020+ }
2121+ }
2222+2323+ async handleSignup({ request, response, oauth }: HttpContext) {
2424+ // input should be a service URL:
2525+ const { input } = await request.validateUsing(signupRequestValidator)
2626+ const service = input ?? 'https://bsky.social'
2727+ const registrationSupported = await oauth.canRegister(service)
2828+2929+ if (!registrationSupported) {
3030+ // Handle registration not supported, you may want to special case for
3131+ // bsky.social which has public registration behind a click.
3232+3333+ return response.abort('Registration not supported')
3434+ }
3535+3636+ const authorizationUrl = await oauth.register(service)
3737+3838+ return response.redirect().toPath(authorizationUrl)
3939+ }
4040+4141+ async handleLogout({ auth, oauth, response }: HttpContext) {
4242+ await oauth.logout(auth.user?.did)
4343+ await auth.use('web').logout()
4444+4545+ return response.redirect().back()
4646+ }
4747+4848+ async callback({ response, oauth, auth, logger }: HttpContext) {
4949+ try {
5050+ const session = await oauth.handleCallback()
5151+5252+ await auth.use('web').login(session.user)
5353+5454+ // You'll probably want to check if you have an "account" according to Tap
5555+ // or some other method, and if not redirect to an onboarding flow
5656+5757+ return response.redirect().toPath('/')
5858+ } catch (err) {
5959+ // Handle OAuth failing
6060+ logger.error(err, 'Error completing AT Protocol OAuth flow')
6161+6262+ return response.redirect().toPath('/')
6363+ }
6464+ }
6565+}
···11+import app from '@adonisjs/core/services/app'
22+import { type HttpContext, ExceptionHandler } from '@adonisjs/core/http'
33+import type { StatusPageRange, StatusPageRenderer } from '@adonisjs/core/types/http'
44+55+export default class HttpExceptionHandler extends ExceptionHandler {
66+ /**
77+ * In debug mode, the exception handler will display verbose errors
88+ * with pretty printed stack traces.
99+ */
1010+ protected debug = !app.inProduction
1111+1212+ /**
1313+ * Status pages are used to display a custom HTML pages for certain error
1414+ * codes. You might want to enable them in production only, but feel
1515+ * free to enable them in development as well.
1616+ */
1717+ protected renderStatusPages = app.inProduction
1818+1919+ /**
2020+ * Status pages is a collection of error code range and a callback
2121+ * to return the HTML contents to send as a response.
2222+ */
2323+ protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
2424+ '404': (_, { inertia }) => inertia.render('errors/not_found', {}),
2525+ '500..599': (_, { inertia }) => inertia.render('errors/server_error', {}),
2626+ }
2727+2828+ /**
2929+ * The method is used for handling errors and returning
3030+ * response to the client
3131+ */
3232+ async handle(error: unknown, ctx: HttpContext) {
3333+ return super.handle(error, ctx)
3434+ }
3535+3636+ /**
3737+ * The method is used to report error to the logging service or
3838+ * the a third party error monitoring service.
3939+ *
4040+ * @note You should not attempt to send a response from this method.
4141+ */
4242+ async report(error: unknown, ctx: HttpContext) {
4343+ return super.report(error, ctx)
4444+ }
4545+}
+16
app/middleware/auth_middleware.ts
···11+import type { HttpContext } from '@adonisjs/core/http'
22+import type { NextFn } from '@adonisjs/core/types/http'
33+import type { Authenticators } from '@adonisjs/auth/types'
44+55+export default class AuthMiddleware {
66+ redirectTo = '/login'
77+88+ async handle(
99+ ctx: HttpContext,
1010+ next: NextFn,
1111+ options: { guards?: (keyof Authenticators)[] } = {}
1212+ ) {
1313+ await ctx.auth.authenticateUsing(options.guards, { loginRoute: this.redirectTo })
1414+ return next()
1515+ }
1616+}
+19
app/middleware/container_bindings_middleware.ts
···11+import { Logger } from '@adonisjs/core/logger'
22+import { HttpContext } from '@adonisjs/core/http'
33+import { type NextFn } from '@adonisjs/core/types/http'
44+55+/**
66+ * The container bindings middleware binds classes to their request
77+ * specific value using the container resolver.
88+ *
99+ * - We bind "HttpContext" class to the "ctx" object
1010+ * - And bind "Logger" class to the "ctx.logger" object
1111+ */
1212+export default class ContainerBindingsMiddleware {
1313+ handle(ctx: HttpContext, next: NextFn) {
1414+ ctx.containerResolver.bindValue(HttpContext, ctx)
1515+ ctx.containerResolver.bindValue(Logger, ctx.logger)
1616+1717+ return next()
1818+ }
1919+}
+32
app/middleware/guest_middleware.ts
···11+import type { HttpContext } from '@adonisjs/core/http'
22+import type { NextFn } from '@adonisjs/core/types/http'
33+import type { Authenticators } from '@adonisjs/auth/types'
44+55+/**
66+ * Guest middleware is used to deny access to routes that should
77+ * be accessed by unauthenticated users.
88+ *
99+ * For example, the login page should not be accessible if the user
1010+ * is already logged-in
1111+ */
1212+export default class GuestMiddleware {
1313+ /**
1414+ * The URL to redirect to when user is logged-in
1515+ */
1616+ redirectTo = '/'
1717+1818+ async handle(
1919+ ctx: HttpContext,
2020+ next: NextFn,
2121+ options: { guards?: (keyof Authenticators)[] } = {}
2222+ ) {
2323+ for (let guard of options.guards || [ctx.auth.defaultGuard]) {
2424+ if (await ctx.auth.use(guard).check()) {
2525+ ctx.session.reflash()
2626+ return ctx.response.redirect(this.redirectTo, true)
2727+ }
2828+ }
2929+3030+ return next()
3131+ }
3232+}
+52
app/middleware/inertia_middleware.ts
···11+import type { HttpContext } from '@adonisjs/core/http'
22+import type { NextFn } from '@adonisjs/core/types/http'
33+import UserTransformer from '#transformers/user_transformer'
44+import BaseInertiaMiddleware from '@adonisjs/inertia/inertia_middleware'
55+66+export default class InertiaMiddleware extends BaseInertiaMiddleware {
77+ share(ctx: HttpContext) {
88+ /**
99+ * The share method is called everytime an Inertia page is rendered. In
1010+ * certain cases, a page may get rendered before the session middleware
1111+ * or the auth middleware are executed. For example: During a 404 request.
1212+ *
1313+ * In that case, we must always assume that HttpContext is not fully hydrated
1414+ * with all the properties
1515+ */
1616+ const { session, auth } = ctx as Partial<HttpContext>
1717+1818+ /**
1919+ * Fetching the first error from the flash messages
2020+ */
2121+ const errorsBag = session?.flashMessages.get('errorsBag') ?? {}
2222+ const error: string | undefined = Object.keys(errorsBag)
2323+ .filter((code) => code !== 'E_VALIDATION_ERROR')
2424+ .map((code) => errorsBag[code])[0]
2525+2626+ /**
2727+ * Data shared with all Inertia pages. Make sure you are using
2828+ * transformers for rich data-types like Models.
2929+ */
3030+ return {
3131+ errors: ctx.inertia.always(this.getValidationErrors(ctx)),
3232+ flash: ctx.inertia.always({
3333+ error: error,
3434+ }),
3535+ user: ctx.inertia.always(auth?.user ? UserTransformer.transform(auth.user) : undefined),
3636+ }
3737+ }
3838+3939+ async handle(ctx: HttpContext, next: NextFn) {
4040+ await this.init(ctx)
4141+4242+ const output = await next()
4343+ this.dispose(ctx)
4444+4545+ return output
4646+ }
4747+}
4848+4949+declare module '@adonisjs/inertia/types' {
5050+ type MiddlewareSharedProps = InferSharedProps<InertiaMiddleware>
5151+ export interface SharedProps extends MiddlewareSharedProps {}
5252+}
+16
app/middleware/silent_auth_middleware.ts
···11+import type { HttpContext } from '@adonisjs/core/http'
22+import type { NextFn } from '@adonisjs/core/types/http'
33+44+/**
55+ * Silent auth middleware can be used as a global middleware to silent check
66+ * if the user is logged-in or not.
77+ *
88+ * The request continues as usual, even when the user is not logged-in.
99+ */
1010+export default class SilentAuthMiddleware {
1111+ async handle(ctx: HttpContext, next: NextFn) {
1212+ await ctx.auth.check()
1313+1414+ return next()
1515+ }
1616+}
···11+/*
22+|--------------------------------------------------------------------------
33+| Ace entry point
44+|--------------------------------------------------------------------------
55+|
66+| The "console.ts" file is the entrypoint for booting the AdonisJS
77+| command-line framework and executing commands.
88+|
99+| Commands do not boot the application, unless the currently running command
1010+| has "options.startApp" flag set to true.
1111+|
1212+*/
1313+1414+await import('reflect-metadata')
1515+const { Ignitor, prettyPrintError } = await import('@adonisjs/core')
1616+1717+/**
1818+ * URL to the application root. AdonisJS need it to resolve
1919+ * paths to file and directories for scaffolding commands
2020+ */
2121+const APP_ROOT = new URL('../', import.meta.url)
2222+2323+/**
2424+ * The importer is used to import files in context of the
2525+ * application.
2626+ */
2727+const IMPORTER = (filePath: string) => {
2828+ if (filePath.startsWith('./') || filePath.startsWith('../')) {
2929+ return import(new URL(filePath, APP_ROOT).href)
3030+ }
3131+ return import(filePath)
3232+}
3333+3434+new Ignitor(APP_ROOT, { importer: IMPORTER })
3535+ .tap((app) => {
3636+ app.booting(async () => {
3737+ await import('#start/env')
3838+ })
3939+ app.listen('SIGTERM', () => app.terminate())
4040+ app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate())
4141+ })
4242+ .ace()
4343+ .handle(process.argv.splice(2))
4444+ .catch((error) => {
4545+ process.exitCode = 1
4646+ prettyPrintError(error)
4747+ })
+45
bin/server.ts
···11+/*
22+|--------------------------------------------------------------------------
33+| HTTP server entrypoint
44+|--------------------------------------------------------------------------
55+|
66+| The "server.ts" file is the entrypoint for starting the AdonisJS HTTP
77+| server. Either you can run this file directly or use the "serve"
88+| command to run this file and monitor file changes
99+|
1010+*/
1111+1212+await import('reflect-metadata')
1313+const { Ignitor, prettyPrintError } = await import('@adonisjs/core')
1414+1515+/**
1616+ * URL to the application root. AdonisJS need it to resolve
1717+ * paths to file and directories for scaffolding commands
1818+ */
1919+const APP_ROOT = new URL('../', import.meta.url)
2020+2121+/**
2222+ * The importer is used to import files in context of the
2323+ * application.
2424+ */
2525+const IMPORTER = (filePath: string) => {
2626+ if (filePath.startsWith('./') || filePath.startsWith('../')) {
2727+ return import(new URL(filePath, APP_ROOT).href)
2828+ }
2929+ return import(filePath)
3030+}
3131+3232+new Ignitor(APP_ROOT, { importer: IMPORTER })
3333+ .tap((app) => {
3434+ app.booting(async () => {
3535+ await import('#start/env')
3636+ })
3737+ app.listen('SIGTERM', () => app.terminate())
3838+ app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate())
3939+ })
4040+ .httpServer()
4141+ .start()
4242+ .catch((error) => {
4343+ process.exitCode = 1
4444+ prettyPrintError(error)
4545+ })
+62
bin/test.ts
···11+/*
22+|--------------------------------------------------------------------------
33+| Test runner entrypoint
44+|--------------------------------------------------------------------------
55+|
66+| The "test.ts" file is the entrypoint for running tests using Japa.
77+|
88+| Either you can run this file directly or use the "test"
99+| command to run this file and monitor file changes.
1010+|
1111+*/
1212+1313+process.env.NODE_ENV = 'test'
1414+1515+import 'reflect-metadata'
1616+import { Ignitor, prettyPrintError } from '@adonisjs/core'
1717+import { configure, processCLIArgs, run } from '@japa/runner'
1818+1919+/**
2020+ * URL to the application root. AdonisJS need it to resolve
2121+ * paths to file and directories for scaffolding commands
2222+ */
2323+const APP_ROOT = new URL('../', import.meta.url)
2424+2525+/**
2626+ * The importer is used to import files in context of the
2727+ * application.
2828+ */
2929+const IMPORTER = (filePath: string) => {
3030+ if (filePath.startsWith('./') || filePath.startsWith('../')) {
3131+ return import(new URL(filePath, APP_ROOT).href)
3232+ }
3333+ return import(filePath)
3434+}
3535+3636+new Ignitor(APP_ROOT, { importer: IMPORTER })
3737+ .tap((app) => {
3838+ app.booting(async () => {
3939+ await import('#start/env')
4040+ })
4141+ app.listen('SIGTERM', () => app.terminate())
4242+ app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate())
4343+ })
4444+ .testRunner()
4545+ .configure(async (app) => {
4646+ const { runnerHooks, ...config } = await import('../tests/bootstrap.js')
4747+4848+ processCLIArgs(process.argv.splice(2))
4949+ configure({
5050+ ...app.rcFile.tests,
5151+ ...config,
5252+ ...{
5353+ setup: runnerHooks.setup,
5454+ teardown: runnerHooks.teardown.concat([() => app.terminate()]),
5555+ },
5656+ })
5757+ })
5858+ .run(() => run())
5959+ .catch((error) => {
6060+ process.exitCode = 1
6161+ prettyPrintError(error)
6262+ })
+73
config/app.ts
···11+import env from '#start/env'
22+import app from '@adonisjs/core/services/app'
33+import { defineConfig } from '@adonisjs/core/http'
44+55+/**
66+ * The app key is used for encrypting cookies, generating signed URLs,
77+ * and by the "encryption" module.
88+ *
99+ * The encryption module will fail to decrypt data if the key is lost or
1010+ * changed. Therefore it is recommended to keep the app key secure.
1111+ */
1212+export const appKey = env.get('APP_KEY')
1313+1414+/**
1515+ * The configuration settings used by the HTTP server
1616+ */
1717+export const http = defineConfig({
1818+ /**
1919+ * Generate a unique request id for each incoming request.
2020+ * Useful to correlate logs and debug a request flow.
2121+ */
2222+ generateRequestId: true,
2323+2424+ /**
2525+ * Allow HTTP method spoofing via the "_method" form/query parameter.
2626+ * This lets HTML forms target PUT/PATCH/DELETE routes while still
2727+ * submitting with POST.
2828+ */
2929+ allowMethodSpoofing: false,
3030+3131+ /**
3232+ * Enabling async local storage will let you access HTTP context
3333+ * from anywhere inside your application.
3434+ */
3535+ useAsyncLocalStorage: false,
3636+3737+ /**
3838+ * Manage cookies configuration. The settings for the session id cookie are
3939+ * defined inside the "config/session.ts" file.
4040+ */
4141+ cookie: {
4242+ /**
4343+ * Restrict the cookie to a specific domain.
4444+ * Keep empty to use the current host.
4545+ */
4646+ domain: '',
4747+4848+ /**
4949+ * Restrict the cookie to a URL path. '/' means all routes.
5050+ */
5151+ path: '/',
5252+5353+ /**
5454+ * Default lifetime for cookies managed by the HTTP layer.
5555+ */
5656+ maxAge: '2h',
5757+5858+ /**
5959+ * Prevent JavaScript access to the cookie in the browser.
6060+ */
6161+ httpOnly: true,
6262+6363+ /**
6464+ * Send cookies only over HTTPS in production.
6565+ */
6666+ secure: app.inProduction,
6767+6868+ /**
6969+ * Cross-site policy for cookie sending.
7070+ */
7171+ sameSite: 'lax',
7272+ },
7373+})
+21
config/atproto_oauth.ts
···11+import { defineConfig } from '@thisismissem/adonisjs-atproto-oauth'
22+import env from '#start/env'
33+import OAuthState from '#models/oauth_state'
44+import OAuthSession from '#models/oauth_session'
55+66+export default defineConfig({
77+ publicUrl: env.get('PUBLIC_URL'),
88+ metadata: {
99+ // If ATPROTO_OAUTH_CLIENT_ID is set, the client metadata will be fetched from that URL:
1010+ client_id: env.get('ATPROTO_OAUTH_CLIENT_ID'),
1111+ client_name: 'Some App',
1212+ redirect_uris: ['/oauth/callback'],
1313+ },
1414+1515+ // For a confidential client:
1616+ // jwks: [env.get('ATPROTO_OAUTH_JWT_PRIVATE_KEY')],
1717+1818+ // Models to store OAuth State and Sessions:
1919+ stateStore: OAuthState,
2020+ sessionStore: OAuthSession,
2121+})
+38
config/auth.ts
···11+import { defineConfig } from '@adonisjs/auth'
22+import { sessionGuard } from '@adonisjs/auth/session'
33+import { atprotoAuthProvider } from '@thisismissem/adonisjs-atproto-oauth/auth/provider'
44+import type { InferAuthenticators, InferAuthEvents, Authenticators } from '@adonisjs/auth/types'
55+66+const authConfig = defineConfig({
77+ /**
88+ * Default guard used when no guard is explicitly specified.
99+ */
1010+ default: 'web',
1111+1212+ guards: {
1313+ /**
1414+ * Session-based guard for browser authentication.
1515+ */
1616+ web: sessionGuard({
1717+ /**
1818+ * Enable persistent login using remember-me tokens.
1919+ */
2020+ useRememberMeTokens: false,
2121+2222+ provider: atprotoAuthProvider,
2323+ }),
2424+ },
2525+})
2626+2727+export default authConfig
2828+2929+/**
3030+ * Inferring types from the configured auth
3131+ * guards.
3232+ */
3333+declare module '@adonisjs/auth/types' {
3434+ export interface Authenticators extends InferAuthenticators<typeof authConfig> {}
3535+}
3636+declare module '@adonisjs/core/types' {
3737+ interface EventsList extends InferAuthEvents<Authenticators> {}
3838+}
+78
config/bodyparser.ts
···11+import { defineConfig } from '@adonisjs/core/bodyparser'
22+33+const bodyParserConfig = defineConfig({
44+ /**
55+ * Parse request bodies for these HTTP methods.
66+ * Keep this aligned with methods that receive payloads in your routes.
77+ */
88+ allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
99+1010+ /**
1111+ * Config for the "application/x-www-form-urlencoded"
1212+ * content-type parser.
1313+ */
1414+ form: {
1515+ /**
1616+ * Normalize empty string values to null.
1717+ */
1818+ convertEmptyStringsToNull: true,
1919+2020+ /**
2121+ * Content types handled by the form parser.
2222+ */
2323+ types: ['application/x-www-form-urlencoded'],
2424+ },
2525+2626+ /**
2727+ * Config for the JSON parser.
2828+ */
2929+ json: {
3030+ /**
3131+ * Normalize empty string values to null.
3232+ */
3333+ convertEmptyStringsToNull: true,
3434+3535+ /**
3636+ * Content types handled by the JSON parser.
3737+ */
3838+ types: [
3939+ 'application/json',
4040+ 'application/json-patch+json',
4141+ 'application/vnd.api+json',
4242+ 'application/csp-report',
4343+ ],
4444+ },
4545+4646+ /**
4747+ * Config for the "multipart/form-data" content-type parser.
4848+ * File uploads are handled by the multipart parser.
4949+ */
5050+ multipart: {
5151+ /**
5252+ * Automatically process uploaded files into the system tmp directory.
5353+ */
5454+ autoProcess: true,
5555+5656+ /**
5757+ * Normalize empty string values to null.
5858+ */
5959+ convertEmptyStringsToNull: true,
6060+6161+ /**
6262+ * Routes where multipart processing is handled manually.
6363+ */
6464+ processManually: [],
6565+6666+ /**
6767+ * Maximum accepted payload size for multipart requests.
6868+ */
6969+ limit: '20mb',
7070+7171+ /**
7272+ * Content types handled by the multipart parser.
7373+ */
7474+ types: ['multipart/form-data'],
7575+ },
7676+})
7777+7878+export default bodyParserConfig
+50
config/cors.ts
···11+import app from '@adonisjs/core/services/app'
22+import { defineConfig } from '@adonisjs/cors'
33+44+/**
55+ * Configuration options to tweak the CORS policy. The following
66+ * options are documented on the official documentation website.
77+ *
88+ * https://docs.adonisjs.com/guides/security/cors
99+ */
1010+const corsConfig = defineConfig({
1111+ /**
1212+ * Enable or disable CORS handling globally.
1313+ */
1414+ enabled: true,
1515+1616+ /**
1717+ * In development, allow every origin to simplify local front/backend setup.
1818+ * In production, keep an explicit allowlist (empty by default, so no
1919+ * cross-origin browser access is allowed until configured).
2020+ */
2121+ origin: app.inDev ? true : [],
2222+2323+ /**
2424+ * HTTP methods accepted for cross-origin requests.
2525+ */
2626+ methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
2727+2828+ /**
2929+ * Reflect request headers by default. Use a string array to restrict
3030+ * allowed headers.
3131+ */
3232+ headers: true,
3333+3434+ /**
3535+ * Response headers exposed to the browser.
3636+ */
3737+ exposeHeaders: [],
3838+3939+ /**
4040+ * Allow cookies/authorization headers on cross-origin requests.
4141+ */
4242+ credentials: true,
4343+4444+ /**
4545+ * Cache CORS preflight response for N seconds.
4646+ */
4747+ maxAge: 90,
4848+})
4949+5050+export default corsConfig
···11+import env from '#start/env'
22+import { defineConfig, drivers } from '@adonisjs/core/encryption'
33+44+const encryptionConfig = defineConfig({
55+ /**
66+ * Default encryption driver used by the application.
77+ */
88+ default: 'gcm',
99+1010+ list: {
1111+ gcm: drivers.aes256gcm({
1212+ /**
1313+ * Keys used for encryption/decryption.
1414+ * First key encrypts, all keys are tried for decryption.
1515+ */
1616+ keys: [env.get('APP_KEY').release()],
1717+1818+ /**
1919+ * Stable identifier for this driver.
2020+ */
2121+ id: 'gcm',
2222+ }),
2323+ },
2424+})
2525+2626+export default encryptionConfig
2727+2828+/**
2929+ * Inferring types for the list of encryptors you have configured
3030+ * in your application.
3131+ */
3232+declare module '@adonisjs/core/types' {
3333+ export interface EncryptorsList extends InferEncryptors<typeof encryptionConfig> {}
3434+}
+75
config/hash.ts
···11+import { defineConfig, drivers } from '@adonisjs/core/hash'
22+33+/**
44+ * Hashing configuration.
55+ *
66+ * This starter uses Node.js scrypt under the hood.
77+ * Node.js reference: https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback
88+ */
99+const hashConfig = defineConfig({
1010+ /**
1111+ * Default hasher used by the application.
1212+ */
1313+ default: 'scrypt',
1414+1515+ list: {
1616+ /**
1717+ * Scrypt is memory-hard, which makes brute-force attacks more expensive.
1818+ */
1919+ scrypt: drivers.scrypt({
2020+ /**
2121+ * Work factor (Node alias: N / cost).
2222+ * Higher values increase security and CPU+memory usage.
2323+ *
2424+ * Tuning guideline:
2525+ * - Start with 16384.
2626+ * - Increase gradually (for example 32768) and benchmark login/signup latency.
2727+ * - Keep values practical for your slowest production machine.
2828+ *
2929+ * Node constraint: value must be a power of two greater than 1.
3030+ */
3131+ cost: 16384,
3232+3333+ /**
3434+ * Block size (Node alias: r / blockSize).
3535+ * Increases memory and CPU linearly.
3636+ *
3737+ * Tuning guideline:
3838+ * - Keep 8 unless you have a measured reason to change it.
3939+ * - Raise only with benchmark data, because memory usage grows quickly.
4040+ */
4141+ blockSize: 8,
4242+4343+ /**
4444+ * Parallelization (Node alias: p / parallelization).
4545+ * Controls how many independent computations are performed.
4646+ *
4747+ * Tuning guideline:
4848+ * - Keep 1 for most applications.
4949+ * - Increase only after load testing if your infrastructure benefits from it.
5050+ */
5151+ parallelization: 1,
5252+5353+ /**
5454+ * Maximum memory limit in bytes (Node alias: maxmem / maxMemory).
5555+ * Hashing throws if the estimated memory usage is above this limit.
5656+ * Node documents the check as approximately: 128 * N * r > maxmem.
5757+ *
5858+ * Tuning guideline:
5959+ * - Keep this aligned with your cost/blockSize choices.
6060+ * - Increase carefully on memory-constrained environments.
6161+ */
6262+ maxMemory: 33554432,
6363+ }),
6464+ },
6565+})
6666+6767+export default hashConfig
6868+6969+/**
7070+ * Inferring types for the list of hashers you have configured
7171+ * in your application.
7272+ */
7373+declare module '@adonisjs/core/types' {
7474+ export interface HashersList extends InferHashers<typeof hashConfig> {}
7575+}
+20
config/inertia.ts
···11+import { defineConfig } from '@adonisjs/inertia'
22+33+const inertiaConfig = defineConfig({
44+ /**
55+ * Server-side rendering options.
66+ */
77+ ssr: {
88+ /**
99+ * Toggle SSR mode for Inertia pages.
1010+ */
1111+ enabled: false,
1212+1313+ /**
1414+ * Entry file used by the SSR server build.
1515+ */
1616+ entrypoint: 'inertia/ssr.ts',
1717+ },
1818+})
1919+2020+export default inertiaConfig
+50
config/logger.ts
···11+import env from '#start/env'
22+import app from '@adonisjs/core/services/app'
33+import { defineConfig, targets } from '@adonisjs/core/logger'
44+55+const loggerConfig = defineConfig({
66+ /**
77+ * Default logger name used by ctx.logger and app logger calls.
88+ */
99+ default: 'app',
1010+1111+ loggers: {
1212+ app: {
1313+ /**
1414+ * Toggle this logger on/off.
1515+ */
1616+ enabled: true,
1717+1818+ /**
1919+ * Logger name shown in log records.
2020+ */
2121+ name: env.get('APP_NAME'),
2222+2323+ /**
2424+ * Minimum level to output (trace, debug, info, warn, error, fatal).
2525+ */
2626+ level: env.get('LOG_LEVEL'),
2727+2828+ /**
2929+ * Configure where logs are written.
3030+ * Pretty logs in development, stdout in production.
3131+ */
3232+ transport: {
3333+ targets: targets()
3434+ .pushIf(!app.inProduction, targets.pretty())
3535+ .pushIf(app.inProduction, targets.file({ destination: 1 }))
3636+ .toArray(),
3737+ },
3838+ },
3939+ },
4040+})
4141+4242+export default loggerConfig
4343+4444+/**
4545+ * Inferring types for the list of loggers you have configured
4646+ * in your application.
4747+ */
4848+declare module '@adonisjs/core/types' {
4949+ export interface LoggersList extends InferLoggers<typeof loggerConfig> {}
5050+}
+78
config/session.ts
···11+import env from '#start/env'
22+import app from '@adonisjs/core/services/app'
33+import { defineConfig, stores } from '@adonisjs/session'
44+55+const sessionConfig = defineConfig({
66+ /**
77+ * Enable or disable session support globally.
88+ */
99+ enabled: true,
1010+1111+ /**
1212+ * Cookie name storing the session identifier.
1313+ */
1414+ cookieName: 'adonis-session',
1515+1616+ /**
1717+ * When set to true, the session id cookie will be deleted
1818+ * once the user closes the browser.
1919+ */
2020+ clearWithBrowser: false,
2121+2222+ /**
2323+ * Define how long to keep the session data alive without
2424+ * any activity.
2525+ */
2626+ age: '2h',
2727+2828+ /**
2929+ * Configuration for session cookie and the
3030+ * cookie store.
3131+ */
3232+ cookie: {
3333+ /**
3434+ * Restrict the cookie to a URL path. '/' means all routes.
3535+ */
3636+ path: '/',
3737+3838+ /**
3939+ * Prevent JavaScript access to the cookie in the browser.
4040+ */
4141+ httpOnly: true,
4242+4343+ /**
4444+ * Send cookies only over HTTPS in production.
4545+ */
4646+ secure: app.inProduction,
4747+4848+ /**
4949+ * Cross-site policy for cookie sending.
5050+ */
5151+ sameSite: 'lax',
5252+ },
5353+5454+ /**
5555+ * The store to use. Make sure to validate the environment
5656+ * variable in order to infer the store name without any
5757+ * errors.
5858+ */
5959+ store: env.get('SESSION_DRIVER'),
6060+6161+ /**
6262+ * List of configured stores. Refer documentation to see
6363+ * list of available stores and their config.
6464+ */
6565+ stores: {
6666+ /**
6767+ * Store session data inside encrypted cookies.
6868+ */
6969+ cookie: stores.cookie(),
7070+7171+ /**
7272+ * Store session data inside the configured database.
7373+ */
7474+ database: stores.database(),
7575+ },
7676+})
7777+7878+export default sessionConfig
+95
config/shield.ts
···11+import { defineConfig } from '@adonisjs/shield'
22+33+const shieldConfig = defineConfig({
44+ /**
55+ * Configure CSP policies for your app. Refer documentation
66+ * to learn more.
77+ */
88+ csp: {
99+ /**
1010+ * Enable the Content-Security-Policy header.
1111+ */
1212+ enabled: false,
1313+1414+ /**
1515+ * Per-resource CSP directives.
1616+ */
1717+ directives: {},
1818+1919+ /**
2020+ * Report violations without blocking resources.
2121+ */
2222+ reportOnly: false,
2323+ },
2424+2525+ /**
2626+ * Configure CSRF protection options. Refer documentation
2727+ * to learn more.
2828+ */
2929+ csrf: {
3030+ /**
3131+ * Enable CSRF token verification for state-changing requests.
3232+ */
3333+ enabled: true,
3434+3535+ /**
3636+ * Route patterns to exclude from CSRF checks.
3737+ * Useful for external webhooks or API endpoints.
3838+ */
3939+ exceptRoutes: [],
4040+4141+ /**
4242+ * Expose an encrypted XSRF-TOKEN cookie for frontend HTTP clients.
4343+ */
4444+ enableXsrfCookie: true,
4545+4646+ /**
4747+ * HTTP methods protected by CSRF validation.
4848+ */
4949+ methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
5050+ },
5151+5252+ /**
5353+ * Control how your website should be embedded inside
5454+ * iframes.
5555+ */
5656+ xFrame: {
5757+ /**
5858+ * Enable the X-Frame-Options header.
5959+ */
6060+ enabled: true,
6161+6262+ /**
6363+ * Block all framing attempts. Default value is DENY.
6464+ */
6565+ action: 'DENY',
6666+ },
6767+6868+ /**
6969+ * Force browser to always use HTTPS.
7070+ */
7171+ hsts: {
7272+ /**
7373+ * Enable the Strict-Transport-Security header.
7474+ */
7575+ enabled: true,
7676+7777+ /**
7878+ * HSTS policy duration remembered by browsers.
7979+ */
8080+ maxAge: '180 days',
8181+ },
8282+8383+ /**
8484+ * Disable browsers from sniffing content types and rely only
8585+ * on the response content-type header.
8686+ */
8787+ contentTypeSniffing: {
8888+ /**
8989+ * Enable X-Content-Type-Options: nosniff.
9090+ */
9191+ enabled: true,
9292+ },
9393+})
9494+9595+export default shieldConfig
+32
config/static.ts
···11+import { defineConfig } from '@adonisjs/static'
22+33+/**
44+ * Configuration options to tweak the static files middleware.
55+ * The complete set of options are documented on the
66+ * official documentation website.
77+ *
88+ * https://docs.adonisjs.com/guides/basics/static-file-server
99+ */
1010+const staticServerConfig = defineConfig({
1111+ /**
1212+ * Enable or disable static file serving middleware.
1313+ */
1414+ enabled: true,
1515+1616+ /**
1717+ * Generate ETag headers for client/proxy caching.
1818+ */
1919+ etag: true,
2020+2121+ /**
2222+ * Include Last-Modified headers for conditional requests.
2323+ */
2424+ lastModified: true,
2525+2626+ /**
2727+ * Policy for files starting with a dot.
2828+ */
2929+ dotFiles: 'ignore',
3030+})
3131+3232+export default staticServerConfig
+34
config/vite.ts
···11+import { defineConfig } from '@adonisjs/vite'
22+33+const viteBackendConfig = defineConfig({
44+ /**
55+ * The output of vite will be written inside this
66+ * directory. The path should be relative from
77+ * the application root.
88+ */
99+ buildDirectory: 'public/assets',
1010+1111+ /**
1212+ * The path to the manifest file generated by the
1313+ * "vite build" command.
1414+ */
1515+ manifestFile: 'public/assets/.vite/manifest.json',
1616+1717+ /**
1818+ * Feel free to change the value of the "assetsUrl" to
1919+ * point to a CDN in production.
2020+ */
2121+ assetsUrl: '/assets',
2222+2323+ /**
2424+ * HTML attributes added to generated script tags.
2525+ */
2626+ scriptAttributes: {
2727+ /**
2828+ * Execute scripts after HTML parsing is complete.
2929+ */
3030+ defer: true,
3131+ },
3232+})
3333+3434+export default viteBackendConfig
···11+import { HttpContext } from '@adonisjs/core/http'
22+import { BaseSerializer } from '@adonisjs/core/transformers'
33+import { type SimplePaginatorMetaKeys } from '@adonisjs/lucid/types/querybuilder'
44+55+/**
66+ * Custom serializer for API responses that ensures consistent JSON structure
77+ * across all API endpoints. Wraps response data in a 'data' property and handles
88+ * pagination metadata for Lucid ORM query results.
99+ */
1010+class ApiSerializer extends BaseSerializer<{
1111+ Wrap: 'data'
1212+ PaginationMetaData: SimplePaginatorMetaKeys
1313+}> {
1414+ /**
1515+ * Wraps all serialized data under this key in the response object.
1616+ * Example: { data: [...] } instead of returning raw arrays/objects
1717+ */
1818+ wrap: 'data' = 'data'
1919+2020+ /**
2121+ * Validates and defines pagination metadata structure for paginated responses.
2222+ * Ensures that pagination info from Lucid queries is properly formatted.
2323+ *
2424+ * @throws Error if metadata doesn't match Lucid's pagination structure
2525+ */
2626+ definePaginationMetaData(metaData: unknown): SimplePaginatorMetaKeys {
2727+ if (!this.isLucidPaginatorMetaData(metaData)) {
2828+ throw new Error(
2929+ 'Invalid pagination metadata. Expected metadata to contain Lucid pagination keys'
3030+ )
3131+ }
3232+ return metaData
3333+ }
3434+}
3535+3636+/**
3737+ * Single instance of ApiSerializer used across the application
3838+ */
3939+const serializer = new ApiSerializer()
4040+const serialize = serializer.serialize.bind(serializer) as ApiSerializer['serialize'] & {
4141+ withoutWrapping: ApiSerializer['serializeWithoutWrapping']
4242+}
4343+serialize.withoutWrapping = serializer.serializeWithoutWrapping.bind(serializer)
4444+4545+/**
4646+ * Adds the serialize method to all HttpContext instances.
4747+ * Usage in controllers: return ctx.serialize(data)
4848+ * This ensures all API responses follow the same structure with data wrapping.
4949+ */
5050+HttpContext.instanceProperty('serialize', serialize)
5151+5252+/**
5353+ * Module augmentation to add the serialize method to HttpContext.
5454+ * This allows controllers to use ctx.serialize() for consistent API responses.
5555+ */
5656+declare module '@adonisjs/core/http' {
5757+ export interface HttpContext {
5858+ serialize: typeof serialize
5959+ }
6060+}
···11+/*
22+|--------------------------------------------------------------------------
33+| Validator file
44+|--------------------------------------------------------------------------
55+|
66+| The validator file is used for configuring global transforms for VineJS.
77+| The transform below converts all VineJS date outputs from JavaScript
88+| Date objects to Luxon DateTime instances, so that validated dates are
99+| ready to use with Lucid models and other parts of the app that expect
1010+| Luxon DateTime.
1111+|
1212+*/
1313+1414+import { DateTime } from 'luxon'
1515+import { VineDate } from '@vinejs/vine'
1616+1717+declare module '@vinejs/vine/types' {
1818+ interface VineGlobalTransforms {
1919+ date: DateTime
2020+ }
2121+}
2222+2323+VineDate.transform((value) => DateTime.fromJSDate(value))
+37
tests/bootstrap.ts
···11+import { assert } from '@japa/assert'
22+import app from '@adonisjs/core/services/app'
33+import type { Config } from '@japa/runner/types'
44+import { pluginAdonisJS } from '@japa/plugin-adonisjs'
55+import testUtils from '@adonisjs/core/services/test_utils'
66+77+/**
88+ * This file is imported by the "bin/test.ts" entrypoint file
99+ */
1010+1111+/**
1212+ * Configure Japa plugins in the plugins array.
1313+ * Learn more - https://japa.dev/docs/runner-config#plugins-optional
1414+ */
1515+export const plugins: Config['plugins'] = [assert(), pluginAdonisJS(app)]
1616+1717+/**
1818+ * Configure lifecycle function to run before and after all the
1919+ * tests.
2020+ *
2121+ * The setup functions are executed before all the tests
2222+ * The teardown functions are executed after all the tests
2323+ */
2424+export const runnerHooks: Required<Pick<Config, 'setup' | 'teardown'>> = {
2525+ setup: [],
2626+ teardown: [],
2727+}
2828+2929+/**
3030+ * Configure suites by tapping into the test suite instance.
3131+ * Learn more - https://japa.dev/docs/test-suites#lifecycle-hooks
3232+ */
3333+export const configureSuite: Config['configureSuite'] = (suite) => {
3434+ if (['browser', 'functional', 'e2e'].includes(suite.name)) {
3535+ return suite.setup(() => testUtils.httpServer().start())
3636+ }
3737+}