@classytic/arc 2.1.2 → 2.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/bin/arc.js +1 -0
- package/dist/{EventTransport-BD2U0BTc.d.mts → EventTransport-BkUDYZEb.d.mts} +1 -2
- package/dist/HookSystem-BsGV-j2l.mjs +1 -2
- package/dist/{ResourceRegistry-DsN4KJjV.mjs → ResourceRegistry-7Ic20ZMw.mjs} +1 -2
- package/dist/adapters/index.d.mts +4 -4
- package/dist/audit/index.d.mts +5 -6
- package/dist/audit/index.mjs +2 -3
- package/dist/audit/mongodb.d.mts +4 -4
- package/dist/audit/mongodb.mjs +1 -1
- package/dist/{audited-C3T5DTUx.mjs → audited-CGdLiSlE.mjs} +1 -2
- package/dist/auth/index.d.mts +6 -7
- package/dist/auth/index.mjs +10 -16
- package/dist/auth/redis-session.d.mts +2 -3
- package/dist/auth/redis-session.mjs +1 -2
- package/dist/{betterAuthOpenApi-BrHKeSAx.mjs → betterAuthOpenApi-DjWDddNc.mjs} +2 -3
- package/dist/cache/index.d.mts +3 -4
- package/dist/cache/index.mjs +4 -5
- package/dist/{caching-Bl28lYsR.mjs → caching-GSDJcA6-.mjs} +1 -2
- package/dist/{circuitBreaker-DeY4FCjs.mjs → circuitBreaker-DYhWBW_D.mjs} +1 -2
- package/dist/cli/commands/describe.d.mts +1 -2
- package/dist/cli/commands/describe.mjs +1 -2
- package/dist/cli/commands/docs.d.mts +1 -2
- package/dist/cli/commands/docs.mjs +3 -4
- package/dist/cli/commands/generate.d.mts +6 -2
- package/dist/cli/commands/generate.mjs +89 -58
- package/dist/cli/commands/init.d.mts +1 -2
- package/dist/cli/commands/init.mjs +15 -19
- package/dist/cli/commands/introspect.d.mts +1 -2
- package/dist/cli/commands/introspect.mjs +2 -3
- package/dist/cli/index.d.mts +1 -2
- package/dist/cli/index.mjs +1 -2
- package/dist/constants-DdXFXQtN.mjs +1 -2
- package/dist/core/index.d.mts +4 -4
- package/dist/core/index.mjs +1 -1
- package/dist/{createApp-CUgNqegw.mjs → createApp-D2D5XXaV.mjs} +9 -10
- package/dist/{defineResource-k0_BDn8v.mjs → defineResource-DZVbwsFb.mjs} +17 -39
- package/dist/discovery/index.d.mts +1 -2
- package/dist/discovery/index.mjs +1 -2
- package/dist/docs/index.d.mts +5 -6
- package/dist/docs/index.mjs +5 -4
- package/dist/{elevation-B_2dRLVP.d.mts → elevation-DGo5shaX.d.mts} +1 -2
- package/dist/{elevation-BRy3yFWT.mjs → elevation-DSTbVvYj.mjs} +4 -4
- package/dist/{errorHandler-C1okiriz.mjs → errorHandler-C3GY3_ow.mjs} +2 -3
- package/dist/{errorHandler-BbcgBmIH.d.mts → errorHandler-CW3OOeYq.d.mts} +2 -3
- package/dist/{errors-ChKiFz62.d.mts → errors-DAWRdiYP.d.mts} +1 -2
- package/dist/{errors-B9bZok84.mjs → errors-DBANPbGr.mjs} +1 -2
- package/dist/{eventPlugin-DGR_B2on.mjs → eventPlugin-BEOvaDqo.mjs} +2 -3
- package/dist/{eventPlugin-CTrLH3mt.d.mts → eventPlugin-H6wDDjGO.d.mts} +2 -3
- package/dist/events/index.d.mts +4 -5
- package/dist/events/index.mjs +2 -3
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +1 -2
- package/dist/events/transports/redis.d.mts +2 -3
- package/dist/events/transports/redis.mjs +1 -2
- package/dist/{externalPaths-DlINfKbP.d.mts → externalPaths-SyPF2tgK.d.mts} +1 -2
- package/dist/factory/index.d.mts +8 -9
- package/dist/factory/index.mjs +1 -1
- package/dist/{fastifyAdapter-BkrGrlFi.d.mts → fastifyAdapter-sGkvUvf5.d.mts} +4 -5
- package/dist/{fields-DyaDVX4J.d.mts → fields-Bi_AVKSo.d.mts} +2 -3
- package/dist/{fields-iagOozy0.mjs → fields-CTd_CrKr.mjs} +2 -3
- package/dist/hooks/index.d.mts +3 -3
- package/dist/idempotency/index.d.mts +4 -5
- package/dist/idempotency/index.mjs +1 -2
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/mongodb.mjs +1 -2
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +1 -2
- package/dist/index.d.mts +9 -10
- package/dist/index.mjs +7 -8
- package/dist/integrations/event-gateway.d.mts +2 -3
- package/dist/integrations/event-gateway.mjs +2 -3
- package/dist/integrations/jobs.d.mts +1 -2
- package/dist/integrations/jobs.mjs +1 -2
- package/dist/integrations/streamline.d.mts +1 -2
- package/dist/integrations/streamline.mjs +1 -2
- package/dist/integrations/websocket.d.mts +1 -2
- package/dist/integrations/websocket.mjs +1 -2
- package/dist/{interface-B01JvPVc.d.mts → interface-CSNjltAc.d.mts} +1 -2
- package/dist/{interface-Ch8HU9uM.d.mts → interface-Cb2klgid.d.mts} +10 -10
- package/dist/{interface-CZe8IkMf.d.mts → interface-DTbsvIWe.d.mts} +1 -2
- package/dist/{introspectionPlugin-rFdO8ZUa.mjs → introspectionPlugin-B3JkrjwU.mjs} +1 -2
- package/dist/{keys-BqNejWup.mjs → keys-DhqDRxv3.mjs} +1 -2
- package/dist/{logger-Df2O2WsW.mjs → logger-ByrvQWZO.mjs} +1 -2
- package/dist/{memory-cQgelFOj.mjs → memory-B2v7KrCB.mjs} +1 -2
- package/dist/migrations/index.d.mts +1 -2
- package/dist/migrations/index.mjs +1 -2
- package/dist/{mongodb-CGzRbfAK.d.mts → mongodb-ClykrfGo.d.mts} +2 -3
- package/dist/{mongodb-BfJVlUJH.mjs → mongodb-DNKEExbf.mjs} +1 -2
- package/dist/{mongodb-JN-9JA7K.d.mts → mongodb-Dg8O_gvd.d.mts} +2 -3
- package/dist/{openapi-G3Cw7XuM.mjs → openapi-9nB_kiuR.mjs} +5 -4
- package/dist/org/index.d.mts +4 -5
- package/dist/org/index.mjs +1 -2
- package/dist/org/types.d.mts +1 -2
- package/dist/permissions/index.d.mts +5 -6
- package/dist/permissions/index.mjs +7 -7
- package/dist/plugins/index.d.mts +7 -8
- package/dist/plugins/index.mjs +7 -8
- package/dist/plugins/response-cache.d.mts +1 -2
- package/dist/plugins/response-cache.mjs +2 -3
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -2
- package/dist/{pluralize-CEweyOEm.mjs → pluralize-CM-jZg7p.mjs} +1 -2
- package/dist/policies/index.d.mts +4 -5
- package/dist/policies/index.mjs +1 -2
- package/dist/presets/index.d.mts +4 -5
- package/dist/presets/index.mjs +2 -3
- package/dist/presets/multiTenant.d.mts +4 -5
- package/dist/presets/multiTenant.mjs +1 -2
- package/dist/{presets-DzSMwlKj.d.mts → presets-BTeYbw7h.d.mts} +2 -3
- package/dist/{presets-BITljm96.mjs → presets-CeFtfDR8.mjs} +1 -2
- package/dist/prisma-DJbMt3yf.mjs +1 -2
- package/dist/{prisma-Dg9GoVdj.d.mts → prisma-DQBSSHAB.d.mts} +2 -3
- package/dist/{queryCachePlugin-DMBnp2Q0.mjs → queryCachePlugin-B6R0d4av.mjs} +4 -5
- package/dist/{queryCachePlugin-7THaI5mt.d.mts → queryCachePlugin-Q6SYuHZ6.d.mts} +2 -3
- package/dist/{redis-D-JAeLtm.d.mts → redis-UwjEp8Ea.d.mts} +2 -3
- package/dist/{redis-stream-Bdh_vUU8.d.mts → redis-stream-CBg0upHI.d.mts} +2 -3
- package/dist/registry/index.d.mts +4 -5
- package/dist/registry/index.mjs +2 -2
- package/dist/{requestContext-QQD6ROJc.mjs → requestContext-xi6OKBL-.mjs} +1 -2
- package/dist/{schemaConverter-BwrmWroW.mjs → schemaConverter-Dtg0Kt9T.mjs} +1 -2
- package/dist/schemas/index.d.mts +1 -2
- package/dist/schemas/index.mjs +1 -2
- package/dist/scope/index.d.mts +2 -3
- package/dist/scope/index.mjs +2 -3
- package/dist/{sessionManager-jPKLbHE0.d.mts → sessionManager-D_iEHjQl.d.mts} +1 -2
- package/dist/{sse-B3c3_yZp.mjs → sse-DkqQ1uxb.mjs} +2 -3
- package/dist/testing/index.d.mts +8 -9
- package/dist/testing/index.mjs +3 -4
- package/dist/{tracing-Cc7vVQPp.d.mts → tracing-8CEbhF0w.d.mts} +1 -2
- package/dist/{typeGuards-DhMNLuvU.mjs → typeGuards-DwxA1t_L.mjs} +1 -2
- package/dist/types/index.d.mts +7 -8
- package/dist/types/index.mjs +1 -2
- package/dist/{types-CIgB7UUl.d.mts → types-B0dhNrnd.d.mts} +9 -10
- package/dist/types-Beqn1Un7.mjs +1 -2
- package/dist/types-DelU6kln.mjs +25 -0
- package/dist/{types-aYB4V7uN.d.mts → types-RLkFVgaw.d.mts} +18 -4
- package/dist/utils/index.d.mts +5 -6
- package/dist/utils/index.mjs +4 -4
- package/package.json +1 -1
- package/dist/EventTransport-BD2U0BTc.d.mts.map +0 -1
- package/dist/HookSystem-BsGV-j2l.mjs.map +0 -1
- package/dist/ResourceRegistry-DsN4KJjV.mjs.map +0 -1
- package/dist/audit/index.d.mts.map +0 -1
- package/dist/audit/index.mjs.map +0 -1
- package/dist/audited-C3T5DTUx.mjs.map +0 -1
- package/dist/auth/index.d.mts.map +0 -1
- package/dist/auth/index.mjs.map +0 -1
- package/dist/auth/redis-session.d.mts.map +0 -1
- package/dist/auth/redis-session.mjs.map +0 -1
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +0 -1
- package/dist/cache/index.d.mts.map +0 -1
- package/dist/cache/index.mjs.map +0 -1
- package/dist/caching-Bl28lYsR.mjs.map +0 -1
- package/dist/circuitBreaker-DeY4FCjs.mjs.map +0 -1
- package/dist/cli/commands/describe.d.mts.map +0 -1
- package/dist/cli/commands/describe.mjs.map +0 -1
- package/dist/cli/commands/docs.d.mts.map +0 -1
- package/dist/cli/commands/docs.mjs.map +0 -1
- package/dist/cli/commands/generate.d.mts.map +0 -1
- package/dist/cli/commands/generate.mjs.map +0 -1
- package/dist/cli/commands/init.d.mts.map +0 -1
- package/dist/cli/commands/init.mjs.map +0 -1
- package/dist/cli/commands/introspect.d.mts.map +0 -1
- package/dist/cli/commands/introspect.mjs.map +0 -1
- package/dist/cli/index.d.mts.map +0 -1
- package/dist/cli/index.mjs.map +0 -1
- package/dist/constants-DdXFXQtN.mjs.map +0 -1
- package/dist/createApp-CUgNqegw.mjs.map +0 -1
- package/dist/defineResource-k0_BDn8v.mjs.map +0 -1
- package/dist/discovery/index.d.mts.map +0 -1
- package/dist/discovery/index.mjs.map +0 -1
- package/dist/docs/index.d.mts.map +0 -1
- package/dist/docs/index.mjs.map +0 -1
- package/dist/elevation-BRy3yFWT.mjs.map +0 -1
- package/dist/elevation-B_2dRLVP.d.mts.map +0 -1
- package/dist/errorHandler-BbcgBmIH.d.mts.map +0 -1
- package/dist/errorHandler-C1okiriz.mjs.map +0 -1
- package/dist/errors-B9bZok84.mjs.map +0 -1
- package/dist/errors-ChKiFz62.d.mts.map +0 -1
- package/dist/eventPlugin-CTrLH3mt.d.mts.map +0 -1
- package/dist/eventPlugin-DGR_B2on.mjs.map +0 -1
- package/dist/events/index.d.mts.map +0 -1
- package/dist/events/index.mjs.map +0 -1
- package/dist/events/transports/redis-stream-entry.mjs.map +0 -1
- package/dist/events/transports/redis.d.mts.map +0 -1
- package/dist/events/transports/redis.mjs.map +0 -1
- package/dist/externalPaths-DlINfKbP.d.mts.map +0 -1
- package/dist/factory/index.d.mts.map +0 -1
- package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +0 -1
- package/dist/fields-DyaDVX4J.d.mts.map +0 -1
- package/dist/fields-iagOozy0.mjs.map +0 -1
- package/dist/idempotency/index.d.mts.map +0 -1
- package/dist/idempotency/index.mjs.map +0 -1
- package/dist/idempotency/mongodb.mjs.map +0 -1
- package/dist/idempotency/redis.mjs.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/integrations/event-gateway.d.mts.map +0 -1
- package/dist/integrations/event-gateway.mjs.map +0 -1
- package/dist/integrations/jobs.d.mts.map +0 -1
- package/dist/integrations/jobs.mjs.map +0 -1
- package/dist/integrations/streamline.d.mts.map +0 -1
- package/dist/integrations/streamline.mjs.map +0 -1
- package/dist/integrations/websocket.d.mts.map +0 -1
- package/dist/integrations/websocket.mjs.map +0 -1
- package/dist/interface-B01JvPVc.d.mts.map +0 -1
- package/dist/interface-CZe8IkMf.d.mts.map +0 -1
- package/dist/interface-Ch8HU9uM.d.mts.map +0 -1
- package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +0 -1
- package/dist/keys-BqNejWup.mjs.map +0 -1
- package/dist/logger-Df2O2WsW.mjs.map +0 -1
- package/dist/memory-cQgelFOj.mjs.map +0 -1
- package/dist/migrations/index.d.mts.map +0 -1
- package/dist/migrations/index.mjs.map +0 -1
- package/dist/mongodb-BfJVlUJH.mjs.map +0 -1
- package/dist/mongodb-CGzRbfAK.d.mts.map +0 -1
- package/dist/mongodb-JN-9JA7K.d.mts.map +0 -1
- package/dist/openapi-G3Cw7XuM.mjs.map +0 -1
- package/dist/org/index.d.mts.map +0 -1
- package/dist/org/index.mjs.map +0 -1
- package/dist/org/types.d.mts.map +0 -1
- package/dist/permissions/index.d.mts.map +0 -1
- package/dist/permissions/index.mjs.map +0 -1
- package/dist/plugins/index.d.mts.map +0 -1
- package/dist/plugins/index.mjs.map +0 -1
- package/dist/plugins/response-cache.d.mts.map +0 -1
- package/dist/plugins/response-cache.mjs.map +0 -1
- package/dist/plugins/tracing-entry.mjs.map +0 -1
- package/dist/pluralize-CEweyOEm.mjs.map +0 -1
- package/dist/policies/index.d.mts.map +0 -1
- package/dist/policies/index.mjs.map +0 -1
- package/dist/presets/index.d.mts.map +0 -1
- package/dist/presets/index.mjs.map +0 -1
- package/dist/presets/multiTenant.d.mts.map +0 -1
- package/dist/presets/multiTenant.mjs.map +0 -1
- package/dist/presets-BITljm96.mjs.map +0 -1
- package/dist/presets-DzSMwlKj.d.mts.map +0 -1
- package/dist/prisma-DJbMt3yf.mjs.map +0 -1
- package/dist/prisma-Dg9GoVdj.d.mts.map +0 -1
- package/dist/queryCachePlugin-7THaI5mt.d.mts.map +0 -1
- package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +0 -1
- package/dist/redis-D-JAeLtm.d.mts.map +0 -1
- package/dist/redis-stream-Bdh_vUU8.d.mts.map +0 -1
- package/dist/registry/index.d.mts.map +0 -1
- package/dist/requestContext-QQD6ROJc.mjs.map +0 -1
- package/dist/schemaConverter-BwrmWroW.mjs.map +0 -1
- package/dist/schemas/index.d.mts.map +0 -1
- package/dist/schemas/index.mjs.map +0 -1
- package/dist/scope/index.d.mts.map +0 -1
- package/dist/scope/index.mjs.map +0 -1
- package/dist/sessionManager-jPKLbHE0.d.mts.map +0 -1
- package/dist/sse-B3c3_yZp.mjs.map +0 -1
- package/dist/testing/index.d.mts.map +0 -1
- package/dist/testing/index.mjs.map +0 -1
- package/dist/tracing-Cc7vVQPp.d.mts.map +0 -1
- package/dist/typeGuards-DhMNLuvU.mjs.map +0 -1
- package/dist/types/index.d.mts.map +0 -1
- package/dist/types/index.mjs.map +0 -1
- package/dist/types-Beqn1Un7.mjs.map +0 -1
- package/dist/types-CIgB7UUl.d.mts.map +0 -1
- package/dist/types-aYB4V7uN.d.mts.map +0 -1
- package/dist/utils/index.d.mts.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"circuitBreaker-DeY4FCjs.mjs","names":["MAX_LIMIT"],"sources":["../src/utils/queryParser.ts","../src/utils/responseSchemas.ts","../src/utils/stateMachine.ts","../src/utils/circuitBreaker.ts"],"sourcesContent":["/**\r\n * Arc Query Parser - Default URL-to-Query Parser\r\n *\r\n * Framework-agnostic query parser that converts URL parameters to query options.\r\n * This is Arc's built-in parser; users can swap in MongoKit's QueryParser,\r\n * pgkit's parser, or any custom parser implementing QueryParserInterface.\r\n *\r\n * @example\r\n * // Use Arc default parser (auto-applied if no queryParser option)\r\n * defineResource({ name: 'product', adapter: ... });\r\n *\r\n * // Use MongoKit's QueryParser (recommended for MongoDB - has $lookup, aggregations, etc.)\r\n * import { QueryParser } from '@classytic/mongokit';\r\n * defineResource({\r\n * name: 'product',\r\n * adapter: ...,\r\n * queryParser: new QueryParser(),\r\n * });\r\n *\r\n * // Use custom parser for SQL databases\r\n * defineResource({\r\n * name: 'user',\r\n * adapter: ...,\r\n * queryParser: new PgQueryParser(),\r\n * });\r\n */\r\n\r\nimport type { ParsedQuery, PopulateOption, QueryParserInterface } from '../types/index.js';\r\nimport { MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MAX_FILTER_DEPTH, DEFAULT_MAX_LIMIT as MAX_LIMIT, DEFAULT_LIMIT, RESERVED_QUERY_PARAMS } from '../constants.js';\r\n\r\n// ============================================================================\r\n// Dangerous Patterns (ReDoS protection)\r\n// ============================================================================\r\n\r\n/**\r\n * Regex patterns that can cause catastrophic backtracking (ReDoS attacks)\r\n * Detects:\r\n * - Quantifiers: {n,m}\r\n * - Possessive quantifiers: *+, ++, ?+\r\n * - Nested quantifiers: (a+)+, (a*)*\r\n * - Backreferences: \\1, \\2, etc.\r\n */\r\nconst DANGEROUS_REGEX_PATTERNS = /(\\{[0-9,]+\\}|\\*\\+|\\+\\+|\\?\\+|(\\(.+\\))\\+|\\(\\?\\:|\\\\[0-9]|(\\[.+\\]).+(\\[.+\\]))/;\r\n\r\n// ============================================================================\r\n// Arc Query Parser\r\n// ============================================================================\r\n\r\nexport interface ArcQueryParserOptions {\r\n /** Maximum allowed limit value (default: 1000) */\r\n maxLimit?: number;\r\n /** Default limit for pagination (default: 20) */\r\n defaultLimit?: number;\r\n /** Maximum regex pattern length (default: 500) */\r\n maxRegexLength?: number;\r\n /** Maximum search query length (default: 200) */\r\n maxSearchLength?: number;\r\n /** Maximum filter nesting depth (default: 10) */\r\n maxFilterDepth?: number;\r\n}\r\n\r\n/**\r\n * Arc's default query parser\r\n *\r\n * Converts URL query parameters to a structured query format:\r\n * - Pagination: ?page=1&limit=20\r\n * - Sorting: ?sort=-createdAt,name (- prefix = descending)\r\n * - Filtering: ?status=active&price[gte]=100&price[lte]=500\r\n * - Search: ?search=keyword\r\n * - Populate: ?populate=author,category\r\n * - Field selection: ?select=name,price,status\r\n * - Keyset pagination: ?after=cursor_value\r\n *\r\n * For advanced MongoDB features ($lookup, aggregations), use MongoKit's QueryParser.\r\n */\r\nexport class ArcQueryParser implements QueryParserInterface {\r\n private readonly maxLimit: number;\r\n private readonly defaultLimit: number;\r\n private readonly maxRegexLength: number;\r\n private readonly maxSearchLength: number;\r\n private readonly maxFilterDepth: number;\r\n\r\n /** Supported filter operators */\r\n private readonly operators: Record<string, string> = {\r\n eq: '$eq',\r\n ne: '$ne',\r\n gt: '$gt',\r\n gte: '$gte',\r\n lt: '$lt',\r\n lte: '$lte',\r\n in: '$in',\r\n nin: '$nin',\r\n like: '$regex',\r\n contains: '$regex',\r\n regex: '$regex',\r\n exists: '$exists',\r\n };\r\n\r\n constructor(options: ArcQueryParserOptions = {}) {\r\n this.maxLimit = options.maxLimit ?? MAX_LIMIT;\r\n this.defaultLimit = options.defaultLimit ?? DEFAULT_LIMIT;\r\n this.maxRegexLength = options.maxRegexLength ?? MAX_REGEX_LENGTH;\r\n this.maxSearchLength = options.maxSearchLength ?? MAX_SEARCH_LENGTH;\r\n this.maxFilterDepth = options.maxFilterDepth ?? MAX_FILTER_DEPTH;\r\n }\r\n\r\n /**\r\n * Parse URL query parameters into structured query options\r\n */\r\n parse(query: Record<string, unknown> | null | undefined): ParsedQuery {\r\n const q = query ?? {};\r\n\r\n // Extract pagination params\r\n const page = this.parseNumber(q.page, 1);\r\n const limit = Math.min(this.parseNumber(q.limit, this.defaultLimit), this.maxLimit);\r\n const after = this.parseString(q.after ?? q.cursor);\r\n\r\n // Extract sort\r\n const sort = this.parseSort(q.sort);\r\n\r\n // Extract populate — handles both simple string and bracket notation object\r\n const { populate, populateOptions } = this.parsePopulate(q.populate);\r\n\r\n // Extract search\r\n const search = this.parseSearch(q.search);\r\n\r\n // Extract select\r\n const select = this.parseSelect(q.select);\r\n\r\n // Extract filters (everything else)\r\n const filters = this.parseFilters(q);\r\n\r\n return {\r\n filters,\r\n limit,\r\n sort,\r\n populate,\r\n populateOptions,\r\n search,\r\n page: after ? undefined : page,\r\n after,\r\n select,\r\n };\r\n }\r\n\r\n // ============================================================================\r\n // Parse Helpers\r\n // ============================================================================\r\n\r\n private parseNumber(value: unknown, defaultValue: number): number {\r\n if (value === undefined || value === null) return defaultValue;\r\n const num = parseInt(String(value), 10);\r\n return Number.isNaN(num) ? defaultValue : Math.max(1, num);\r\n }\r\n\r\n private parseString(value: unknown): string | undefined {\r\n if (value === undefined || value === null) return undefined;\r\n const str = String(value).trim();\r\n return str.length > 0 ? str : undefined;\r\n }\r\n\r\n /**\r\n * Parse populate parameter — handles both simple string and bracket notation.\r\n *\r\n * Simple: ?populate=author,category → { populate: 'author,category' }\r\n * Bracket: ?populate[author][select]=name,email → { populateOptions: [{ path: 'author', select: 'name email' }] }\r\n */\r\n private parsePopulate(value: unknown): { populate?: string; populateOptions?: PopulateOption[] } {\r\n if (value === undefined || value === null) return {};\r\n\r\n // Simple string: ?populate=author,category\r\n if (typeof value === 'string') {\r\n const trimmed = value.trim();\r\n return trimmed.length > 0 ? { populate: trimmed } : {};\r\n }\r\n\r\n // Bracket notation object: ?populate[author][select]=name,email\r\n // qs parses this as { author: { select: 'name,email' } }\r\n if (typeof value === 'object' && !Array.isArray(value)) {\r\n const obj = value as Record<string, unknown>;\r\n const keys = Object.keys(obj);\r\n if (keys.length === 0) return {};\r\n\r\n const options: PopulateOption[] = [];\r\n for (const path of keys) {\r\n // Validate path name (prevent injection)\r\n if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(path)) continue;\r\n\r\n const config = obj[path];\r\n if (typeof config === 'object' && config !== null && !Array.isArray(config)) {\r\n const cfg = config as Record<string, unknown>;\r\n const option: PopulateOption = { path };\r\n\r\n // Parse select: convert comma-separated to space-separated (Mongoose format)\r\n if (typeof cfg.select === 'string') {\r\n option.select = cfg.select.split(',').map(s => s.trim()).filter(Boolean).join(' ');\r\n }\r\n\r\n // Parse match (filter conditions)\r\n if (typeof cfg.match === 'object' && cfg.match !== null) {\r\n option.match = cfg.match as Record<string, unknown>;\r\n }\r\n\r\n options.push(option);\r\n } else {\r\n // Simple value like populate[author]=true → treat as simple populate\r\n options.push({ path });\r\n }\r\n }\r\n\r\n return options.length > 0 ? { populateOptions: options } : {};\r\n }\r\n\r\n return {};\r\n }\r\n\r\n private parseSort(value: unknown): Record<string, 1 | -1> | undefined {\r\n if (!value) return undefined;\r\n\r\n const sortStr = String(value);\r\n const result: Record<string, 1 | -1> = {};\r\n\r\n for (const field of sortStr.split(',')) {\r\n const trimmed = field.trim();\r\n if (!trimmed) continue;\r\n\r\n // Validate field name (prevent injection)\r\n if (!/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(trimmed)) continue;\r\n\r\n if (trimmed.startsWith('-')) {\r\n result[trimmed.slice(1)] = -1;\r\n } else {\r\n result[trimmed] = 1;\r\n }\r\n }\r\n\r\n return Object.keys(result).length > 0 ? result : undefined;\r\n }\r\n\r\n private parseSearch(value: unknown): string | undefined {\r\n if (!value) return undefined;\r\n\r\n const search = String(value).trim();\r\n if (search.length === 0) return undefined;\r\n if (search.length > this.maxSearchLength) {\r\n return search.slice(0, this.maxSearchLength);\r\n }\r\n\r\n return search;\r\n }\r\n\r\n private parseSelect(value: unknown): Record<string, 0 | 1> | undefined {\r\n if (!value) return undefined;\r\n\r\n const selectStr = String(value);\r\n const result: Record<string, 0 | 1> = {};\r\n\r\n for (const field of selectStr.split(',')) {\r\n const trimmed = field.trim();\r\n if (!trimmed) continue;\r\n\r\n // Validate field name (prevent injection)\r\n if (!/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(trimmed)) continue;\r\n\r\n if (trimmed.startsWith('-')) {\r\n result[trimmed.slice(1)] = 0;\r\n } else {\r\n result[trimmed] = 1;\r\n }\r\n }\r\n\r\n return Object.keys(result).length > 0 ? result : undefined;\r\n }\r\n\r\n /**\r\n * Check if a value exceeds the maximum nesting depth.\r\n * Prevents filter bombs where deeply nested objects consume excessive memory/CPU.\r\n */\r\n private exceedsDepth(obj: unknown, currentDepth: number = 0): boolean {\r\n if (currentDepth > this.maxFilterDepth) return true;\r\n if (obj === null || obj === undefined) return false;\r\n if (Array.isArray(obj)) {\r\n return obj.some(v => this.exceedsDepth(v, currentDepth));\r\n }\r\n if (typeof obj !== 'object') return false;\r\n return Object.values(obj as Record<string, unknown>).some(\r\n v => this.exceedsDepth(v, currentDepth + 1)\r\n );\r\n }\r\n\r\n private parseFilters(query: Record<string, unknown>): Record<string, unknown> {\r\n const filters: Record<string, unknown> = {};\r\n\r\n for (const [key, value] of Object.entries(query)) {\r\n if (RESERVED_QUERY_PARAMS.has(key)) continue;\r\n if (value === undefined || value === null) continue;\r\n\r\n // Validate field name (prevent injection)\r\n if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(key)) continue;\r\n\r\n // Enforce max filter depth (prevents filter bombs)\r\n if (this.exceedsDepth(value)) continue;\r\n\r\n // Handle nested object format from qs parser: { price: { gte: '40', lte: '100' } }\r\n // This happens when URL is ?price[gte]=40&price[lte]=100 and qs parses it\r\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\r\n const operatorObj = value as Record<string, unknown>;\r\n const operatorKeys = Object.keys(operatorObj);\r\n\r\n // Check if all keys are known operators\r\n const allOperators = operatorKeys.every(op => this.operators[op]);\r\n\r\n if (allOperators && operatorKeys.length > 0) {\r\n // Convert operator object: { gte: '40', lte: '100' } → { $gte: 40, $lte: 100 }\r\n const mongoFilters: Record<string, unknown> = {};\r\n for (const [op, opValue] of Object.entries(operatorObj)) {\r\n const mongoOp = this.operators[op];\r\n if (mongoOp) {\r\n mongoFilters[mongoOp] = this.parseFilterValue(opValue, op);\r\n }\r\n }\r\n filters[key] = mongoFilters;\r\n continue;\r\n }\r\n }\r\n\r\n // Handle key-based bracket notation: price[gte]=100 (when not parsed by qs)\r\n const match = key.match(/^([a-zA-Z_][a-zA-Z0-9_.]*)(?:\\[([a-z]+)\\])?$/);\r\n if (!match) continue;\r\n\r\n const [, fieldName, operator] = match;\r\n if (!fieldName) continue;\r\n\r\n if (operator && this.operators[operator]) {\r\n // Operator filter: status[ne]=deleted → { status: { $ne: 'deleted' } }\r\n const mongoOp = this.operators[operator];\r\n const parsedValue = this.parseFilterValue(value, operator);\r\n\r\n if (!filters[fieldName]) {\r\n filters[fieldName] = {};\r\n }\r\n (filters[fieldName] as Record<string, unknown>)[mongoOp] = parsedValue;\r\n } else if (!operator) {\r\n // Direct equality: status=active → { status: 'active' }\r\n filters[fieldName] = this.parseFilterValue(value);\r\n }\r\n }\r\n\r\n return filters;\r\n }\r\n\r\n private parseFilterValue(value: unknown, operator?: string): unknown {\r\n // Handle arrays (for $in, $nin operators)\r\n if (operator === 'in' || operator === 'nin') {\r\n if (Array.isArray(value)) {\r\n return value.map(v => this.coerceValue(v));\r\n }\r\n if (typeof value === 'string' && value.includes(',')) {\r\n return value.split(',').map(v => this.coerceValue(v.trim()));\r\n }\r\n return [this.coerceValue(value)];\r\n }\r\n\r\n // Handle regex operators\r\n if (operator === 'like' || operator === 'contains' || operator === 'regex') {\r\n return this.sanitizeRegex(String(value));\r\n }\r\n\r\n // Handle exists operator\r\n if (operator === 'exists') {\r\n const str = String(value).toLowerCase();\r\n return str === 'true' || str === '1';\r\n }\r\n\r\n return this.coerceValue(value);\r\n }\r\n\r\n private coerceValue(value: unknown): unknown {\r\n if (value === 'true') return true;\r\n if (value === 'false') return false;\r\n if (value === 'null') return null;\r\n\r\n // Try to parse as number\r\n if (typeof value === 'string') {\r\n const num = Number(value);\r\n if (!Number.isNaN(num) && value.trim() !== '') {\r\n return num;\r\n }\r\n }\r\n\r\n return value;\r\n }\r\n\r\n // ============================================================================\r\n // OpenAPI Schema Generation\r\n // ============================================================================\r\n\r\n /**\r\n * Generate OpenAPI-compatible JSON Schema for query parameters.\r\n * Arc's defineResource() auto-detects this method and uses it\r\n * to document list endpoint query parameters in OpenAPI/Swagger.\r\n */\r\n getQuerySchema(): {\r\n type: 'object';\r\n properties: Record<string, unknown>;\r\n required?: string[];\r\n } {\r\n const operatorEntries = Object.entries(this.operators);\r\n const operatorLines = operatorEntries.map(([op, mongoOp]) => {\r\n const desc: Record<string, string> = {\r\n eq: 'Equal (default when no operator specified)',\r\n ne: 'Not equal',\r\n gt: 'Greater than',\r\n gte: 'Greater than or equal',\r\n lt: 'Less than',\r\n lte: 'Less than or equal',\r\n in: 'In list (comma-separated values)',\r\n nin: 'Not in list',\r\n like: 'Pattern match (case-insensitive)',\r\n contains: 'Contains substring (case-insensitive)',\r\n regex: 'Regex pattern',\r\n exists: 'Field exists (true/false)',\r\n };\r\n return ` ${op} → ${mongoOp}: ${desc[op] || op}`;\r\n });\r\n\r\n return {\r\n type: 'object',\r\n properties: {\r\n page: {\r\n type: 'integer',\r\n description: 'Page number for offset pagination',\r\n default: 1,\r\n minimum: 1,\r\n },\r\n limit: {\r\n type: 'integer',\r\n description: 'Number of items per page',\r\n default: this.defaultLimit,\r\n minimum: 1,\r\n maximum: this.maxLimit,\r\n },\r\n sort: {\r\n type: 'string',\r\n description: 'Sort fields (comma-separated). Prefix with - for descending. Example: -createdAt,name',\r\n },\r\n search: {\r\n type: 'string',\r\n description: 'Full-text search query',\r\n maxLength: this.maxSearchLength,\r\n },\r\n select: {\r\n type: 'string',\r\n description: 'Fields to include/exclude (comma-separated). Prefix with - to exclude. Example: name,email,-password',\r\n },\r\n populate: {\r\n type: 'string',\r\n description: 'Fields to populate/join (comma-separated). Example: author,category',\r\n },\r\n after: {\r\n type: 'string',\r\n description: 'Cursor value for keyset pagination',\r\n },\r\n _filterOperators: {\r\n type: 'string',\r\n description: ['Available filter operators (use as field[operator]=value):', ...operatorLines].join('\\n'),\r\n 'x-internal': true,\r\n },\r\n },\r\n };\r\n }\r\n\r\n // ============================================================================\r\n // Regex Sanitization\r\n // ============================================================================\r\n\r\n private sanitizeRegex(pattern: string): string {\r\n // Limit length\r\n let sanitized = pattern.slice(0, this.maxRegexLength);\r\n\r\n // Check for dangerous patterns\r\n if (DANGEROUS_REGEX_PATTERNS.test(sanitized)) {\r\n // Escape the entire pattern to treat as literal string\r\n sanitized = sanitized.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\r\n }\r\n\r\n return sanitized;\r\n }\r\n}\r\n\r\n/**\r\n * Create a new ArcQueryParser instance\r\n */\r\nexport function createQueryParser(options?: ArcQueryParserOptions): ArcQueryParser {\r\n return new ArcQueryParser(options);\r\n}\r\n\r\nexport default ArcQueryParser;\r\n","/**\n * Response Schemas\n *\n * Standard JSON Schema definitions for API responses.\n */\n\nimport type { AnyRecord } from '../types/index.js';\n\n// ============================================================================\n// Schema Types\n// ============================================================================\n\nexport interface JsonSchema {\n type: string;\n properties?: Record<string, JsonSchema | AnyRecord>;\n required?: string[];\n items?: JsonSchema | AnyRecord;\n additionalProperties?: boolean | JsonSchema;\n description?: string;\n example?: unknown;\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Response Wrapper Schemas\n// ============================================================================\n\n/**\n * Base success response schema\n */\nexport const successResponseSchema: JsonSchema = {\n type: 'object',\n properties: {\n success: { type: 'boolean', example: true },\n },\n required: ['success'],\n};\n\n/**\n * Error response schema\n */\nexport const errorResponseSchema: JsonSchema = {\n type: 'object',\n properties: {\n success: { type: 'boolean', example: false },\n error: { type: 'string', description: 'Error message' },\n code: { type: 'string', description: 'Error code' },\n message: { type: 'string', description: 'Detailed message' },\n },\n required: ['success', 'error'],\n};\n\n/**\n * Pagination schema - matches MongoKit/Arc runtime format\n *\n * Runtime format (flat fields):\n * { page, limit, total, pages, hasNext, hasPrev }\n */\nexport const paginationSchema: JsonSchema = {\n type: 'object',\n properties: {\n page: { type: 'integer', example: 1 },\n limit: { type: 'integer', example: 20 },\n total: { type: 'integer', example: 100 },\n pages: { type: 'integer', example: 5 },\n hasNext: { type: 'boolean', example: true },\n hasPrev: { type: 'boolean', example: false },\n },\n required: ['page', 'limit', 'total', 'pages', 'hasNext', 'hasPrev'],\n};\n\n// ============================================================================\n// Schema Builders\n// ============================================================================\n\n/**\n * Wrap a data schema in a success response\n */\nexport function wrapResponse(dataSchema: JsonSchema): JsonSchema {\n return {\n type: 'object',\n properties: {\n success: { type: 'boolean', example: true },\n data: dataSchema,\n },\n required: ['success', 'data'],\n // Allow extra fields (fieldPermissions, meta spreads, etc.)\n additionalProperties: true,\n };\n}\n\n/**\n * Create a list response schema with pagination - matches MongoKit/Arc runtime format\n *\n * Runtime format:\n * { success, docs: [...], page, limit, total, pages, hasNext, hasPrev }\n *\n * Note: Uses 'docs' array (not 'data') with flat pagination fields\n */\nexport function listResponse(itemSchema: JsonSchema): JsonSchema {\n return {\n type: 'object',\n properties: {\n success: { type: 'boolean', example: true },\n docs: {\n type: 'array',\n items: itemSchema,\n },\n // Flat pagination fields (not nested)\n page: { type: 'integer', example: 1 },\n limit: { type: 'integer', example: 20 },\n total: { type: 'integer', example: 100 },\n pages: { type: 'integer', example: 5 },\n hasNext: { type: 'boolean', example: false },\n hasPrev: { type: 'boolean', example: false },\n },\n required: ['success', 'docs'],\n // Allow extra fields (fieldPermissions, meta spreads, etc.)\n additionalProperties: true,\n };\n}\n\n/**\n * Alias for listResponse - matches local responseSchemas.js naming\n */\nexport const paginateWrapper = listResponse;\n\n/**\n * Create a single item response schema\n *\n * Runtime format: { success, data: {...} }\n */\nexport function itemResponse(itemSchema: JsonSchema): JsonSchema {\n return wrapResponse(itemSchema);\n}\n\n/**\n * Alias for itemResponse - matches local responseSchemas.js naming\n */\nexport const itemWrapper = itemResponse;\n\n/**\n * Create a create/update response schema\n */\nexport function mutationResponse(itemSchema: JsonSchema): JsonSchema {\n return {\n type: 'object',\n properties: {\n success: { type: 'boolean', example: true },\n data: itemSchema,\n message: { type: 'string', example: 'Created successfully' },\n },\n required: ['success', 'data'],\n // Allow extra fields (fieldPermissions, meta spreads, etc.)\n additionalProperties: true,\n };\n}\n\n/**\n * Create a delete response schema\n *\n * Runtime format: { success, message }\n */\nexport function deleteResponse(): JsonSchema {\n return {\n type: 'object',\n properties: {\n success: { type: 'boolean', example: true },\n message: { type: 'string', example: 'Deleted successfully' },\n },\n required: ['success'],\n // Allow extra fields (fieldPermissions, meta spreads, etc.)\n additionalProperties: true,\n };\n}\n\n/**\n * Alias for deleteResponse - matches local responseSchemas.js naming\n */\nexport const messageWrapper = deleteResponse;\n\n// ============================================================================\n// HTTP Status Response Schemas\n// ============================================================================\n\nexport const responses = {\n 200: (schema: JsonSchema) => ({\n description: 'Successful response',\n content: {\n 'application/json': { schema },\n },\n }),\n\n 201: (schema: JsonSchema) => ({\n description: 'Created successfully',\n content: {\n 'application/json': { schema: mutationResponse(schema) },\n },\n }),\n\n 400: {\n description: 'Bad Request',\n content: {\n 'application/json': {\n schema: {\n ...errorResponseSchema,\n properties: {\n ...errorResponseSchema.properties,\n code: { type: 'string', example: 'VALIDATION_ERROR' },\n details: {\n type: 'object',\n properties: {\n errors: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n field: { type: 'string' },\n message: { type: 'string' },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n },\n\n 401: {\n description: 'Unauthorized',\n content: {\n 'application/json': {\n schema: {\n ...errorResponseSchema,\n properties: {\n ...errorResponseSchema.properties,\n code: { type: 'string', example: 'UNAUTHORIZED' },\n },\n },\n },\n },\n },\n\n 403: {\n description: 'Forbidden',\n content: {\n 'application/json': {\n schema: {\n ...errorResponseSchema,\n properties: {\n ...errorResponseSchema.properties,\n code: { type: 'string', example: 'FORBIDDEN' },\n },\n },\n },\n },\n },\n\n 404: {\n description: 'Not Found',\n content: {\n 'application/json': {\n schema: {\n ...errorResponseSchema,\n properties: {\n ...errorResponseSchema.properties,\n code: { type: 'string', example: 'NOT_FOUND' },\n },\n },\n },\n },\n },\n\n 409: {\n description: 'Conflict',\n content: {\n 'application/json': {\n schema: {\n ...errorResponseSchema,\n properties: {\n ...errorResponseSchema.properties,\n code: { type: 'string', example: 'CONFLICT' },\n },\n },\n },\n },\n },\n\n 500: {\n description: 'Internal Server Error',\n content: {\n 'application/json': {\n schema: {\n ...errorResponseSchema,\n properties: {\n ...errorResponseSchema.properties,\n code: { type: 'string', example: 'INTERNAL_ERROR' },\n },\n },\n },\n },\n },\n};\n\n// ============================================================================\n// Query Parameter Schemas\n// ============================================================================\n\nexport const queryParams = {\n pagination: {\n page: {\n type: 'integer',\n minimum: 1,\n default: 1,\n description: 'Page number',\n },\n limit: {\n type: 'integer',\n minimum: 1,\n maximum: 100,\n default: 20,\n description: 'Items per page',\n },\n },\n\n sorting: {\n sort: {\n type: 'string',\n description: 'Sort field (prefix with - for descending)',\n example: '-createdAt',\n },\n },\n\n filtering: {\n select: {\n description: 'Fields to include (space-separated or object)',\n example: 'name email createdAt',\n },\n populate: {\n description: 'Relations to populate (comma-separated string or bracket-notation object)',\n example: 'author,category',\n },\n },\n};\n\n/**\n * Get standard list query parameters schema\n */\nexport function getListQueryParams(): AnyRecord {\n return {\n type: 'object',\n properties: {\n ...queryParams.pagination,\n ...queryParams.sorting,\n ...queryParams.filtering,\n },\n // Allow additional/complex query params (e.g., bracket-notation populate, filters)\n // Without this, qs-parsed nested objects like ?populate[author][select]=name would be rejected\n additionalProperties: true,\n };\n}\n\n// ============================================================================\n// Default CRUD Schemas\n// ============================================================================\n\n/**\n * Generic item schema that allows any properties.\n * Used as default when no user schema is provided.\n * Enables fast-json-stringify while still passing through all fields.\n */\nconst genericItemSchema: JsonSchema = {\n type: 'object',\n additionalProperties: true,\n};\n\n/**\n * Recursively strip `example` keys from a schema object.\n * The `example` keyword is OpenAPI metadata — not standard JSON Schema —\n * and triggers Ajv strict mode errors when used on routes without the\n * `keywords: ['example']` AJV config (e.g., raw Fastify without createApp).\n */\nfunction stripExamples<T>(schema: T): T {\n if (schema === null || typeof schema !== 'object') return schema;\n if (Array.isArray(schema)) return schema.map(stripExamples) as T;\n\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(schema as Record<string, unknown>)) {\n if (key === 'example') continue;\n result[key] = stripExamples(value);\n }\n return result as T;\n}\n\n/**\n * Get default response schemas for all CRUD operations.\n *\n * When routes have response schemas, Fastify compiles them with\n * fast-json-stringify for 2-3x faster serialization and prevents\n * accidental field disclosure.\n *\n * These defaults use `additionalProperties: true` so all fields pass through.\n * Override with specific schemas for full serialization performance + safety.\n *\n * Note: `example` properties are stripped from defaults so they work with\n * any Fastify instance (not just createApp which adds `keywords: ['example']`).\n */\nexport function getDefaultCrudSchemas(): Record<string, Record<string, unknown>> {\n return stripExamples({\n list: {\n querystring: getListQueryParams(),\n response: { 200: listResponse(genericItemSchema) },\n },\n get: {\n response: { 200: itemResponse(genericItemSchema) },\n },\n create: {\n response: { 201: mutationResponse(genericItemSchema) },\n },\n update: {\n response: { 200: itemResponse(genericItemSchema) },\n },\n delete: {\n response: { 200: deleteResponse() },\n },\n });\n}\n","/**\n * State Machine Utility\n *\n * Pure utility for validating state transitions in workflow systems.\n * Zero dependencies, framework-agnostic.\n *\n * @example\n * const orderState = createStateMachine('Order', {\n * approve: ['pending', 'draft'],\n * cancel: ['pending', 'approved'],\n * fulfill: ['approved'],\n * });\n *\n * // Check if transition is allowed\n * if (orderState.can('approve', currentStatus)) {\n * // Perform approval\n * }\n *\n * // Assert transition (throws if invalid)\n * orderState.assert('approve', currentStatus, ValidationError);\n */\n\nexport interface StateMachine {\n /**\n * Synchronously check if action can be performed from current status.\n * Only checks the transition map — does NOT evaluate guards.\n * Use `canAsync()` when guards need to be evaluated.\n */\n can(action: string, status: string | null | undefined): boolean;\n\n /**\n * Asynchronously check if action can be performed, including guard evaluation.\n * Falls back to simple transition check when no guard is defined.\n */\n canAsync(action: string, status: string | null | undefined, context?: any): Promise<boolean>;\n\n /**\n * Assert action can be performed, throw error if invalid\n * @param action - Action to perform\n * @param status - Current status\n * @param errorFactory - Optional error constructor\n * @param message - Optional custom error message\n */\n assert(\n action: string,\n status: string | null | undefined,\n errorFactory?: (msg: string) => Error,\n message?: string\n ): void;\n\n /**\n * Get transition history\n */\n getHistory?(): TransitionHistoryEntry[];\n\n /**\n * Record a transition\n */\n recordTransition?(from: string, to: string, action: string, metadata?: any): void;\n\n /**\n * Clear history\n */\n clearHistory?(): void;\n\n /**\n * Get available actions for current status\n */\n getAvailableActions?(status: string): string[];\n}\n\nexport interface TransitionHistoryEntry {\n from: string;\n to: string;\n action: string;\n timestamp: Date;\n metadata?: any;\n}\n\nexport interface TransitionGuard {\n (context: { from: string; to: string; action: string; data?: any }): boolean | Promise<boolean>;\n}\n\nexport interface TransitionAction {\n (context: { from: string; to: string; action: string; data?: any }): void | Promise<void>;\n}\n\nexport interface TransitionHook {\n before?: TransitionAction;\n after?: TransitionAction;\n}\n\nexport type TransitionConfig = Record<\n string,\n | string[]\n | {\n from: string[];\n to?: string;\n guard?: TransitionGuard;\n before?: TransitionAction;\n after?: TransitionAction;\n }\n>;\n\n/**\n * Create a state machine for validating transitions\n *\n * @param name - Name of the state machine (used in error messages)\n * @param transitions - Map of actions to allowed source statuses\n * @param options - Additional options (history, guards, actions)\n * @returns State machine with can() and assert() methods\n *\n * @example\n * // Basic usage\n * const transferState = createStateMachine('Transfer', {\n * approve: ['draft'],\n * dispatch: ['approved'],\n * receive: ['dispatched', 'in_transit'],\n * cancel: ['draft', 'approved'],\n * });\n *\n * @example\n * // With guards and actions\n * const orderState = createStateMachine('Order', {\n * approve: {\n * from: ['pending'],\n * to: 'approved',\n * guard: ({ data }) => data.paymentConfirmed,\n * before: ({ from, to }) => console.log(`Approving order from ${from} to ${to}`),\n * after: ({ data }) => sendApprovalEmail(data.customerId),\n * },\n * }, { trackHistory: true });\n */\nexport function createStateMachine(\n name: string,\n transitions: TransitionConfig = {},\n options: { trackHistory?: boolean } = {}\n): StateMachine {\n const normalized = new Map<\n string,\n {\n from: Set<string>;\n to?: string;\n guard?: TransitionGuard;\n before?: TransitionAction;\n after?: TransitionAction;\n }\n >();\n const history: TransitionHistoryEntry[] | undefined = options.trackHistory ? [] : undefined;\n\n // Normalize transition config (support both array and object formats)\n Object.entries(transitions).forEach(([action, allowed]) => {\n if (Array.isArray(allowed)) {\n // Simple array format: action: ['state1', 'state2']\n normalized.set(action, { from: new Set(allowed) });\n } else if (typeof allowed === 'object' && 'from' in allowed) {\n // Object format with guards/actions\n normalized.set(action, {\n from: new Set(Array.isArray(allowed.from) ? allowed.from : [allowed.from]),\n to: allowed.to,\n guard: allowed.guard,\n before: allowed.before,\n after: allowed.after,\n });\n }\n });\n\n const can = (action: string, status: string | null | undefined): boolean => {\n const transition = normalized.get(action);\n if (!transition || !status) return false;\n return transition.from.has(status);\n };\n\n const canAsync = async (\n action: string,\n status: string | null | undefined,\n context?: any\n ): Promise<boolean> => {\n const transition = normalized.get(action);\n if (!transition || !status) return false;\n\n // Check if transition is allowed from current state\n if (!transition.from.has(status)) return false;\n\n // Check guard condition if present\n if (transition.guard) {\n try {\n const guardResult = await transition.guard({\n from: status,\n to: transition.to || '',\n action,\n data: context,\n });\n return guardResult;\n } catch {\n return false;\n }\n }\n\n return true;\n };\n\n const assert = (\n action: string,\n status: string | null | undefined,\n errorFactory?: (msg: string) => Error,\n message?: string\n ): void => {\n if (can(action, status)) return;\n\n const errorMessage =\n message || `${name} cannot '${action}' when status is '${status || 'unknown'}'`;\n\n if (typeof errorFactory === 'function') {\n throw errorFactory(errorMessage);\n }\n throw new Error(errorMessage);\n };\n\n const recordTransition = (from: string, to: string, action: string, metadata?: any): void => {\n if (history) {\n history.push({\n from,\n to,\n action,\n timestamp: new Date(),\n metadata,\n });\n }\n };\n\n const getHistory = (): TransitionHistoryEntry[] => {\n return history ? [...history] : [];\n };\n\n const clearHistory = (): void => {\n if (history) {\n history.length = 0;\n }\n };\n\n const getAvailableActions = (status: string): string[] => {\n const actions: string[] = [];\n for (const [action, transition] of normalized.entries()) {\n if (transition.from.has(status)) {\n actions.push(action);\n }\n }\n return actions;\n };\n\n return {\n can,\n canAsync,\n assert,\n recordTransition,\n getHistory,\n clearHistory,\n getAvailableActions,\n };\n}\n\n/**\n * Execute a state transition with guards and actions\n *\n * @example\n * const result = await executeTransition(\n * orderState,\n * 'approve',\n * 'pending',\n * 'approved',\n * { paymentConfirmed: true }\n * );\n *\n * if (result.success) {\n * console.log('Transition successful');\n * } else {\n * console.error(result.error);\n * }\n */\nexport async function executeTransition(\n stateMachine: StateMachine,\n action: string,\n from: string,\n to: string,\n context?: any\n): Promise<{ success: boolean; error?: string }> {\n try {\n // Check if transition is allowed (use canAsync for guard evaluation)\n const canTransition = await stateMachine.canAsync(action, from, context);\n if (!canTransition) {\n return {\n success: false,\n error: `Cannot transition from ${from} to ${to} via ${action}`,\n };\n }\n\n // Record the transition if history is enabled\n if (stateMachine.recordTransition) {\n stateMachine.recordTransition(from, to, action, context);\n }\n\n return { success: true };\n } catch (error: any) {\n return {\n success: false,\n error: error.message || 'Transition failed',\n };\n }\n}\n","/**\n * Circuit Breaker Pattern\n *\n * Wraps external service calls with failure protection.\n * Prevents cascading failures by \"opening\" the circuit when\n * a service is failing, allowing it time to recover.\n *\n * States:\n * - CLOSED: Normal operation, requests pass through\n * - OPEN: Too many failures, all requests fail fast\n * - HALF_OPEN: Testing if service recovered, limited requests\n *\n * @example\n * import { CircuitBreaker } from '@classytic/arc/utils';\n *\n * const paymentBreaker = new CircuitBreaker(async (amount) => {\n * return await stripe.charges.create({ amount });\n * }, {\n * failureThreshold: 5,\n * resetTimeout: 30000,\n * timeout: 5000,\n * });\n *\n * try {\n * const result = await paymentBreaker.call(100);\n * } catch (error) {\n * // Handle failure or circuit open\n * }\n */\n\nexport const CircuitState = {\n CLOSED: 'CLOSED',\n OPEN: 'OPEN',\n HALF_OPEN: 'HALF_OPEN',\n} as const;\n\nexport type CircuitState = (typeof CircuitState)[keyof typeof CircuitState];\n\nexport interface CircuitBreakerOptions {\n /**\n * Number of failures before opening circuit\n * @default 5\n */\n failureThreshold?: number;\n\n /**\n * Time in ms before attempting to close circuit\n * @default 60000 (60 seconds)\n */\n resetTimeout?: number;\n\n /**\n * Request timeout in ms\n * @default 10000 (10 seconds)\n */\n timeout?: number;\n\n /**\n * Number of successful requests in HALF_OPEN before closing\n * @default 1\n */\n successThreshold?: number;\n\n /**\n * Fallback function when circuit is open\n */\n fallback?: (...args: any[]) => Promise<any>;\n\n /**\n * Callback when state changes\n */\n onStateChange?: (from: CircuitState, to: CircuitState) => void;\n\n /**\n * Callback on error\n */\n onError?: (error: Error) => void;\n\n /**\n * Name for logging/monitoring\n */\n name?: string;\n}\n\nexport interface CircuitBreakerStats {\n name?: string;\n state: CircuitState;\n failures: number;\n successes: number;\n totalCalls: number;\n openedAt: number | null;\n lastCallAt: number | null;\n}\n\nexport class CircuitBreakerError extends Error {\n state: CircuitState;\n\n constructor(\n message: string,\n state: CircuitState\n ) {\n super(message);\n this.name = 'CircuitBreakerError';\n this.state = state;\n }\n}\n\nexport class CircuitBreaker<T extends (...args: any[]) => Promise<any>> {\n private state: CircuitState = CircuitState.CLOSED;\n private failures: number = 0;\n private successes: number = 0;\n private totalCalls: number = 0;\n private nextAttempt: number = 0;\n private lastCallAt: number | null = null;\n private openedAt: number | null = null;\n\n private readonly failureThreshold: number;\n private readonly resetTimeout: number;\n private readonly timeout: number;\n private readonly successThreshold: number;\n private readonly fallback?: (...args: any[]) => Promise<any>;\n private readonly onStateChange?: (from: CircuitState, to: CircuitState) => void;\n private readonly onError?: (error: Error) => void;\n private readonly name: string;\n\n private readonly fn: T;\n\n constructor(\n fn: T,\n options: CircuitBreakerOptions = {}\n ) {\n this.fn = fn;\n this.failureThreshold = options.failureThreshold ?? 5;\n this.resetTimeout = options.resetTimeout ?? 60000;\n this.timeout = options.timeout ?? 10000;\n this.successThreshold = options.successThreshold ?? 1;\n this.fallback = options.fallback;\n this.onStateChange = options.onStateChange;\n this.onError = options.onError;\n this.name = options.name ?? 'CircuitBreaker';\n }\n\n /**\n * Call the wrapped function with circuit breaker protection\n */\n async call(...args: Parameters<T>): Promise<ReturnType<T>> {\n this.totalCalls++;\n this.lastCallAt = Date.now();\n\n // Check circuit state\n if (this.state === CircuitState.OPEN) {\n if (Date.now() < this.nextAttempt) {\n // Circuit still open, fail fast\n const error = new CircuitBreakerError(\n `Circuit breaker is OPEN for ${this.name}`,\n CircuitState.OPEN\n );\n\n // Use fallback if available\n if (this.fallback) {\n return this.fallback(...args);\n }\n\n throw error;\n }\n\n // Try transitioning to HALF_OPEN\n this.setState(CircuitState.HALF_OPEN);\n }\n\n try {\n // Execute with timeout\n const result = await this.executeWithTimeout(args);\n\n // Success\n this.onSuccess();\n return result;\n } catch (error) {\n // Failure\n this.onFailure(error as Error);\n throw error;\n }\n }\n\n /**\n * Execute function with timeout\n */\n private async executeWithTimeout(args: Parameters<T>): Promise<ReturnType<T>> {\n return new Promise<ReturnType<T>>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(`Request timeout after ${this.timeout}ms`));\n }, this.timeout);\n\n this.fn(...args)\n .then((result) => {\n clearTimeout(timeoutId);\n resolve(result);\n })\n .catch((error) => {\n clearTimeout(timeoutId);\n reject(error);\n });\n });\n }\n\n /**\n * Handle successful call\n */\n private onSuccess(): void {\n this.failures = 0;\n this.successes++;\n\n if (this.state === CircuitState.HALF_OPEN) {\n // Check if we should close the circuit\n if (this.successes >= this.successThreshold) {\n this.setState(CircuitState.CLOSED);\n this.successes = 0;\n }\n }\n }\n\n /**\n * Handle failed call\n */\n private onFailure(error: Error): void {\n this.failures++;\n this.successes = 0;\n\n if (this.onError) {\n this.onError(error);\n }\n\n if (this.state === CircuitState.HALF_OPEN || this.failures >= this.failureThreshold) {\n this.setState(CircuitState.OPEN);\n this.nextAttempt = Date.now() + this.resetTimeout;\n this.openedAt = Date.now();\n }\n }\n\n /**\n * Change circuit state\n */\n private setState(newState: CircuitState): void {\n const oldState = this.state;\n if (oldState !== newState) {\n this.state = newState;\n\n if (this.onStateChange) {\n this.onStateChange(oldState, newState);\n }\n }\n }\n\n /**\n * Manually open the circuit\n */\n open(): void {\n this.setState(CircuitState.OPEN);\n this.nextAttempt = Date.now() + this.resetTimeout;\n this.openedAt = Date.now();\n }\n\n /**\n * Manually close the circuit\n */\n close(): void {\n this.failures = 0;\n this.successes = 0;\n this.setState(CircuitState.CLOSED);\n this.openedAt = null;\n }\n\n /**\n * Get current statistics\n */\n getStats(): CircuitBreakerStats {\n return {\n name: this.name,\n state: this.state,\n failures: this.failures,\n successes: this.successes,\n totalCalls: this.totalCalls,\n openedAt: this.openedAt,\n lastCallAt: this.lastCallAt,\n };\n }\n\n /**\n * Get current state\n */\n getState(): CircuitState {\n return this.state;\n }\n\n /**\n * Check if circuit is open\n */\n isOpen(): boolean {\n return this.state === CircuitState.OPEN;\n }\n\n /**\n * Check if circuit is closed\n */\n isClosed(): boolean {\n return this.state === CircuitState.CLOSED;\n }\n\n /**\n * Reset statistics\n */\n reset(): void {\n this.failures = 0;\n this.successes = 0;\n this.totalCalls = 0;\n this.lastCallAt = null;\n this.openedAt = null;\n this.setState(CircuitState.CLOSED);\n }\n}\n\n/**\n * Create a circuit breaker with sensible defaults\n *\n * @example\n * const emailBreaker = createCircuitBreaker(\n * async (to, subject, body) => sendEmail(to, subject, body),\n * { name: 'email-service' }\n * );\n */\nexport function createCircuitBreaker<T extends (...args: any[]) => Promise<any>>(\n fn: T,\n options?: CircuitBreakerOptions\n): CircuitBreaker<T> {\n return new CircuitBreaker(fn, options);\n}\n\n/**\n * Circuit breaker registry for managing multiple breakers\n */\nexport class CircuitBreakerRegistry {\n private breakers: Map<string, CircuitBreaker<any>> = new Map();\n\n /**\n * Register a circuit breaker\n */\n register<T extends (...args: any[]) => Promise<any>>(\n name: string,\n fn: T,\n options?: Omit<CircuitBreakerOptions, 'name'>\n ): CircuitBreaker<T> {\n const breaker = new CircuitBreaker(fn, { ...options, name });\n this.breakers.set(name, breaker);\n return breaker;\n }\n\n /**\n * Get a circuit breaker by name\n */\n get(name: string): CircuitBreaker<any> | undefined {\n return this.breakers.get(name);\n }\n\n /**\n * Get all breakers\n */\n getAll(): Map<string, CircuitBreaker<any>> {\n return this.breakers;\n }\n\n /**\n * Get statistics for all breakers\n */\n getAllStats(): Record<string, CircuitBreakerStats> {\n const stats: Record<string, CircuitBreakerStats> = {};\n for (const [name, breaker] of this.breakers.entries()) {\n stats[name] = breaker.getStats();\n }\n return stats;\n }\n\n /**\n * Reset all breakers\n */\n resetAll(): void {\n for (const breaker of this.breakers.values()) {\n breaker.reset();\n }\n }\n\n /**\n * Open all breakers\n */\n openAll(): void {\n for (const breaker of this.breakers.values()) {\n breaker.open();\n }\n }\n\n /**\n * Close all breakers\n */\n closeAll(): void {\n for (const breaker of this.breakers.values()) {\n breaker.close();\n }\n }\n}\n\n/**\n * Create a new CircuitBreakerRegistry instance.\n * Use this instead of a global singleton — attach to fastify.arc or pass explicitly.\n */\nexport function createCircuitBreakerRegistry(): CircuitBreakerRegistry {\n return new CircuitBreakerRegistry();\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"describe.d.mts","names":[],"sources":["../../../src/cli/commands/describe.ts"],"mappings":";;AAmVA;;;;;;;;;;;;;iBAAsB,QAAA,CAAS,IAAA,aAAiB,OAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"describe.mjs","names":[],"sources":["../../../src/cli/commands/describe.ts"],"sourcesContent":["/**\n * Arc CLI - Describe Command\n *\n * Machine-readable resource metadata for AI agents.\n * Outputs JSON with fields, permissions, pipeline, routes, events —\n * everything an LLM needs to understand and generate code for the API.\n *\n * @example\n * ```bash\n * arc describe ./src/resources.js --json\n * arc describe ./src/resources.js --resource product\n * arc describe ./src/resources.js --pretty\n * ```\n */\n\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { ResourceDefinition } from '../../core/defineResource.js';\nimport type {\n AnyRecord,\n ResourcePermissions,\n RouteSchemaOptions,\n EventDefinition,\n RateLimitConfig,\n MiddlewareConfig,\n} from '../../types/index.js';\nimport type { PermissionCheck } from '../../permissions/types.js';\nimport type { FieldPermissionMap, FieldPermission } from '../../permissions/fields.js';\nimport type { PipelineConfig, PipelineStep } from '../../pipeline/types.js';\nimport { CRUD_OPERATIONS } from '../../constants.js';\n\n// ---------------------------------------------------------------------------\n// Output Schema\n// ---------------------------------------------------------------------------\n\ninterface DescribeOutput {\n $schema: 'arc-describe/v1';\n generatedAt: string;\n resources: DescribedResource[];\n stats: DescribeStats;\n}\n\ninterface DescribedResource {\n name: string;\n displayName: string;\n prefix: string;\n tag: string;\n module?: string;\n adapter: { type: string; name: string } | null;\n\n permissions: Record<string, PermissionDescription>;\n presets: string[];\n\n fields?: Record<string, FieldDescription>;\n pipeline?: PipelineDescription;\n\n routes: RouteDescription[];\n events: EventDescription[];\n\n schemaOptions?: RouteSchemaOptions;\n rateLimit?: RateLimitConfig | false;\n middlewares: string[];\n}\n\ninterface PermissionDescription {\n type: 'public' | 'requireAuth' | 'requireRoles' | 'custom';\n roles?: readonly string[];\n}\n\ninterface FieldDescription {\n type: string;\n roles?: readonly string[];\n redactValue?: unknown;\n}\n\ninterface PipelineDescription {\n guards: PipelineStepDescription[];\n transforms: PipelineStepDescription[];\n interceptors: PipelineStepDescription[];\n}\n\ninterface PipelineStepDescription {\n name: string;\n operations?: string[];\n}\n\ninterface RouteDescription {\n method: string;\n path: string;\n operation: string;\n summary?: string;\n description?: string;\n permission?: PermissionDescription;\n}\n\ninterface EventDescription {\n name: string;\n description?: string;\n hasSchema: boolean;\n}\n\ninterface DescribeStats {\n totalResources: number;\n totalRoutes: number;\n totalEvents: number;\n totalFields: number;\n presetUsage: Record<string, number>;\n pipelineSteps: number;\n}\n\n// ---------------------------------------------------------------------------\n// Permission Introspection\n// ---------------------------------------------------------------------------\n\nfunction describePermission(check: unknown): PermissionDescription {\n if (!check || typeof check !== 'function') {\n return { type: 'custom' };\n }\n\n const fn = check as PermissionCheck;\n\n // allowPublic() sets _isPublic = true\n if (fn._isPublic === true) {\n return { type: 'public' };\n }\n\n // requireRoles() sets _roles = [...]\n if (Array.isArray(fn._roles)) {\n return { type: 'requireRoles', roles: fn._roles as string[] };\n }\n\n // requireAuth() — function that checks ctx.user\n // Infer from function source as a best-effort heuristic\n const src = check.toString();\n if (src.includes('ctx.user') && !src.includes('roles') && src.length < 200) {\n return { type: 'requireAuth' };\n }\n\n return { type: 'custom' };\n}\n\nfunction describePermissions(perms?: ResourcePermissions): Record<string, PermissionDescription> {\n if (!perms) return {};\n\n const result: Record<string, PermissionDescription> = {};\n for (const op of CRUD_OPERATIONS) {\n const check = perms[op];\n if (check) {\n result[op] = describePermission(check);\n }\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Field Permission Introspection\n// ---------------------------------------------------------------------------\n\nfunction describeFields(fieldPerms?: FieldPermissionMap): Record<string, FieldDescription> | undefined {\n if (!fieldPerms || Object.keys(fieldPerms).length === 0) return undefined;\n\n const result: Record<string, FieldDescription> = {};\n for (const [field, perm] of Object.entries(fieldPerms)) {\n const desc: FieldDescription = { type: perm._type };\n if (perm.roles?.length) desc.roles = perm.roles;\n if (perm.redactValue !== undefined) desc.redactValue = perm.redactValue;\n result[field] = desc;\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline Introspection\n// ---------------------------------------------------------------------------\n\nfunction describePipeline(pipe?: PipelineConfig): PipelineDescription | undefined {\n if (!pipe) return undefined;\n\n const steps: PipelineStep[] = [];\n\n if (Array.isArray(pipe)) {\n steps.push(...pipe);\n } else {\n // Per-operation map — collect all unique steps\n const seen = new Set<string>();\n for (const opSteps of Object.values(pipe)) {\n if (Array.isArray(opSteps)) {\n for (const step of opSteps) {\n const key = `${step._type}:${step.name}`;\n if (!seen.has(key)) {\n seen.add(key);\n steps.push(step);\n }\n }\n }\n }\n }\n\n if (steps.length === 0) return undefined;\n\n const guards: PipelineStepDescription[] = [];\n const transforms: PipelineStepDescription[] = [];\n const interceptors: PipelineStepDescription[] = [];\n\n for (const step of steps) {\n const desc: PipelineStepDescription = { name: step.name };\n if (step.operations?.length) desc.operations = [...step.operations];\n\n switch (step._type) {\n case 'guard': guards.push(desc); break;\n case 'transform': transforms.push(desc); break;\n case 'interceptor': interceptors.push(desc); break;\n }\n }\n\n return { guards, transforms, interceptors };\n}\n\n// ---------------------------------------------------------------------------\n// Route Introspection\n// ---------------------------------------------------------------------------\n\nfunction describeRoutes(resource: ResourceDefinition<unknown>): RouteDescription[] {\n const routes: RouteDescription[] = [];\n\n // Default CRUD routes\n if (!resource.disableDefaultRoutes) {\n const disabled = new Set(resource.disabledRoutes ?? []);\n const crudOps = [\n { method: 'GET', suffix: '', op: 'list' },\n { method: 'GET', suffix: '/:id', op: 'get' },\n { method: 'POST', suffix: '', op: 'create' },\n { method: 'PATCH', suffix: '/:id', op: 'update' },\n { method: 'DELETE', suffix: '/:id', op: 'delete' },\n ] as const;\n\n for (const { method, suffix, op } of crudOps) {\n if (!disabled.has(op)) {\n const route: RouteDescription = {\n method,\n path: `${resource.prefix}${suffix}`,\n operation: op,\n };\n const perm = resource.permissions[op];\n if (perm) route.permission = describePermission(perm);\n routes.push(route);\n }\n }\n }\n\n // Additional routes\n for (const ar of resource.additionalRoutes) {\n routes.push({\n method: ar.method,\n path: `${resource.prefix}${ar.path}`,\n operation: typeof ar.handler === 'string' ? ar.handler : 'custom',\n summary: ar.summary,\n description: ar.description,\n permission: describePermission(ar.permissions),\n });\n }\n\n return routes;\n}\n\n// ---------------------------------------------------------------------------\n// Event Introspection\n// ---------------------------------------------------------------------------\n\nfunction describeEvents(\n resourceName: string,\n events?: Record<string, EventDefinition>,\n): EventDescription[] {\n if (!events) return [];\n\n return Object.entries(events).map(([action, def]) => ({\n name: `${resourceName}:${action}`,\n description: def.description,\n hasSchema: !!def.schema,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Middleware Introspection\n// ---------------------------------------------------------------------------\n\nfunction describeMiddlewares(middlewares?: MiddlewareConfig): string[] {\n if (!middlewares) return [];\n\n const ops: string[] = [];\n for (const [op, handlers] of Object.entries(middlewares)) {\n if (handlers?.length) {\n ops.push(`${op}(${handlers.length})`);\n }\n }\n return ops;\n}\n\n// ---------------------------------------------------------------------------\n// Main Describe Function\n// ---------------------------------------------------------------------------\n\nfunction describeResource(\n resource: ResourceDefinition<unknown>,\n module?: string,\n): DescribedResource {\n return {\n name: resource.name,\n displayName: resource.displayName,\n prefix: resource.prefix,\n tag: resource.tag,\n module,\n\n adapter: resource.adapter\n ? { type: resource.adapter.type, name: resource.adapter.name }\n : null,\n\n permissions: describePermissions(resource.permissions),\n presets: resource._appliedPresets ?? [],\n\n fields: describeFields(resource.fields),\n pipeline: describePipeline(resource.pipe),\n\n routes: describeRoutes(resource),\n events: describeEvents(resource.name, resource.events),\n\n schemaOptions: Object.keys(resource.schemaOptions ?? {}).length > 0\n ? resource.schemaOptions\n : undefined,\n rateLimit: resource.rateLimit,\n middlewares: describeMiddlewares(resource.middlewares),\n };\n}\n\n// ---------------------------------------------------------------------------\n// CLI Entry\n// ---------------------------------------------------------------------------\n\nexport async function describe(args: string[]): Promise<void> {\n try {\n // Parse flags\n const flags = new Set(args.filter(a => a.startsWith('--')));\n const positional = args.filter(a => !a.startsWith('--'));\n const pretty = flags.has('--pretty') || !flags.has('--json');\n const filterResource = positional[1]; // optional resource name filter\n\n const entryPath = positional[0];\n if (!entryPath) {\n console.log('Usage: arc describe <entry-file> [resource-name] [--json] [--pretty]\\n');\n console.log('Outputs machine-readable JSON metadata for AI agents.\\n');\n console.log('Options:');\n console.log(' --json Output compact JSON (default if piped)');\n console.log(' --pretty Output formatted JSON (default if terminal)');\n console.log('\\nExamples:');\n console.log(' arc describe ./src/resources.js');\n console.log(' arc describe ./src/resources.js product');\n console.log(' arc describe ./src/resources.js --json | jq .');\n return;\n }\n\n // Dynamically import user's entry file (pathToFileURL needed for Windows)\n const entryFileUrl = pathToFileURL(resolve(process.cwd(), entryPath)).href;\n const entryModule = await import(entryFileUrl);\n\n // Collect ResourceDefinition objects\n // Also handles arrays of resources (e.g. `export const resources = [r1, r2]`)\n const resources: ResourceDefinition<unknown>[] = [];\n\n function tryCollect(value: unknown): void {\n if (\n value &&\n typeof value === 'object' &&\n 'name' in value &&\n '_registryMeta' in value &&\n 'toPlugin' in value\n ) {\n resources.push(value as ResourceDefinition<unknown>);\n }\n }\n\n for (const exported of Object.values(entryModule)) {\n if (Array.isArray(exported)) {\n exported.forEach(tryCollect);\n } else {\n tryCollect(exported);\n }\n }\n\n if (resources.length === 0) {\n throw new Error(\n 'No resource definitions found in entry file.\\nMake sure your file exports defineResource() results:\\n export const productResource = defineResource({ ... });',\n );\n }\n\n // Filter to single resource if requested\n const filtered = filterResource\n ? resources.filter(r => r.name === filterResource)\n : resources;\n\n if (filterResource && filtered.length === 0) {\n throw new Error(\n `Resource '${filterResource}' not found.\\nAvailable: ${resources.map(r => r.name).join(', ')}`,\n );\n }\n\n // Build described resources\n const described = filtered.map(r =>\n describeResource(r, (r._registryMeta as AnyRecord | undefined)?.module as string | undefined),\n );\n\n // Compute stats\n const presetCounts: Record<string, number> = {};\n let totalPipelineSteps = 0;\n let totalFields = 0;\n\n for (const res of described) {\n for (const preset of res.presets) {\n presetCounts[preset] = (presetCounts[preset] ?? 0) + 1;\n }\n if (res.pipeline) {\n totalPipelineSteps += res.pipeline.guards.length\n + res.pipeline.transforms.length\n + res.pipeline.interceptors.length;\n }\n if (res.fields) {\n totalFields += Object.keys(res.fields).length;\n }\n }\n\n const output: DescribeOutput = {\n $schema: 'arc-describe/v1',\n generatedAt: new Date().toISOString(),\n resources: described,\n stats: {\n totalResources: described.length,\n totalRoutes: described.reduce((sum, r) => sum + r.routes.length, 0),\n totalEvents: described.reduce((sum, r) => sum + r.events.length, 0),\n totalFields,\n presetUsage: presetCounts,\n pipelineSteps: totalPipelineSteps,\n },\n };\n\n // Output\n const json = pretty\n ? JSON.stringify(output, null, 2)\n : JSON.stringify(output);\n\n console.log(json);\n } catch (error: unknown) {\n if (error instanceof Error) throw error;\n throw new Error(String(error));\n }\n}\n\nexport default describe;\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"docs.d.mts","names":[],"sources":["../../../src/cli/commands/docs.ts"],"mappings":";;AAyBA;;;;;iBAAsB,UAAA,CAAW,IAAA,aAAiB,OAAA;AAAA,cA8DjD,QAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"docs.mjs","names":[],"sources":["../../../src/cli/commands/docs.ts"],"sourcesContent":["/**\n * Arc CLI - Docs Command\n *\n * Export OpenAPI specification from registered resources.\n * Requires an entry file that exports defineResource() results.\n */\n\nimport { writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { ResourceRegistry } from '../../registry/index.js';\nimport type { RegistryEntry } from '../../types/index.js';\nimport { buildOpenApiSpec } from '../../docs/openapi.js';\n\ninterface ParsedDocsArgs {\n entryPath?: string;\n outputPath: string;\n}\n\nfunction parseDocsArgs(args: string[]): ParsedDocsArgs {\n const outputPath = args.find((a) => a.endsWith('.json')) ?? './openapi.json';\n const entryPath = args.find((a) => !a.endsWith('.json'));\n return { entryPath, outputPath };\n}\n\nexport async function exportDocs(args: string[]): Promise<void> {\n const { entryPath, outputPath } = parseDocsArgs(args);\n\n console.log('Exporting OpenAPI specification...\\n');\n\n if (!entryPath) {\n throw new Error(\n 'Missing entry file.\\n\\nUsage: arc docs <entry-file> [output.json]\\nExample: arc docs ./src/resources.js ./openapi.json',\n );\n }\n\n // Dynamically import user's entry file\n const entryFileUrl = pathToFileURL(resolve(process.cwd(), entryPath)).href;\n const entryModule = await import(entryFileUrl);\n\n // Collect ResourceDefinition objects from exports\n // Also handles arrays of resources (e.g. `export const resources = [r1, r2]`)\n const registry = new ResourceRegistry();\n let registered = 0;\n\n function tryRegister(value: unknown): void {\n if (\n value &&\n typeof value === 'object' &&\n 'name' in value &&\n '_registryMeta' in value &&\n 'toPlugin' in value\n ) {\n registry.register(value as any, (value as any)._registryMeta ?? {});\n registered++;\n }\n }\n\n for (const exported of Object.values(entryModule)) {\n if (Array.isArray(exported)) {\n exported.forEach(tryRegister);\n } else {\n tryRegister(exported);\n }\n }\n\n if (registered === 0) {\n throw new Error(\n 'No resource definitions found in entry file.\\nMake sure your file exports defineResource() results:\\n export const productResource = defineResource({ ... });',\n );\n }\n\n const resources: RegistryEntry[] = registry.getAll();\n\n const spec = buildOpenApiSpec(resources, {\n title: 'Arc API',\n version: '1.0.0',\n description: 'Auto-generated from Arc resources',\n });\n\n // Write to file (resolve handles both relative and absolute paths)\n const fullPath = resolve(process.cwd(), outputPath);\n writeFileSync(fullPath, JSON.stringify(spec, null, 2));\n\n console.log(`OpenAPI spec exported to: ${fullPath}`);\n console.log(`\\nResources included: ${resources.length}`);\n console.log(`Total endpoints: ${Object.keys(spec.paths).length}`);\n}\n\nexport default { exportDocs };\n"],"mappings":";;;;;;;;;;;;;AAmBA,SAAS,cAAc,MAAgC;CACrD,MAAM,aAAa,KAAK,MAAM,MAAM,EAAE,SAAS,QAAQ,CAAC,IAAI;AAE5D,QAAO;EAAE,WADS,KAAK,MAAM,MAAM,CAAC,EAAE,SAAS,QAAQ,CAAC;EACpC;EAAY;;AAGlC,eAAsB,WAAW,MAA+B;CAC9D,MAAM,EAAE,WAAW,eAAe,cAAc,KAAK;AAErD,SAAQ,IAAI,uCAAuC;AAEnD,KAAI,CAAC,UACH,OAAM,IAAI,MACR,yHACD;CAKH,MAAM,cAAc,MAAM,OADL,cAAc,QAAQ,QAAQ,KAAK,EAAE,UAAU,CAAC,CAAC;CAKtE,MAAM,WAAW,IAAI,kBAAkB;CACvC,IAAI,aAAa;CAEjB,SAAS,YAAY,OAAsB;AACzC,MACE,SACA,OAAO,UAAU,YACjB,UAAU,SACV,mBAAmB,SACnB,cAAc,OACd;AACA,YAAS,SAAS,OAAe,MAAc,iBAAiB,EAAE,CAAC;AACnE;;;AAIJ,MAAK,MAAM,YAAY,OAAO,OAAO,YAAY,CAC/C,KAAI,MAAM,QAAQ,SAAS,CACzB,UAAS,QAAQ,YAAY;KAE7B,aAAY,SAAS;AAIzB,KAAI,eAAe,EACjB,OAAM,IAAI,MACR,iKACD;CAGH,MAAM,YAA6B,SAAS,QAAQ;CAEpD,MAAM,OAAO,iBAAiB,WAAW;EACvC,OAAO;EACP,SAAS;EACT,aAAa;EACd,CAAC;CAGF,MAAM,WAAW,QAAQ,QAAQ,KAAK,EAAE,WAAW;AACnD,eAAc,UAAU,KAAK,UAAU,MAAM,MAAM,EAAE,CAAC;AAEtD,SAAQ,IAAI,6BAA6B,WAAW;AACpD,SAAQ,IAAI,yBAAyB,UAAU,SAAS;AACxD,SAAQ,IAAI,oBAAoB,OAAO,KAAK,KAAK,MAAM,CAAC,SAAS;;AAGnE,mBAAe,EAAE,YAAY"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.mts","names":[],"sources":["../../../src/cli/commands/generate.ts"],"mappings":";;AAmRA;;;;;;;;;;;;iBAAsB,QAAA,CACpB,IAAA,sBACA,IAAA,aACC,OAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate.mjs","names":[],"sources":["../../../src/cli/commands/generate.ts"],"sourcesContent":["/**\n * Arc CLI - Generate Command\n *\n * Scaffolds resources with consistent naming:\n * - src/resources/product/product.model.ts\n * - src/resources/product/product.repository.ts\n * - src/resources/product/product.resource.ts\n * - src/resources/product/product.controller.ts\n * - src/resources/product/product.schemas.ts\n */\n\nimport { writeFileSync, mkdirSync, existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pluralize } from \"../utils/pluralize.js\";\n\ninterface ArcProjectConfig {\n adapter?: \"mongokit\" | \"custom\";\n auth?: \"jwt\" | \"better-auth\";\n tenant?: \"multi\" | \"single\";\n typescript?: boolean;\n}\n\n// Read .arcrc project config (written by `arc init`)\nfunction readProjectConfig(): ArcProjectConfig {\n try {\n const rcPath = join(process.cwd(), \".arcrc\");\n return JSON.parse(readFileSync(rcPath, \"utf-8\"));\n } catch {\n return {};\n }\n}\n\n// Check if TypeScript project\nfunction isTypeScriptProject(): boolean {\n return existsSync(join(process.cwd(), \"tsconfig.json\"));\n}\n\n// Templates\nfunction getTemplates(ts: boolean, config: ArcProjectConfig = {}) {\n const isMultiTenant = config.tenant === \"multi\";\n\n return {\n model: (name: string) => `/**\n * ${name} Model\n * Generated by Arc CLI\n */\n\nimport mongoose${ts ? \", { type HydratedDocument }\" : \"\"} from 'mongoose';\n\nconst { Schema } = mongoose;\n${\n ts\n ? `\nexport interface I${name} {\n name: string;\n description?: string;\n isActive: boolean;\n}\n\nexport type ${name}Document = HydratedDocument<I${name}>;\n`\n : \"\"\n}\nconst ${name.toLowerCase()}Schema = new Schema${ts ? `<I${name}>` : \"\"}(\n {\n name: { type: String, required: true, trim: true },\n description: { type: String, trim: true },\n isActive: { type: Boolean, default: true },\n },\n { timestamps: true }\n);\n\n// Indexes\n${name.toLowerCase()}Schema.index({ name: 1 });\n${name.toLowerCase()}Schema.index({ isActive: 1 });\n\nconst ${name} = mongoose.models.${name} || mongoose.model('${name}', ${name.toLowerCase()}Schema);\nexport default ${name};\n`,\n\n repository: (name: string) => `/**\n * ${name} Repository\n * Generated by Arc CLI\n */\n\nimport {\n Repository,\n methodRegistryPlugin,\n softDeletePlugin,\n mongoOperationsPlugin,\n} from '@classytic/mongokit';\nimport ${name} from './${name.toLowerCase()}.model.js';\n\nclass ${name}Repository extends Repository {\n constructor() {\n super(${name}, [\n methodRegistryPlugin(),\n softDeletePlugin(),\n mongoOperationsPlugin(),\n ]);\n }\n\n /**\n * Find all active records\n */\n async findActive() {\n return this.Model.find({ isActive: true, deletedAt: null }).lean();\n }\n}\n\nconst ${name.toLowerCase()}Repository = new ${name}Repository();\nexport default ${name.toLowerCase()}Repository;\nexport { ${name}Repository };\n`,\n\n controller: (name: string) => `/**\n * ${name} Controller\n * Generated by Arc CLI\n *\n * Note: defineResource() auto-creates a controller from the adapter.\n * Only create a custom controller when you need custom methods.\n */\n\nimport { BaseController } from '@classytic/arc';\nimport ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';\n\nclass ${name}Controller extends BaseController {\n constructor() {\n super(${name.toLowerCase()}Repository, {\n resourceName: '${name.toLowerCase()}',\n });\n }\n\n // Add custom controller methods here\n}\n\nconst ${name.toLowerCase()}Controller = new ${name}Controller();\nexport default ${name.toLowerCase()}Controller;\n`,\n\n schemas: (name: string) => `/**\n * ${name} Schemas\n * Generated by Arc CLI\n */\n\nimport ${name} from './${name.toLowerCase()}.model.js';\nimport { buildCrudSchemasFromModel } from '@classytic/mongokit/utils';\n\n/**\n * CRUD Schemas with Field Rules\n */\nconst crudSchemas = buildCrudSchemasFromModel(${name}, {\n strictAdditionalProperties: true,\n fieldRules: {\n // Mark fields as system-managed (excluded from create/update)\n // deletedAt: { systemManaged: true },\n },\n query: {\n filterableFields: {\n isActive: 'boolean',\n createdAt: 'date',\n },\n },\n});\n\nexport default crudSchemas;\n`,\n\n resource: (name: string) => {\n const useMongoKit = config.adapter === \"mongokit\" || !config.adapter;\n const queryParserImport = useMongoKit\n ? `\\nimport { QueryParser } from '@classytic/mongokit';\\n\\nconst queryParser = new QueryParser();\\n`\n : \"\";\n const queryParserConfig = useMongoKit ? `\\n queryParser,` : \"\";\n\n return isMultiTenant\n ? `/**\n * ${name} Resource\n * Generated by Arc CLI\n */\n\nimport { defineResource, createMongooseAdapter } from '@classytic/arc';\nimport { requireOrgMembership, requireOrgRole } from '@classytic/arc/permissions';\nimport ${name}${ts ? `, { type I${name} }` : \"\"} from './${name.toLowerCase()}.model.js';\nimport ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';${queryParserImport}\n\nconst ${name.toLowerCase()}Resource = defineResource${ts ? `<I${name}>` : \"\"}({\n name: '${name.toLowerCase()}',\n adapter: createMongooseAdapter(${name}, ${name.toLowerCase()}Repository),${queryParserConfig}\n presets: ['softDelete'],\n permissions: {\n list: requireOrgMembership(),\n get: requireOrgMembership(),\n create: requireOrgRole('admin'),\n update: requireOrgRole('admin'),\n delete: requireOrgRole('admin'),\n },\n});\n\nexport default ${name.toLowerCase()}Resource;\n`\n : `/**\n * ${name} Resource\n * Generated by Arc CLI\n */\n\nimport { defineResource, createMongooseAdapter } from '@classytic/arc';\nimport { requireAuth, requireRoles } from '@classytic/arc/permissions';\nimport ${name}${ts ? `, { type I${name} }` : \"\"} from './${name.toLowerCase()}.model.js';\nimport ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';${queryParserImport}\n\nconst ${name.toLowerCase()}Resource = defineResource${ts ? `<I${name}>` : \"\"}({\n name: '${name.toLowerCase()}',\n adapter: createMongooseAdapter(${name}, ${name.toLowerCase()}Repository),${queryParserConfig}\n presets: ['softDelete'],\n permissions: {\n list: requireAuth(),\n get: requireAuth(),\n create: requireRoles(['admin']),\n update: requireRoles(['admin']),\n delete: requireRoles(['admin']),\n },\n});\n\nexport default ${name.toLowerCase()}Resource;\n`;\n },\n\n test: (name: string) => `/**\n * ${name} Tests\n * Generated by Arc CLI\n */\n\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest';\nimport mongoose from 'mongoose';\nimport { createMinimalTestApp } from '@classytic/arc/testing';\n${ts ? \"import type { FastifyInstance } from 'fastify';\\n\" : \"\"}import ${name.toLowerCase()}Resource from '../src/resources/${name.toLowerCase()}/${name.toLowerCase()}.resource.js';\n\ndescribe('${name} Resource', () => {\n let app${ts ? \": FastifyInstance\" : \"\"};\n\n beforeAll(async () => {\n const testDbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/arc-app-test';\n await mongoose.connect(testDbUri);\n\n app = createMinimalTestApp();\n await app.register(${name.toLowerCase()}Resource.toPlugin());\n await app.ready();\n });\n\n afterAll(async () => {\n await app.close();\n await mongoose.connection.close();\n });\n\n describe('GET /${pluralize(name.toLowerCase())}', () => {\n it('should return a list', async () => {\n const response = await app.inject({\n method: 'GET',\n url: '/${pluralize(name.toLowerCase())}',\n });\n\n expect(response.statusCode).toBe(200);\n const body = JSON.parse(response.body);\n expect(body).toHaveProperty('docs');\n });\n });\n});\n`,\n };\n}\n\n/**\n * Generate command handler\n */\nexport async function generate(\n type: string | undefined,\n args: string[],\n): Promise<void> {\n if (!type) {\n throw new Error(\n \"Missing type argument\\nUsage: arc generate <resource|controller|model|repository|schemas> <name>\",\n );\n }\n\n const [name] = args;\n if (!name) {\n throw new Error(\n \"Missing name argument\\nUsage: arc generate <type> <name>\\nExample: arc generate resource product\",\n );\n }\n\n // Capitalize first letter\n const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);\n const lowerName = name.toLowerCase();\n\n // Detect project config\n const projectConfig = readProjectConfig();\n const ts = projectConfig.typescript ?? isTypeScriptProject();\n const ext = ts ? \"ts\" : \"js\";\n const templates = getTemplates(ts, projectConfig);\n\n // Resource path: src/resources/<name>/\n const resourcePath = join(process.cwd(), \"src\", \"resources\", lowerName);\n\n switch (type) {\n case \"resource\":\n case \"r\":\n await generateResource(\n capitalizedName,\n lowerName,\n resourcePath,\n templates,\n ext,\n );\n break;\n\n case \"controller\":\n case \"c\":\n await generateFile(\n capitalizedName,\n lowerName,\n resourcePath,\n \"controller\",\n templates.controller,\n ext,\n );\n break;\n\n case \"model\":\n case \"m\":\n await generateFile(\n capitalizedName,\n lowerName,\n resourcePath,\n \"model\",\n templates.model,\n ext,\n );\n break;\n\n case \"repository\":\n case \"repo\":\n await generateFile(\n capitalizedName,\n lowerName,\n resourcePath,\n \"repository\",\n templates.repository,\n ext,\n );\n break;\n\n case \"schemas\":\n case \"s\":\n await generateFile(\n capitalizedName,\n lowerName,\n resourcePath,\n \"schemas\",\n templates.schemas,\n ext,\n );\n break;\n\n default:\n throw new Error(\n `Unknown type: ${type}\\nAvailable types: resource, controller, model, repository, schemas`,\n );\n }\n}\n\n/**\n * Generate a full resource\n */\nasync function generateResource(\n name: string,\n lowerName: string,\n resourcePath: string,\n templates: ReturnType<typeof getTemplates>,\n ext: string,\n): Promise<void> {\n console.log(`\\nGenerating resource: ${name}...\\n`);\n\n // Create directory\n if (!existsSync(resourcePath)) {\n mkdirSync(resourcePath, { recursive: true });\n console.log(` + Created: src/resources/${lowerName}/`);\n }\n\n // Generate files (defineResource() auto-creates controller, no schemas needed)\n const files: Record<string, string> = {\n [`${lowerName}.model.${ext}`]: templates.model(name),\n [`${lowerName}.repository.${ext}`]: templates.repository(name),\n [`${lowerName}.resource.${ext}`]: templates.resource(name),\n };\n\n for (const [filename, content] of Object.entries(files)) {\n const filepath = join(resourcePath, filename);\n if (existsSync(filepath)) {\n console.warn(` ! Skipped: ${filename} (already exists)`);\n } else {\n writeFileSync(filepath, content);\n console.log(` + Created: ${filename}`);\n }\n }\n\n // Generate test file\n const testsDir = join(process.cwd(), \"tests\");\n if (!existsSync(testsDir)) {\n mkdirSync(testsDir, { recursive: true });\n }\n const testPath = join(testsDir, `${lowerName}.test.${ext}`);\n if (!existsSync(testPath)) {\n writeFileSync(testPath, templates.test(name));\n console.log(` + Created: tests/${lowerName}.test.${ext}`);\n }\n\n const isMultiTenant = readProjectConfig().tenant === \"multi\";\n\n console.log(`\n╔═══════════════════════════════════════════════════════════════╗\n║ Resource Generated ║\n╚═══════════════════════════════════════════════════════════════╝\n\nNext steps:\n\n1. Register in src/resources/index.${ext}:\n import ${lowerName}Resource from './${lowerName}/${lowerName}.resource.js';\n\n export const resources = [\n // ... existing resources\n ${lowerName}Resource,\n ];\n\n2. Customize the model schema in:\n src/resources/${lowerName}/${lowerName}.model.${ext}\n\n3. Adjust permissions in ${lowerName}.resource.${ext}:\n${\n isMultiTenant\n ? ` - requireOrgMembership() → any org member\n - requireOrgRole('admin') → specific org roles`\n : ` - requireAuth() → any authenticated user\n - requireRoles(['admin']) → specific platform roles`\n}\n\n4. Run tests:\n npm test\n`);\n}\n\n/**\n * Generate a single file\n */\nasync function generateFile(\n name: string,\n lowerName: string,\n resourcePath: string,\n fileType: string,\n template: (name: string) => string,\n ext: string,\n): Promise<void> {\n console.log(`\\nGenerating ${fileType}: ${name}...\\n`);\n\n if (!existsSync(resourcePath)) {\n mkdirSync(resourcePath, { recursive: true });\n console.log(` + Created: src/resources/${lowerName}/`);\n }\n\n const filename = `${lowerName}.${fileType}.${ext}`;\n const filepath = join(resourcePath, filename);\n\n if (existsSync(filepath)) {\n throw new Error(\n `${filename} already exists. Remove it first or use a different name.`,\n );\n }\n\n writeFileSync(filepath, template(name));\n console.log(` + Created: ${filename}`);\n}\n\nexport default generate;\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.mts","names":[],"sources":["../../../src/cli/commands/init.ts"],"mappings":";;AAuBA;;;;;;;;;UAAiB,WAAA;EACf,IAAA;EACA,OAAA;EACA,IAAA;EACA,MAAA;EACA,UAAA;EACA,IAAA;EACA,WAAA;EACA,KAAA;AAAA;;;;iBAmBoB,IAAA,CAAK,OAAA,GAAS,WAAA,GAAmB,OAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init.mjs","names":["existsSync"],"sources":["../../../src/cli/commands/init.ts"],"sourcesContent":["/**\r\n * Arc CLI - Init Command\r\n *\r\n * Scaffolds a new Arc project with clean architecture:\r\n * - MongoKit or Custom adapter\r\n * - Multi-tenant or Single-tenant\r\n * - TypeScript or JavaScript\r\n *\r\n * Automatically installs dependencies using detected package manager.\r\n */\r\n\r\nimport * as fs from \"node:fs/promises\";\r\nimport { accessSync } from \"node:fs\";\r\nimport * as path from \"node:path\";\r\nimport * as readline from \"node:readline\";\r\nimport { execSync, spawn } from \"node:child_process\";\r\n\r\ntype PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface InitOptions {\r\n name?: string;\r\n adapter?: \"mongokit\" | \"custom\";\r\n auth?: \"jwt\" | \"better-auth\";\r\n tenant?: \"multi\" | \"single\";\r\n typescript?: boolean;\r\n edge?: boolean;\r\n skipInstall?: boolean;\r\n force?: boolean;\r\n}\r\n\r\ninterface ProjectConfig {\r\n name: string;\r\n adapter: \"mongokit\" | \"custom\";\r\n auth: \"jwt\" | \"better-auth\";\r\n tenant: \"multi\" | \"single\";\r\n typescript: boolean;\r\n edge: boolean;\r\n}\r\n\r\n// ============================================================================\r\n// Main Init Function\r\n// ============================================================================\r\n\r\n/**\r\n * Initialize a new Arc project\r\n */\r\nexport async function init(options: InitOptions = {}): Promise<void> {\r\n console.log(`\r\n╔═══════════════════════════════════════════════════════════════╗\r\n║ Arc Project Setup ║\r\n║ Resource-Oriented Backend Framework ║\r\n╚═══════════════════════════════════════════════════════════════╝\r\n`);\r\n\r\n // Gather configuration (from options or prompts)\r\n const config = await gatherConfig(options);\r\n\r\n console.log(`\\nCreating project: ${config.name}`);\r\n console.log(\r\n ` Adapter: ${config.adapter === \"mongokit\" ? \"MongoKit (MongoDB)\" : \"Custom\"}`,\r\n );\r\n console.log(\r\n ` Auth: ${config.auth === \"better-auth\" ? \"Better Auth (recommended)\" : \"Arc JWT\"}`,\r\n );\r\n console.log(\r\n ` Tenant: ${config.tenant === \"multi\" ? \"Multi-tenant\" : \"Single-tenant\"}`,\r\n );\r\n console.log(\r\n ` Language: ${config.typescript ? \"TypeScript\" : \"JavaScript\"}`,\r\n );\r\n console.log(\r\n ` Target: ${config.edge ? \"Edge/Serverless\" : \"Node.js Server\"}\\n`,\r\n );\r\n\r\n const projectPath = path.join(process.cwd(), config.name);\r\n\r\n // Check if directory exists\r\n try {\r\n await fs.access(projectPath);\r\n // If we reach here, the directory EXISTS\r\n if (!options.force) {\r\n throw new Error(\r\n `Directory \"${config.name}\" already exists. Use --force to overwrite.`,\r\n );\r\n }\r\n } catch (err) {\r\n // ENOENT = directory doesn't exist = good, fall through to scaffolding\r\n const isNotFound =\r\n err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\";\r\n if (!isNotFound) throw err;\r\n // else: directory doesn't exist, continue normally\r\n }\r\n\r\n // Detect package manager\r\n const packageManager = detectPackageManager();\r\n console.log(`Using package manager: ${packageManager}\\n`);\r\n\r\n // Create project structure (without dependencies in package.json)\r\n await createProjectStructure(projectPath, config);\r\n\r\n // Install dependencies unless --skip-install\r\n if (!options.skipInstall) {\r\n console.log(\"\\n📥 Installing dependencies...\\n\");\r\n await installDependencies(projectPath, config, packageManager);\r\n }\r\n\r\n // Print success message\r\n printSuccessMessage(config, options.skipInstall);\r\n}\r\n\r\n// ============================================================================\r\n// Package Manager Detection & Installation\r\n// ============================================================================\r\n\r\n/**\r\n * Detect which package manager to use\r\n * Priority: pnpm > yarn > bun > npm (based on lockfile or global availability)\r\n */\r\nfunction detectPackageManager(): PackageManager {\r\n // Check for lockfiles in current directory (user preference)\r\n try {\r\n const cwd = process.cwd();\r\n if (existsSync(path.join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\r\n if (existsSync(path.join(cwd, \"yarn.lock\"))) return \"yarn\";\r\n if (existsSync(path.join(cwd, \"bun.lockb\"))) return \"bun\";\r\n if (existsSync(path.join(cwd, \"package-lock.json\"))) return \"npm\";\r\n } catch {\r\n // Ignore errors\r\n }\r\n\r\n // Check which package managers are available\r\n if (isCommandAvailable(\"pnpm\")) return \"pnpm\";\r\n if (isCommandAvailable(\"yarn\")) return \"yarn\";\r\n if (isCommandAvailable(\"bun\")) return \"bun\";\r\n\r\n // Default to npm\r\n return \"npm\";\r\n}\r\n\r\n/**\r\n * Check if a command is available in PATH\r\n */\r\nfunction isCommandAvailable(command: string): boolean {\r\n try {\r\n execSync(`${command} --version`, { stdio: \"ignore\" });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Sync check if file exists (ESM-compatible — no require())\r\n */\r\nfunction existsSync(filePath: string): boolean {\r\n try {\r\n accessSync(filePath);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Install dependencies using the detected package manager\r\n */\r\nasync function installDependencies(\r\n projectPath: string,\r\n config: ProjectConfig,\r\n pm: PackageManager,\r\n): Promise<void> {\r\n // Build dependency lists\r\n const deps = [\r\n \"@classytic/arc@latest\",\r\n \"fastify@latest\",\r\n \"@fastify/cors@latest\",\r\n \"@fastify/helmet@latest\",\r\n \"@fastify/rate-limit@latest\",\r\n \"@fastify/sensible@latest\",\r\n \"@fastify/under-pressure@latest\",\r\n \"dotenv@latest\",\r\n ];\r\n\r\n if (config.auth === \"better-auth\") {\r\n deps.push(\"better-auth@latest\", \"mongodb@latest\");\r\n } else {\r\n deps.push(\"@fastify/jwt@latest\", \"bcryptjs@latest\");\r\n }\r\n\r\n if (config.adapter === \"mongokit\") {\r\n deps.push(\"@classytic/mongokit@latest\", \"mongoose@latest\");\r\n }\r\n\r\n const devDeps = [\"vitest@latest\", \"pino-pretty@latest\"];\r\n\r\n if (config.typescript) {\r\n devDeps.push(\"typescript@latest\", \"@types/node@latest\", \"tsx@latest\");\r\n }\r\n\r\n // Build install commands based on package manager\r\n const installCmd = getInstallCommand(pm, deps, false);\r\n const installDevCmd = getInstallCommand(pm, devDeps, true);\r\n\r\n // Run installation\r\n console.log(` Installing dependencies...`);\r\n await runCommand(installCmd, projectPath);\r\n\r\n console.log(` Installing dev dependencies...`);\r\n await runCommand(installDevCmd, projectPath);\r\n\r\n console.log(`\\nDependencies installed successfully.`);\r\n}\r\n\r\n/**\r\n * Get the install command for a package manager\r\n */\r\nfunction getInstallCommand(\r\n pm: PackageManager,\r\n packages: string[],\r\n isDev: boolean,\r\n): string {\r\n const pkgList = packages.join(\" \");\r\n\r\n switch (pm) {\r\n case \"pnpm\":\r\n return `pnpm add ${isDev ? \"-D\" : \"\"} ${pkgList}`;\r\n case \"yarn\":\r\n return `yarn add ${isDev ? \"-D\" : \"\"} ${pkgList}`;\r\n case \"bun\":\r\n return `bun add ${isDev ? \"-d\" : \"\"} ${pkgList}`;\r\n case \"npm\":\r\n default:\r\n return `npm install ${isDev ? \"--save-dev\" : \"\"} ${pkgList}`;\r\n }\r\n}\r\n\r\n/**\r\n * Run a shell command in a directory\r\n */\r\nfunction runCommand(command: string, cwd: string): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const isWindows = process.platform === \"win32\";\r\n const shell = isWindows ? \"cmd\" : \"/bin/sh\";\r\n const shellFlag = isWindows ? \"/c\" : \"-c\";\r\n\r\n const child = spawn(shell, [shellFlag, command], {\r\n cwd,\r\n stdio: \"inherit\",\r\n env: { ...process.env, FORCE_COLOR: \"1\" },\r\n });\r\n\r\n child.on(\"close\", (code) => {\r\n if (code === 0) {\r\n resolve();\r\n } else {\r\n reject(new Error(`Command failed with exit code ${code}`));\r\n }\r\n });\r\n\r\n child.on(\"error\", reject);\r\n });\r\n}\r\n\r\n// ============================================================================\r\n// Configuration Gathering\r\n// ============================================================================\r\n\r\nasync function gatherConfig(options: InitOptions): Promise<ProjectConfig> {\r\n const rl = readline.createInterface({\r\n input: process.stdin,\r\n output: process.stdout,\r\n });\r\n\r\n const question = (prompt: string): Promise<string> =>\r\n new Promise((resolve) => rl.question(prompt, resolve));\r\n\r\n // Non-interactive mode: if a project name was provided via args, skip prompts\r\n // and use defaults for any unspecified options\r\n const nonInteractive = !!options.name;\r\n\r\n try {\r\n // Project name\r\n const name =\r\n options.name || (await question(\"Project name: \")) || \"my-arc-app\";\r\n\r\n // Adapter choice\r\n let adapter: \"mongokit\" | \"custom\" = options.adapter || \"mongokit\";\r\n if (!options.adapter && !nonInteractive) {\r\n const adapterChoice = await question(\r\n \"Database adapter [1=MongoKit (recommended), 2=Custom]: \",\r\n );\r\n adapter = adapterChoice === \"2\" ? \"custom\" : \"mongokit\";\r\n }\r\n\r\n // Auth strategy\r\n let auth: \"jwt\" | \"better-auth\" = options.auth || \"better-auth\";\r\n if (!options.auth && !nonInteractive) {\r\n const authChoice = await question(\r\n \"Auth strategy [1=Better Auth (recommended), 2=Arc JWT]: \",\r\n );\r\n auth = authChoice === \"2\" ? \"jwt\" : \"better-auth\";\r\n }\r\n\r\n // Tenant mode\r\n let tenant: \"multi\" | \"single\" = options.tenant || \"single\";\r\n if (!options.tenant && !nonInteractive) {\r\n const tenantChoice = await question(\r\n \"Tenant mode [1=Single-tenant, 2=Multi-tenant]: \",\r\n );\r\n tenant = tenantChoice === \"2\" ? \"multi\" : \"single\";\r\n }\r\n\r\n // TypeScript or JavaScript\r\n let typescript = options.typescript ?? true;\r\n if (options.typescript === undefined && !nonInteractive) {\r\n const tsChoice = await question(\r\n \"Language [1=TypeScript (recommended), 2=JavaScript]: \",\r\n );\r\n typescript = tsChoice !== \"2\";\r\n }\r\n\r\n // Environment/Target choice\r\n let edge = options.edge ?? false;\r\n if (options.edge === undefined && !nonInteractive) {\r\n const edgeChoice = await question(\r\n \"Deployment target [1=Node.js Server (default), 2=Edge/Serverless]: \",\r\n );\r\n edge = edgeChoice === \"2\";\r\n }\r\n\r\n return { name, adapter, auth, tenant, typescript, edge };\r\n } finally {\r\n rl.close();\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Project Structure Creation\r\n// ============================================================================\r\n\r\nasync function createProjectStructure(\r\n projectPath: string,\r\n config: ProjectConfig,\r\n): Promise<void> {\r\n const ext = config.typescript ? \"ts\" : \"js\";\r\n\r\n // Create directories - Clean architecture (organized by resource, no barrels)\r\n const dirs = [\r\n \"\",\r\n \"src\",\r\n \"src/config\", // Config & env loading (import first!)\r\n \"src/shared\", // Shared utilities (adapters, presets, permissions)\r\n \"src/shared/presets\", // Preset definitions\r\n \"src/plugins\", // App-specific plugins\r\n \"src/resources\", // Resource definitions\r\n ...(config.auth === \"jwt\"\r\n ? [\r\n \"src/resources/user\", // User resource (user.model, user.repository, etc.)\r\n \"src/resources/auth\", // Auth resource (auth.resource, auth.handlers, etc.)\r\n ]\r\n : []),\r\n \"src/resources/example\", // Example resource\r\n \"tests\",\r\n ];\r\n\r\n for (const dir of dirs) {\r\n await fs.mkdir(path.join(projectPath, dir), { recursive: true });\r\n console.log(` + Created: ${dir || \"/\"}`);\r\n }\r\n\r\n // Generate and write files\r\n const files: Record<string, string> = {\r\n \"package.json\": packageJsonTemplate(config),\r\n \".gitignore\": gitignoreTemplate(),\r\n \".env.example\": envExampleTemplate(config),\r\n \".env.dev\": envDevTemplate(config),\r\n \"README.md\": readmeTemplate(config),\r\n };\r\n\r\n // TypeScript config\r\n if (config.typescript) {\r\n files[\"tsconfig.json\"] = tsconfigTemplate();\r\n }\r\n\r\n // Vitest config (always needed for path alias resolution)\r\n files[\"vitest.config.ts\"] = vitestConfigTemplate(config);\r\n\r\n // Config files (env loader FIRST - imported before everything)\r\n files[`src/config/env.${ext}`] = envLoaderTemplate(config);\r\n files[`src/config/index.${ext}`] = configTemplate(config);\r\n\r\n // App factory + Entry point (separation for workers/tests)\r\n files[`src/app.${ext}`] = appTemplate(config);\r\n files[`src/index.${ext}`] = indexTemplate(config);\r\n\r\n // Shared utilities\r\n files[`src/shared/index.${ext}`] = sharedIndexTemplate(config);\r\n files[`src/shared/adapter.${ext}`] =\r\n config.adapter === \"mongokit\"\r\n ? createAdapterTemplate(config)\r\n : customAdapterTemplate(config);\r\n files[`src/shared/permissions.${ext}`] = permissionsTemplate(config);\r\n\r\n // Presets\r\n if (config.tenant === \"multi\") {\r\n files[`src/shared/presets/index.${ext}`] =\r\n presetsMultiTenantTemplate(config);\r\n files[`src/shared/presets/flexible-multi-tenant.${ext}`] =\r\n flexibleMultiTenantPresetTemplate(config);\r\n } else {\r\n files[`src/shared/presets/index.${ext}`] =\r\n presetsSingleTenantTemplate(config);\r\n }\r\n\r\n // Plugins (app-specific, easy to extend)\r\n files[`src/plugins/index.${ext}`] = pluginsIndexTemplate(config);\r\n\r\n // Resources (organized by folder, no barrels - prefixed filenames)\r\n files[`src/resources/index.${ext}`] = resourcesIndexTemplate(config);\r\n\r\n // Auth setup — depends on strategy\r\n if (config.auth === \"better-auth\") {\r\n // Better Auth: single config file, no manual auth handlers\r\n files[`src/auth.${ext}`] = betterAuthSetupTemplate(config);\r\n } else {\r\n // JWT: manual user model + auth handlers\r\n files[`src/resources/user/user.model.${ext}`] = userModelTemplate(config);\r\n files[`src/resources/user/user.repository.${ext}`] =\r\n userRepositoryTemplate(config);\r\n files[`src/resources/user/user.controller.${ext}`] =\r\n userControllerTemplate(config);\r\n files[`src/resources/auth/auth.resource.${ext}`] =\r\n authResourceTemplate(config);\r\n files[`src/resources/auth/auth.handlers.${ext}`] =\r\n authHandlersTemplate(config);\r\n files[`src/resources/auth/auth.schemas.${ext}`] =\r\n authSchemasTemplate(config);\r\n }\r\n\r\n // Example resource (src/resources/example/)\r\n files[`src/resources/example/example.model.${ext}`] =\r\n exampleModelTemplate(config);\r\n files[`src/resources/example/example.repository.${ext}`] =\r\n exampleRepositoryTemplate(config);\r\n files[`src/resources/example/example.resource.${ext}`] =\r\n exampleResourceTemplate(config);\r\n files[`src/resources/example/example.controller.${ext}`] =\r\n exampleControllerTemplate(config);\r\n files[`src/resources/example/example.schemas.${ext}`] =\r\n exampleSchemasTemplate(config);\r\n\r\n // Tests\r\n files[`tests/example.test.${ext}`] = exampleTestTemplate(config);\r\n if (config.auth === \"jwt\") {\r\n files[`tests/auth.test.${ext}`] = authTestTemplate(config);\r\n }\r\n\r\n // Save project config for CLI tools (generate, etc.)\r\n files[\".arcrc\"] =\r\n JSON.stringify(\r\n {\r\n adapter: config.adapter,\r\n auth: config.auth,\r\n tenant: config.tenant,\r\n typescript: config.typescript,\r\n },\r\n null,\r\n 2,\r\n ) + \"\\n\";\r\n\r\n // Write all files\r\n for (const [filePath, content] of Object.entries(files)) {\r\n const fullPath = path.join(projectPath, filePath);\r\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\r\n await fs.writeFile(fullPath, content);\r\n console.log(` + Created: ${filePath}`);\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Templates\r\n// ============================================================================\r\n\r\nfunction packageJsonTemplate(config: ProjectConfig): string {\r\n // Minimal package.json - dependencies are installed via package manager\r\n const scripts: Record<string, string> = config.typescript\r\n ? {\r\n dev: \"tsx watch src/index.ts\",\r\n build: \"tsc\",\r\n start: \"node dist/index.js\",\r\n test: \"vitest run\",\r\n \"test:watch\": \"vitest\",\r\n }\r\n : {\r\n dev: \"node --watch src/index.js\",\r\n start: \"node src/index.js\",\r\n test: \"vitest run\",\r\n \"test:watch\": \"vitest\",\r\n };\r\n\r\n // Subpath imports for clean DX\r\n const imports: Record<string, string> = config.typescript\r\n ? {\r\n \"#config/*\": \"./dist/config/*\",\r\n \"#shared/*\": \"./dist/shared/*\",\r\n \"#resources/*\": \"./dist/resources/*\",\r\n \"#plugins/*\": \"./dist/plugins/*\",\r\n }\r\n : {\r\n \"#config/*\": \"./src/config/*\",\r\n \"#shared/*\": \"./src/shared/*\",\r\n \"#resources/*\": \"./src/resources/*\",\r\n \"#plugins/*\": \"./src/plugins/*\",\r\n };\r\n\r\n return JSON.stringify(\r\n {\r\n name: config.name,\r\n version: \"1.0.0\",\r\n type: \"module\",\r\n main: config.typescript ? \"dist/index.js\" : \"src/index.js\",\r\n imports,\r\n scripts,\r\n engines: {\r\n node: \">=20\",\r\n },\r\n },\r\n null,\r\n 2,\r\n );\r\n}\r\n\r\nfunction tsconfigTemplate(): string {\r\n return JSON.stringify(\r\n {\r\n compilerOptions: {\r\n target: \"ES2022\",\r\n module: \"NodeNext\",\r\n moduleResolution: \"NodeNext\",\r\n lib: [\"ES2022\"],\r\n outDir: \"./dist\",\r\n rootDir: \"./src\",\r\n strict: true,\r\n esModuleInterop: true,\r\n skipLibCheck: true,\r\n forceConsistentCasingInFileNames: true,\r\n declaration: true,\r\n declarationMap: true,\r\n sourceMap: true,\r\n resolveJsonModule: true,\r\n paths: {\r\n \"#shared/*\": [\"./src/shared/*\"],\r\n \"#resources/*\": [\"./src/resources/*\"],\r\n \"#config/*\": [\"./src/config/*\"],\r\n \"#plugins/*\": [\"./src/plugins/*\"],\r\n },\r\n },\r\n include: [\"src/**/*\"],\r\n exclude: [\"node_modules\", \"dist\"],\r\n },\r\n null,\r\n 2,\r\n );\r\n}\r\n\r\nfunction vitestConfigTemplate(config: ProjectConfig): string {\r\n const srcDir = config.typescript ? \"./src\" : \"./src\";\r\n\r\n return `import { defineConfig } from 'vitest/config';\r\nimport { resolve } from 'path';\r\n\r\nexport default defineConfig({\r\n test: {\r\n globals: true,\r\n environment: 'node',\r\n },\r\n resolve: {\r\n alias: {\r\n '#config': resolve(__dirname, '${srcDir}/config'),\r\n '#shared': resolve(__dirname, '${srcDir}/shared'),\r\n '#resources': resolve(__dirname, '${srcDir}/resources'),\r\n '#plugins': resolve(__dirname, '${srcDir}/plugins'),\r\n },\r\n },\r\n});\r\n`;\r\n}\r\n\r\nfunction gitignoreTemplate(): string {\r\n return `# Dependencies\r\nnode_modules/\r\n\r\n# Build\r\ndist/\r\n*.js.map\r\n\r\n# Environment\r\n.env\r\n.env.local\r\n.env.*.local\r\n\r\n# IDE\r\n.vscode/\r\n.idea/\r\n*.swp\r\n*.swo\r\n\r\n# OS\r\n.DS_Store\r\nThumbs.db\r\n\r\n# Logs\r\n*.log\r\nnpm-debug.log*\r\n\r\n# Test coverage\r\ncoverage/\r\n`;\r\n}\r\n\r\nfunction envExampleTemplate(config: ProjectConfig): string {\r\n let content = `# Server\r\nPORT=8040\r\nHOST=0.0.0.0\r\nNODE_ENV=development\r\n`;\r\n\r\n if (config.auth === \"better-auth\") {\r\n content += `\r\n# Better Auth\r\nBETTER_AUTH_SECRET=your-32-character-minimum-secret-here\r\nFRONTEND_URL=http://localhost:3000\r\n\r\n# Google OAuth (optional)\r\n# GOOGLE_CLIENT_ID=\r\n# GOOGLE_CLIENT_SECRET=\r\n`;\r\n } else {\r\n content += `\r\n# JWT\r\nJWT_SECRET=your-32-character-minimum-secret-here\r\nJWT_EXPIRES_IN=7d\r\n`;\r\n }\r\n\r\n content += `\r\n# CORS - Allowed origins\r\n# Options:\r\n# * = allow all origins (not recommended for production)\r\n# Comma-separated list = specific origins only\r\nCORS_ORIGINS=http://localhost:3000,http://localhost:5173\r\n`;\r\n\r\n if (config.adapter === \"mongokit\") {\r\n content += `\r\n# MongoDB\r\nMONGODB_URI=mongodb://localhost:27017/${config.name}\r\n`;\r\n }\r\n\r\n if (config.tenant === \"multi\") {\r\n content += `\r\n# Multi-tenant\r\nORG_HEADER=x-organization-id\r\n`;\r\n }\r\n\r\n return content;\r\n}\r\n\r\nfunction readmeTemplate(config: ProjectConfig): string {\r\n const ext = config.typescript ? \"ts\" : \"js\";\r\n\r\n return `# ${config.name}\r\n\r\nBuilt with [Arc](https://github.com/classytic/arc) - Resource-Oriented Backend Framework\r\n\r\n## Quick Start\r\n\r\n\\`\\`\\`bash\r\n# Install dependencies\r\nnpm install\r\n\r\n# Start development server (uses .env.dev)\r\nnpm run dev\r\n\r\n# Run tests\r\nnpm test\r\n\\`\\`\\`\r\n\r\n## Project Structure\r\n\r\n\\`\\`\\`\r\nsrc/\r\n├── config/ # Configuration (loaded first)\r\n│ ├── env.${ext} # Env loader (import first!)\r\n│ └── index.${ext} # App config\r\n├── shared/ # Shared utilities\r\n│ ├── adapter.${ext} # ${config.adapter === \"mongokit\" ? \"MongoKit adapter factory\" : \"Custom adapter\"}\r\n│ ├── permissions.${ext} # Permission helpers\r\n│ └── presets/ # ${config.tenant === \"multi\" ? \"Multi-tenant presets\" : \"Standard presets\"}\r\n├── plugins/ # App-specific plugins\r\n│ └── index.${ext} # Plugin registry\r\n├── resources/ # API Resources\r\n│ ├── index.${ext} # Resource registry\r\n│ └── example/ # Example resource\r\n│ ├── index.${ext} # Resource definition\r\n│ ├── model.${ext} # Mongoose schema\r\n│ └── repository.${ext} # MongoKit repository\r\n├── app.${ext} # App factory (reusable)\r\n└── index.${ext} # Server entry point\r\ntests/\r\n└── example.test.${ext} # Example tests\r\n\\`\\`\\`\r\n\r\n## Architecture\r\n\r\n### Entry Points\r\n\r\n- **\\`src/index.${ext}\\`** - HTTP server entry point\r\n- **\\`src/app.${ext}\\`** - App factory (import for workers/tests)\r\n\r\n\\`\\`\\`${config.typescript ? \"typescript\" : \"javascript\"}\r\n// For workers or custom entry points:\r\nimport { createAppInstance } from './app.js';\r\n\r\nconst app = await createAppInstance();\r\n// Use app for your worker logic\r\n\\`\\`\\`\r\n\r\n### Adding Resources\r\n\r\n1. Create a new folder in \\`src/resources/\\`:\r\n\r\n\\`\\`\\`\r\nsrc/resources/product/\r\n├── index.${ext} # Resource definition\r\n├── model.${ext} # Mongoose schema\r\n└── repository.${ext} # MongoKit repository\r\n\\`\\`\\`\r\n\r\n2. Register in \\`src/resources/index.${ext}\\`:\r\n\r\n\\`\\`\\`${config.typescript ? \"typescript\" : \"javascript\"}\r\nimport productResource from './product/index.js';\r\n\r\nexport const resources = [\r\n exampleResource,\r\n productResource, // Add here\r\n];\r\n\\`\\`\\`\r\n\r\n### Adding Plugins\r\n\r\nAdd custom plugins in \\`src/plugins/index.${ext}\\`:\r\n\r\n\\`\\`\\`${config.typescript ? \"typescript\" : \"javascript\"}\r\nexport async function registerPlugins(app, deps) {\r\n const { config } = deps; // Explicit dependency injection\r\n\r\n await app.register(myCustomPlugin, { ...options });\r\n}\r\n\\`\\`\\`\r\n\r\n## CLI Commands\r\n\r\n\\`\\`\\`bash\r\n# Generate a new resource\r\narc generate resource product\r\n\r\n# Introspect existing schema\r\narc introspect\r\n\r\n# Generate API docs\r\narc docs\r\n\\`\\`\\`\r\n\r\n## Environment Files\r\n\r\n- \\`.env.development\\` / \\`.env.dev\\` - Development (default)\r\n- \\`.env.test\\` / \\`.env.qa\\` - Testing / QA\r\n- \\`.env.production\\` / \\`.env.prod\\` - Production\r\n- \\`.env\\` - Fallback\r\n\r\n## API Documentation\r\n\r\nAPI documentation is available via Scalar UI:\r\n\r\n- **Interactive UI**: [http://localhost:8040/docs](http://localhost:8040/docs)\r\n- **OpenAPI Spec**: [http://localhost:8040/_docs/openapi.json](http://localhost:8040/_docs/openapi.json)\r\n\r\n## API Endpoints\r\n\r\n| Method | Endpoint | Description |\r\n|--------|----------|-------------|\r\n| GET | /docs | API documentation (Scalar UI) |\r\n| GET | /_docs/openapi.json | OpenAPI 3.0 spec |\r\n| GET | /examples | List all |\r\n| GET | /examples/:id | Get by ID |\r\n| POST | /examples | Create |\r\n| PATCH | /examples/:id | Update |\r\n| DELETE | /examples/:id | Delete |\r\n`;\r\n}\r\n\r\nfunction indexTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * ${config.name} - Server Entry Point\r\n * Generated by Arc CLI\r\n *\r\n * This file starts the HTTP server.\r\n * For workers or other entry points, import createAppInstance from './app.js'\r\n */\r\n\r\n// Load environment FIRST (before any other imports)\r\nimport '#config/env.js';\r\n\r\nimport config from '#config/index.js';\r\n${config.adapter === \"mongokit\" ? \"import mongoose from 'mongoose';\" : \"\"}\r\nimport { createAppInstance } from './app.js';\r\n\r\nasync function main()${ts ? \": Promise<void>\" : \"\"} {\r\n console.log(\\`Environment: \\${config.env}\\`);\r\n${\r\n config.adapter === \"mongokit\"\r\n ? `\r\n // Connect to MongoDB\r\n await mongoose.connect(config.database.uri);\r\n console.log('Connected to MongoDB');\r\n`\r\n : \"\"\r\n}\r\n // Create and configure app\r\n const app = await createAppInstance();\r\n\r\n // Start server\r\n await app.listen({ port: config.server.port, host: config.server.host });\r\n console.log(\\`Server running at http://\\${config.server.host}:\\${config.server.port}\\`);\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error('Failed to start server:', err);\r\n process.exit(1);\r\n});\r\n`;\r\n}\r\n\r\nfunction appTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts\r\n ? \"import type { FastifyInstance } from 'fastify';\\n\"\r\n : \"\";\r\n\r\n const betterAuthImport =\r\n config.auth === \"better-auth\"\r\n ? `import { createBetterAuthAdapter } from '@classytic/arc/auth';\r\nimport { getAuth } from './auth.js';\r\n`\r\n : \"\";\r\n\r\n const authConfig =\r\n config.auth === \"better-auth\"\r\n ? config.tenant === \"multi\"\r\n ? `auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth: getAuth(), orgContext: true }) },`\r\n : `auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth: getAuth() }) },`\r\n : `auth: {\r\n type: 'jwt',\r\n jwt: { secret: config.jwt.secret },\r\n },`;\r\n\r\n return `/**\r\n * ${config.name} - App Factory\r\n * Generated by Arc CLI\r\n *\r\n * Creates and configures the Fastify app instance.\r\n * Can be imported by:\r\n * - index.ts (HTTP server)\r\n * - worker.ts (background workers)\r\n * - tests (integration tests)\r\n */\r\n\r\n${typeImport}import config from '#config/index.js';\r\nimport { createApp } from '@classytic/arc/factory';\r\n${betterAuthImport}\r\n// App-specific plugins\r\nimport { registerPlugins } from '#plugins/index.js';\r\n\r\n// Resource registry\r\nimport { registerResources } from '#resources/index.js';\r\n\r\n/**\r\n * Create a fully configured app instance\r\n *\r\n * @returns Configured Fastify instance ready to use\r\n */\r\nexport async function createAppInstance()${ts ? \": Promise<FastifyInstance>\" : \"\"} {\r\n // Create Arc app with base configuration\r\n const app = await createApp({\r\n preset: config.env === 'production' ? (${config.edge ? \"'edge'\" : \"'production'\"}) : 'development',\r\n ${authConfig}\r\n cors: {\r\n origin: config.cors.origins,\r\n methods: config.cors.methods,\r\n allowedHeaders: config.cors.allowedHeaders,\r\n credentials: config.cors.credentials,\r\n },\r\n trustProxy: true,\r\n });\r\n\r\n // Register app-specific plugins (explicit dependency injection)\r\n await registerPlugins(app, { config });\r\n\r\n // Register all resources\r\n await registerResources(app);\r\n\r\n return app;\r\n}\r\n\r\nexport default createAppInstance;\r\n`;\r\n}\r\n\r\nfunction envLoaderTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Environment Loader\r\n *\r\n * MUST be imported FIRST before any other imports.\r\n * Loads .env files based on NODE_ENV.\r\n *\r\n * Usage:\r\n * import './config/env.js'; // First line of entry point\r\n */\r\n\r\nimport dotenv from 'dotenv';\r\nimport { existsSync } from 'node:fs';\r\nimport { resolve } from 'node:path';\r\n\r\n/**\r\n * Normalize environment string to short form\r\n */\r\nfunction normalizeEnv(env${ts ? \": string | undefined\" : \"\"})${ts ? \": string\" : \"\"} {\r\n const normalized = (env || '').toLowerCase();\r\n if (normalized === 'production' || normalized === 'prod') return 'prod';\r\n if (normalized === 'test' || normalized === 'qa') return 'test';\r\n return 'dev';\r\n}\r\n\r\n// Determine environment\r\nconst env = normalizeEnv(process.env.NODE_ENV);\r\n\r\n// Load environment-specific .env file\r\nconst envFile = resolve(process.cwd(), \\`.env.\\${env}\\`);\r\nconst defaultEnvFile = resolve(process.cwd(), '.env');\r\n\r\nif (existsSync(envFile)) {\r\n dotenv.config({ path: envFile });\r\n console.log(\\`Loaded: .env.\\${env}\\`);\r\n} else if (existsSync(defaultEnvFile)) {\r\n dotenv.config({ path: defaultEnvFile });\r\n console.log('Loaded: .env');\r\n} else {\r\n console.warn('Warning: No .env file found');\r\n}\r\n\r\n// Export for reference\r\nexport const ENV = env;\r\n`;\r\n}\r\n\r\nfunction envDevTemplate(config: ProjectConfig): string {\r\n let content = `# Development Environment\r\nNODE_ENV=development\r\n\r\n# Server\r\nPORT=8040\r\nHOST=0.0.0.0\r\n`;\r\n\r\n if (config.auth === \"better-auth\") {\r\n content += `\r\n# Better Auth\r\nBETTER_AUTH_SECRET=dev-secret-change-in-production-min-32-chars\r\nFRONTEND_URL=http://localhost:3000\r\n\r\n# Google OAuth (optional — leave empty to disable)\r\nGOOGLE_CLIENT_ID=\r\nGOOGLE_CLIENT_SECRET=\r\n`;\r\n } else {\r\n content += `\r\n# JWT\r\nJWT_SECRET=dev-secret-change-in-production-min-32-chars\r\nJWT_EXPIRES_IN=7d\r\n`;\r\n }\r\n\r\n content += `\r\n# CORS - Allowed origins\r\n# Options:\r\n# * = allow all origins (not recommended for production)\r\n# Comma-separated list = specific origins only\r\nCORS_ORIGINS=http://localhost:3000,http://localhost:5173\r\n`;\r\n\r\n if (config.adapter === \"mongokit\") {\r\n content += `\r\n# MongoDB\r\nMONGODB_URI=mongodb://localhost:27017/${config.name}\r\n`;\r\n }\r\n\r\n if (config.tenant === \"multi\") {\r\n content += `\r\n# Multi-tenant\r\nORG_HEADER=x-organization-id\r\n`;\r\n }\r\n\r\n return content;\r\n}\r\n\r\nfunction pluginsIndexTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts\r\n ? \"import type { FastifyInstance } from 'fastify';\\n\"\r\n : \"\";\r\n const configType = ts ? \": { config: AppConfig }\" : \"\";\r\n const appType = ts ? \": FastifyInstance\" : \"\";\r\n\r\n let content = `/**\r\n * App Plugins Registry\r\n *\r\n * Register your app-specific plugins here.\r\n * Dependencies are passed explicitly (no shims, no magic).\r\n */\r\n\r\n${typeImport}${ts ? \"import type { AppConfig } from '../config/index.js';\\n\" : \"\"}import { openApiPlugin, scalarPlugin } from '@classytic/arc/docs';\r\nimport { errorHandlerPlugin } from '@classytic/arc/plugins';\r\n`;\r\n\r\n content += `\r\n/**\r\n * Register all app-specific plugins\r\n *\r\n * @param app - Fastify instance\r\n * @param deps - Explicit dependencies (config, services, etc.)\r\n */\r\nexport async function registerPlugins(\r\n app${appType},\r\n deps${configType}\r\n)${ts ? \": Promise<void>\" : \"\"} {\r\n const { config } = deps;\r\n\r\n // Error handling (CastError → 400, validation → 422, duplicate → 409)\r\n await app.register(errorHandlerPlugin, {\r\n includeStack: config.isDev,\r\n });\r\n\r\n // API Documentation (Scalar UI)\r\n // OpenAPI spec: /_docs/openapi.json\r\n // Scalar UI: /docs\r\n await app.register(openApiPlugin, {\r\n title: '${config.name} API',\r\n version: '1.0.0',\r\n description: 'API documentation for ${config.name}',\r\n apiPrefix: '/api',\r\n });\r\n await app.register(scalarPlugin, {\r\n routePrefix: '/docs',\r\n theme: 'default',\r\n });\r\n\r\n // Add your custom plugins here:\r\n // await app.register(myCustomPlugin, { ...options });\r\n}\r\n`;\r\n\r\n return content;\r\n}\r\n\r\nfunction resourcesIndexTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts\r\n ? \"import type { FastifyInstance } from 'fastify';\\n\"\r\n : \"\";\r\n const appType = ts ? \": FastifyInstance\" : \"\";\r\n\r\n const authImports =\r\n config.auth === \"jwt\"\r\n ? `\r\n// Auth resources (register, login, /users/me)\r\nimport { authResource, userProfileResource } from './auth/auth.resource.js';\r\n`\r\n : `\r\n// Auth is handled by Better Auth — routes at /api/auth/*\r\n// No manual auth resource needed.\r\n`;\r\n\r\n const authResources =\r\n config.auth === \"jwt\"\r\n ? ` authResource,\r\n userProfileResource,\r\n `\r\n : ` `;\r\n\r\n return `/**\r\n * Resources Registry\r\n *\r\n * Central registry for all API resources.\r\n * All resources are mounted under /api prefix via Fastify scoping.\r\n */\r\n\r\n${typeImport}${authImports}\r\n// App resources\r\nimport exampleResource from './example/example.resource.js';\r\n\r\n// Add more resources here:\r\n// import productResource from './product/product.resource.js';\r\n\r\n/**\r\n * All registered resources\r\n */\r\nexport const resources = [\r\n${authResources}exampleResource,\r\n]${ts ? \" as const\" : \"\"};\r\n\r\n/**\r\n * Register all resources with the app under a common prefix.\r\n * Fastify scoping ensures all routes are mounted at /api/*.\r\n * The apiPrefix option in openApiPlugin keeps OpenAPI docs in sync.\r\n */\r\nexport async function registerResources(app${appType}, prefix = '/api')${ts ? \": Promise<void>\" : \"\"} {\r\n await app.register(async (scope) => {\r\n for (const resource of resources) {\r\n await scope.register(resource.toPlugin());\r\n }\r\n }, { prefix });\r\n}\r\n`;\r\n}\r\n\r\nfunction sharedIndexTemplate(_config: ProjectConfig): string {\r\n return `/**\r\n * Shared Utilities\r\n *\r\n * Central exports for resource definitions.\r\n * Import from here for clean, consistent code.\r\n */\r\n\r\n// Adapter factory\r\nexport { createAdapter } from './adapter.js';\r\n\r\n// Core Arc exports\r\nexport { createMongooseAdapter, defineResource } from '@classytic/arc';\r\n\r\n// Permission helpers (core + application-level)\r\nexport * from './permissions.js';\r\n\r\n// Presets\r\nexport * from './presets/index.js';\r\n`;\r\n}\r\n\r\nfunction createAdapterTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * MongoKit Adapter Factory\r\n *\r\n * Creates Arc adapters using MongoKit repositories.\r\n * The repository handles query parsing via MongoKit's built-in QueryParser.\r\n */\r\n\r\nimport { createMongooseAdapter } from '@classytic/arc';\r\n${ts ? \"import type { Model } from 'mongoose';\\nimport type { Repository } from '@classytic/mongokit';\" : \"\"}\r\n\r\n/**\r\n * Create a MongoKit-powered adapter for a resource\r\n *\r\n * Note: Query parsing is handled by MongoKit's Repository class.\r\n * Just pass the model and repository - Arc handles the rest.\r\n */\r\nexport function createAdapter${ts ? \"<TDoc = any>\" : \"\"}(\r\n model${ts ? \": Model<TDoc>\" : \"\"},\r\n repository${ts ? \": Repository<TDoc>\" : \"\"}\r\n) {\r\n return createMongooseAdapter({\r\n model,\r\n repository,\r\n });\r\n}\r\n`;\r\n}\r\n\r\nfunction customAdapterTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Custom Adapter Factory\r\n *\r\n * Implement your own database adapter here.\r\n */\r\n\r\nimport { createMongooseAdapter } from '@classytic/arc';\r\n${ts ? \"import type { Model } from 'mongoose';\" : \"\"}\r\n\r\n/**\r\n * Create a custom adapter for a resource\r\n *\r\n * Implement this based on your database choice:\r\n * - Prisma: Use @classytic/prismakit (coming soon)\r\n * - Drizzle: Create custom adapter\r\n * - Raw SQL: Create custom adapter\r\n */\r\nexport function createAdapter${ts ? \"<TDoc>\" : \"\"}(\r\n model${ts ? \": Model<TDoc>\" : \"\"},\r\n repository${ts ? \": any\" : \"\"}\r\n)${ts ? \": ReturnType<typeof createMongooseAdapter>\" : \"\"} {\r\n // SCAFFOLD: Replace with your custom adapter implementation\r\n return createMongooseAdapter({\r\n model,\r\n repository,\r\n });\r\n}\r\n`;\r\n}\r\n\r\nfunction presetsMultiTenantTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Arc Presets - Multi-Tenant Configuration\r\n *\r\n * Pre-configured presets for multi-tenant applications.\r\n * Includes both strict and flexible tenant isolation options.\r\n */\r\n\r\nimport {\r\n multiTenantPreset,\r\n ownedByUserPreset,\r\n softDeletePreset,\r\n slugLookupPreset,\r\n} from '@classytic/arc/presets';\r\n\r\n// Flexible preset for mixed public/private routes\r\nexport { flexibleMultiTenantPreset } from './flexible-multi-tenant.js';\r\n\r\n/**\r\n * Organization-scoped preset (STRICT)\r\n * Always requires auth, always filters by organizationId.\r\n * Use for admin-only resources.\r\n */\r\nexport const orgScoped = multiTenantPreset({\r\n tenantField: 'organizationId',\r\n});\r\n\r\n/**\r\n * Owned by creator preset\r\n * Filters queries by createdBy field.\r\n */\r\nexport const ownedByCreator = ownedByUserPreset({\r\n ownerField: 'createdBy',\r\n});\r\n\r\n/**\r\n * Owned by user preset\r\n * For resources where userId references the owner.\r\n */\r\nexport const ownedByUser = ownedByUserPreset({\r\n ownerField: 'userId',\r\n});\r\n\r\n/**\r\n * Soft delete preset\r\n * Adds deletedAt filtering and restore endpoint.\r\n */\r\nexport const softDelete = softDeletePreset();\r\n\r\n/**\r\n * Slug lookup preset\r\n * Enables GET by slug in addition to ID.\r\n */\r\nexport const slugLookup = slugLookupPreset();\r\n\r\n// Export all presets\r\nexport const presets = {\r\n orgScoped,\r\n ownedByCreator,\r\n ownedByUser,\r\n softDelete,\r\n slugLookup,\r\n}${ts ? \" as const\" : \"\"};\r\n\r\nexport default presets;\r\n`;\r\n}\r\n\r\nfunction presetsSingleTenantTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Arc Presets - Single-Tenant Configuration\r\n *\r\n * Pre-configured presets for single-tenant applications.\r\n */\r\n\r\nimport {\r\n ownedByUserPreset,\r\n softDeletePreset,\r\n slugLookupPreset,\r\n} from '@classytic/arc/presets';\r\n\r\n/**\r\n * Owned by creator preset\r\n * Filters queries by createdBy field.\r\n */\r\nexport const ownedByCreator = ownedByUserPreset({\r\n ownerField: 'createdBy',\r\n});\r\n\r\n/**\r\n * Owned by user preset\r\n * For resources where userId references the owner.\r\n */\r\nexport const ownedByUser = ownedByUserPreset({\r\n ownerField: 'userId',\r\n});\r\n\r\n/**\r\n * Soft delete preset\r\n * Adds deletedAt filtering and restore endpoint.\r\n */\r\nexport const softDelete = softDeletePreset();\r\n\r\n/**\r\n * Slug lookup preset\r\n * Enables GET by slug in addition to ID.\r\n */\r\nexport const slugLookup = slugLookupPreset();\r\n\r\n// Export all presets\r\nexport const presets = {\r\n ownedByCreator,\r\n ownedByUser,\r\n softDelete,\r\n slugLookup,\r\n}${ts ? \" as const\" : \"\"};\r\n\r\nexport default presets;\r\n`;\r\n}\r\n\r\nfunction flexibleMultiTenantPresetTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeAnnotations = ts\r\n ? `\r\nimport { getOrgId, isElevated, isMember } from '@classytic/arc/scope';\r\nimport type { RequestScope } from '@classytic/arc/scope';\r\n\r\ninterface FlexibleMultiTenantOptions {\r\n tenantField?: string;\r\n}\r\n\r\ninterface PresetMiddlewares {\r\n list: ((request: any, reply: any) => Promise<void>)[];\r\n get: ((request: any, reply: any) => Promise<void>)[];\r\n create: ((request: any, reply: any) => Promise<void>)[];\r\n update: ((request: any, reply: any) => Promise<void>)[];\r\n delete: ((request: any, reply: any) => Promise<void>)[];\r\n}\r\n\r\ninterface Preset {\r\n [key: string]: unknown;\r\n name: string;\r\n middlewares: PresetMiddlewares;\r\n}\r\n`\r\n : `\r\nconst { getOrgId, isElevated, isMember } = require('@classytic/arc/scope');\r\n`;\r\n\r\n return `/**\r\n * Flexible Multi-Tenant Preset\r\n *\r\n * Smarter tenant filtering that works with public + authenticated routes.\r\n *\r\n * Philosophy:\r\n * - No org scope → No filtering (public data, all orgs)\r\n * - Org scope present → Filter by org\r\n * - Elevated scope → No filter (platform admin sees all)\r\n *\r\n * Uses request.scope (RequestScope) from Arc's scope system.\r\n */\r\n${typeAnnotations}\r\n/**\r\n * Create flexible tenant filter middleware.\r\n * Only filters when org context is present.\r\n */\r\nfunction createFlexibleTenantFilter(tenantField${ts ? \": string\" : \"\"}) {\r\n return async (request${ts ? \": any\" : \"\"}, reply${ts ? \": any\" : \"\"}) => {\r\n const scope${ts ? \": RequestScope\" : \"\"} = request.scope ?? { kind: 'public' };\r\n\r\n // Elevated scope — platform admin sees all, no filter\r\n if (isElevated(scope)) {\r\n request.log?.debug?.({ msg: 'Elevated scope — no tenant filter' });\r\n return;\r\n }\r\n\r\n // Member scope — filter by org\r\n if (isMember(scope)) {\r\n request.query = request.query ?? {};\r\n request.query._policyFilters = {\r\n ...(request.query._policyFilters ?? {}),\r\n [tenantField]: scope.organizationId,\r\n };\r\n request.log?.debug?.({ msg: 'Tenant filter applied', orgId: scope.organizationId, tenantField });\r\n return;\r\n }\r\n\r\n // Public / authenticated — no org context, show all data (public routes)\r\n request.log?.debug?.({ msg: 'No org context — showing all data' });\r\n };\r\n}\r\n\r\n/**\r\n * Create tenant injection middleware.\r\n * Injects tenant ID into request body on create.\r\n */\r\nfunction createTenantInjection(tenantField${ts ? \": string\" : \"\"}) {\r\n return async (request${ts ? \": any\" : \"\"}, reply${ts ? \": any\" : \"\"}) => {\r\n const scope${ts ? \": RequestScope\" : \"\"} = request.scope ?? { kind: 'public' };\r\n const orgId = getOrgId(scope);\r\n\r\n // Fail-closed: Require orgId for create operations\r\n if (!orgId) {\r\n return reply.code(403).send({\r\n success: false,\r\n error: 'Forbidden',\r\n message: 'Organization context required to create resources',\r\n });\r\n }\r\n\r\n if (request.body) {\r\n request.body[tenantField] = orgId;\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Flexible Multi-Tenant Preset\r\n *\r\n * @param options.tenantField - Field name in database (default: 'organizationId')\r\n */\r\nexport function flexibleMultiTenantPreset(options${ts ? \": FlexibleMultiTenantOptions = {}\" : \" = {}\"})${ts ? \": Preset\" : \"\"} {\r\n const { tenantField = 'organizationId' } = options;\r\n\r\n const tenantFilter = createFlexibleTenantFilter(tenantField);\r\n const tenantInjection = createTenantInjection(tenantField);\r\n\r\n return {\r\n name: 'flexibleMultiTenant',\r\n middlewares: {\r\n list: [tenantFilter],\r\n get: [tenantFilter],\r\n create: [tenantInjection],\r\n update: [tenantFilter],\r\n delete: [tenantFilter],\r\n },\r\n };\r\n}\r\n\r\nexport default flexibleMultiTenantPreset;\r\n`;\r\n}\r\n\r\nfunction permissionsTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts ? \",\\n type PermissionCheck,\" : \"\";\r\n const returnType = ts ? \": PermissionCheck\" : \"\";\r\n\r\n let content = `/**\r\n * Permission Helpers\r\n *\r\n * Clean, type-safe permission definitions for resources.\r\n */\r\n\r\nimport {\r\n requireAuth,\r\n requireRoles,\r\n requireOwnership,\r\n allowPublic,\r\n anyOf,\r\n allOf,\r\n denyAll,\r\n when${typeImport}\r\n} from '@classytic/arc/permissions';\r\n\r\n// Re-export core helpers\r\nexport {\r\n allowPublic,\r\n requireAuth,\r\n requireRoles,\r\n requireOwnership,\r\n allOf,\r\n anyOf,\r\n denyAll,\r\n when,\r\n};\r\n\r\n// ============================================================================\r\n// Permission Helpers\r\n// ============================================================================\r\n\r\n/**\r\n * Require any authenticated user\r\n */\r\nexport const requireAuthenticated = ()${returnType} =>\r\n requireRoles(['user', 'admin', 'superadmin']);\r\n\r\n/**\r\n * Require admin or superadmin\r\n */\r\nexport const requireAdmin = ()${returnType} =>\r\n requireRoles(['admin', 'superadmin']);\r\n\r\n/**\r\n * Require superadmin only\r\n */\r\nexport const requireSuperadmin = ()${returnType} =>\r\n requireRoles(['superadmin']);\r\n`;\r\n\r\n if (config.tenant === \"multi\") {\r\n if (config.auth === \"better-auth\") {\r\n // Better Auth: use requireOrgRole() which checks per-org member.role\r\n content += `\r\n// ============================================================================\r\n// Better Auth Organization & Team Permission Helpers\r\n// ============================================================================\r\n\r\n/**\r\n * Organization-level guards (per-org member.role):\r\n *\r\n * - requireOrgRole(['admin','owner']) — checks member.role in active org\r\n * - requireOrgMembership() — just checks if user is in the org (any role)\r\n * - requireTeamMembership() — checks if user is in the active team\r\n *\r\n * These are DIFFERENT from platform-level helpers above (requireRoles checks user.roles).\r\n * Platform superadmin automatically bypasses all org role checks.\r\n *\r\n * IMPORTANT: When using Better Auth's Access Control (ac) with custom roles,\r\n * you MUST define ALL roles (owner, admin, member, + any custom) using the\r\n * same AC instance. BA's built-in defaults won't cover custom statements.\r\n * Omitting any role causes BA's hasPermission to fail silently for that role.\r\n *\r\n * @see multi-org-betterauth boilerplate (src/shared/access-control.ts) for the recommended pattern.\r\n */\r\nimport {\r\n requireOrgMembership,\r\n requireOrgRole,\r\n requireTeamMembership,\r\n} from '@classytic/arc/permissions';\r\nexport { requireOrgMembership, requireOrgRole, requireTeamMembership };\r\n\r\n/**\r\n * Require organization owner (checks member.role, not user.roles)\r\n */\r\nexport const requireOrgOwner = ()${returnType} =>\r\n requireOrgRole(['owner']);\r\n\r\n/**\r\n * Require organization manager or higher (checks member.role, not user.roles)\r\n */\r\nexport const requireOrgManager = ()${returnType} =>\r\n requireOrgRole(['manager', 'admin', 'owner']);\r\n\r\n/**\r\n * Require any organization member (any role)\r\n */\r\nexport const requireOrgStaff = ()${returnType} =>\r\n requireOrgMembership();\r\n`;\r\n } else {\r\n // JWT: no BA org plugin — use requireRoles() with user.roles\r\n content += `\r\n/**\r\n * Require organization owner (elevated scope auto-bypasses)\r\n */\r\nexport const requireOrgOwner = ()${returnType} =>\r\n requireRoles(['owner', 'admin', 'superadmin']);\r\n\r\n/**\r\n * Require organization manager or higher\r\n */\r\nexport const requireOrgManager = ()${returnType} =>\r\n requireRoles(['owner', 'manager', 'admin', 'superadmin']);\r\n\r\n/**\r\n * Require organization staff (any org member)\r\n */\r\nexport const requireOrgStaff = ()${returnType} =>\r\n requireRoles(['owner', 'manager', 'staff', 'admin', 'superadmin']);\r\n`;\r\n }\r\n }\r\n\r\n content += `\r\n// ============================================================================\r\n// Standard Permission Sets\r\n// ============================================================================\r\n\r\n/**\r\n * Public read, authenticated write (default for most resources)\r\n */\r\nexport const publicReadPermissions = {\r\n list: allowPublic(),\r\n get: allowPublic(),\r\n create: requireAuthenticated(),\r\n update: requireAuthenticated(),\r\n delete: requireAuthenticated(),\r\n};\r\n\r\n/**\r\n * All operations require authentication\r\n */\r\nexport const authenticatedPermissions = {\r\n list: requireAuth(),\r\n get: requireAuth(),\r\n create: requireAuth(),\r\n update: requireAuth(),\r\n delete: requireAuth(),\r\n};\r\n\r\n/**\r\n * Admin only permissions\r\n */\r\nexport const adminPermissions = {\r\n list: requireAdmin(),\r\n get: requireAdmin(),\r\n create: requireSuperadmin(),\r\n update: requireSuperadmin(),\r\n delete: requireSuperadmin(),\r\n};\r\n`;\r\n\r\n if (config.tenant === \"multi\") {\r\n content += `\r\n/**\r\n * Organization staff permissions\r\n */\r\nexport const orgStaffPermissions = {\r\n list: requireOrgStaff(),\r\n get: requireOrgStaff(),\r\n create: requireOrgManager(),\r\n update: requireOrgManager(),\r\n delete: requireOrgOwner(),\r\n};\r\n`;\r\n\r\n if (config.auth === \"better-auth\") {\r\n content += `\r\n/**\r\n * Team-scoped permissions (requires active team)\r\n * Uses Better Auth's team membership — flat groups, no team-level roles.\r\n */\r\nexport const teamScopedPermissions = {\r\n list: requireTeamMembership(),\r\n get: requireTeamMembership(),\r\n create: requireTeamMembership(),\r\n update: requireTeamMembership(),\r\n delete: requireOrgOwner(),\r\n};\r\n`;\r\n }\r\n }\r\n\r\n return content;\r\n}\r\n\r\nfunction configTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n const authTypeBlock =\r\n config.auth === \"better-auth\"\r\n ? `\r\n betterAuth: {\r\n secret: string;\r\n };\r\n frontend: {\r\n url: string;\r\n };`\r\n : `\r\n jwt: {\r\n secret: string;\r\n expiresIn: string;\r\n };`;\r\n\r\n let typeDefinition = \"\";\r\n if (ts) {\r\n typeDefinition = `\r\nexport interface AppConfig {\r\n env: string;\r\n isDev: boolean;\r\n isProd: boolean;\r\n server: {\r\n port: number;\r\n host: string;\r\n };${authTypeBlock}\r\n cors: {\r\n origins: string[] | boolean; // true = allow all ('*')\r\n methods: string[];\r\n allowedHeaders: string[];\r\n credentials: boolean;\r\n };${\r\n config.adapter === \"mongokit\"\r\n ? `\r\n database: {\r\n uri: string;\r\n };`\r\n : \"\"\r\n }${\r\n config.tenant === \"multi\"\r\n ? `\r\n org: {\r\n header: string;\r\n };`\r\n : \"\"\r\n }\r\n}\r\n`;\r\n }\r\n\r\n const authConfigBlock =\r\n config.auth === \"better-auth\"\r\n ? `\r\n betterAuth: {\r\n secret: process.env.BETTER_AUTH_SECRET || 'dev-secret-change-in-production-min-32-chars',\r\n },\r\n\r\n frontend: {\r\n url: process.env.FRONTEND_URL || 'http://localhost:3000',\r\n },`\r\n : `\r\n jwt: {\r\n secret: process.env.JWT_SECRET || 'dev-secret-change-in-production-min-32',\r\n expiresIn: process.env.JWT_EXPIRES_IN || '7d',\r\n },`;\r\n\r\n return `/**\r\n * Application Configuration\r\n *\r\n * All config is loaded from environment variables.\r\n * ENV file is loaded by config/env.ts (imported first in entry points).\r\n */\r\n${typeDefinition}\r\nconst config${ts ? \": AppConfig\" : \"\"} = {\r\n env: process.env.NODE_ENV || 'development',\r\n isDev: (process.env.NODE_ENV || 'development') !== 'production',\r\n isProd: process.env.NODE_ENV === 'production',\r\n\r\n server: {\r\n port: parseInt(process.env.PORT || '8040', 10),\r\n host: process.env.HOST || '0.0.0.0',\r\n },\r\n${authConfigBlock}\r\n\r\n cors: {\r\n // '*' = allow all origins (true), otherwise comma-separated list\r\n origins:\r\n process.env.CORS_ORIGINS === '*'\r\n ? true\r\n : (process.env.CORS_ORIGINS || 'http://localhost:3000').split(','),\r\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\r\n allowedHeaders: ['Content-Type', 'Authorization', 'x-organization-id', 'x-request-id'],\r\n credentials: true,\r\n },\r\n${\r\n config.adapter === \"mongokit\"\r\n ? `\r\n database: {\r\n uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/${config.name}',\r\n },\r\n`\r\n : \"\"\r\n}${\r\n config.tenant === \"multi\"\r\n ? `\r\n org: {\r\n header: process.env.ORG_HEADER || 'x-organization-id',\r\n },\r\n`\r\n : \"\"\r\n }};\r\n\r\nexport default config;\r\n`;\r\n}\r\n\r\nfunction exampleModelTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeExport = ts\r\n ? `\r\nexport type ExampleDocument = mongoose.InferSchemaType<typeof exampleSchema>;\r\nexport type ExampleModel = mongoose.Model<ExampleDocument>;\r\n`\r\n : \"\";\r\n\r\n return `/**\r\n * Example Model\r\n * Generated by Arc CLI\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\n\r\nconst exampleSchema = new mongoose.Schema(\r\n {\r\n name: { type: String, required: true, trim: true },\r\n description: { type: String, trim: true },\r\n isActive: { type: Boolean, default: true, index: true },\r\n${config.tenant === \"multi\" ? \" organizationId: { type: mongoose.Schema.Types.ObjectId, ref: 'Organization', required: true, index: true },\\n\" : \"\"} createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true },\r\n deletedAt: { type: Date, default: null, index: true },\r\n },\r\n {\r\n timestamps: true,\r\n toJSON: { virtuals: true },\r\n toObject: { virtuals: true },\r\n }\r\n);\r\n\r\n// Indexes for common queries\r\nexampleSchema.index({ name: 1 });\r\nexampleSchema.index({ deletedAt: 1, isActive: 1 });\r\n${config.tenant === \"multi\" ? \"exampleSchema.index({ organizationId: 1, deletedAt: 1 });\\n\" : \"\"}${typeExport}\r\nconst Example = mongoose.model${ts ? \"<ExampleDocument>\" : \"\"}('Example', exampleSchema);\r\n\r\nexport default Example;\r\n`;\r\n}\r\n\r\nfunction exampleRepositoryTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts\r\n ? \"import type { ExampleDocument } from './example.model.js';\\n\"\r\n : \"\";\r\n const generic = ts ? \"<ExampleDocument>\" : \"\";\r\n\r\n return `/**\r\n * Example Repository\r\n * Generated by Arc CLI\r\n *\r\n * MongoKit repository with plugins for:\r\n * - Soft delete (deletedAt filtering)\r\n * - Custom business logic methods\r\n */\r\n\r\nimport {\r\n Repository,\r\n softDeletePlugin,\r\n methodRegistryPlugin,\r\n} from '@classytic/mongokit';\r\n${typeImport}import Example from './example.model.js';\r\n\r\nclass ExampleRepository extends Repository${generic} {\r\n constructor() {\r\n super(Example, [\r\n methodRegistryPlugin(), // Required for plugin method registration\r\n softDeletePlugin(), // Soft delete support\r\n ]);\r\n }\r\n\r\n /**\r\n * Find all active (non-deleted) records\r\n */\r\n async findActive() {\r\n return this.Model.find({ isActive: true, deletedAt: null }).lean();\r\n }\r\n${\r\n config.tenant === \"multi\"\r\n ? `\r\n /**\r\n * Find active records for an organization\r\n */\r\n async findActiveByOrg(organizationId${ts ? \": string\" : \"\"}) {\r\n return this.Model.find({\r\n organizationId,\r\n isActive: true,\r\n deletedAt: null,\r\n }).lean();\r\n }\r\n`\r\n : \"\"\r\n}\r\n // Note: softDeletePlugin provides restore() and getDeleted() methods automatically\r\n}\r\n\r\nconst exampleRepository = new ExampleRepository();\r\n\r\nexport default exampleRepository;\r\nexport { ExampleRepository };\r\n`;\r\n}\r\n\r\nfunction exampleResourceTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Example Resource\r\n * Generated by Arc CLI\r\n *\r\n * A complete resource with:\r\n * - Model (Mongoose schema)\r\n * - Repository (MongoKit with plugins)\r\n * - Permissions (role-based access)\r\n * - Presets (soft delete${config.tenant === \"multi\" ? \", multi-tenant\" : \"\"})\r\n */\r\n\r\nimport { defineResource } from '@classytic/arc';\r\nimport { createAdapter } from '#shared/adapter.js';\r\nimport { ${config.tenant === \"multi\" ? \"orgStaffPermissions\" : \"publicReadPermissions\"} } from '#shared/permissions.js';\r\n${config.tenant === \"multi\" ? \"import { flexibleMultiTenantPreset } from '#shared/presets/flexible-multi-tenant.js';\\n\" : \"\"}import Example${ts ? \", { type ExampleDocument }\" : \"\"} from './example.model.js';\r\nimport exampleRepository from './example.repository.js';\r\nimport exampleController from './example.controller.js';\r\n\r\nconst exampleResource = defineResource${ts ? \"<ExampleDocument>\" : \"\"}({\r\n name: 'example',\r\n displayName: 'Examples',\r\n prefix: '/examples',\r\n\r\n adapter: createAdapter(Example, exampleRepository),\r\n controller: exampleController,\r\n\r\n presets: [\r\n 'softDelete',${\r\n config.tenant === \"multi\"\r\n ? `\r\n flexibleMultiTenantPreset({ tenantField: 'organizationId' }),`\r\n : \"\"\r\n }\r\n ],\r\n\r\n permissions: ${config.tenant === \"multi\" ? \"orgStaffPermissions\" : \"publicReadPermissions\"},\r\n\r\n // Add custom routes here:\r\n // additionalRoutes: [\r\n // {\r\n // method: 'GET',\r\n // path: '/custom',\r\n // summary: 'Custom endpoint',\r\n // handler: async (request, reply) => { ... },\r\n // },\r\n // ],\r\n});\r\n\r\nexport default exampleResource;\r\n`;\r\n}\r\n\r\nfunction exampleControllerTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Example Controller\r\n * Generated by Arc CLI\r\n *\r\n * BaseController provides CRUD operations with:\r\n * - Automatic pagination\r\n * - Query parsing\r\n * - Validation\r\n */\r\n\r\nimport { BaseController } from '@classytic/arc';\r\nimport exampleRepository from './example.repository.js';\r\nimport { exampleSchemaOptions } from './example.schemas.js';\r\n\r\nclass ExampleController extends BaseController {\r\n constructor() {\r\n super(exampleRepository${ts ? \" as any\" : \"\"}, {\r\n schemaOptions: exampleSchemaOptions,${\r\n config.tenant === \"multi\"\r\n ? `\r\n tenantField: 'organizationId', // Configurable tenant field for multi-tenant`\r\n : `\r\n // tenantField: 'organizationId', // For multi-tenant apps`\r\n }\r\n });\r\n }\r\n\r\n // Add custom controller methods here:\r\n // async customAction(request, reply) {\r\n // // Custom logic\r\n // }\r\n}\r\n\r\nconst exampleController = new ExampleController();\r\nexport default exampleController;\r\n`;\r\n}\r\n\r\nfunction exampleSchemasTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const multiTenantFields = config.tenant === \"multi\";\r\n\r\n return `/**\r\n * Example Schemas\r\n * Generated by Arc CLI\r\n *\r\n * Schema options for controller validation and query parsing\r\n */\r\n\r\nimport Example from './example.model.js';\r\nimport { buildCrudSchemasFromModel } from '@classytic/mongokit/utils';\r\n\r\n/**\r\n * CRUD Schemas with Field Rules\r\n * Auto-generated from Mongoose model\r\n */\r\nconst crudSchemas = buildCrudSchemasFromModel(Example, {\r\n strictAdditionalProperties: true,\r\n fieldRules: {\r\n // Mark fields as system-managed (excluded from create/update)\r\n // deletedAt: { systemManaged: true },\r\n },\r\n query: {\r\n filterableFields: {\r\n isActive: 'boolean',${\r\n multiTenantFields\r\n ? `\r\n organizationId: 'ObjectId',`\r\n : \"\"\r\n }\r\n createdAt: 'date',\r\n },\r\n },\r\n});\r\n\r\n// Schema options for controller\r\nexport const exampleSchemaOptions${ts ? \": any\" : \"\"} = {\r\n query: {${\r\n multiTenantFields\r\n ? `\r\n allowedPopulate: ['organizationId'],`\r\n : \"\"\r\n }\r\n filterableFields: {\r\n isActive: 'boolean',${\r\n multiTenantFields\r\n ? `\r\n organizationId: 'ObjectId',`\r\n : \"\"\r\n }\r\n createdAt: 'date',\r\n },\r\n },\r\n};\r\n\r\nexport default crudSchemas;\r\n`;\r\n}\r\n\r\nfunction exampleTestTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Example Resource Tests\r\n * Generated by Arc CLI\r\n *\r\n * Run tests: npm test\r\n * Watch mode: npm run test:watch\r\n */\r\n\r\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest';\r\n${config.adapter === \"mongokit\" ? \"import mongoose from 'mongoose';\\n\" : \"\"}import { createAppInstance } from '../src/app.js';\r\n${ts ? \"import type { FastifyInstance } from 'fastify';\\n\" : \"\"}\r\ndescribe('Example Resource', () => {\r\n let app${ts ? \": FastifyInstance\" : \"\"};\r\n\r\n beforeAll(async () => {\r\n${\r\n config.adapter === \"mongokit\"\r\n ? ` // Connect to test database\r\n const testDbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/${config.name}-test';\r\n await mongoose.connect(testDbUri);\r\n`\r\n : \"\"\r\n}\r\n // Create app instance\r\n app = await createAppInstance();\r\n await app.ready();\r\n });\r\n\r\n afterAll(async () => {\r\n await app.close();\r\n${config.adapter === \"mongokit\" ? \" await mongoose.connection.close();\" : \"\"}\r\n });\r\n\r\n describe('GET /examples', () => {\r\n it('should return a list of examples', async () => {\r\n const response = await app.inject({\r\n method: 'GET',\r\n url: '/examples',\r\n });\r\n\r\n expect(response.statusCode).toBe(200);\r\n const body = JSON.parse(response.body);\r\n expect(body).toHaveProperty('docs');\r\n expect(Array.isArray(body.docs)).toBe(true);\r\n });\r\n });\r\n\r\n describe('POST /examples', () => {\r\n it('should require authentication', async () => {\r\n const response = await app.inject({\r\n method: 'POST',\r\n url: '/examples',\r\n payload: { name: 'Test Example' },\r\n });\r\n\r\n // Should fail without auth token\r\n expect(response.statusCode).toBe(401);\r\n });\r\n });\r\n\r\n // Add more tests as needed:\r\n // - GET /examples/:id\r\n // - PATCH /examples/:id\r\n // - DELETE /examples/:id\r\n // - Custom endpoints\r\n});\r\n`;\r\n}\r\n\r\n// ============================================================================\r\n// User & Auth Templates\r\n// ============================================================================\r\n\r\nfunction userModelTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n const orgRoles =\r\n config.tenant === \"multi\"\r\n ? `\r\n// Organization roles (for multi-tenant)\r\nconst ORG_ROLES = ['owner', 'manager', 'hr', 'staff', 'contractor'] as const;\r\ntype OrgRole = typeof ORG_ROLES[number];\r\n`\r\n : \"\";\r\n\r\n const orgInterface =\r\n config.tenant === \"multi\"\r\n ? `\r\ntype UserOrganization = {\r\n organizationId: Types.ObjectId;\r\n organizationName: string;\r\n roles: OrgRole[];\r\n joinedAt: Date;\r\n};\r\n`\r\n : \"\";\r\n\r\n const orgSchema =\r\n config.tenant === \"multi\"\r\n ? `\r\n // Multi-org support\r\n organizations: [{\r\n organizationId: { type: Schema.Types.ObjectId, ref: 'Organization', required: true },\r\n organizationName: { type: String, required: true },\r\n roles: { type: [String], enum: ORG_ROLES, default: [] },\r\n joinedAt: { type: Date, default: () => new Date() },\r\n }],\r\n`\r\n : \"\";\r\n\r\n const orgMethods =\r\n config.tenant === \"multi\"\r\n ? `\r\n// Organization methods\r\nuserSchema.methods.getOrgRoles = function(orgId${ts ? \": Types.ObjectId | string\" : \"\"}) {\r\n const org = this.organizations.find(o => o.organizationId.toString() === orgId.toString());\r\n return org?.roles || [];\r\n};\r\n\r\nuserSchema.methods.hasOrgAccess = function(orgId${ts ? \": Types.ObjectId | string\" : \"\"}) {\r\n return this.organizations.some(o => o.organizationId.toString() === orgId.toString());\r\n};\r\n\r\nuserSchema.methods.addOrganization = function(\r\n organizationId${ts ? \": Types.ObjectId\" : \"\"},\r\n organizationName${ts ? \": string\" : \"\"},\r\n roles${ts ? \": OrgRole[]\" : \"\"} = []\r\n) {\r\n const existing = this.organizations.find(o => o.organizationId.toString() === organizationId.toString());\r\n if (existing) {\r\n existing.organizationName = organizationName;\r\n existing.roles = [...new Set([...existing.roles, ...roles])];\r\n } else {\r\n this.organizations.push({ organizationId, organizationName, roles, joinedAt: new Date() });\r\n }\r\n return this;\r\n};\r\n\r\nuserSchema.methods.removeOrganization = function(organizationId${ts ? \": Types.ObjectId\" : \"\"}) {\r\n this.organizations = this.organizations.filter(o => o.organizationId.toString() !== organizationId.toString());\r\n return this;\r\n};\r\n\r\n// Index for org queries\r\nuserSchema.index({ 'organizations.organizationId': 1 });\r\n`\r\n : \"\";\r\n\r\n const userType = ts\r\n ? `\r\ntype PlatformRole = 'user' | 'admin' | 'superadmin';\r\n\r\ntype User = {\r\n name: string;\r\n email: string;\r\n password: string;\r\n roles: PlatformRole[];${\r\n config.tenant === \"multi\"\r\n ? `\r\n organizations: UserOrganization[];`\r\n : \"\"\r\n }\r\n resetPasswordToken?: string;\r\n resetPasswordExpires?: Date;\r\n};\r\n\r\ntype UserMethods = {\r\n matchPassword: (enteredPassword: string) => Promise<boolean>;${\r\n config.tenant === \"multi\"\r\n ? `\r\n getOrgRoles: (orgId: Types.ObjectId | string) => OrgRole[];\r\n hasOrgAccess: (orgId: Types.ObjectId | string) => boolean;\r\n addOrganization: (orgId: Types.ObjectId, name: string, roles?: OrgRole[]) => UserDocument;\r\n removeOrganization: (orgId: Types.ObjectId) => UserDocument;`\r\n : \"\"\r\n }\r\n};\r\n\r\nexport type UserDocument = HydratedDocument<User, UserMethods>;\r\nexport type UserModel = Model<User, {}, UserMethods>;\r\n`\r\n : \"\";\r\n\r\n return `/**\r\n * User Model\r\n * Generated by Arc CLI\r\n */\r\n\r\nimport bcrypt from 'bcryptjs';\r\nimport mongoose${ts ? \", { type HydratedDocument, type Model, type Types }\" : \"\"} from 'mongoose';\r\n${orgRoles}\r\nconst { Schema } = mongoose;\r\n${orgInterface}${userType}\r\nconst userSchema = new Schema${ts ? \"<User, UserModel, UserMethods>\" : \"\"}(\r\n {\r\n name: { type: String, required: true, trim: true },\r\n email: {\r\n type: String,\r\n required: true,\r\n unique: true,\r\n lowercase: true,\r\n trim: true,\r\n },\r\n password: { type: String, required: true },\r\n\r\n // Platform roles\r\n roles: {\r\n type: [String],\r\n enum: ['user', 'admin', 'superadmin'],\r\n default: ['user'],\r\n },\r\n${orgSchema}\r\n // Password reset\r\n resetPasswordToken: String,\r\n resetPasswordExpires: Date,\r\n },\r\n { timestamps: true }\r\n);\r\n\r\n// Password hashing\r\nuserSchema.pre('save', async function() {\r\n if (!this.isModified('password')) return;\r\n const salt = await bcrypt.genSalt(10);\r\n this.password = await bcrypt.hash(this.password, salt);\r\n});\r\n\r\n// Password comparison\r\nuserSchema.methods.matchPassword = async function(enteredPassword${ts ? \": string\" : \"\"}) {\r\n return bcrypt.compare(enteredPassword, this.password);\r\n};\r\n${orgMethods}\r\n// Exclude password in JSON\r\nuserSchema.set('toJSON', {\r\n transform: (_doc, ret${ts ? \": any\" : \"\"}) => {\r\n delete ret.password;\r\n delete ret.resetPasswordToken;\r\n delete ret.resetPasswordExpires;\r\n return ret;\r\n },\r\n});\r\n\r\nconst User = mongoose.models.User${ts ? \" as UserModel\" : \"\"} || mongoose.model${ts ? \"<User, UserModel>\" : \"\"}('User', userSchema);\r\nexport default User;\r\n`;\r\n}\r\n\r\nfunction userRepositoryTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts\r\n ? \"import type { UserDocument } from './user.model.js';\\nimport type { ClientSession, Types } from 'mongoose';\\n\"\r\n : \"\";\r\n\r\n return `/**\r\n * User Repository\r\n * Generated by Arc CLI\r\n *\r\n * MongoKit repository with plugins for common operations\r\n */\r\n\r\nimport {\r\n Repository,\r\n methodRegistryPlugin,\r\n mongoOperationsPlugin,\r\n} from '@classytic/mongokit';\r\n${typeImport}import User from './user.model.js';\r\n\r\n${ts ? \"type ID = string | Types.ObjectId;\\n\" : \"\"}\r\nclass UserRepository extends Repository${ts ? \"<UserDocument>\" : \"\"} {\r\n constructor() {\r\n super(User${ts ? \" as any\" : \"\"}, [\r\n methodRegistryPlugin(),\r\n mongoOperationsPlugin(),\r\n ]);\r\n }\r\n\r\n /**\r\n * Find user by email\r\n */\r\n async findByEmail(email${ts ? \": string\" : \"\"}) {\r\n return this.Model.findOne({ email: email.toLowerCase().trim() });\r\n }\r\n\r\n /**\r\n * Find user by reset token\r\n */\r\n async findByResetToken(token${ts ? \": string\" : \"\"}) {\r\n return this.Model.findOne({\r\n resetPasswordToken: token,\r\n resetPasswordExpires: { $gt: Date.now() },\r\n });\r\n }\r\n\r\n /**\r\n * Check if email exists\r\n */\r\n async emailExists(email${ts ? \": string\" : \"\"})${ts ? \": Promise<boolean>\" : \"\"} {\r\n const result = await this.Model.exists({ email: email.toLowerCase().trim() });\r\n return !!result;\r\n }\r\n\r\n /**\r\n * Update user password (triggers hash middleware)\r\n */\r\n async updatePassword(userId${ts ? \": ID\" : \"\"}, newPassword${ts ? \": string\" : \"\"}, options${ts ? \": { session?: ClientSession }\" : \"\"} = {}) {\r\n const user = await this.Model.findById(userId).session(options.session ?? null);\r\n if (!user) throw new Error('User not found');\r\n\r\n user.password = newPassword;\r\n user.resetPasswordToken = undefined;\r\n user.resetPasswordExpires = undefined;\r\n await user.save({ session: options.session ?? undefined });\r\n return user;\r\n }\r\n\r\n /**\r\n * Set reset token\r\n */\r\n async setResetToken(userId${ts ? \": ID\" : \"\"}, token${ts ? \": string\" : \"\"}, expiresAt${ts ? \": Date\" : \"\"}) {\r\n return this.Model.findByIdAndUpdate(\r\n userId,\r\n { resetPasswordToken: token, resetPasswordExpires: expiresAt },\r\n { new: true }\r\n );\r\n }\r\n${\r\n config.tenant === \"multi\"\r\n ? `\r\n /**\r\n * Find users by organization\r\n */\r\n async findByOrganization(organizationId${ts ? \": ID\" : \"\"}) {\r\n return this.Model.find({ 'organizations.organizationId': organizationId })\r\n .select('-password -resetPasswordToken -resetPasswordExpires')\r\n .lean();\r\n }\r\n`\r\n : \"\"\r\n}\r\n}\r\n\r\nconst userRepository = new UserRepository();\r\nexport default userRepository;\r\nexport { UserRepository };\r\n`;\r\n}\r\n\r\nfunction userControllerTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * User Controller\r\n * Generated by Arc CLI\r\n *\r\n * BaseController for user management operations.\r\n * Used by auth resource for /users/me endpoints.\r\n */\r\n\r\nimport { BaseController } from '@classytic/arc';\r\nimport userRepository from './user.repository.js';\r\n\r\nclass UserController extends BaseController {\r\n constructor() {\r\n super(userRepository${ts ? \" as any\" : \"\"});\r\n }\r\n\r\n // Custom user operations can be added here\r\n}\r\n\r\nconst userController = new UserController();\r\nexport default userController;\r\n`;\r\n}\r\n\r\nfunction authResourceTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Auth Resource\r\n * Generated by Arc CLI\r\n *\r\n * Combined auth + user profile endpoints:\r\n * - POST /auth/register\r\n * - POST /auth/login\r\n * - POST /auth/refresh\r\n * - POST /auth/forgot-password\r\n * - POST /auth/reset-password\r\n * - GET /users/me\r\n * - PATCH /users/me\r\n */\r\n\r\nimport { defineResource } from '@classytic/arc';\r\nimport { allowPublic, requireAuth } from '@classytic/arc/permissions';\r\nimport { createAdapter } from '#shared/adapter.js';\r\nimport User from '../user/user.model.js';\r\nimport userRepository from '../user/user.repository.js';\r\nimport * as handlers from './auth.handlers.js';\r\nimport * as schemas from './auth.schemas.js';\r\n\r\n/**\r\n * Auth Resource - handles authentication\r\n */\r\nexport const authResource = defineResource({\r\n name: 'auth',\r\n displayName: 'Authentication',\r\n tag: 'Authentication',\r\n prefix: '/auth',\r\n\r\n adapter: createAdapter(User${ts ? \" as any\" : \"\"}, userRepository${ts ? \" as any\" : \"\"}),\r\n disableDefaultRoutes: true,\r\n\r\n additionalRoutes: [\r\n {\r\n method: 'POST',\r\n path: '/register',\r\n summary: 'Register new user',\r\n permissions: allowPublic(),\r\n handler: handlers.register,\r\n wrapHandler: false,\r\n schema: { body: schemas.registerBody, response: { 201: schemas.successResponse } },\r\n },\r\n {\r\n method: 'POST',\r\n path: '/login',\r\n summary: 'User login',\r\n permissions: allowPublic(),\r\n handler: handlers.login,\r\n wrapHandler: false,\r\n schema: { body: schemas.loginBody, response: { 200: schemas.loginResponse } },\r\n },\r\n {\r\n method: 'POST',\r\n path: '/refresh',\r\n summary: 'Refresh access token',\r\n permissions: allowPublic(),\r\n handler: handlers.refreshToken,\r\n wrapHandler: false,\r\n schema: { body: schemas.refreshBody, response: { 200: schemas.tokenResponse } },\r\n },\r\n {\r\n method: 'POST',\r\n path: '/forgot-password',\r\n summary: 'Request password reset',\r\n permissions: allowPublic(),\r\n handler: handlers.forgotPassword,\r\n wrapHandler: false,\r\n schema: { body: schemas.forgotBody, response: { 200: schemas.successResponse } },\r\n },\r\n {\r\n method: 'POST',\r\n path: '/reset-password',\r\n summary: 'Reset password with token',\r\n permissions: allowPublic(),\r\n handler: handlers.resetPassword,\r\n wrapHandler: false,\r\n schema: { body: schemas.resetBody, response: { 200: schemas.successResponse } },\r\n },\r\n ],\r\n});\r\n\r\n/**\r\n * User Profile Resource - handles /users/me\r\n */\r\nexport const userProfileResource = defineResource({\r\n name: 'user-profile',\r\n displayName: 'User Profile',\r\n tag: 'User Profile',\r\n prefix: '/users',\r\n\r\n adapter: createAdapter(User${ts ? \" as any\" : \"\"}, userRepository${ts ? \" as any\" : \"\"}),\r\n disableDefaultRoutes: true,\r\n\r\n additionalRoutes: [\r\n {\r\n method: 'GET',\r\n path: '/me',\r\n summary: 'Get current user profile',\r\n permissions: requireAuth(),\r\n handler: handlers.getUserProfile,\r\n wrapHandler: false,\r\n schema: { response: { 200: schemas.userProfileResponse } },\r\n },\r\n {\r\n method: 'PATCH',\r\n path: '/me',\r\n summary: 'Update current user profile',\r\n permissions: requireAuth(),\r\n handler: handlers.updateUserProfile,\r\n wrapHandler: false,\r\n schema: { body: schemas.updateUserBody, response: { 200: schemas.userProfileResponse } },\r\n },\r\n ],\r\n});\r\n\r\nexport default authResource;\r\n`;\r\n}\r\n\r\nfunction betterAuthSetupTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const mongoImport =\r\n config.adapter === \"mongokit\"\r\n ? `import mongoose from 'mongoose';\r\nimport { mongodbAdapter } from 'better-auth/adapters/mongodb';`\r\n : \"\";\r\n\r\n const dbAdapter =\r\n config.adapter === \"mongokit\"\r\n ? config.typescript\r\n ? `database: mongodbAdapter(mongoose.connection.getClient().db() as any),`\r\n : `database: mongodbAdapter(mongoose.connection.getClient().db()),`\r\n : `// Configure your database adapter here\r\n // See: https://www.better-auth.com/docs/concepts/database`;\r\n\r\n const orgPlugin =\r\n config.tenant === \"multi\"\r\n ? `\r\nimport { organization } from 'better-auth/plugins/organization';\r\nimport { bearer } from 'better-auth/plugins/bearer';`\r\n : \"\";\r\n\r\n const orgPluginUsage =\r\n config.tenant === \"multi\"\r\n ? `\r\n plugins: [\r\n bearer(),\r\n organization({\r\n allowUserToCreateOrganization: true,\r\n creatorRole: 'owner',\r\n teams: {\r\n enabled: true,\r\n },\r\n }),\r\n ],`\r\n : \"\";\r\n\r\n const googleProvider = `\r\n // Google OAuth (enabled when env vars are set)\r\n ...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET\r\n ? {\r\n socialProviders: {\r\n google: {\r\n clientId: process.env.GOOGLE_CLIENT_ID,\r\n clientSecret: process.env.GOOGLE_CLIENT_SECRET,\r\n },\r\n },\r\n }\r\n : {}),`;\r\n\r\n return `/**\r\n * Better Auth Configuration\r\n * Generated by Arc CLI\r\n *\r\n * Authentication is handled entirely by Better Auth.\r\n * Routes are registered automatically at /api/auth/*\r\n *\r\n * Better Auth manages these collections:\r\n * - user, session, account${config.tenant === \"multi\" ? \", organization, member, invitation, team, teamMember\" : \"\"}\r\n *\r\n * @see https://www.better-auth.com/docs\r\n */\r\n\r\nimport { betterAuth } from 'better-auth';\r\n${mongoImport}${orgPlugin}\r\nimport config from '#config/index.js';\r\n\r\nlet _auth${ts ? \": ReturnType<typeof betterAuth> | null\" : \"\"} = null;\r\n\r\n/**\r\n * Get the Better Auth instance (lazy singleton)\r\n *\r\n * Must be called AFTER database connection is established.\r\n */\r\nexport function getAuth()${ts ? \": ReturnType<typeof betterAuth>\" : \"\"} {\r\n if (process.env.NODE_ENV === 'production' && !process.env.BETTER_AUTH_SECRET) {\r\n throw new Error('BETTER_AUTH_SECRET is required in production (min 32 chars)');\r\n }\r\n\r\n if (!_auth) {\r\n _auth = betterAuth({\r\n secret: config.betterAuth.secret,\r\n baseURL: process.env.BETTER_AUTH_URL || \\`http://localhost:\\${config.server.port}\\`,\r\n basePath: '/api/auth',\r\n\r\n ${dbAdapter}\r\n${\r\n config.tenant === \"multi\"\r\n ? `\r\n user: {\r\n additionalFields: {\r\n roles: {\r\n type: 'string[]',\r\n defaultValue: ['user'],\r\n required: false,\r\n input: false, // Cannot be set during signup\r\n },\r\n },\r\n },\r\n`\r\n : \"\"\r\n}\r\n emailAndPassword: {\r\n enabled: true,\r\n minPasswordLength: 6,\r\n },\r\n${googleProvider}\r\n${orgPluginUsage}\r\n session: {\r\n cookieCache: {\r\n enabled: true,\r\n maxAge: 5 * 60, // 5 minutes\r\n },\r\n },\r\n\r\n trustedOrigins: [config.frontend.url],\r\n\r\n rateLimit: {\r\n enabled: process.env.NODE_ENV === 'production',\r\n },\r\n });\r\n }\r\n\r\n return _auth;\r\n}\r\n\r\nexport default getAuth;\r\n`;\r\n}\r\n\r\nfunction authHandlersTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeAnnotations = ts\r\n ? `\r\nimport type { FastifyRequest, FastifyReply } from 'fastify';\r\n// Load Arc auth type augmentations (adds request.server.auth typings)\r\nimport '@classytic/arc/auth';\r\n`\r\n : \"\";\r\n\r\n return `/**\r\n * Auth Handlers\r\n * Generated by Arc CLI\r\n *\r\n * Uses Arc's built-in JWT utilities via fastify.auth (provided by @fastify/jwt v10).\r\n * No standalone jsonwebtoken dependency needed.\r\n */\r\n\r\nimport userRepository from '../user/user.repository.js';\r\n${typeAnnotations}\r\n\r\n/**\r\n * Register new user\r\n */\r\nexport async function register(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const { name, email, password } = request.body${ts ? \" as any\" : \"\"};\r\n\r\n // Check if email exists\r\n if (await userRepository.emailExists(email)) {\r\n return reply.code(400).send({ success: false, message: 'Email already registered' });\r\n }\r\n\r\n // Create user\r\n await userRepository.create({ name, email, password, roles: ['user'] });\r\n\r\n return reply.code(201).send({ success: true, message: 'User registered successfully' });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Register error');\r\n return reply.code(500).send({ success: false, message: 'Registration failed' });\r\n }\r\n}\r\n\r\n/**\r\n * Login user\r\n */\r\nexport async function login(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const { email, password } = request.body${ts ? \" as any\" : \"\"};\r\n\r\n const user = await userRepository.findByEmail(email);\r\n if (!user || !(await user.matchPassword(password))) {\r\n return reply.code(401).send({ success: false, message: 'Invalid credentials' });\r\n }\r\n\r\n const tokens = request.server.auth.issueTokens({ id: user._id.toString(), roles: user.roles });\r\n\r\n return reply.send({\r\n success: true,\r\n user: { id: user._id, name: user.name, email: user.email, roles: user.roles },\r\n ...tokens,\r\n });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Login error');\r\n return reply.code(500).send({ success: false, message: 'Login failed' });\r\n }\r\n}\r\n\r\n/**\r\n * Refresh access token\r\n */\r\nexport async function refreshToken(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const { token } = request.body${ts ? \" as any\" : \"\"};\r\n if (!token) {\r\n return reply.code(401).send({ success: false, message: 'Refresh token required' });\r\n }\r\n\r\n const decoded = request.server.auth.verifyRefreshToken(token)${ts ? \" as { id: string }\" : \"\"};\r\n const tokens = request.server.auth.issueTokens({ id: decoded.id });\r\n\r\n return reply.send({ success: true, ...tokens });\r\n } catch {\r\n return reply.code(401).send({ success: false, message: 'Invalid refresh token' });\r\n }\r\n}\r\n\r\n/**\r\n * Forgot password\r\n */\r\nexport async function forgotPassword(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const { email } = request.body${ts ? \" as any\" : \"\"};\r\n const user = await userRepository.findByEmail(email);\r\n\r\n if (user) {\r\n const { randomBytes } = await import('node:crypto');\r\n const token = randomBytes(32).toString('hex');\r\n const expires = new Date(Date.now() + 3600000); // 1 hour\r\n await userRepository.setResetToken(user._id, token, expires);\r\n // SCAFFOLD: Integrate your email provider to send the reset link\r\n request.log.info(\\`Password reset requested for \\${email}\\`);\r\n }\r\n\r\n // Always return success to prevent email enumeration\r\n return reply.send({ success: true, message: 'If email exists, reset link sent' });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Forgot password error');\r\n return reply.code(500).send({ success: false, message: 'Failed to process request' });\r\n }\r\n}\r\n\r\n/**\r\n * Reset password\r\n */\r\nexport async function resetPassword(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const { token, newPassword } = request.body${ts ? \" as any\" : \"\"};\r\n const user = await userRepository.findByResetToken(token);\r\n\r\n if (!user) {\r\n return reply.code(400).send({ success: false, message: 'Invalid or expired token' });\r\n }\r\n\r\n await userRepository.updatePassword(user._id, newPassword);\r\n return reply.send({ success: true, message: 'Password has been reset' });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Reset password error');\r\n return reply.code(500).send({ success: false, message: 'Failed to reset password' });\r\n }\r\n}\r\n\r\n/**\r\n * Get current user profile\r\n */\r\nexport async function getUserProfile(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const userId = (request${ts ? \" as any\" : \"\"}).user?._id || (request${ts ? \" as any\" : \"\"}).user?.id;\r\n const user = await userRepository.getById(userId);\r\n\r\n if (!user) {\r\n return reply.code(404).send({ success: false, message: 'User not found' });\r\n }\r\n\r\n return reply.send({ success: true, data: user });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Get profile error');\r\n return reply.code(500).send({ success: false, message: 'Failed to get profile' });\r\n }\r\n}\r\n\r\n/**\r\n * Update current user profile\r\n */\r\nexport async function updateUserProfile(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const userId = (request${ts ? \" as any\" : \"\"}).user?._id || (request${ts ? \" as any\" : \"\"}).user?.id;\r\n const updates = { ...request.body${ts ? \" as any\" : \"\"} };\r\n\r\n // Prevent updating protected fields\r\n if ('password' in updates) delete updates.password;\r\n if ('roles' in updates) delete updates.roles;\r\n if ('organizations' in updates) delete updates.organizations;\r\n\r\n const user = await userRepository.Model.findByIdAndUpdate(userId, updates, { new: true });\r\n return reply.send({ success: true, data: user });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Update profile error');\r\n return reply.code(500).send({ success: false, message: 'Failed to update profile' });\r\n }\r\n}\r\n`;\r\n}\r\n\r\nfunction authSchemasTemplate(_config: ProjectConfig): string {\r\n return `/**\r\n * Auth Schemas\r\n * Generated by Arc CLI\r\n */\r\n\r\nexport const registerBody = {\r\n type: 'object',\r\n required: ['name', 'email', 'password'],\r\n properties: {\r\n name: { type: 'string', minLength: 2 },\r\n email: { type: 'string', format: 'email' },\r\n password: { type: 'string', minLength: 6 },\r\n },\r\n};\r\n\r\nexport const loginBody = {\r\n type: 'object',\r\n required: ['email', 'password'],\r\n properties: {\r\n email: { type: 'string', format: 'email' },\r\n password: { type: 'string' },\r\n },\r\n};\r\n\r\nexport const refreshBody = {\r\n type: 'object',\r\n required: ['token'],\r\n properties: {\r\n token: { type: 'string' },\r\n },\r\n};\r\n\r\nexport const forgotBody = {\r\n type: 'object',\r\n required: ['email'],\r\n properties: {\r\n email: { type: 'string', format: 'email' },\r\n },\r\n};\r\n\r\nexport const resetBody = {\r\n type: 'object',\r\n required: ['token', 'newPassword'],\r\n properties: {\r\n token: { type: 'string' },\r\n newPassword: { type: 'string', minLength: 6 },\r\n },\r\n};\r\n\r\nexport const updateUserBody = {\r\n type: 'object',\r\n properties: {\r\n name: { type: 'string', minLength: 2 },\r\n email: { type: 'string', format: 'email' },\r\n },\r\n};\r\n\r\n// Response schemas (enables fast-json-stringify serialization)\r\n\r\nexport const successResponse = {\r\n type: 'object',\r\n properties: {\r\n success: { type: 'boolean' },\r\n message: { type: 'string' },\r\n },\r\n};\r\n\r\nexport const loginResponse = {\r\n type: 'object',\r\n properties: {\r\n success: { type: 'boolean' },\r\n user: {\r\n type: 'object',\r\n properties: {\r\n id: { type: 'string' },\r\n name: { type: 'string' },\r\n email: { type: 'string' },\r\n roles: { type: 'array', items: { type: 'string' } },\r\n },\r\n },\r\n accessToken: { type: 'string' },\r\n refreshToken: { type: 'string' },\r\n },\r\n};\r\n\r\nexport const tokenResponse = {\r\n type: 'object',\r\n properties: {\r\n success: { type: 'boolean' },\r\n accessToken: { type: 'string' },\r\n refreshToken: { type: 'string' },\r\n },\r\n};\r\n\r\nexport const userProfileResponse = {\r\n type: 'object',\r\n properties: {\r\n success: { type: 'boolean' },\r\n data: { type: 'object', additionalProperties: true },\r\n },\r\n};\r\n`;\r\n}\r\n\r\nfunction authTestTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Auth Tests\r\n * Generated by Arc CLI\r\n */\r\n\r\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest';\r\n${config.adapter === \"mongokit\" ? \"import mongoose from 'mongoose';\\n\" : \"\"}import { createAppInstance } from '../src/app.js';\r\n${ts ? \"import type { FastifyInstance } from 'fastify';\\n\" : \"\"}\r\ndescribe('Auth', () => {\r\n let app${ts ? \": FastifyInstance\" : \"\"};\r\n const testUser = {\r\n name: 'Test User',\r\n email: 'test@example.com',\r\n password: 'password123',\r\n };\r\n\r\n beforeAll(async () => {\r\n${\r\n config.adapter === \"mongokit\"\r\n ? ` const testDbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/${config.name}-test';\r\n await mongoose.connect(testDbUri);\r\n // Clean up test data\r\n await mongoose.connection.collection('users').deleteMany({ email: testUser.email });\r\n`\r\n : \"\"\r\n}\r\n app = await createAppInstance();\r\n await app.ready();\r\n });\r\n\r\n afterAll(async () => {\r\n${\r\n config.adapter === \"mongokit\"\r\n ? ` await mongoose.connection.collection('users').deleteMany({ email: testUser.email });\r\n await mongoose.connection.close();\r\n`\r\n : \"\"\r\n} await app.close();\r\n });\r\n\r\n describe('POST /auth/register', () => {\r\n it('should register a new user', async () => {\r\n const response = await app.inject({\r\n method: 'POST',\r\n url: '/auth/register',\r\n payload: testUser,\r\n });\r\n\r\n expect(response.statusCode).toBe(201);\r\n const body = JSON.parse(response.body);\r\n expect(body.success).toBe(true);\r\n });\r\n\r\n it('should reject duplicate email', async () => {\r\n const response = await app.inject({\r\n method: 'POST',\r\n url: '/auth/register',\r\n payload: testUser,\r\n });\r\n\r\n expect(response.statusCode).toBe(400);\r\n });\r\n });\r\n\r\n describe('POST /auth/login', () => {\r\n it('should login with valid credentials', async () => {\r\n const response = await app.inject({\r\n method: 'POST',\r\n url: '/auth/login',\r\n payload: { email: testUser.email, password: testUser.password },\r\n });\r\n\r\n expect(response.statusCode).toBe(200);\r\n const body = JSON.parse(response.body);\r\n expect(body.success).toBe(true);\r\n expect(body.accessToken).toBeDefined();\r\n expect(body.refreshToken).toBeDefined();\r\n });\r\n\r\n it('should reject invalid credentials', async () => {\r\n const response = await app.inject({\r\n method: 'POST',\r\n url: '/auth/login',\r\n payload: { email: testUser.email, password: 'wrongpassword' },\r\n });\r\n\r\n expect(response.statusCode).toBe(401);\r\n });\r\n });\r\n\r\n describe('GET /users/me', () => {\r\n it('should require authentication', async () => {\r\n const response = await app.inject({\r\n method: 'GET',\r\n url: '/users/me',\r\n });\r\n\r\n expect(response.statusCode).toBe(401);\r\n });\r\n });\r\n});\r\n`;\r\n}\r\n\r\n// ============================================================================\r\n// Success Message\r\n// ============================================================================\r\n\r\nfunction printSuccessMessage(\r\n config: ProjectConfig,\r\n skipInstall?: boolean,\r\n): void {\r\n const installStep = skipInstall ? ` npm install\\n` : \"\";\r\n const ext = config.typescript ? \"ts\" : \"js\";\r\n\r\n const authInfo =\r\n config.auth === \"better-auth\"\r\n ? `\r\nAuth (Better Auth):\r\n\r\n Auth routes: http://localhost:8040/api/auth/*\r\n Better Auth handles: registration, login, sessions, OAuth\r\n Config file: src/auth.${ext}\r\n`\r\n : `\r\nAuth (JWT):\r\n\r\n POST /auth/register # Register\r\n POST /auth/login # Login (returns JWT)\r\n POST /auth/refresh # Refresh token\r\n GET /users/me # Current user profile\r\n`;\r\n\r\n console.log(`\r\n╔═══════════════════════════════════════════════════════════════╗\r\n║ Project Created ║\r\n╚═══════════════════════════════════════════════════════════════╝\r\n\r\nNext steps:\r\n\r\n cd ${config.name}\r\n${installStep} npm run dev # Uses .env.dev automatically\r\n${authInfo}\r\nAPI Documentation:\r\n\r\n http://localhost:8040/docs # Scalar UI\r\n http://localhost:8040/_docs/openapi.json # OpenAPI spec\r\n\r\nRun tests:\r\n\r\n npm test # Run once\r\n npm run test:watch # Watch mode\r\n\r\nAdd resources:\r\n\r\n arc generate resource product\r\n\r\nProject structure:\r\n\r\n src/\r\n ├── app.${ext} # App factory (for workers/tests)\r\n ├── index.${ext} # Server entry${config.auth === \"better-auth\" ? `\\n ├── auth.${ext} # Better Auth config` : \"\"}\r\n ├── config/ # Configuration\r\n ├── shared/ # Adapters, presets, permissions\r\n ├── plugins/ # App plugins (DI pattern)\r\n └── resources/ # API resources\r\n\r\nDocumentation:\r\n https://github.com/classytic/arc\r\n`);\r\n}\r\n\r\nexport default init;\r\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"introspect.d.mts","names":[],"sources":["../../../src/cli/commands/introspect.ts"],"mappings":";;AAsBA;;;;;iBAAsB,UAAA,CAAW,IAAA,aAAiB,OAAA"}
|