@classytic/arc 1.1.0 → 2.1.3

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.
Files changed (200) hide show
  1. package/README.md +247 -794
  2. package/bin/arc.js +91 -52
  3. package/dist/EventTransport-BkUDYZEb.d.mts +99 -0
  4. package/dist/HookSystem-BsGV-j2l.mjs +404 -0
  5. package/dist/ResourceRegistry-7Ic20ZMw.mjs +249 -0
  6. package/dist/adapters/index.d.mts +5 -0
  7. package/dist/adapters/index.mjs +3 -0
  8. package/dist/audit/index.d.mts +81 -0
  9. package/dist/audit/index.mjs +275 -0
  10. package/dist/audit/mongodb.d.mts +5 -0
  11. package/dist/audit/mongodb.mjs +3 -0
  12. package/dist/audited-CGdLiSlE.mjs +140 -0
  13. package/dist/auth/index.d.mts +188 -0
  14. package/dist/auth/index.mjs +1096 -0
  15. package/dist/auth/redis-session.d.mts +43 -0
  16. package/dist/auth/redis-session.mjs +75 -0
  17. package/dist/betterAuthOpenApi-DjWDddNc.mjs +249 -0
  18. package/dist/cache/index.d.mts +145 -0
  19. package/dist/cache/index.mjs +91 -0
  20. package/dist/caching-GSDJcA6-.mjs +93 -0
  21. package/dist/chunk-C7Uep-_p.mjs +20 -0
  22. package/dist/circuitBreaker-DYhWBW_D.mjs +1096 -0
  23. package/dist/cli/commands/describe.d.mts +18 -0
  24. package/dist/cli/commands/describe.mjs +238 -0
  25. package/dist/cli/commands/docs.d.mts +13 -0
  26. package/dist/cli/commands/docs.mjs +52 -0
  27. package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -2
  28. package/dist/cli/commands/generate.mjs +357 -0
  29. package/dist/cli/commands/{init.d.ts → init.d.mts} +11 -8
  30. package/dist/cli/commands/{init.js → init.mjs} +807 -617
  31. package/dist/cli/commands/introspect.d.mts +10 -0
  32. package/dist/cli/commands/introspect.mjs +75 -0
  33. package/dist/cli/index.d.mts +16 -0
  34. package/dist/cli/index.mjs +156 -0
  35. package/dist/constants-DdXFXQtN.mjs +84 -0
  36. package/dist/core/index.d.mts +5 -0
  37. package/dist/core/index.mjs +4 -0
  38. package/dist/createApp-D2D5XXaV.mjs +559 -0
  39. package/dist/defineResource-PXzSJ15_.mjs +2197 -0
  40. package/dist/discovery/index.d.mts +46 -0
  41. package/dist/discovery/index.mjs +109 -0
  42. package/dist/docs/index.d.mts +162 -0
  43. package/dist/docs/index.mjs +74 -0
  44. package/dist/elevation-DGo5shaX.d.mts +87 -0
  45. package/dist/elevation-DSTbVvYj.mjs +113 -0
  46. package/dist/errorHandler-C3GY3_ow.mjs +108 -0
  47. package/dist/errorHandler-CW3OOeYq.d.mts +72 -0
  48. package/dist/errors-DAWRdiYP.d.mts +124 -0
  49. package/dist/errors-DBANPbGr.mjs +211 -0
  50. package/dist/eventPlugin-BEOvaDqo.mjs +229 -0
  51. package/dist/eventPlugin-H6wDDjGO.d.mts +124 -0
  52. package/dist/events/index.d.mts +53 -0
  53. package/dist/events/index.mjs +51 -0
  54. package/dist/events/transports/redis-stream-entry.d.mts +2 -0
  55. package/dist/events/transports/redis-stream-entry.mjs +177 -0
  56. package/dist/events/transports/redis.d.mts +76 -0
  57. package/dist/events/transports/redis.mjs +124 -0
  58. package/dist/externalPaths-SyPF2tgK.d.mts +50 -0
  59. package/dist/factory/index.d.mts +63 -0
  60. package/dist/factory/index.mjs +3 -0
  61. package/dist/fastifyAdapter-C8DlE0YH.d.mts +216 -0
  62. package/dist/fields-Bi_AVKSo.d.mts +109 -0
  63. package/dist/fields-CTd_CrKr.mjs +114 -0
  64. package/dist/hooks/index.d.mts +4 -0
  65. package/dist/hooks/index.mjs +3 -0
  66. package/dist/idempotency/index.d.mts +96 -0
  67. package/dist/idempotency/index.mjs +319 -0
  68. package/dist/idempotency/mongodb.d.mts +2 -0
  69. package/dist/idempotency/mongodb.mjs +114 -0
  70. package/dist/idempotency/redis.d.mts +2 -0
  71. package/dist/idempotency/redis.mjs +103 -0
  72. package/dist/index.d.mts +260 -0
  73. package/dist/index.mjs +104 -0
  74. package/dist/integrations/event-gateway.d.mts +46 -0
  75. package/dist/integrations/event-gateway.mjs +43 -0
  76. package/dist/integrations/index.d.mts +5 -0
  77. package/dist/integrations/index.mjs +1 -0
  78. package/dist/integrations/jobs.d.mts +103 -0
  79. package/dist/integrations/jobs.mjs +123 -0
  80. package/dist/integrations/streamline.d.mts +60 -0
  81. package/dist/integrations/streamline.mjs +125 -0
  82. package/dist/integrations/websocket.d.mts +82 -0
  83. package/dist/integrations/websocket.mjs +288 -0
  84. package/dist/interface-CSNjltAc.d.mts +77 -0
  85. package/dist/interface-DTbsvIWe.d.mts +54 -0
  86. package/dist/interface-e9XfSsUV.d.mts +1097 -0
  87. package/dist/introspectionPlugin-B3JkrjwU.mjs +53 -0
  88. package/dist/keys-DhqDRxv3.mjs +42 -0
  89. package/dist/logger-ByrvQWZO.mjs +78 -0
  90. package/dist/memory-B2v7KrCB.mjs +143 -0
  91. package/dist/migrations/index.d.mts +156 -0
  92. package/dist/migrations/index.mjs +260 -0
  93. package/dist/mongodb-ClykrfGo.d.mts +118 -0
  94. package/dist/mongodb-DNKEExbf.mjs +93 -0
  95. package/dist/mongodb-Dg8O_gvd.d.mts +71 -0
  96. package/dist/openapi-9nB_kiuR.mjs +525 -0
  97. package/dist/org/index.d.mts +68 -0
  98. package/dist/org/index.mjs +513 -0
  99. package/dist/org/types.d.mts +82 -0
  100. package/dist/org/types.mjs +1 -0
  101. package/dist/permissions/index.d.mts +278 -0
  102. package/dist/permissions/index.mjs +579 -0
  103. package/dist/plugins/index.d.mts +172 -0
  104. package/dist/plugins/index.mjs +522 -0
  105. package/dist/plugins/response-cache.d.mts +87 -0
  106. package/dist/plugins/response-cache.mjs +283 -0
  107. package/dist/plugins/tracing-entry.d.mts +2 -0
  108. package/dist/plugins/tracing-entry.mjs +185 -0
  109. package/dist/pluralize-CM-jZg7p.mjs +86 -0
  110. package/dist/policies/{index.d.ts → index.d.mts} +204 -170
  111. package/dist/policies/index.mjs +321 -0
  112. package/dist/presets/{index.d.ts → index.d.mts} +62 -131
  113. package/dist/presets/index.mjs +143 -0
  114. package/dist/presets/multiTenant.d.mts +24 -0
  115. package/dist/presets/multiTenant.mjs +113 -0
  116. package/dist/presets-BTeYbw7h.d.mts +57 -0
  117. package/dist/presets-CeFtfDR8.mjs +119 -0
  118. package/dist/prisma-C3iornoK.d.mts +274 -0
  119. package/dist/prisma-DJbMt3yf.mjs +627 -0
  120. package/dist/queryCachePlugin-B6R0d4av.mjs +138 -0
  121. package/dist/queryCachePlugin-Q6SYuHZ6.d.mts +71 -0
  122. package/dist/redis-UwjEp8Ea.d.mts +49 -0
  123. package/dist/redis-stream-CBg0upHI.d.mts +103 -0
  124. package/dist/registry/index.d.mts +11 -0
  125. package/dist/registry/index.mjs +4 -0
  126. package/dist/requestContext-xi6OKBL-.mjs +55 -0
  127. package/dist/schemaConverter-Dtg0Kt9T.mjs +98 -0
  128. package/dist/schemas/index.d.mts +63 -0
  129. package/dist/schemas/index.mjs +82 -0
  130. package/dist/scope/index.d.mts +21 -0
  131. package/dist/scope/index.mjs +65 -0
  132. package/dist/sessionManager-D_iEHjQl.d.mts +186 -0
  133. package/dist/sse-DkqQ1uxb.mjs +123 -0
  134. package/dist/testing/index.d.mts +907 -0
  135. package/dist/testing/index.mjs +1976 -0
  136. package/dist/tracing-8CEbhF0w.d.mts +70 -0
  137. package/dist/typeGuards-DwxA1t_L.mjs +9 -0
  138. package/dist/types/index.d.mts +946 -0
  139. package/dist/types/index.mjs +14 -0
  140. package/dist/types-B0dhNrnd.d.mts +445 -0
  141. package/dist/types-Beqn1Un7.mjs +38 -0
  142. package/dist/types-DelU6kln.mjs +25 -0
  143. package/dist/types-RLkFVgaw.d.mts +101 -0
  144. package/dist/utils/index.d.mts +747 -0
  145. package/dist/utils/index.mjs +6 -0
  146. package/package.json +194 -68
  147. package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
  148. package/dist/adapters/index.d.ts +0 -237
  149. package/dist/adapters/index.js +0 -668
  150. package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
  151. package/dist/audit/index.d.ts +0 -195
  152. package/dist/audit/index.js +0 -319
  153. package/dist/auth/index.d.ts +0 -47
  154. package/dist/auth/index.js +0 -174
  155. package/dist/cli/commands/docs.d.ts +0 -11
  156. package/dist/cli/commands/docs.js +0 -474
  157. package/dist/cli/commands/generate.js +0 -334
  158. package/dist/cli/commands/introspect.d.ts +0 -8
  159. package/dist/cli/commands/introspect.js +0 -338
  160. package/dist/cli/index.d.ts +0 -4
  161. package/dist/cli/index.js +0 -3269
  162. package/dist/core/index.d.ts +0 -220
  163. package/dist/core/index.js +0 -2786
  164. package/dist/createApp-Ce9wl8W9.d.ts +0 -77
  165. package/dist/docs/index.d.ts +0 -166
  166. package/dist/docs/index.js +0 -658
  167. package/dist/errors-8WIxGS_6.d.ts +0 -122
  168. package/dist/events/index.d.ts +0 -117
  169. package/dist/events/index.js +0 -89
  170. package/dist/factory/index.d.ts +0 -38
  171. package/dist/factory/index.js +0 -1652
  172. package/dist/hooks/index.d.ts +0 -4
  173. package/dist/hooks/index.js +0 -199
  174. package/dist/idempotency/index.d.ts +0 -323
  175. package/dist/idempotency/index.js +0 -500
  176. package/dist/index-B4t03KQ0.d.ts +0 -1366
  177. package/dist/index.d.ts +0 -135
  178. package/dist/index.js +0 -4756
  179. package/dist/migrations/index.d.ts +0 -185
  180. package/dist/migrations/index.js +0 -274
  181. package/dist/org/index.d.ts +0 -129
  182. package/dist/org/index.js +0 -220
  183. package/dist/permissions/index.d.ts +0 -144
  184. package/dist/permissions/index.js +0 -103
  185. package/dist/plugins/index.d.ts +0 -46
  186. package/dist/plugins/index.js +0 -1069
  187. package/dist/policies/index.js +0 -196
  188. package/dist/presets/index.js +0 -384
  189. package/dist/presets/multiTenant.d.ts +0 -39
  190. package/dist/presets/multiTenant.js +0 -112
  191. package/dist/registry/index.d.ts +0 -16
  192. package/dist/registry/index.js +0 -253
  193. package/dist/testing/index.d.ts +0 -618
  194. package/dist/testing/index.js +0 -48020
  195. package/dist/types/index.d.ts +0 -4
  196. package/dist/types/index.js +0 -8
  197. package/dist/types-B99TBmFV.d.ts +0 -76
  198. package/dist/types-BvckRbs2.d.ts +0 -143
  199. package/dist/utils/index.d.ts +0 -679
  200. package/dist/utils/index.js +0 -931
@@ -0,0 +1,46 @@
1
+ import { FastifyPluginAsync } from "fastify";
2
+
3
+ //#region src/discovery/index.d.ts
4
+ /** A discovered resource — must have toPlugin() method */
5
+ interface DiscoverableResource {
6
+ toPlugin(): FastifyPluginAsync;
7
+ name?: string;
8
+ definition?: {
9
+ name: string;
10
+ };
11
+ }
12
+ interface DiscoveryOptions {
13
+ /** Directories to scan (relative to cwd or absolute) */
14
+ paths: string[];
15
+ /**
16
+ * File name pattern to match.
17
+ * Supports simple globs: *.resource.ts, *.resource.js
18
+ * Default: '*.resource.{ts,js}'
19
+ */
20
+ pattern?: string;
21
+ /** Export name to look for in each file (default: 'default' then first ResourceDefinition) */
22
+ exportName?: string;
23
+ /** Filter function to include/exclude discovered resources */
24
+ filter?: (resource: DiscoverableResource, filePath: string) => boolean;
25
+ /** Called for each discovered resource (for logging) */
26
+ onDiscover?: (name: string, filePath: string) => void;
27
+ /** Whether to scan recursively (default: true) */
28
+ recursive?: boolean;
29
+ }
30
+ interface DiscoveryPluginOptions extends DiscoveryOptions {
31
+ /** URL prefix applied to all discovered resources */
32
+ prefix?: string;
33
+ }
34
+ /**
35
+ * Discover and import resource files.
36
+ *
37
+ * @returns Array of discovered resources with their file paths
38
+ */
39
+ declare function discoverResources(options: DiscoveryOptions): Promise<Array<{
40
+ resource: DiscoverableResource;
41
+ filePath: string;
42
+ }>>;
43
+ /** Auto-discovery plugin for Arc resources */
44
+ declare const discoveryPlugin: FastifyPluginAsync<DiscoveryPluginOptions>;
45
+ //#endregion
46
+ export { DiscoverableResource, DiscoveryOptions, DiscoveryPluginOptions, discoveryPlugin as default, discoveryPlugin, discoverResources };
@@ -0,0 +1,109 @@
1
+ import { extname, join, resolve } from "node:path";
2
+ import { readdir } from "node:fs/promises";
3
+ import { pathToFileURL } from "node:url";
4
+
5
+ //#region src/discovery/index.ts
6
+ /**
7
+ * Match a filename against a simple pattern.
8
+ * Supports: *.resource.ts, *.resource.js, *.resource.{ts,js}
9
+ */
10
+ function matchPattern(filename, pattern) {
11
+ if (pattern.includes("{") && pattern.includes("}")) {
12
+ const match = pattern.match(/\{([^}]+)\}/);
13
+ if (match) return match[1].split(",").map((s) => s.trim()).some((alt) => {
14
+ return matchPattern(filename, pattern.replace(match[0], alt));
15
+ });
16
+ }
17
+ if (pattern.startsWith("*")) {
18
+ const suffix = pattern.slice(1);
19
+ return filename.endsWith(suffix);
20
+ }
21
+ return filename === pattern;
22
+ }
23
+ /**
24
+ * Recursively scan directories for files matching a pattern.
25
+ */
26
+ async function scanDirectory(dir, pattern, recursive) {
27
+ const results = [];
28
+ const resolvedDir = resolve(dir);
29
+ const entries = await readdir(resolvedDir, { withFileTypes: true }).catch(() => []);
30
+ for (const entry of entries) {
31
+ const fullPath = join(resolvedDir, String(entry.name));
32
+ if (entry.isDirectory() && recursive) {
33
+ const nested = await scanDirectory(fullPath, pattern, recursive);
34
+ results.push(...nested);
35
+ } else if (entry.isFile()) {
36
+ const filePattern = pattern.replace(/^\*\*\//, "");
37
+ if (matchPattern(String(entry.name), filePattern)) results.push(fullPath);
38
+ }
39
+ }
40
+ return results;
41
+ }
42
+ /**
43
+ * Discover and import resource files.
44
+ *
45
+ * @returns Array of discovered resources with their file paths
46
+ */
47
+ async function discoverResources(options) {
48
+ const { paths, pattern = "*.resource.{ts,js}", exportName, filter, onDiscover, recursive = true } = options;
49
+ const discovered = [];
50
+ const allFiles = [];
51
+ for (const dir of paths) {
52
+ const files = await scanDirectory(dir, pattern, recursive);
53
+ allFiles.push(...files);
54
+ }
55
+ allFiles.sort();
56
+ for (const filePath of allFiles) {
57
+ extname(filePath);
58
+ const fileUrl = pathToFileURL(filePath).href;
59
+ let module;
60
+ try {
61
+ module = await import(fileUrl);
62
+ } catch (err) {
63
+ const message = err instanceof Error ? err.message : String(err);
64
+ throw new Error(`Failed to import resource file: ${filePath}\n${message}`);
65
+ }
66
+ let resource = null;
67
+ if (exportName && module[exportName]) resource = module[exportName];
68
+ if (!resource && module.default && typeof module.default.toPlugin === "function") resource = module.default;
69
+ if (!resource) {
70
+ for (const value of Object.values(module)) if (value && typeof value === "object" && typeof value.toPlugin === "function") {
71
+ resource = value;
72
+ break;
73
+ }
74
+ }
75
+ if (!resource) throw new Error(`No resource found in: ${filePath}\nResource files must export an object with a toPlugin() method.
76
+ Use defineResource() or export the resource as default.`);
77
+ if (filter && !filter(resource, filePath)) continue;
78
+ const name = resource.definition?.name ?? resource.name ?? filePath;
79
+ onDiscover?.(name, filePath);
80
+ discovered.push({
81
+ resource,
82
+ filePath
83
+ });
84
+ }
85
+ return discovered;
86
+ }
87
+ const discoveryPluginImpl = async (fastify, options) => {
88
+ const { prefix, ...discoveryOptions } = options;
89
+ const discovered = await discoverResources({
90
+ ...discoveryOptions,
91
+ onDiscover: discoveryOptions.onDiscover ?? ((name, filePath) => {
92
+ fastify.log.debug({
93
+ resource: name,
94
+ file: filePath
95
+ }, "Auto-discovered resource");
96
+ })
97
+ });
98
+ for (const { resource } of discovered) {
99
+ const plugin = resource.toPlugin();
100
+ if (prefix) await fastify.register(plugin, { prefix });
101
+ else await fastify.register(plugin);
102
+ }
103
+ fastify.log.debug(`Auto-discovery: registered ${discovered.length} resource(s)`);
104
+ };
105
+ /** Auto-discovery plugin for Arc resources */
106
+ const discoveryPlugin = discoveryPluginImpl;
107
+
108
+ //#endregion
109
+ export { discoveryPlugin as default, discoveryPlugin, discoverResources };
@@ -0,0 +1,162 @@
1
+ import "../elevation-DGo5shaX.mjs";
2
+ import "../interface-e9XfSsUV.mjs";
3
+ import "../types-RLkFVgaw.mjs";
4
+ import { RegistryEntry } from "../types/index.mjs";
5
+ import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
6
+ import { FastifyPluginAsync } from "fastify";
7
+
8
+ //#region src/docs/openapi.d.ts
9
+ interface OpenApiOptions {
10
+ /** API title */
11
+ title?: string;
12
+ /** API version */
13
+ version?: string;
14
+ /** API description */
15
+ description?: string;
16
+ /** Server URL */
17
+ serverUrl?: string;
18
+ /** Route prefix for spec endpoint (default: '/_docs') */
19
+ prefix?: string;
20
+ /** API prefix for all resource paths (e.g., '/api/v1') */
21
+ apiPrefix?: string;
22
+ /** Auth roles required to access spec (default: [] = public) */
23
+ authRoles?: string[];
24
+ /** Include internal routes (default: false) */
25
+ includeInternal?: boolean;
26
+ /** Custom OpenAPI extensions */
27
+ extensions?: Record<string, unknown>;
28
+ }
29
+ interface OpenApiSpec {
30
+ openapi: string;
31
+ info: {
32
+ title: string;
33
+ version: string;
34
+ description?: string;
35
+ };
36
+ servers?: Array<{
37
+ url: string;
38
+ description?: string;
39
+ }>;
40
+ paths: Record<string, PathItem>;
41
+ components: {
42
+ schemas: Record<string, SchemaObject>;
43
+ securitySchemes?: Record<string, SecurityScheme>;
44
+ };
45
+ tags: Array<{
46
+ name: string;
47
+ description?: string;
48
+ }>;
49
+ security?: Array<Record<string, string[]>>;
50
+ }
51
+ interface OpenApiBuildOptions {
52
+ title?: string;
53
+ version?: string;
54
+ description?: string;
55
+ serverUrl?: string;
56
+ apiPrefix?: string;
57
+ }
58
+ interface PathItem {
59
+ get?: Operation;
60
+ post?: Operation;
61
+ put?: Operation;
62
+ patch?: Operation;
63
+ delete?: Operation;
64
+ options?: Operation;
65
+ head?: Operation;
66
+ }
67
+ interface Operation {
68
+ tags: string[];
69
+ summary: string;
70
+ description?: string;
71
+ operationId: string;
72
+ parameters?: Parameter[];
73
+ requestBody?: RequestBody;
74
+ responses: Record<string, Response>;
75
+ security?: Array<Record<string, string[]>>;
76
+ /** Arc permission metadata (OpenAPI extension) */
77
+ 'x-arc-permission'?: {
78
+ type: string;
79
+ roles?: readonly string[];
80
+ };
81
+ /** Arc pipeline steps (OpenAPI extension) */
82
+ 'x-arc-pipeline'?: Array<{
83
+ type: string;
84
+ name: string;
85
+ }>;
86
+ }
87
+ interface Parameter {
88
+ name: string;
89
+ in: 'path' | 'query' | 'header';
90
+ required?: boolean;
91
+ schema: SchemaObject;
92
+ description?: string;
93
+ }
94
+ interface RequestBody {
95
+ required?: boolean;
96
+ content: Record<string, {
97
+ schema: SchemaObject;
98
+ }>;
99
+ }
100
+ interface Response {
101
+ description: string;
102
+ content?: Record<string, {
103
+ schema: SchemaObject;
104
+ }>;
105
+ }
106
+ interface SchemaObject {
107
+ type?: string;
108
+ format?: string;
109
+ properties?: Record<string, SchemaObject>;
110
+ items?: SchemaObject;
111
+ required?: string[];
112
+ $ref?: string;
113
+ description?: string;
114
+ example?: unknown;
115
+ additionalProperties?: boolean | SchemaObject;
116
+ enum?: string[];
117
+ minimum?: number;
118
+ maximum?: number;
119
+ minLength?: number;
120
+ maxLength?: number;
121
+ pattern?: string;
122
+ }
123
+ interface SecurityScheme {
124
+ type: string;
125
+ scheme?: string;
126
+ bearerFormat?: string;
127
+ in?: string;
128
+ name?: string;
129
+ }
130
+ declare const openApiPlugin: FastifyPluginAsync<OpenApiOptions>;
131
+ /**
132
+ * Build OpenAPI spec from registry resources.
133
+ * Shared by HTTP docs endpoint and CLI export command.
134
+ */
135
+ declare function buildOpenApiSpec(resources: RegistryEntry[], options?: OpenApiBuildOptions, externalPaths?: ExternalOpenApiPaths[]): OpenApiSpec;
136
+ declare const _default: FastifyPluginAsync<OpenApiOptions>;
137
+ //#endregion
138
+ //#region src/docs/scalar.d.ts
139
+ interface ScalarOptions {
140
+ /** Route prefix for UI (default: '/docs') */
141
+ routePrefix?: string;
142
+ /** OpenAPI spec URL (default: '/_docs/openapi.json') */
143
+ specUrl?: string;
144
+ /** Page title */
145
+ title?: string;
146
+ /** Theme (default: 'default') */
147
+ theme?: 'default' | 'alternate' | 'moon' | 'purple' | 'solarized' | 'bluePlanet' | 'saturn' | 'kepler' | 'mars' | 'deepSpace';
148
+ /** Show sidebar (default: true) */
149
+ showSidebar?: boolean;
150
+ /** Dark mode (default: false) */
151
+ darkMode?: boolean;
152
+ /** Auth roles required to access docs */
153
+ authRoles?: string[];
154
+ /** Custom CSS */
155
+ customCss?: string;
156
+ /** Favicon URL */
157
+ favicon?: string;
158
+ }
159
+ declare const scalarPlugin: FastifyPluginAsync<ScalarOptions>;
160
+ declare const _default$1: FastifyPluginAsync<ScalarOptions>;
161
+ //#endregion
162
+ export { type ExternalOpenApiPaths, type OpenApiBuildOptions, type OpenApiOptions, type OpenApiSpec, type ScalarOptions, buildOpenApiSpec, _default as openApiPlugin, openApiPlugin as openApiPluginFn, _default$1 as scalarPlugin, scalarPlugin as scalarPluginFn };
@@ -0,0 +1,74 @@
1
+ import { t as getUserRoles } from "../types-DelU6kln.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-9nB_kiuR.mjs";
3
+ import fp from "fastify-plugin";
4
+
5
+ //#region src/docs/scalar.ts
6
+ /**
7
+ * Scalar API Reference Plugin
8
+ *
9
+ * Beautiful, modern API documentation UI.
10
+ * Lighter and more modern than Swagger UI.
11
+ *
12
+ * @example
13
+ * import { scalarPlugin } from '@classytic/arc/docs';
14
+ *
15
+ * await fastify.register(scalarPlugin, {
16
+ * routePrefix: '/docs',
17
+ * specUrl: '/_docs/openapi.json',
18
+ * });
19
+ *
20
+ * // UI available at /docs
21
+ */
22
+ const scalarPlugin = async (fastify, opts = {}) => {
23
+ const { routePrefix = "/docs", specUrl = "/_docs/openapi.json", title = "API Documentation", theme = "default", showSidebar = true, darkMode = false, authRoles = [], customCss = "", favicon } = opts;
24
+ const scalarConfig = JSON.stringify({
25
+ spec: { url: specUrl },
26
+ theme,
27
+ showSidebar,
28
+ darkMode,
29
+ ...favicon && { favicon }
30
+ });
31
+ const html = `<!DOCTYPE html>
32
+ <html>
33
+ <head>
34
+ <meta charset="utf-8">
35
+ <meta name="viewport" content="width=device-width, initial-scale=1">
36
+ <title>${title}</title>
37
+ ${favicon ? `<link rel="icon" href="${favicon}">` : ""}
38
+ <style>
39
+ body { margin: 0; padding: 0; }
40
+ ${customCss}
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <script id="api-reference" data-url="${specUrl}"><\/script>
45
+ <script>
46
+ var configuration = ${scalarConfig};
47
+ document.getElementById('api-reference').dataset.configuration = JSON.stringify(configuration);
48
+ <\/script>
49
+ <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"><\/script>
50
+ </body>
51
+ </html>`;
52
+ fastify.get(routePrefix, async (request, reply) => {
53
+ if (authRoles.length > 0) {
54
+ const user = request.user;
55
+ const roles = getUserRoles(user);
56
+ if (!authRoles.some((r) => roles.includes(r)) && !roles.includes("superadmin")) {
57
+ reply.code(403).send({ error: "Access denied" });
58
+ return;
59
+ }
60
+ }
61
+ reply.type("text/html").send(html);
62
+ });
63
+ if (!routePrefix.endsWith("/")) fastify.get(`${routePrefix}/`, async (_, reply) => {
64
+ reply.redirect(routePrefix);
65
+ });
66
+ fastify.log?.debug?.(`Scalar API docs available at ${routePrefix}`);
67
+ };
68
+ var scalar_default = fp(scalarPlugin, {
69
+ name: "arc-scalar",
70
+ fastify: "5.x"
71
+ });
72
+
73
+ //#endregion
74
+ export { buildOpenApiSpec, openapi_default as openApiPlugin, openApiPlugin as openApiPluginFn, scalar_default as scalarPlugin, scalarPlugin as scalarPluginFn };
@@ -0,0 +1,87 @@
1
+ import { FastifyPluginAsync, FastifyRequest } from "fastify";
2
+
3
+ //#region src/scope/types.d.ts
4
+ /**
5
+ * Request Scope — The One Standard
6
+ *
7
+ * Discriminated union representing the access context of every request.
8
+ * Replaces scattered orgScope/orgRoles/organizationId/bypassRoles.
9
+ *
10
+ * Set once by auth adapters, read everywhere by permissions/presets/guards.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // In a permission check
15
+ * const scope = request.scope;
16
+ * if (isElevated(scope)) return true;
17
+ * if (isMember(scope) && scope.orgRoles.includes('admin')) return true;
18
+ * ```
19
+ */
20
+ /**
21
+ * Request scope — 4 kinds, 4 states, no ambiguity.
22
+ *
23
+ * | Kind | Meaning |
24
+ * |---------------|-----------------------------------|
25
+ * | public | No authentication |
26
+ * | authenticated | Logged in, no org context |
27
+ * | member | In an org with specific roles |
28
+ * | elevated | Platform admin, explicit elevation |
29
+ */
30
+ type RequestScope = {
31
+ kind: 'public';
32
+ } | {
33
+ kind: 'authenticated';
34
+ } | {
35
+ kind: 'member';
36
+ organizationId: string;
37
+ orgRoles: string[];
38
+ teamId?: string;
39
+ } | {
40
+ kind: 'elevated';
41
+ organizationId?: string;
42
+ elevatedBy: string;
43
+ };
44
+ /** Check if scope is `member` kind */
45
+ declare function isMember(scope: RequestScope): scope is Extract<RequestScope, {
46
+ kind: 'member';
47
+ }>;
48
+ /** Check if scope is `elevated` kind */
49
+ declare function isElevated(scope: RequestScope): scope is Extract<RequestScope, {
50
+ kind: 'elevated';
51
+ }>;
52
+ /** Check if scope has org access (member OR elevated) */
53
+ declare function hasOrgAccess(scope: RequestScope): boolean;
54
+ /** Check if request is authenticated (any kind except public) */
55
+ declare function isAuthenticated(scope: RequestScope): boolean;
56
+ /** Get organizationId from scope (if present) */
57
+ declare function getOrgId(scope: RequestScope): string | undefined;
58
+ /** Get org roles from scope (empty array if not a member) */
59
+ declare function getOrgRoles(scope: RequestScope): string[];
60
+ /** Get team ID from scope (only available on member kind) */
61
+ declare function getTeamId(scope: RequestScope): string | undefined;
62
+ /** Default public scope — used as initial decoration value */
63
+ declare const PUBLIC_SCOPE: Readonly<RequestScope>;
64
+ /** Default authenticated scope — used when user is logged in but no org */
65
+ declare const AUTHENTICATED_SCOPE: Readonly<RequestScope>;
66
+ //#endregion
67
+ //#region src/scope/elevation.d.ts
68
+ interface ElevationOptions {
69
+ /** Roles that can use elevation (default: ['superadmin']) */
70
+ platformRoles?: string[];
71
+ /** Header name for scope declaration (default: 'x-arc-scope') */
72
+ scopeHeader?: string;
73
+ /** Header name for target organization (default: 'x-organization-id') */
74
+ orgHeader?: string;
75
+ /** Called when elevation happens — use for audit logging */
76
+ onElevation?: (event: ElevationEvent) => void | Promise<void>;
77
+ }
78
+ interface ElevationEvent {
79
+ userId: string;
80
+ organizationId?: string;
81
+ request: FastifyRequest;
82
+ timestamp: Date;
83
+ }
84
+ declare const elevationPlugin: FastifyPluginAsync<ElevationOptions>;
85
+ declare const _default: FastifyPluginAsync<ElevationOptions>;
86
+ //#endregion
87
+ export { AUTHENTICATED_SCOPE as a, getOrgId as c, hasOrgAccess as d, isAuthenticated as f, elevationPlugin as i, getOrgRoles as l, isMember as m, ElevationOptions as n, PUBLIC_SCOPE as o, isElevated as p, _default as r, RequestScope as s, ElevationEvent as t, getTeamId as u };
@@ -0,0 +1,113 @@
1
+ import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
2
+ import { t as getUserRoles } from "./types-DelU6kln.mjs";
3
+ import { t as arcLog } from "./logger-ByrvQWZO.mjs";
4
+ import fp from "fastify-plugin";
5
+
6
+ //#region src/scope/elevation.ts
7
+ /**
8
+ * Elevation Plugin — Explicit Platform Admin Access
9
+ *
10
+ * Opt-in Fastify plugin that allows platform admins to explicitly
11
+ * elevate their scope via the `x-arc-scope: platform` header.
12
+ *
13
+ * Without this header, a superadmin is treated as a normal user.
14
+ * This prevents implicit bypass and enables audit logging.
15
+ *
16
+ * ## Lifecycle
17
+ *
18
+ * Elevation wraps `fastify.authenticate` so it always runs AFTER
19
+ * authentication has set `request.user`. This avoids the `onRequest`
20
+ * timing issue where `request.user` doesn't exist yet.
21
+ *
22
+ * Flow: `authenticate()` → user is set → `elevation check` → scope is set
23
+ *
24
+ * Inspired by Stripe Connect's `Stripe-Account` header.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const app = await createApp({
29
+ * auth: { type: 'betterAuth', betterAuth: adapter },
30
+ * elevation: {
31
+ * platformRoles: ['superadmin'],
32
+ * onElevation: (event) => auditLog.write(event),
33
+ * },
34
+ * });
35
+ * ```
36
+ */
37
+ var elevation_exports = /* @__PURE__ */ __exportAll({
38
+ default: () => elevation_default,
39
+ elevationPlugin: () => elevationPlugin
40
+ });
41
+ const log = arcLog("elevation");
42
+ const elevationPlugin = async (fastify, opts = {}) => {
43
+ const { platformRoles = ["superadmin"], scopeHeader = "x-arc-scope", orgHeader = "x-organization-id", onElevation } = opts;
44
+ if (!fastify.hasDecorator("authenticate")) {
45
+ log.warn("authenticate decorator not found. Register auth before elevation. Elevation will not function.");
46
+ return;
47
+ }
48
+ const originalAuthenticate = fastify.authenticate;
49
+ const authenticateWithElevation = async (request, reply) => {
50
+ await originalAuthenticate.call(fastify, request, reply);
51
+ if (reply.sent) return;
52
+ if (request.headers[scopeHeader] !== "platform") return;
53
+ const user = request.user;
54
+ if (!user) {
55
+ log.debug("Elevation requested but no user after auth");
56
+ reply.code(401).send({
57
+ success: false,
58
+ error: "Unauthorized",
59
+ message: "Authentication required for platform elevation",
60
+ code: "ELEVATION_AUTH_REQUIRED"
61
+ });
62
+ return;
63
+ }
64
+ const userRoles = getUserRoles(user);
65
+ if (!platformRoles.some((r) => userRoles.includes(r))) {
66
+ log.debug("Elevation rejected — insufficient roles", {
67
+ userId: user.id ?? user._id,
68
+ userRoles,
69
+ required: platformRoles
70
+ });
71
+ reply.code(403).send({
72
+ success: false,
73
+ error: "Forbidden",
74
+ message: "Insufficient privileges for platform elevation",
75
+ code: "ELEVATION_FORBIDDEN"
76
+ });
77
+ return;
78
+ }
79
+ const orgId = request.headers[orgHeader];
80
+ const userId = String(user.id ?? user._id ?? "unknown");
81
+ request.scope = {
82
+ kind: "elevated",
83
+ organizationId: orgId || void 0,
84
+ elevatedBy: userId
85
+ };
86
+ log.debug("Scope elevated", {
87
+ userId,
88
+ organizationId: orgId
89
+ });
90
+ if (onElevation) try {
91
+ await onElevation({
92
+ userId,
93
+ organizationId: orgId || void 0,
94
+ request,
95
+ timestamp: /* @__PURE__ */ new Date()
96
+ });
97
+ } catch {
98
+ log.warn("onElevation callback threw — continuing request");
99
+ }
100
+ };
101
+ fastify.authenticate = authenticateWithElevation;
102
+ log.debug("Plugin registered", {
103
+ platformRoles,
104
+ scopeHeader
105
+ });
106
+ };
107
+ var elevation_default = fp(elevationPlugin, {
108
+ name: "arc-elevation",
109
+ fastify: "5.x"
110
+ });
111
+
112
+ //#endregion
113
+ export { elevation_default as n, elevation_exports as r, elevationPlugin as t };